@fedify/botkit 0.5.0-dev.209 → 0.5.0-dev.225

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 (85) hide show
  1. package/dist/bot-group.test.d.ts +2 -0
  2. package/dist/bot-group.test.js +220 -0
  3. package/dist/bot-group.test.js.map +1 -0
  4. package/dist/bot-impl.d.ts +132 -13
  5. package/dist/bot-impl.d.ts.map +1 -1
  6. package/dist/bot-impl.js +400 -178
  7. package/dist/bot-impl.js.map +1 -1
  8. package/dist/bot-impl.test.js +214 -76
  9. package/dist/bot-impl.test.js.map +1 -1
  10. package/dist/bot.d.ts +94 -48
  11. package/dist/bot.d.ts.map +1 -1
  12. package/dist/bot.js +2 -104
  13. package/dist/bot.js.map +1 -1
  14. package/dist/bot.test.js +59 -0
  15. package/dist/bot.test.js.map +1 -1
  16. package/dist/components/FollowButton.d.ts +5 -3
  17. package/dist/components/FollowButton.d.ts.map +1 -1
  18. package/dist/components/FollowButton.js +2 -2
  19. package/dist/components/FollowButton.js.map +1 -1
  20. package/dist/components/Follower.d.ts +2 -2
  21. package/dist/components/Layout.js +1 -1
  22. package/dist/components/Layout.js.map +1 -1
  23. package/dist/components/Message.d.ts +2 -2
  24. package/dist/deno.js +2 -1
  25. package/dist/deno.js.map +1 -1
  26. package/dist/follow-impl.test.js +3 -3
  27. package/dist/follow-impl.test.js.map +1 -1
  28. package/dist/instance-impl.d.ts +158 -0
  29. package/dist/instance-impl.d.ts.map +1 -0
  30. package/dist/instance-impl.js +603 -0
  31. package/dist/instance-impl.js.map +1 -0
  32. package/dist/instance-impl.test.d.ts +2 -0
  33. package/dist/instance-impl.test.js +103 -0
  34. package/dist/instance-impl.test.js.map +1 -0
  35. package/dist/instance-multi.test.d.ts +2 -0
  36. package/dist/instance-multi.test.js +151 -0
  37. package/dist/instance-multi.test.js.map +1 -0
  38. package/dist/instance-routing.test.d.ts +2 -0
  39. package/dist/instance-routing.test.js +367 -0
  40. package/dist/instance-routing.test.js.map +1 -0
  41. package/dist/instance.d.ts +318 -0
  42. package/dist/instance.d.ts.map +1 -0
  43. package/dist/instance.js +51 -0
  44. package/dist/instance.js.map +1 -0
  45. package/dist/message-impl.d.ts.map +1 -1
  46. package/dist/message-impl.js +17 -10
  47. package/dist/message-impl.js.map +1 -1
  48. package/dist/message-impl.test.js +43 -9
  49. package/dist/message-impl.test.js.map +1 -1
  50. package/dist/mod.d.ts +5 -3
  51. package/dist/mod.js +4 -2
  52. package/dist/pages.d.ts +10 -1
  53. package/dist/pages.d.ts.map +1 -1
  54. package/dist/pages.js +112 -41
  55. package/dist/pages.js.map +1 -1
  56. package/dist/pages.test.d.ts +2 -0
  57. package/dist/pages.test.js +170 -0
  58. package/dist/pages.test.js.map +1 -0
  59. package/dist/repository.d.ts +385 -138
  60. package/dist/repository.d.ts.map +1 -1
  61. package/dist/repository.js +595 -223
  62. package/dist/repository.js.map +1 -1
  63. package/dist/repository.test.js +564 -136
  64. package/dist/repository.test.js.map +1 -1
  65. package/dist/session-impl.d.ts.map +1 -1
  66. package/dist/session-impl.js +13 -4
  67. package/dist/session-impl.js.map +1 -1
  68. package/dist/session-impl.test.d.ts.map +1 -1
  69. package/dist/session-impl.test.js +9 -9
  70. package/dist/session-impl.test.js.map +1 -1
  71. package/dist/session.d.ts +8 -3
  72. package/dist/session.d.ts.map +1 -1
  73. package/dist/text.d.ts.map +1 -1
  74. package/dist/text.js +27 -10
  75. package/dist/text.js.map +1 -1
  76. package/dist/text.test.js +37 -2
  77. package/dist/text.test.js.map +1 -1
  78. package/dist/uri.d.ts +46 -0
  79. package/dist/uri.d.ts.map +1 -0
  80. package/dist/uri.js +64 -0
  81. package/dist/uri.js.map +1 -0
  82. package/dist/uri.test.d.ts +2 -0
  83. package/dist/uri.test.js +93 -0
  84. package/dist/uri.test.js.map +1 -0
  85. package/package.json +5 -1
package/dist/bot-impl.js CHANGED
@@ -2,21 +2,37 @@
2
2
  import { Temporal, toTemporalInstant } from "@js-temporal/polyfill";
3
3
  Date.prototype.toTemporalInstant = toTemporalInstant;
4
4
 
5
- import deno_default from "./deno.js";
6
5
  import { isEmoji } from "./emoji.js";
7
6
  import { FollowRequestImpl } from "./follow-impl.js";
7
+ import { parseLocalUri } from "./uri.js";
8
8
  import { createMessage, getMessageVisibility, isMessageObject, isQuoteLink, messageClasses } from "./message-impl.js";
9
- import { app } from "./pages.js";
10
- import { KvRepository } from "./repository.js";
9
+ import { ActorScopedRepository, KvRepository } from "./repository.js";
11
10
  import { SessionImpl } from "./session-impl.js";
12
- import { Accept, Announce, Article, ChatMessage, Create, Delete, Emoji, EmojiReact, Endpoints, Follow, Image, Like, Link, Mention, Note, Object as Object$1, PUBLIC_COLLECTION, PropertyValue, Question, Reject, Service, Undo, Update, isActor } from "@fedify/vocab";
13
- import { createFederation, generateCryptoKeyPair } from "@fedify/fedify";
14
- import { getLogger } from "@logtape/logtape";
15
- import mimeDb from "mime-db";
16
- import fs from "node:fs/promises";
17
- import { getXForwardedRequest } from "x-forwarded-fetch";
11
+ import { InstanceImpl } from "./instance-impl.js";
12
+ import { Announce, Article, ChatMessage, Create, Emoji, EmojiReact, Endpoints, Follow, Image, Like, Link, Mention, Note, Object as Object$1, PUBLIC_COLLECTION, PropertyValue, Question, Service, Update, isActor } from "@fedify/vocab";
13
+ import { generateCryptoKeyPair } from "@fedify/fedify";
18
14
 
19
15
  //#region src/bot-impl.ts
16
+ /**
17
+ * The names of the event handler properties of {@link BotEventHandlers}.
18
+ * @internal
19
+ */
20
+ const botEventHandlerNames = [
21
+ "onFollow",
22
+ "onUnfollow",
23
+ "onAcceptFollow",
24
+ "onRejectFollow",
25
+ "onMention",
26
+ "onReply",
27
+ "onQuote",
28
+ "onMessage",
29
+ "onSharedMessage",
30
+ "onLike",
31
+ "onUnlike",
32
+ "onReact",
33
+ "onUnreact",
34
+ "onVote"
35
+ ];
20
36
  var BotImpl = class {
21
37
  identifier;
22
38
  class;
@@ -29,13 +45,40 @@ var BotImpl = class {
29
45
  properties;
30
46
  #properties;
31
47
  followerPolicy;
32
- customEmojis;
33
48
  repository;
34
- software;
35
- behindProxy;
36
- pages;
37
- collectionWindow;
38
- federation;
49
+ /**
50
+ * The instance hosting the bot. It owns the shared infrastructure:
51
+ * the Fedify federation, the key–value store, the message queue,
52
+ * the root repository, and HTTP handling.
53
+ */
54
+ instance;
55
+ get customEmojis() {
56
+ return this.instance.customEmojis;
57
+ }
58
+ get software() {
59
+ return this.instance.software;
60
+ }
61
+ get behindProxy() {
62
+ return this.instance.behindProxy;
63
+ }
64
+ get pages() {
65
+ return this.instance.pages;
66
+ }
67
+ get collectionWindow() {
68
+ return this.instance.collectionWindow;
69
+ }
70
+ get federation() {
71
+ return this.instance.federation;
72
+ }
73
+ /**
74
+ * The identifier of the bot actor that owns local objects whose URIs are
75
+ * in the legacy (pre-0.5) format, which did not carry the identifier.
76
+ * Legacy URIs can only occur in deployments that hosted a single bot
77
+ * before the upgrade, so they are attributed to that bot.
78
+ */
79
+ get legacyObjectUrisIdentifier() {
80
+ return this.instance.legacyObjectUrisIdentifier;
81
+ }
39
82
  onFollow;
40
83
  onUnfollow;
41
84
  onAcceptFollow;
@@ -62,52 +105,19 @@ var BotImpl = class {
62
105
  this.properties = options.properties ?? {};
63
106
  this.#properties = null;
64
107
  this.followerPolicy = options.followerPolicy ?? "accept";
65
- this.customEmojis = {};
66
- this.repository = options.repository ?? new KvRepository(options.kv);
67
- this.software = options.software;
68
- this.pages = {
69
- color: "green",
70
- css: "",
71
- ...options.pages ?? {}
72
- };
73
- this.federation = createFederation({
108
+ this.instance = options.instance ?? new InstanceImpl({
74
109
  kv: options.kv,
110
+ repository: new MigrationGatedRepository(options.repository ?? new KvRepository(options.kv), this.identifier),
75
111
  queue: options.queue,
76
- userAgent: { software: `BotKit/${deno_default.version}` }
112
+ software: options.software,
113
+ behindProxy: options.behindProxy,
114
+ pages: options.pages,
115
+ collectionWindow: options.collectionWindow,
116
+ legacyObjectUris: { identifier: this.identifier },
117
+ compatMode: true
77
118
  });
78
- this.behindProxy = options.behindProxy ?? false;
79
- this.collectionWindow = options.collectionWindow ?? 50;
80
- this.initialize();
81
- }
82
- initialize() {
83
- this.federation.setActorDispatcher("/ap/actor/{identifier}", this.dispatchActor.bind(this)).mapHandle(this.mapHandle.bind(this)).setKeyPairsDispatcher(this.dispatchActorKeyPairs.bind(this));
84
- this.federation.setFollowersDispatcher("/ap/actor/{identifier}/followers", this.dispatchFollowers.bind(this)).setFirstCursor(this.getFollowersFirstCursor.bind(this)).setCounter(this.countFollowers.bind(this));
85
- this.federation.setOutboxDispatcher("/ap/actor/{identifier}/outbox", this.dispatchOutbox.bind(this)).setFirstCursor(this.getOutboxFirstCursor.bind(this)).setCounter(this.countOutbox.bind(this));
86
- this.federation.setObjectDispatcher(Follow, "/ap/follow/{id}", this.dispatchFollow.bind(this)).authorize(this.authorizeFollow.bind(this));
87
- this.federation.setObjectDispatcher(Create, "/ap/create/{id}", this.dispatchCreate.bind(this));
88
- this.federation.setObjectDispatcher(Article, "/ap/article/{id}", (ctx, values) => this.dispatchMessage(Article, ctx, values.id));
89
- this.federation.setObjectDispatcher(ChatMessage, "/ap/chat-message/{id}", (ctx, values) => this.dispatchMessage(ChatMessage, ctx, values.id));
90
- this.federation.setObjectDispatcher(Note, "/ap/note/{id}", (ctx, values) => this.dispatchMessage(Note, ctx, values.id));
91
- this.federation.setObjectDispatcher(Question, "/ap/question/{id}", (ctx, values) => this.dispatchMessage(Question, ctx, values.id));
92
- this.federation.setObjectDispatcher(Announce, "/ap/announce/{id}", this.dispatchAnnounce.bind(this));
93
- this.federation.setObjectDispatcher(Emoji, "/ap/emoji/{name}", this.dispatchEmoji.bind(this));
94
- this.federation.setInboxListeners("/ap/actor/{identifier}/inbox", "/ap/inbox").onUnverifiedActivity(this.onUnverifiedActivity.bind(this)).on(Follow, this.onFollowed.bind(this)).on(Undo, async (ctx, undo) => {
95
- const object = await undo.getObject(ctx);
96
- if (object instanceof Follow) await this.onUnfollowed(ctx, undo);
97
- else if (object instanceof Like) await this.onUnliked(ctx, undo);
98
- else {
99
- const logger = getLogger([
100
- "botkit",
101
- "bot",
102
- "inbox"
103
- ]);
104
- logger.warn("The Undo object {undoId} is not about Follow or Like: {object}.", {
105
- undoId: undo.id?.href,
106
- object
107
- });
108
- }
109
- }).on(Accept, this.onFollowAccepted.bind(this)).on(Reject, this.onFollowRejected.bind(this)).on(Create, this.onCreated.bind(this)).on(Announce, this.onAnnounced.bind(this)).on(Like, this.onLiked.bind(this)).setSharedKeyDispatcher(this.dispatchSharedKey.bind(this));
110
- if (this.software != null) this.federation.setNodeInfoDispatcher("/nodeinfo/2.1", this.dispatchNodeInfo.bind(this));
119
+ this.repository = this.instance.repository.forIdentifier(this.identifier);
120
+ if (!options.transient) this.instance.addBot(this);
111
121
  }
112
122
  async getActorSummary(session) {
113
123
  if (this.summary == null) return null;
@@ -163,7 +173,7 @@ var BotImpl = class {
163
173
  outbox: ctx.getOutboxUri(identifier),
164
174
  publicKey: keyPairs[0].cryptographicKey,
165
175
  assertionMethods: keyPairs.map((pair) => pair.multikey),
166
- url: new URL("/", ctx.origin)
176
+ url: this.instance.getBotWebUrl(this, ctx.origin)
167
177
  });
168
178
  }
169
179
  mapHandle(_ctx, username) {
@@ -266,11 +276,13 @@ var BotImpl = class {
266
276
  return await this.repository.countMessages();
267
277
  }
268
278
  async dispatchFollow(_ctx, values) {
279
+ if (values.identifier !== this.identifier) return null;
269
280
  const id = values.id;
270
281
  const follow = await this.repository.getSentFollow(id);
271
282
  return follow ?? null;
272
283
  }
273
284
  async authorizeFollow(ctx, values) {
285
+ if (values.identifier !== this.identifier) return false;
274
286
  const signedKeyOwner = await ctx.getSignedKeyOwner();
275
287
  if (signedKeyOwner == null || signedKeyOwner.id == null) return false;
276
288
  const id = values.id;
@@ -279,6 +291,7 @@ var BotImpl = class {
279
291
  return signedKeyOwner.id.href === follow.objectId?.href || signedKeyOwner.id.href === follow.actorId?.href;
280
292
  }
281
293
  async dispatchCreate(ctx, values) {
294
+ if (values.identifier !== this.identifier) return null;
282
295
  const activity = await this.repository.getMessage(values.id);
283
296
  if (!(activity instanceof Create)) return null;
284
297
  const isVisible = await this.getPermissionChecker(ctx);
@@ -296,21 +309,20 @@ var BotImpl = class {
296
309
  return object;
297
310
  }
298
311
  async dispatchAnnounce(ctx, values) {
312
+ if (values.identifier !== this.identifier) return null;
299
313
  const activity = await this.repository.getMessage(values.id);
300
314
  if (!(activity instanceof Announce)) return null;
301
315
  const isVisible = await this.getPermissionChecker(ctx);
302
316
  return isVisible(activity) ? activity : null;
303
317
  }
304
318
  dispatchEmoji(ctx, values) {
305
- const customEmoji = this.customEmojis[values.name];
306
- if (customEmoji == null) return null;
307
- return this.getEmoji(ctx, values.name, customEmoji);
319
+ return this.instance.dispatchEmoji(ctx, values);
308
320
  }
309
- dispatchSharedKey(_ctx) {
310
- return { identifier: this.identifier };
321
+ dispatchSharedKey(ctx) {
322
+ return this.instance.dispatchSharedKey(ctx);
311
323
  }
312
- onUnverifiedActivity(_ctx, activity, reason) {
313
- if (activity instanceof Delete && reason.type === "keyFetchError" && "status" in reason.result && reason.result.status === 410) return new Response(null, { status: 202 });
324
+ onUnverifiedActivity(ctx, activity, reason) {
325
+ return this.instance.onUnverifiedActivity(ctx, activity, reason);
314
326
  }
315
327
  async onFollowed(ctx, follow) {
316
328
  const botUri = ctx.getActorUri(this.identifier);
@@ -339,8 +351,8 @@ var BotImpl = class {
339
351
  }
340
352
  }
341
353
  async onFollowAccepted(ctx, accept) {
342
- const parsedObj = ctx.parseUri(accept.objectId);
343
- if (parsedObj?.type !== "object" || parsedObj.class !== Follow) return;
354
+ const parsedObj = parseLocalUri(ctx, accept.objectId, this.legacyObjectUrisIdentifier);
355
+ if (parsedObj?.type !== "object" || parsedObj.class !== Follow || parsedObj.values.identifier !== this.identifier) return;
344
356
  const follow = await this.repository.getSentFollow(parsedObj.values.id);
345
357
  if (follow == null) return;
346
358
  const followee = await follow.getObject(ctx);
@@ -352,8 +364,8 @@ var BotImpl = class {
352
364
  }
353
365
  }
354
366
  async onFollowRejected(ctx, reject) {
355
- const parsedObj = ctx.parseUri(reject.objectId);
356
- if (parsedObj?.type !== "object" || parsedObj.class !== Follow) return;
367
+ const parsedObj = parseLocalUri(ctx, reject.objectId, this.legacyObjectUrisIdentifier);
368
+ if (parsedObj?.type !== "object" || parsedObj.class !== Follow || parsedObj.values.identifier !== this.identifier) return;
357
369
  const id = parsedObj.values.id;
358
370
  const follow = await this.repository.getSentFollow(id);
359
371
  if (follow == null) return;
@@ -374,8 +386,8 @@ var BotImpl = class {
374
386
  if (messageCache != null) return messageCache;
375
387
  return messageCache = await createMessage(object, session, {});
376
388
  };
377
- const replyTarget = ctx.parseUri(object.replyTargetId);
378
- if (this.onVote != null && object instanceof Note && replyTarget?.type === "object" && messageClasses.includes(replyTarget.class) && object.name != null) {
389
+ const replyTarget = parseLocalUri(ctx, object.replyTargetId, this.legacyObjectUrisIdentifier);
390
+ if (this.onVote != null && object instanceof Note && replyTarget?.type === "object" && messageClasses.includes(replyTarget.class) && replyTarget.values.identifier === this.identifier && object.name != null) {
379
391
  if (create.actorId == null || create.actorId.href === session.actorId.href) return;
380
392
  const actorId = create.actorId;
381
393
  const actor = await create.getActor(ctx);
@@ -460,7 +472,7 @@ var BotImpl = class {
460
472
  }
461
473
  return;
462
474
  }
463
- if (this.onReply != null && replyTarget?.type === "object" && messageClasses.includes(replyTarget.class)) {
475
+ if (this.onReply != null && replyTarget?.type === "object" && messageClasses.includes(replyTarget.class) && replyTarget.values.identifier === this.identifier) {
464
476
  const message = await getMessage();
465
477
  if (message.visibility === "public" || message.visibility === "unlisted") await ctx.forwardActivity(this, "followers", {
466
478
  skipIfUnsigned: true,
@@ -475,8 +487,8 @@ var BotImpl = class {
475
487
  break;
476
488
  }
477
489
  if (quoteUrl == null) quoteUrl = object.quoteUrl;
478
- const quoteTarget = ctx.parseUri(quoteUrl);
479
- if (this.onQuote != null && quoteTarget?.type === "object" && messageClasses.includes(quoteTarget.class)) {
490
+ const quoteTarget = parseLocalUri(ctx, quoteUrl, this.legacyObjectUrisIdentifier);
491
+ if (this.onQuote != null && quoteTarget?.type === "object" && messageClasses.includes(quoteTarget.class) && quoteTarget.values.identifier === this.identifier) {
480
492
  const message = await getMessage();
481
493
  if (message.visibility === "public" || message.visibility === "unlisted") await ctx.forwardActivity(this, "followers", {
482
494
  skipIfUnsigned: true,
@@ -496,9 +508,9 @@ var BotImpl = class {
496
508
  }
497
509
  async onAnnounced(ctx, announce) {
498
510
  if (this.onSharedMessage == null || announce.id == null || announce.actorId == null) return;
499
- const objectUri = ctx.parseUri(announce.objectId);
511
+ const objectUri = parseLocalUri(ctx, announce.objectId, this.legacyObjectUrisIdentifier);
500
512
  let object = null;
501
- if (objectUri?.type === "object" && messageClasses.includes(objectUri.class)) {
513
+ if (objectUri?.type === "object" && messageClasses.includes(objectUri.class) && objectUri.values.identifier === this.identifier) {
502
514
  const msg = await this.repository.getMessage(objectUri.values.id);
503
515
  if (msg instanceof Create) object = await msg.getObject(ctx);
504
516
  } else object = await announce.getObject(ctx);
@@ -518,9 +530,10 @@ var BotImpl = class {
518
530
  }
519
531
  async #parseLike(ctx, like) {
520
532
  if (like.id == null || like.actorId == null) return void 0;
521
- const objectUri = ctx.parseUri(like.objectId);
533
+ const objectUri = parseLocalUri(ctx, like.objectId, this.legacyObjectUrisIdentifier);
522
534
  let object = null;
523
535
  if (objectUri?.type === "object" && messageClasses.includes(objectUri.class)) {
536
+ if (objectUri.values.identifier !== this.identifier) return void 0;
524
537
  const msg = await this.repository.getMessage(objectUri.values.id);
525
538
  if (msg instanceof Create) object = await msg.getObject(ctx);
526
539
  } else object = await like.getObject(ctx);
@@ -569,9 +582,10 @@ var BotImpl = class {
569
582
  }
570
583
  }
571
584
  if (emoji == null) return void 0;
572
- const objectUri = ctx.parseUri(react.objectId);
585
+ const objectUri = parseLocalUri(ctx, react.objectId, this.legacyObjectUrisIdentifier);
573
586
  let object = null;
574
587
  if (objectUri?.type === "object" && messageClasses.includes(objectUri.class)) {
588
+ if (objectUri.values.identifier !== this.identifier) return void 0;
575
589
  const msg = await this.repository.getMessage(objectUri.values.id);
576
590
  if (msg instanceof Create) object = await msg.getObject(ctx);
577
591
  } else object = await react.getObject(ctx);
@@ -608,119 +622,327 @@ var BotImpl = class {
608
622
  const { session, reaction } = sessionAndReaction;
609
623
  await this.onUnreact(session, reaction);
610
624
  }
611
- dispatchNodeInfo(_ctx) {
612
- return {
613
- software: this.software,
614
- protocols: ["activitypub"],
615
- services: { outbound: ["atom1.0"] },
616
- usage: {
617
- users: {
618
- total: 1,
619
- activeMonth: 1,
620
- activeHalfyear: 1
621
- },
622
- localPosts: 0,
623
- localComments: 0
624
- }
625
- };
625
+ dispatchNodeInfo(ctx) {
626
+ return this.instance.dispatchNodeInfo(ctx);
626
627
  }
627
628
  getSession(origin, contextData) {
628
629
  const ctx = typeof origin === "string" || origin instanceof URL ? this.federation.createContext(new URL(origin), contextData) : origin;
629
630
  return new SessionImpl(this, ctx);
630
631
  }
631
- async addCollectionInverseProperty(request, contextData, response) {
632
- if (!response.ok) return response;
633
- const ctx = this.federation.createContext(request, contextData);
634
- const parsed = ctx.parseUri(new URL(request.url));
635
- if (parsed == null || parsed.type !== "outbox" && parsed.type !== "followers" || parsed.identifier == null) return response;
636
- const contentType = response.headers.get("Content-Type");
637
- if (contentType == null || !contentType.startsWith("application/activity+json") && !contentType.startsWith("application/ld+json")) return response;
638
- const body = await response.json();
639
- if (typeof body !== "object" || body == null || Array.isArray(body)) return new Response(JSON.stringify(body), {
640
- headers: response.headers,
641
- status: response.status,
642
- statusText: response.statusText
643
- });
644
- const property = parsed.type === "outbox" ? "outboxOf" : "followersOf";
645
- const actorUri = ctx.getActorUri(parsed.identifier).href;
646
- if (body[property] === actorUri) return new Response(JSON.stringify(body), {
647
- headers: response.headers,
648
- status: response.status,
649
- statusText: response.statusText
650
- });
651
- const headers = new Headers(response.headers);
652
- headers.delete("Content-Length");
653
- return new Response(JSON.stringify({
654
- ...body,
655
- [property]: actorUri
656
- }), {
657
- headers,
658
- status: response.status,
659
- statusText: response.statusText
660
- });
632
+ addCollectionInverseProperty(request, contextData, response) {
633
+ return this.instance.addCollectionInverseProperty(request, contextData, response);
661
634
  }
662
- async fetch(request, contextData) {
663
- if (this.behindProxy) request = await getXForwardedRequest(request);
664
- const url = new URL(request.url);
665
- if (url.pathname.startsWith("/.well-known/") || url.pathname.startsWith("/ap/") || url.pathname.startsWith("/nodeinfo/")) {
666
- const response = await this.federation.fetch(request, { contextData });
667
- return await this.addCollectionInverseProperty(request, contextData, response);
668
- }
669
- const match = /^\/emojis\/([a-z0-9-_]+)(?:$|\.)/.exec(url.pathname);
670
- if (match != null) {
671
- const customEmoji = this.customEmojis[match[1]];
672
- if (customEmoji == null || !("file" in customEmoji)) return new Response("Not Found", { status: 404 });
673
- let file;
674
- try {
675
- file = await fs.open(customEmoji.file, "r");
676
- } catch (error) {
677
- if (typeof error === "object" && error != null && "code" in error && error.code === "ENOENT") return new Response("Not Found", { status: 404 });
678
- throw error;
679
- }
680
- const fileInfo = await file.stat();
681
- return new Response(file.readableWebStream(), { headers: {
682
- "Content-Type": customEmoji.type,
683
- "Content-Length": fileInfo.size.toString(),
684
- "Cache-Control": "public, max-age=31536000, immutable",
685
- "Last-Modified": (fileInfo.mtime ?? /* @__PURE__ */ new Date()).toUTCString(),
686
- "ETag": `"${fileInfo.mtime?.getTime().toString(36)}${fileInfo.size.toString(36)}"`
687
- } });
688
- }
689
- return await app.fetch(request, {
690
- bot: this,
691
- contextData
692
- });
635
+ fetch(request, contextData) {
636
+ return this.instance.fetch(request, contextData);
693
637
  }
694
638
  getEmoji(ctx, name, data) {
695
- let url;
696
- if ("url" in data) url = new URL(data.url);
697
- else {
698
- const t = mimeDb[data.type];
699
- url = new URL(`/emojis/${name}${t == null || t.extensions == null || t.extensions.length < 1 ? "" : `.${t.extensions[0]}`}`, ctx.origin);
700
- }
701
- return new Emoji({
702
- id: ctx.getObjectUri(Emoji, { name }),
703
- name: `:${name}:`,
704
- icon: new Image({
705
- mediaType: data.type,
706
- url
707
- })
708
- });
639
+ return this.instance.getEmoji(ctx, name, data);
709
640
  }
710
641
  addCustomEmoji(name, data) {
711
- if (!name.match(/^[a-z0-9-_]+$/i)) throw new TypeError(`Invalid custom emoji name: ${name}. It must match /^[a-z0-9-_]+$/i.`);
712
- else if (name in this.customEmojis) throw new TypeError(`Duplicate custom emoji name: ${name}`);
713
- else if (!data.type.startsWith("image/")) throw new TypeError(`Unsupported media type: ${data.type}`);
714
- this.customEmojis[name] = data;
715
- return (session) => this.getEmoji(session.context, name, data);
642
+ return this.instance.addCustomEmoji(name, data);
716
643
  }
717
644
  addCustomEmojis(emojis) {
718
- const emojiMap = {};
719
- for (const name in emojis) emojiMap[name] = this.addCustomEmoji(name, emojis[name]);
720
- return emojiMap;
645
+ return this.instance.addCustomEmojis(emojis);
646
+ }
647
+ };
648
+ /**
649
+ * Wraps a {@link BotImpl} instance with a plain object implementing
650
+ * the {@link Bot} interface. Since `deno serve` does not recognize a class
651
+ * instance having fetch(), we wrap a BotImpl instance with a plain object.
652
+ * See also https://github.com/denoland/deno/issues/24062
653
+ * @param bot The bot implementation to wrap.
654
+ * @returns The wrapped bot.
655
+ * @internal
656
+ */
657
+ function wrapBotImpl(bot) {
658
+ const wrapper = {
659
+ impl: bot,
660
+ get federation() {
661
+ return bot.federation;
662
+ },
663
+ get identifier() {
664
+ return bot.identifier;
665
+ },
666
+ getSession(a, b) {
667
+ return bot.getSession(a, b);
668
+ },
669
+ fetch(request, contextData) {
670
+ return bot.fetch(request, contextData);
671
+ },
672
+ addCustomEmojis(emojis) {
673
+ return bot.addCustomEmojis(emojis);
674
+ },
675
+ get onFollow() {
676
+ return bot.onFollow;
677
+ },
678
+ set onFollow(value) {
679
+ bot.onFollow = value;
680
+ },
681
+ get onUnfollow() {
682
+ return bot.onUnfollow;
683
+ },
684
+ set onUnfollow(value) {
685
+ bot.onUnfollow = value;
686
+ },
687
+ get onAcceptFollow() {
688
+ return bot.onAcceptFollow;
689
+ },
690
+ set onAcceptFollow(value) {
691
+ bot.onAcceptFollow = value;
692
+ },
693
+ get onRejectFollow() {
694
+ return bot.onRejectFollow;
695
+ },
696
+ set onRejectFollow(value) {
697
+ bot.onRejectFollow = value;
698
+ },
699
+ get onMention() {
700
+ return bot.onMention;
701
+ },
702
+ set onMention(value) {
703
+ bot.onMention = value;
704
+ },
705
+ get onReply() {
706
+ return bot.onReply;
707
+ },
708
+ set onReply(value) {
709
+ bot.onReply = value;
710
+ },
711
+ get onQuote() {
712
+ return bot.onQuote;
713
+ },
714
+ set onQuote(value) {
715
+ bot.onQuote = value;
716
+ },
717
+ get onMessage() {
718
+ return bot.onMessage;
719
+ },
720
+ set onMessage(value) {
721
+ bot.onMessage = value;
722
+ },
723
+ get onSharedMessage() {
724
+ return bot.onSharedMessage;
725
+ },
726
+ set onSharedMessage(value) {
727
+ bot.onSharedMessage = value;
728
+ },
729
+ get onLike() {
730
+ return bot.onLike;
731
+ },
732
+ set onLike(value) {
733
+ bot.onLike = value;
734
+ },
735
+ get onUnlike() {
736
+ return bot.onUnlike;
737
+ },
738
+ set onUnlike(value) {
739
+ bot.onUnlike = value;
740
+ },
741
+ get onReact() {
742
+ return bot.onReact;
743
+ },
744
+ set onReact(value) {
745
+ bot.onReact = value;
746
+ },
747
+ get onUnreact() {
748
+ return bot.onUnreact;
749
+ },
750
+ set onUnreact(value) {
751
+ bot.onUnreact = value;
752
+ },
753
+ get onVote() {
754
+ return bot.onVote;
755
+ },
756
+ set onVote(value) {
757
+ bot.onVote = value;
758
+ }
759
+ };
760
+ return wrapper;
761
+ }
762
+ /**
763
+ * A repository decorator that adopts legacy (pre-0.5) data for a bot actor
764
+ * before the first repository operation. The migration is kicked off at
765
+ * construction time and every operation awaits its completion, so data
766
+ * stored by BotKit 0.4 or earlier is visible from the start.
767
+ * @internal
768
+ */
769
+ var MigrationGatedRepository = class {
770
+ #repository;
771
+ #migration;
772
+ constructor(repository, identifier) {
773
+ this.#repository = repository;
774
+ this.#migration = repository.migrate?.(identifier) ?? Promise.resolve();
775
+ this.#migration.catch(() => {});
776
+ }
777
+ async setKeyPairs(identifier, keyPairs) {
778
+ await this.#migration;
779
+ return await this.#repository.setKeyPairs(identifier, keyPairs);
780
+ }
781
+ async getKeyPairs(identifier) {
782
+ await this.#migration;
783
+ return await this.#repository.getKeyPairs(identifier);
784
+ }
785
+ async addMessage(identifier, id, activity) {
786
+ await this.#migration;
787
+ return await this.#repository.addMessage(identifier, id, activity);
788
+ }
789
+ async updateMessage(identifier, id, updater) {
790
+ await this.#migration;
791
+ return await this.#repository.updateMessage(identifier, id, updater);
792
+ }
793
+ async removeMessage(identifier, id) {
794
+ await this.#migration;
795
+ return await this.#repository.removeMessage(identifier, id);
796
+ }
797
+ async *getMessages(identifier, options) {
798
+ await this.#migration;
799
+ yield* this.#repository.getMessages(identifier, options);
800
+ }
801
+ async getMessage(identifier, id) {
802
+ await this.#migration;
803
+ return await this.#repository.getMessage(identifier, id);
804
+ }
805
+ async countMessages(identifier) {
806
+ await this.#migration;
807
+ return await this.#repository.countMessages(identifier);
808
+ }
809
+ async addFollower(identifier, followId, follower) {
810
+ await this.#migration;
811
+ return await this.#repository.addFollower(identifier, followId, follower);
812
+ }
813
+ async removeFollower(identifier, followId, followerId) {
814
+ await this.#migration;
815
+ return await this.#repository.removeFollower(identifier, followId, followerId);
816
+ }
817
+ async hasFollower(identifier, followerId) {
818
+ await this.#migration;
819
+ return await this.#repository.hasFollower(identifier, followerId);
820
+ }
821
+ async *getFollowers(identifier, options) {
822
+ await this.#migration;
823
+ yield* this.#repository.getFollowers(identifier, options);
824
+ }
825
+ async countFollowers(identifier) {
826
+ await this.#migration;
827
+ return await this.#repository.countFollowers(identifier);
828
+ }
829
+ async addSentFollow(identifier, id, follow) {
830
+ await this.#migration;
831
+ return await this.#repository.addSentFollow(identifier, id, follow);
832
+ }
833
+ async removeSentFollow(identifier, id) {
834
+ await this.#migration;
835
+ return await this.#repository.removeSentFollow(identifier, id);
836
+ }
837
+ async getSentFollow(identifier, id) {
838
+ await this.#migration;
839
+ return await this.#repository.getSentFollow(identifier, id);
840
+ }
841
+ async addFollowee(identifier, followeeId, follow) {
842
+ await this.#migration;
843
+ return await this.#repository.addFollowee(identifier, followeeId, follow);
844
+ }
845
+ async removeFollowee(identifier, followeeId) {
846
+ await this.#migration;
847
+ return await this.#repository.removeFollowee(identifier, followeeId);
848
+ }
849
+ async getFollowee(identifier, followeeId) {
850
+ await this.#migration;
851
+ return await this.#repository.getFollowee(identifier, followeeId);
852
+ }
853
+ async *findFollowedBots(followeeId) {
854
+ await this.#migration;
855
+ yield* this.#repository.findFollowedBots(followeeId);
856
+ }
857
+ async vote(identifier, messageId, voterId, option) {
858
+ await this.#migration;
859
+ return await this.#repository.vote(identifier, messageId, voterId, option);
860
+ }
861
+ async countVoters(identifier, messageId) {
862
+ await this.#migration;
863
+ return await this.#repository.countVoters(identifier, messageId);
864
+ }
865
+ async countVotes(identifier, messageId) {
866
+ await this.#migration;
867
+ return await this.#repository.countVotes(identifier, messageId);
868
+ }
869
+ forIdentifier(identifier) {
870
+ return new ActorScopedRepository(this, identifier);
871
+ }
872
+ async migrate(identifier) {
873
+ await this.#migration;
874
+ await this.#repository.migrate?.(identifier);
875
+ }
876
+ };
877
+ /**
878
+ * The internal implementation of a {@link BotGroup}: a registry of event
879
+ * handlers shared by every bot its dispatcher resolves.
880
+ * @internal
881
+ */
882
+ var BotGroupImpl = class {
883
+ instance;
884
+ dispatcher;
885
+ mapUsername;
886
+ onFollow;
887
+ onUnfollow;
888
+ onAcceptFollow;
889
+ onRejectFollow;
890
+ onMention;
891
+ onReply;
892
+ onQuote;
893
+ onMessage;
894
+ onSharedMessage;
895
+ onLike;
896
+ onUnlike;
897
+ onReact;
898
+ onUnreact;
899
+ onVote;
900
+ constructor(instance, dispatcher, options = {}) {
901
+ this.instance = instance;
902
+ this.dispatcher = dispatcher;
903
+ this.mapUsername = options.mapUsername;
904
+ }
905
+ async getSession(origin, identifier, contextData) {
906
+ const ctx = this.instance.federation.createContext(new URL(origin), contextData);
907
+ const bot = await this.instance.resolveBot(ctx, identifier);
908
+ if (bot == null || !(bot instanceof GroupBotImpl) || bot.group !== this) throw new TypeError(`The group's dispatcher does not resolve the identifier: ${identifier}`);
909
+ return bot.getSession(ctx);
910
+ }
911
+ };
912
+ /**
913
+ * A transient per-bot view of a dynamically resolved bot. It behaves like
914
+ * a regular {@link BotImpl}, except that its event handlers are read live
915
+ * from the owning {@link BotGroupImpl} at dispatch time, so handlers
916
+ * registered on the group after a bot was resolved still fire. Views are
917
+ * not registered on the instance and live only as long as the resolution
918
+ * cache of the request that produced them.
919
+ * @internal
920
+ */
921
+ var GroupBotImpl = class extends BotImpl {
922
+ group;
923
+ constructor(group, identifier, profile) {
924
+ super({
925
+ instance: group.instance,
926
+ transient: true,
927
+ identifier,
928
+ kv: group.instance.kv,
929
+ class: profile.class,
930
+ username: profile.username,
931
+ name: profile.name,
932
+ summary: profile.summary,
933
+ icon: profile.icon,
934
+ image: profile.image,
935
+ properties: profile.properties,
936
+ followerPolicy: profile.followerPolicy
937
+ });
938
+ this.group = group;
939
+ for (const name of botEventHandlerNames) globalThis.Object.defineProperty(this, name, {
940
+ get: () => group[name],
941
+ configurable: true
942
+ });
721
943
  }
722
944
  };
723
945
 
724
946
  //#endregion
725
- export { BotImpl };
947
+ export { BotGroupImpl, BotImpl, GroupBotImpl, MigrationGatedRepository, botEventHandlerNames, wrapBotImpl };
726
948
  //# sourceMappingURL=bot-impl.js.map