@fedify/botkit 0.4.0-dev.182 → 0.4.0-dev.184
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bot-impl.d.ts +3 -1
- package/dist/bot-impl.d.ts.map +1 -1
- package/dist/bot-impl.js +40 -3
- package/dist/bot-impl.js.map +1 -1
- package/dist/bot-impl.test.js +125 -1
- package/dist/bot-impl.test.js.map +1 -1
- package/dist/deno.js +3 -3
- package/dist/deno.js.map +1 -1
- package/package.json +4 -4
package/dist/bot-impl.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { Repository } from "./repository.js";
|
|
|
9
9
|
import { Bot, CreateBotOptions, PagesOptions } from "./bot.js";
|
|
10
10
|
import { SessionImpl } from "./session-impl.js";
|
|
11
11
|
import { Accept, Activity, Actor, Announce, Application, Create, Emoji, EmojiReact, Follow, Image, Like, Link, Object as Object$1, PropertyValue, Recipient, Reject, Service, Undo } from "@fedify/vocab";
|
|
12
|
-
import { Context, Federation, InboxContext, NodeInfo, PageItems, RequestContext, Software } from "@fedify/fedify";
|
|
12
|
+
import { Context, Federation, InboxContext, NodeInfo, PageItems, RequestContext, Software, UnverifiedActivityReason } from "@fedify/fedify";
|
|
13
13
|
|
|
14
14
|
//#region src/bot-impl.d.ts
|
|
15
15
|
interface BotImplOptions<TContextData> extends CreateBotOptions<TContextData> {
|
|
@@ -86,6 +86,7 @@ declare class BotImpl<TContextData> implements Bot<TContextData> {
|
|
|
86
86
|
dispatchSharedKey(_ctx: Context<TContextData>): {
|
|
87
87
|
identifier: string;
|
|
88
88
|
};
|
|
89
|
+
onUnverifiedActivity(_ctx: RequestContext<TContextData>, activity: Activity, reason: UnverifiedActivityReason): Response | void;
|
|
89
90
|
onFollowed(ctx: InboxContext<TContextData>, follow: Follow): Promise<void>;
|
|
90
91
|
onUnfollowed(ctx: InboxContext<TContextData>, undo: Undo): Promise<void>;
|
|
91
92
|
onFollowAccepted(ctx: InboxContext<TContextData>, accept: Accept): Promise<void>;
|
|
@@ -100,6 +101,7 @@ declare class BotImpl<TContextData> implements Bot<TContextData> {
|
|
|
100
101
|
getSession(origin: string | URL, contextData: TContextData): SessionImpl<TContextData>;
|
|
101
102
|
getSession(origin: string | URL): SessionImpl<TContextData>;
|
|
102
103
|
getSession(context: Context<TContextData>): SessionImpl<TContextData>;
|
|
104
|
+
addCollectionInverseProperty(request: Request, contextData: TContextData, response: Response): Promise<Response>;
|
|
103
105
|
fetch(request: Request, contextData: TContextData): Promise<Response>;
|
|
104
106
|
getEmoji(ctx: Context<TContextData>, name: string, data: CustomEmoji): Emoji;
|
|
105
107
|
addCustomEmoji<TEmojiName extends string>(name: TEmojiName, data: CustomEmoji): DeferredCustomEmoji<TContextData>;
|
package/dist/bot-impl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bot-impl.d.ts","names":[],"sources":["../src/bot-impl.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"bot-impl.d.ts","names":[],"sources":["../src/bot-impl.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/bot-impl.js
CHANGED
|
@@ -9,7 +9,7 @@ import { createMessage, getMessageVisibility, isMessageObject, isQuoteLink, mess
|
|
|
9
9
|
import { app } from "./pages.js";
|
|
10
10
|
import { KvRepository } from "./repository.js";
|
|
11
11
|
import { SessionImpl } from "./session-impl.js";
|
|
12
|
-
import { Accept, Announce, Article, ChatMessage, Create, 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";
|
|
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
13
|
import { createFederation, generateCryptoKeyPair } from "@fedify/fedify";
|
|
14
14
|
import { getLogger } from "@logtape/logtape";
|
|
15
15
|
import mimeDb from "mime-db";
|
|
@@ -91,7 +91,7 @@ var BotImpl = class {
|
|
|
91
91
|
this.federation.setObjectDispatcher(Question, "/ap/question/{id}", (ctx, values) => this.dispatchMessage(Question, ctx, values.id));
|
|
92
92
|
this.federation.setObjectDispatcher(Announce, "/ap/announce/{id}", this.dispatchAnnounce.bind(this));
|
|
93
93
|
this.federation.setObjectDispatcher(Emoji, "/ap/emoji/{name}", this.dispatchEmoji.bind(this));
|
|
94
|
-
this.federation.setInboxListeners("/ap/actor/{identifier}/inbox", "/ap/inbox").on(Follow, this.onFollowed.bind(this)).on(Undo, async (ctx, undo) => {
|
|
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
95
|
const object = await undo.getObject(ctx);
|
|
96
96
|
if (object instanceof Follow) await this.onUnfollowed(ctx, undo);
|
|
97
97
|
else if (object instanceof Like) await this.onUnliked(ctx, undo);
|
|
@@ -309,6 +309,9 @@ var BotImpl = class {
|
|
|
309
309
|
dispatchSharedKey(_ctx) {
|
|
310
310
|
return { identifier: this.identifier };
|
|
311
311
|
}
|
|
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 });
|
|
314
|
+
}
|
|
312
315
|
async onFollowed(ctx, follow) {
|
|
313
316
|
const botUri = ctx.getActorUri(this.identifier);
|
|
314
317
|
if (follow.actorId?.href === botUri.href || follow.objectId?.href !== botUri.href) return;
|
|
@@ -625,10 +628,44 @@ var BotImpl = class {
|
|
|
625
628
|
const ctx = typeof origin === "string" || origin instanceof URL ? this.federation.createContext(new URL(origin), contextData) : origin;
|
|
626
629
|
return new SessionImpl(this, ctx);
|
|
627
630
|
}
|
|
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
|
+
});
|
|
661
|
+
}
|
|
628
662
|
async fetch(request, contextData) {
|
|
629
663
|
if (this.behindProxy) request = await getXForwardedRequest(request);
|
|
630
664
|
const url = new URL(request.url);
|
|
631
|
-
if (url.pathname.startsWith("/.well-known/") || url.pathname.startsWith("/ap/") || url.pathname.startsWith("/nodeinfo/"))
|
|
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
|
+
}
|
|
632
669
|
const match = /^\/emojis\/([a-z0-9-_]+)(?:$|\.)/.exec(url.pathname);
|
|
633
670
|
if (match != null) {
|
|
634
671
|
const customEmoji = this.customEmojis[match[1]];
|
package/dist/bot-impl.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bot-impl.js","names":["options: BotImplOptions<TContextData>","#summary","#properties","metadata","APEmoji","RawLike","session: Session<TContextData>","tags: (Link | Object)[]","pairs: PropertyValue[]","ctx: Context<TContextData>","identifier: string","Object","_ctx: Context<TContextData>","username: string","cursor: string | null","followers: AsyncIterable<Actor>","nextCursor: string | null","items: Recipient[]","ctx: RequestContext<TContextData>","owner: Actor | null","object: Object","items: Activity[]","nextPublished: Temporal.Instant | null","_ctx: RequestContext<TContextData>","values: { id: string }","cls: new (values: any) => T","ctx: Context<TContextData> | RequestContext<TContextData>","id: string","values: { name: string }","ctx: InboxContext<TContextData>","follow: Follow","undo: Undo","accept: Accept","reject: Reject","create: Create","messageCache: Message<MessageClass, TContextData> | null","optionNotes: Note[]","options: string[]","updatedQuestion: Question","updatedOptionNotes: Note[]","vote: Vote<TContextData>","quoteUrl: URL | null","announce: Announce","object: Object | null","sharedMessage: SharedMessage<MessageClass, TContextData>","#parseLike","like: RawLike","#parseReaction","react: EmojiReact | RawLike","emoji: Emoji | APEmoji | undefined","origin: string | URL | Context<TContextData>","contextData?: TContextData","request: Request","contextData: TContextData","file: fs.FileHandle","name: string","data: CustomEmoji","url: URL","name: TEmojiName","emojis: Readonly<Record<TEmojiName, CustomEmoji>>"],"sources":["../src/bot-impl.ts"],"sourcesContent":["// BotKit by Fedify: A framework for creating ActivityPub bots\n// Copyright (C) 2025 Hong Minhee <https://hongminhee.org/>\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as\n// published by the Free Software Foundation, either version 3 of the\n// License, or (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\nimport {\n type Context,\n createFederation,\n type Federation,\n generateCryptoKeyPair,\n type InboxContext,\n type NodeInfo,\n type PageItems,\n type RequestContext,\n type Software,\n} from \"@fedify/fedify\";\nimport {\n Accept,\n type Activity,\n type Actor,\n Announce,\n type Application,\n Article,\n ChatMessage,\n Create,\n Emoji as APEmoji,\n EmojiReact,\n Endpoints,\n Follow,\n Image,\n isActor,\n Like as RawLike,\n Link,\n Mention,\n Note,\n Object,\n PropertyValue,\n PUBLIC_COLLECTION,\n Question,\n type Recipient,\n Reject,\n Service,\n Undo,\n Update,\n} from \"@fedify/vocab\";\nimport { getLogger } from \"@logtape/logtape\";\nimport mimeDb from \"mime-db\";\nimport fs from \"node:fs/promises\";\nimport { getXForwardedRequest } from \"x-forwarded-fetch\";\nimport metadata from \"../deno.json\" with { type: \"json\" };\nimport type { Bot, CreateBotOptions, PagesOptions } from \"./bot.ts\";\nimport {\n type CustomEmoji,\n type DeferredCustomEmoji,\n type Emoji,\n isEmoji,\n} from \"./emoji.ts\";\nimport type {\n AcceptEventHandler,\n FollowEventHandler,\n LikeEventHandler,\n MentionEventHandler,\n MessageEventHandler,\n QuoteEventHandler,\n ReactionEventHandler,\n RejectEventHandler,\n ReplyEventHandler,\n SharedMessageEventHandler,\n UndoneReactionEventHandler,\n UnfollowEventHandler,\n UnlikeEventHandler,\n VoteEventHandler,\n} from \"./events.ts\";\nimport { FollowRequestImpl } from \"./follow-impl.ts\";\nimport {\n createMessage,\n getMessageVisibility,\n isMessageObject,\n isQuoteLink,\n messageClasses,\n} from \"./message-impl.ts\";\nimport type { Message, MessageClass, SharedMessage } from \"./message.ts\";\nimport { app } from \"./pages.tsx\";\nimport type { Vote } from \"./poll.ts\";\nimport type { Like, Reaction } from \"./reaction.ts\";\nimport { KvRepository, type Repository, type Uuid } from \"./repository.ts\";\nimport { SessionImpl } from \"./session-impl.ts\";\nimport type { Session } from \"./session.ts\";\nimport type { Text } from \"./text.ts\";\n\nexport interface BotImplOptions<TContextData>\n extends CreateBotOptions<TContextData> {\n collectionWindow?: number;\n}\n\nexport class BotImpl<TContextData> implements Bot<TContextData> {\n readonly identifier: string;\n readonly class: typeof Service | typeof Application;\n readonly username: string;\n readonly name?: string;\n readonly summary?: Text<\"block\", TContextData>;\n #summary: { text: string; tags: (Link | Object)[] } | null;\n readonly icon?: URL | Image;\n readonly image?: URL | Image;\n readonly properties: Record<string, Text<\"block\" | \"inline\", TContextData>>;\n #properties: { pairs: PropertyValue[]; tags: (Link | Object)[] } | null;\n readonly followerPolicy: \"accept\" | \"reject\" | \"manual\";\n readonly customEmojis: Record<string, CustomEmoji>;\n readonly repository: Repository;\n readonly software?: Software;\n readonly behindProxy: boolean;\n readonly pages: Required<PagesOptions>;\n readonly collectionWindow: number;\n readonly federation: Federation<TContextData>;\n\n onFollow?: FollowEventHandler<TContextData>;\n onUnfollow?: UnfollowEventHandler<TContextData>;\n onAcceptFollow?: AcceptEventHandler<TContextData>;\n onRejectFollow?: RejectEventHandler<TContextData>;\n onMention?: MentionEventHandler<TContextData>;\n onReply?: ReplyEventHandler<TContextData>;\n onQuote?: QuoteEventHandler<TContextData>;\n onMessage?: MessageEventHandler<TContextData>;\n onSharedMessage?: SharedMessageEventHandler<TContextData>;\n onLike?: LikeEventHandler<TContextData>;\n onUnlike?: UnlikeEventHandler<TContextData>;\n onReact?: ReactionEventHandler<TContextData>;\n onUnreact?: UndoneReactionEventHandler<TContextData>;\n onVote?: VoteEventHandler<TContextData>;\n\n constructor(options: BotImplOptions<TContextData>) {\n this.identifier = options.identifier ?? \"bot\";\n this.class = options.class ?? Service;\n this.username = options.username;\n this.name = options.name;\n this.summary = options.summary;\n this.#summary = null;\n this.icon = options.icon;\n this.image = options.image;\n this.properties = options.properties ?? {};\n this.#properties = null;\n this.followerPolicy = options.followerPolicy ?? \"accept\";\n this.customEmojis = {};\n this.repository = options.repository ?? new KvRepository(options.kv);\n this.software = options.software;\n this.pages = {\n color: \"green\",\n css: \"\",\n ...(options.pages ?? {}),\n };\n this.federation = createFederation<TContextData>({\n kv: options.kv,\n queue: options.queue,\n userAgent: {\n software: `BotKit/${metadata.version}`,\n },\n });\n this.behindProxy = options.behindProxy ?? false;\n this.collectionWindow = options.collectionWindow ?? 50;\n this.initialize();\n }\n\n initialize(): void {\n this.federation\n .setActorDispatcher(\n \"/ap/actor/{identifier}\",\n this.dispatchActor.bind(this),\n )\n .mapHandle(this.mapHandle.bind(this))\n .setKeyPairsDispatcher(this.dispatchActorKeyPairs.bind(this));\n this.federation\n .setFollowersDispatcher(\n \"/ap/actor/{identifier}/followers\",\n this.dispatchFollowers.bind(this),\n )\n .setFirstCursor(this.getFollowersFirstCursor.bind(this))\n .setCounter(this.countFollowers.bind(this));\n this.federation\n .setOutboxDispatcher(\n \"/ap/actor/{identifier}/outbox\",\n this.dispatchOutbox.bind(this),\n )\n .setFirstCursor(this.getOutboxFirstCursor.bind(this))\n .setCounter(this.countOutbox.bind(this));\n this.federation\n .setObjectDispatcher(\n Follow,\n \"/ap/follow/{id}\",\n this.dispatchFollow.bind(this),\n )\n .authorize(this.authorizeFollow.bind(this));\n this.federation.setObjectDispatcher(\n Create,\n \"/ap/create/{id}\",\n this.dispatchCreate.bind(this),\n );\n this.federation.setObjectDispatcher(\n Article,\n \"/ap/article/{id}\",\n (ctx, values) => this.dispatchMessage(Article, ctx, values.id),\n );\n this.federation.setObjectDispatcher(\n ChatMessage,\n \"/ap/chat-message/{id}\",\n (ctx, values) => this.dispatchMessage(ChatMessage, ctx, values.id),\n );\n this.federation.setObjectDispatcher(\n Note,\n \"/ap/note/{id}\",\n (ctx, values) => this.dispatchMessage(Note, ctx, values.id),\n );\n this.federation.setObjectDispatcher(\n Question,\n \"/ap/question/{id}\",\n (ctx, values) => this.dispatchMessage(Question, ctx, values.id),\n );\n this.federation.setObjectDispatcher(\n Announce,\n \"/ap/announce/{id}\",\n this.dispatchAnnounce.bind(this),\n );\n this.federation.setObjectDispatcher(\n APEmoji,\n \"/ap/emoji/{name}\",\n this.dispatchEmoji.bind(this),\n );\n this.federation\n .setInboxListeners(\"/ap/actor/{identifier}/inbox\", \"/ap/inbox\")\n .on(Follow, this.onFollowed.bind(this))\n .on(Undo, async (ctx, undo) => {\n const object = await undo.getObject(ctx);\n if (object instanceof Follow) await this.onUnfollowed(ctx, undo);\n else if (object instanceof RawLike) await this.onUnliked(ctx, undo);\n else {\n const logger = getLogger([\"botkit\", \"bot\", \"inbox\"]);\n logger.warn(\n \"The Undo object {undoId} is not about Follow or Like: {object}.\",\n { undoId: undo.id?.href, object },\n );\n }\n })\n .on(Accept, this.onFollowAccepted.bind(this))\n .on(Reject, this.onFollowRejected.bind(this))\n .on(Create, this.onCreated.bind(this))\n .on(Announce, this.onAnnounced.bind(this))\n .on(RawLike, this.onLiked.bind(this))\n .setSharedKeyDispatcher(this.dispatchSharedKey.bind(this));\n if (this.software != null) {\n this.federation.setNodeInfoDispatcher(\n \"/nodeinfo/2.1\",\n this.dispatchNodeInfo.bind(this),\n );\n }\n }\n\n async getActorSummary(\n session: Session<TContextData>,\n ): Promise<{ text: string; tags: (Link | Object)[] } | null> {\n if (this.summary == null) return null;\n if (this.#summary == null) {\n let summary = \"\";\n const tags: (Link | Object)[] = [];\n for await (const chunk of this.summary.getHtml(session)) {\n summary += chunk;\n }\n for await (const tag of this.summary.getTags(session)) {\n tags.push(tag);\n }\n return this.#summary = { text: summary, tags };\n }\n return this.#summary;\n }\n\n async getActorProperties(\n session: Session<TContextData>,\n ): Promise<{ pairs: PropertyValue[]; tags: (Link | Object)[] }> {\n if (this.#properties != null) return this.#properties;\n const pairs: PropertyValue[] = [];\n const tags: (Link | Object)[] = [];\n for (const name in this.properties) {\n const value = this.properties[name];\n const pair = new PropertyValue({\n name,\n value: (await Array.fromAsync(value.getHtml(session))).join(\"\"),\n });\n pairs.push(pair);\n for await (const tag of value.getTags(session)) {\n tags.push(tag);\n }\n }\n return this.#properties = { pairs, tags };\n }\n\n async dispatchActor(\n ctx: Context<TContextData>,\n identifier: string,\n ): Promise<Actor | null> {\n if (this.identifier !== identifier) return null;\n const session = this.getSession(ctx);\n const summary = await this.getActorSummary(session);\n const { pairs, tags } = await this.getActorProperties(session);\n const allTags = summary == null ? tags : [...tags, ...summary.tags];\n const keyPairs = await ctx.getActorKeyPairs(identifier);\n return new this.class({\n id: ctx.getActorUri(identifier),\n preferredUsername: this.username,\n name: this.name,\n summary: summary == null ? null : summary.text,\n attachments: pairs,\n tags: allTags.filter((tag, i) =>\n allTags.findIndex((t) =>\n t.name?.toString() === tag.name?.toString() &&\n (t instanceof Link\n ? tag instanceof Link && t.href?.href === tag.href?.href\n : tag instanceof Object && t.id?.href === tag.id?.href)\n ) === i\n ),\n icon: this.icon == null\n ? null\n : this.icon instanceof Image\n ? this.icon\n : new Image({ url: this.icon }),\n image: this.image == null\n ? null\n : this.image instanceof Image\n ? this.image\n : new Image({ url: this.image }),\n inbox: ctx.getInboxUri(identifier),\n endpoints: new Endpoints({\n sharedInbox: ctx.getInboxUri(),\n }),\n followers: ctx.getFollowersUri(identifier),\n outbox: ctx.getOutboxUri(identifier),\n publicKey: keyPairs[0].cryptographicKey,\n assertionMethods: keyPairs.map((pair) => pair.multikey),\n url: new URL(\"/\", ctx.origin),\n });\n }\n\n mapHandle(_ctx: Context<TContextData>, username: string): string | null {\n return username === this.username ? this.identifier : null;\n }\n\n async dispatchActorKeyPairs(\n _ctx: Context<TContextData>,\n identifier: string,\n ): Promise<CryptoKeyPair[]> {\n if (identifier !== this.identifier) return [];\n let keyPairs = await this.repository.getKeyPairs();\n if (keyPairs == null) {\n const rsa = await generateCryptoKeyPair(\"RSASSA-PKCS1-v1_5\");\n const ed25519 = await generateCryptoKeyPair(\"Ed25519\");\n keyPairs = [rsa, ed25519];\n await this.repository.setKeyPairs(keyPairs);\n }\n return keyPairs;\n }\n\n async dispatchFollowers(\n _ctx: Context<TContextData>,\n identifier: string,\n cursor: string | null,\n ): Promise<PageItems<Recipient> | null> {\n if (identifier !== this.identifier) return null;\n let followers: AsyncIterable<Actor>;\n let nextCursor: string | null;\n if (cursor == null) {\n followers = this.repository.getFollowers();\n nextCursor = null;\n } else {\n const offset = cursor.match(/^\\d+$/) ? parseInt(cursor) : 0;\n followers = this.repository.getFollowers({\n offset,\n limit: this.collectionWindow,\n });\n nextCursor = (offset + this.collectionWindow).toString();\n }\n const items: Recipient[] = [];\n let i = 0;\n for await (const follower of followers) {\n items.push(follower);\n i++;\n }\n if (i < this.collectionWindow) nextCursor = null;\n return { items, nextCursor };\n }\n\n getFollowersFirstCursor(\n _ctx: Context<TContextData>,\n identifier: string,\n ): string | null {\n if (identifier !== this.identifier) return null;\n return \"0\";\n }\n\n async countFollowers(\n _ctx: Context<TContextData>,\n identifier: string,\n ): Promise<number | null> {\n if (identifier !== this.identifier) return null;\n return await this.repository.countFollowers();\n }\n\n async getPermissionChecker(\n ctx: RequestContext<TContextData>,\n ): Promise<(object: Object) => boolean> {\n let owner: Actor | null;\n try {\n owner = await ctx.getSignedKeyOwner();\n } catch {\n owner = null;\n }\n let follower = false;\n const ownerUri = owner?.id;\n if (ownerUri != null) {\n follower = await this.repository.hasFollower(ownerUri);\n }\n const followersUri = ctx.getFollowersUri(this.identifier);\n return (object: Object): boolean => {\n const recipients = [...object.toIds, ...object.ccIds].map((u) => u.href);\n if (recipients.includes(PUBLIC_COLLECTION.href)) return true;\n if (recipients.includes(followersUri.href) && follower) return true;\n return ownerUri == null ? false : recipients.includes(ownerUri.href);\n };\n }\n\n async dispatchOutbox(\n ctx: RequestContext<TContextData>,\n identifier: string,\n cursor: string | null,\n ): Promise<PageItems<Activity> | null> {\n if (identifier !== this.identifier) return null;\n const activities = this.repository.getMessages({\n order: \"newest\",\n until: cursor == null || cursor === \"\"\n ? undefined\n : Temporal.Instant.from(cursor),\n limit: cursor == null ? undefined : this.collectionWindow + 1,\n });\n const items: Activity[] = [];\n const isVisible = await this.getPermissionChecker(ctx);\n let i = 0;\n let nextPublished: Temporal.Instant | null = null;\n for await (const activity of activities) {\n if (cursor != null && i >= this.collectionWindow) {\n nextPublished = activity.published ??\n (await activity.getObject())?.published ?? null;\n break;\n }\n if (isVisible(activity)) items.push(activity);\n i++;\n }\n return { items, nextCursor: nextPublished?.toString() ?? null };\n }\n\n getOutboxFirstCursor(\n _ctx: Context<TContextData>,\n identifier: string,\n ): string | null {\n if (identifier !== this.identifier) return null;\n return \"\";\n }\n\n async countOutbox(\n _ctx: Context<TContextData>,\n identifier: string,\n ): Promise<number | null> {\n if (identifier !== this.identifier) return null;\n return await this.repository.countMessages();\n }\n\n async dispatchFollow(\n _ctx: RequestContext<TContextData>,\n values: { id: string },\n ): Promise<Follow | null> {\n const id = values.id as Uuid;\n const follow = await this.repository.getSentFollow(id);\n return follow ?? null;\n }\n\n async authorizeFollow(\n ctx: RequestContext<TContextData>,\n values: { id: string },\n ): Promise<boolean> {\n const signedKeyOwner = await ctx.getSignedKeyOwner();\n if (signedKeyOwner == null || signedKeyOwner.id == null) return false;\n const id = values.id as Uuid;\n const follow = await this.repository.getSentFollow(id);\n if (follow == null) return false;\n return signedKeyOwner.id.href === follow.objectId?.href ||\n signedKeyOwner.id.href === follow.actorId?.href;\n }\n\n async dispatchCreate(\n ctx: RequestContext<TContextData>,\n values: { id: string },\n ): Promise<Create | null> {\n const activity = await this.repository.getMessage(values.id as Uuid);\n if (!(activity instanceof Create)) return null;\n const isVisible = await this.getPermissionChecker(ctx);\n return isVisible(activity) ? activity : null;\n }\n\n async dispatchMessage<T extends MessageClass>(\n // deno-lint-ignore no-explicit-any\n cls: new (values: any) => T,\n ctx: Context<TContextData> | RequestContext<TContextData>,\n id: string,\n ): Promise<T | null> {\n const activity = await this.repository.getMessage(id as Uuid);\n if (!(activity instanceof Create)) return null;\n if (\"request\" in ctx) {\n // TODO: Split this method into two\n const isVisible = await this.getPermissionChecker(ctx);\n if (!isVisible(activity)) return null;\n }\n const object = await activity.getObject(ctx);\n if (object == null || !(object instanceof cls)) return null;\n return object;\n }\n\n async dispatchAnnounce(\n ctx: RequestContext<TContextData>,\n values: { id: string },\n ): Promise<Announce | null> {\n const activity = await this.repository.getMessage(values.id as Uuid);\n if (!(activity instanceof Announce)) return null;\n const isVisible = await this.getPermissionChecker(ctx);\n return isVisible(activity) ? activity : null;\n }\n\n dispatchEmoji(\n ctx: Context<TContextData>,\n values: { name: string },\n ): APEmoji | null {\n const customEmoji = this.customEmojis[values.name];\n if (customEmoji == null) return null;\n return this.getEmoji(ctx, values.name, customEmoji);\n }\n\n dispatchSharedKey(_ctx: Context<TContextData>): { identifier: string } {\n return { identifier: this.identifier };\n }\n\n async onFollowed(\n ctx: InboxContext<TContextData>,\n follow: Follow,\n ): Promise<void> {\n const botUri = ctx.getActorUri(this.identifier);\n if (\n follow.actorId?.href === botUri.href ||\n follow.objectId?.href !== botUri.href\n ) {\n return;\n }\n const follower = await follow.getActor({\n contextLoader: ctx.contextLoader,\n documentLoader: ctx.documentLoader,\n suppressError: true,\n });\n if (follower == null || follower.id == null) return;\n const session = this.getSession(ctx);\n const followRequest = new FollowRequestImpl<TContextData>(\n session,\n follow,\n follower,\n );\n await this.onFollow?.(session, followRequest);\n if (followRequest.state === \"pending\") {\n if (this.followerPolicy === \"accept\") await followRequest.accept();\n else if (this.followerPolicy === \"reject\") await followRequest.reject();\n }\n }\n\n async onUnfollowed(\n ctx: InboxContext<TContextData>,\n undo: Undo,\n ): Promise<void> {\n const followId = undo.objectId;\n if (followId == null || undo.actorId == null) return;\n const follower = await this.repository.removeFollower(\n followId,\n undo.actorId,\n );\n if (this.onUnfollow != null && follower != null) {\n const session = this.getSession(ctx);\n await this.onUnfollow(session, follower);\n }\n }\n\n async onFollowAccepted(\n ctx: InboxContext<TContextData>,\n accept: Accept,\n ): Promise<void> {\n const parsedObj = ctx.parseUri(accept.objectId);\n if (parsedObj?.type !== \"object\" || parsedObj.class !== Follow) return;\n const follow = await this.repository.getSentFollow(\n parsedObj.values.id as Uuid,\n );\n if (follow == null) return;\n const followee = await follow.getObject(ctx);\n if (\n !isActor(followee) || followee.id == null ||\n followee.id.href !== accept.actorId?.href\n ) {\n return;\n }\n await this.repository.addFollowee(followee.id, follow);\n if (this.onAcceptFollow != null) {\n const session = this.getSession(ctx);\n await this.onAcceptFollow(session, followee);\n }\n }\n\n async onFollowRejected(\n ctx: InboxContext<TContextData>,\n reject: Reject,\n ): Promise<void> {\n const parsedObj = ctx.parseUri(reject.objectId);\n if (parsedObj?.type !== \"object\" || parsedObj.class !== Follow) return;\n const id = parsedObj.values.id as Uuid;\n const follow = await this.repository.getSentFollow(id);\n if (follow == null) return;\n const followee = await follow.getObject(ctx);\n if (\n !isActor(followee) || followee.id == null ||\n followee.id.href !== reject.actorId?.href\n ) {\n return;\n }\n await this.repository.removeSentFollow(id);\n if (this.onRejectFollow != null) {\n const session = this.getSession(ctx);\n await this.onRejectFollow(session, followee);\n }\n }\n\n async onCreated(\n ctx: InboxContext<TContextData>,\n create: Create,\n ): Promise<void> {\n const object = await create.getObject(ctx);\n if (\n !(object instanceof Article || object instanceof ChatMessage ||\n object instanceof Note || object instanceof Question) ||\n object.attributionId?.href !== create.actorId?.href\n ) {\n return;\n }\n const session = this.getSession(ctx);\n let messageCache: Message<MessageClass, TContextData> | null = null;\n const getMessage = async () => {\n if (messageCache != null) return messageCache;\n return messageCache = await createMessage(object, session, {});\n };\n const replyTarget = ctx.parseUri(object.replyTargetId);\n if (\n this.onVote != null &&\n object instanceof Note && replyTarget?.type === \"object\" &&\n // @ts-ignore: replyTarget.class satisfies (typeof messageClasses)[number]\n messageClasses.includes(replyTarget.class) &&\n object.name != null\n ) {\n if (\n create.actorId == null || create.actorId.href === session.actorId.href\n ) {\n return;\n }\n const actorId = create.actorId;\n const actor = await create.getActor(ctx);\n if (actor == null) return;\n const messageId = replyTarget.values.id as Uuid;\n const pollMessage = await this.repository.getMessage(messageId);\n if (!(pollMessage instanceof Create)) return;\n const question = await pollMessage.getObject(ctx);\n if (\n !(question instanceof Question) || question.endTime == null ||\n Temporal.Instant.compare(question.endTime, Temporal.Now.instant()) < 0\n ) {\n return;\n }\n const optionNotes: Note[] = [];\n const options: string[] = [];\n for await (const note of question.getInclusiveOptions(ctx)) {\n if (!(note instanceof Note)) continue;\n optionNotes.push(note);\n if (note.name != null) options.push(note.name.toString());\n }\n const multiple = options.length > 0;\n for await (const note of question.getExclusiveOptions(ctx)) {\n if (!(note instanceof Note)) continue;\n optionNotes.push(note);\n if (note.name != null) options.push(note.name.toString());\n }\n const option = object.name.toString();\n if (!options.includes(option)) return;\n let updatedQuestion: Question = question;\n let updatedPollMessage = pollMessage;\n await this.repository.vote(messageId, actorId, option);\n await this.repository.updateMessage(\n replyTarget.values.id as Uuid,\n async () => {\n const votes = await this.repository.countVotes(messageId);\n const updatedOptionNotes: Note[] = [...optionNotes];\n let i = 0;\n for (const note of updatedOptionNotes) {\n if (note.name != null) {\n const replies = await note.getReplies(ctx);\n if (replies != null && replies.totalItems != null) {\n updatedOptionNotes[i] = note.clone({\n replies: replies.clone({\n totalItems: votes[note.name.toString()],\n }),\n });\n }\n }\n i++;\n }\n updatedQuestion = question.clone({\n inclusiveOptions: multiple ? updatedOptionNotes : [],\n exclusiveOptions: !multiple ? updatedOptionNotes : [],\n voters: await this.repository.countVoters(messageId),\n });\n return updatedPollMessage = pollMessage.clone({\n object: updatedQuestion,\n });\n },\n );\n const message = await createMessage(updatedQuestion, session, {});\n const vote: Vote<TContextData> = {\n raw: object,\n actor,\n message,\n poll: {\n multiple,\n options,\n endTime: question.endTime,\n },\n option,\n };\n await this.onVote(session, vote);\n const update = new Update({\n id: new URL(\n `#update-votes/${crypto.randomUUID()}`,\n updatedQuestion.id ?? ctx.origin,\n ),\n actor: ctx.getActorUri(this.identifier),\n object: updatedPollMessage.id,\n tos: updatedPollMessage.toIds,\n ccs: updatedPollMessage.ccIds,\n });\n if (message.visibility === \"direct\") {\n await ctx.forwardActivity(this, [...message.mentions], {\n skipIfUnsigned: true,\n excludeBaseUris: [new URL(ctx.origin)],\n });\n await ctx.sendActivity(\n this,\n [...message.mentions],\n update,\n { excludeBaseUris: [new URL(ctx.origin)] },\n );\n } else {\n await ctx.forwardActivity(this, \"followers\", {\n skipIfUnsigned: true,\n preferSharedInbox: true,\n excludeBaseUris: [new URL(ctx.origin)],\n });\n await ctx.sendActivity(\n this,\n \"followers\",\n update,\n {\n preferSharedInbox: true,\n excludeBaseUris: [new URL(ctx.origin)],\n },\n );\n }\n return;\n }\n if (\n this.onReply != null &&\n replyTarget?.type === \"object\" &&\n // @ts-ignore: replyTarget.class satisfies (typeof messageClasses)[number]\n messageClasses.includes(replyTarget.class)\n ) {\n const message = await getMessage();\n if (\n message.visibility === \"public\" || message.visibility === \"unlisted\"\n ) {\n await ctx.forwardActivity(this, \"followers\", {\n skipIfUnsigned: true,\n preferSharedInbox: true,\n excludeBaseUris: [new URL(ctx.origin)],\n });\n }\n await this.onReply(session, message);\n }\n let quoteUrl: URL | null = null;\n // FIXME: eliminate this duplication\n for await (const tag of object.getTags(ctx)) {\n if (tag instanceof Link && isQuoteLink(tag)) {\n quoteUrl = tag.href;\n break;\n }\n }\n if (quoteUrl == null) quoteUrl = object.quoteUrl;\n const quoteTarget = ctx.parseUri(quoteUrl);\n if (\n this.onQuote != null &&\n quoteTarget?.type === \"object\" &&\n // @ts-ignore: quoteTarget.class satisfies (typeof messageClasses)[number]\n messageClasses.includes(quoteTarget.class)\n ) {\n const message = await getMessage();\n if (\n message.visibility === \"public\" || message.visibility === \"unlisted\"\n ) {\n await ctx.forwardActivity(this, \"followers\", {\n skipIfUnsigned: true,\n preferSharedInbox: true,\n excludeBaseUris: [new URL(ctx.origin)],\n });\n }\n await this.onQuote(session, message);\n }\n for await (const tag of object.getTags(ctx)) {\n if (\n tag instanceof Mention && tag.href != null && this.onMention != null\n ) {\n const parsed = ctx.parseUri(tag.href);\n if (\n parsed?.type === \"actor\" && parsed.identifier === this.identifier\n ) {\n await this.onMention(session, await getMessage());\n break;\n }\n }\n }\n if (this.onMessage != null) {\n await this.onMessage(session, await getMessage());\n }\n }\n\n async onAnnounced(\n ctx: InboxContext<TContextData>,\n announce: Announce,\n ): Promise<void> {\n if (\n this.onSharedMessage == null || announce.id == null ||\n announce.actorId == null\n ) return;\n const objectUri = ctx.parseUri(announce.objectId);\n let object: Object | null = null;\n if (\n objectUri?.type === \"object\" &&\n // deno-lint-ignore no-explicit-any\n messageClasses.includes(objectUri.class as any)\n ) {\n const msg = await this.repository.getMessage(objectUri.values.id as Uuid);\n if (msg instanceof Create) object = await msg.getObject(ctx);\n } else {\n object = await announce.getObject(ctx);\n }\n if (!isMessageObject(object)) return;\n const session = this.getSession(ctx);\n const actor = announce.actorId.href == session.actorId.href\n ? await session.getActor()\n : await announce.getActor(ctx);\n if (actor == null) return;\n const original = await createMessage(object, session, {});\n const sharedMessage: SharedMessage<MessageClass, TContextData> = {\n raw: announce,\n id: announce.id,\n actor,\n visibility: getMessageVisibility(announce.toIds, announce.ccIds, actor),\n original,\n };\n await this.onSharedMessage(session, sharedMessage);\n }\n\n async #parseLike(\n ctx: InboxContext<TContextData>,\n like: RawLike,\n ): Promise<\n { session: Session<TContextData>; like: Like<TContextData> } | undefined\n > {\n if (like.id == null || like.actorId == null) return undefined;\n const objectUri = ctx.parseUri(like.objectId);\n let object: Object | null = null;\n if (\n objectUri?.type === \"object\" &&\n // deno-lint-ignore no-explicit-any\n messageClasses.includes(objectUri.class as any)\n ) {\n const msg = await this.repository.getMessage(objectUri.values.id as Uuid);\n if (msg instanceof Create) object = await msg.getObject(ctx);\n } else {\n object = await like.getObject(ctx);\n }\n if (!isMessageObject(object)) return undefined;\n const session = this.getSession(ctx);\n const actor = like.actorId.href == session.actorId.href\n ? await session.getActor()\n : await like.getActor(ctx);\n if (actor == null) return;\n const message = await createMessage(object, session, {});\n return {\n session,\n like: {\n raw: like,\n id: like.id,\n actor,\n message,\n },\n };\n }\n\n async onLiked(ctx: InboxContext<TContextData>, like: RawLike): Promise<void> {\n if (like.name != null) return this.onReacted(ctx, like);\n if (this.onLike == null) return;\n const sessionAndLike = await this.#parseLike(ctx, like);\n if (sessionAndLike == null) return;\n const { session, like: likeObject } = sessionAndLike;\n await this.onLike(session, likeObject);\n }\n\n async onUnliked(ctx: InboxContext<TContextData>, undo: Undo): Promise<void> {\n const like = await undo.getObject(ctx);\n if (!(like instanceof RawLike)) return;\n if (like.name != null) return this.onUnreacted(ctx, undo);\n if (this.onUnlike == null) return;\n if (undo.actorId?.href !== like.actorId?.href) return;\n const sessionAndLike = await this.#parseLike(ctx, like);\n if (sessionAndLike == null) return;\n const { session, like: likeObject } = sessionAndLike;\n await this.onUnlike(session, likeObject);\n }\n\n async #parseReaction(\n ctx: InboxContext<TContextData>,\n react: EmojiReact | RawLike,\n ): Promise<\n | { session: Session<TContextData>; reaction: Reaction<TContextData> }\n | undefined\n > {\n if (react.id == null || react.actorId == null || react.name == null) {\n return undefined;\n }\n let emoji: Emoji | APEmoji | undefined;\n if (isEmoji(react.name)) {\n emoji = react.name;\n } else if (\n typeof react.name === \"string\" && react.name.startsWith(\":\") &&\n react.name.endsWith(\":\")\n ) {\n for await (const tag of react.getTags(ctx)) {\n if (tag instanceof APEmoji && tag.name === react.name) {\n emoji = tag;\n break;\n }\n }\n }\n if (emoji == null) return undefined;\n const objectUri = ctx.parseUri(react.objectId);\n let object: Object | null = null;\n if (\n objectUri?.type === \"object\" &&\n // deno-lint-ignore no-explicit-any\n messageClasses.includes(objectUri.class as any)\n ) {\n const msg = await this.repository.getMessage(objectUri.values.id as Uuid);\n if (msg instanceof Create) object = await msg.getObject(ctx);\n } else {\n object = await react.getObject(ctx);\n }\n if (!isMessageObject(object)) return undefined;\n const session = this.getSession(ctx);\n const actor = react.actorId.href == session.actorId.href\n ? await session.getActor()\n : await react.getActor(ctx);\n if (actor == null) return;\n const message = await createMessage(object, session, {});\n return {\n session,\n reaction: {\n raw: react,\n id: react.id,\n actor,\n message,\n emoji,\n },\n };\n }\n\n async onReacted(\n ctx: InboxContext<TContextData>,\n react: EmojiReact | RawLike,\n ): Promise<void> {\n if (this.onReact == null) return;\n const sessionAndReaction = await this.#parseReaction(ctx, react);\n if (sessionAndReaction == null) return;\n const { session, reaction } = sessionAndReaction;\n await this.onReact(session, reaction);\n }\n\n async onUnreacted(\n ctx: InboxContext<TContextData>,\n undo: Undo,\n ): Promise<void> {\n if (this.onUnreact == null) return;\n const react = await undo.getObject(ctx);\n if (!(react instanceof EmojiReact || react instanceof RawLike)) return;\n if (undo.actorId?.href !== react.actorId?.href) return;\n const sessionAndReaction = await this.#parseReaction(ctx, react);\n if (sessionAndReaction == null) return;\n const { session, reaction } = sessionAndReaction;\n await this.onUnreact(session, reaction);\n }\n\n dispatchNodeInfo(_ctx: Context<TContextData>): NodeInfo {\n return {\n software: this.software!,\n protocols: [\"activitypub\"],\n services: {\n outbound: [\"atom1.0\"], // TODO\n },\n usage: {\n users: {\n total: 1,\n activeMonth: 1, // FIXME\n activeHalfyear: 1, // FIXME\n },\n localPosts: 0, // FIXME\n localComments: 0,\n },\n };\n }\n\n getSession(\n origin: string | URL,\n contextData: TContextData,\n ): SessionImpl<TContextData>;\n getSession(origin: string | URL): SessionImpl<TContextData>;\n getSession(context: Context<TContextData>): SessionImpl<TContextData>;\n\n getSession(\n origin: string | URL | Context<TContextData>,\n contextData?: TContextData,\n ): SessionImpl<TContextData> {\n const ctx = typeof origin === \"string\" || origin instanceof URL\n ? this.federation.createContext(new URL(origin), contextData!)\n : origin;\n return new SessionImpl(this, ctx);\n }\n\n async fetch(request: Request, contextData: TContextData): Promise<Response> {\n if (this.behindProxy) {\n request = await getXForwardedRequest(request);\n }\n const url = new URL(request.url);\n if (\n url.pathname.startsWith(\"/.well-known/\") ||\n url.pathname.startsWith(\"/ap/\") ||\n url.pathname.startsWith(\"/nodeinfo/\")\n ) {\n return await this.federation.fetch(request, { contextData });\n }\n const match = /^\\/emojis\\/([a-z0-9-_]+)(?:$|\\.)/.exec(url.pathname);\n if (match != null) {\n const customEmoji = this.customEmojis[match[1]];\n if (customEmoji == null || !(\"file\" in customEmoji)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n let file: fs.FileHandle;\n try {\n file = await fs.open(customEmoji.file, \"r\");\n } catch (error) {\n if (\n typeof error === \"object\" && error != null && \"code\" in error &&\n error.code === \"ENOENT\"\n ) {\n return new Response(\"Not Found\", { status: 404 });\n }\n throw error;\n }\n const fileInfo = await file.stat();\n return new Response(file.readableWebStream(), {\n headers: {\n \"Content-Type\": customEmoji.type,\n \"Content-Length\": fileInfo.size.toString(),\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n \"Last-Modified\": (fileInfo.mtime ?? new Date()).toUTCString(),\n \"ETag\": `\"${fileInfo.mtime?.getTime().toString(36)}${\n fileInfo.size.toString(36)\n }\"`,\n },\n });\n }\n return await app.fetch(request, { bot: this, contextData });\n }\n\n getEmoji(\n ctx: Context<TContextData>,\n name: string,\n data: CustomEmoji,\n ): APEmoji {\n let url: URL;\n if (\"url\" in data) {\n url = new URL(data.url);\n } else {\n // @ts-ignore: data.type satisfies keyof typeof mimeDb\n const t = mimeDb[data.type];\n url = new URL(\n `/emojis/${name}${\n t == null || t.extensions == null || t.extensions.length < 1\n ? \"\"\n : `.${t.extensions[0]}`\n }`,\n ctx.origin,\n );\n }\n return new APEmoji({\n id: ctx.getObjectUri(APEmoji, { name }),\n name: `:${name}:`,\n icon: new Image({\n mediaType: data.type,\n url,\n }),\n });\n }\n\n addCustomEmoji<TEmojiName extends string>(\n name: TEmojiName,\n data: CustomEmoji,\n ): DeferredCustomEmoji<TContextData> {\n if (!name.match(/^[a-z0-9-_]+$/i)) {\n throw new TypeError(\n `Invalid custom emoji name: ${name}. It must match /^[a-z0-9-_]+$/i.`,\n );\n } else if (name in this.customEmojis) {\n throw new TypeError(`Duplicate custom emoji name: ${name}`);\n } else if (!data.type.startsWith(\"image/\")) {\n throw new TypeError(`Unsupported media type: ${data.type}`);\n }\n this.customEmojis[name] = data;\n return (session: Session<TContextData>) =>\n this.getEmoji(\n session.context,\n name,\n data,\n );\n }\n\n addCustomEmojis<TEmojiName extends string>(\n emojis: Readonly<Record<TEmojiName, CustomEmoji>>,\n ): Readonly<Record<TEmojiName, DeferredCustomEmoji<TContextData>>> {\n const emojiMap = {} as Record<\n TEmojiName,\n DeferredCustomEmoji<TContextData>\n >;\n for (const name in emojis) {\n emojiMap[name] = this.addCustomEmoji(name, emojis[name]);\n }\n return emojiMap;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bot-impl.js","names":["options: BotImplOptions<TContextData>","#summary","#properties","metadata","APEmoji","RawLike","session: Session<TContextData>","tags: (Link | Object)[]","pairs: PropertyValue[]","ctx: Context<TContextData>","identifier: string","Object","_ctx: Context<TContextData>","username: string","cursor: string | null","followers: AsyncIterable<Actor>","nextCursor: string | null","items: Recipient[]","ctx: RequestContext<TContextData>","owner: Actor | null","object: Object","items: Activity[]","nextPublished: Temporal.Instant | null","_ctx: RequestContext<TContextData>","values: { id: string }","cls: new (values: any) => T","ctx: Context<TContextData> | RequestContext<TContextData>","id: string","values: { name: string }","activity: Activity","reason: UnverifiedActivityReason","ctx: InboxContext<TContextData>","follow: Follow","undo: Undo","accept: Accept","reject: Reject","create: Create","messageCache: Message<MessageClass, TContextData> | null","optionNotes: Note[]","options: string[]","updatedQuestion: Question","updatedOptionNotes: Note[]","vote: Vote<TContextData>","quoteUrl: URL | null","announce: Announce","object: Object | null","sharedMessage: SharedMessage<MessageClass, TContextData>","#parseLike","like: RawLike","#parseReaction","react: EmojiReact | RawLike","emoji: Emoji | APEmoji | undefined","origin: string | URL | Context<TContextData>","contextData?: TContextData","request: Request","contextData: TContextData","response: Response","file: fs.FileHandle","name: string","data: CustomEmoji","url: URL","name: TEmojiName","emojis: Readonly<Record<TEmojiName, CustomEmoji>>"],"sources":["../src/bot-impl.ts"],"sourcesContent":["// BotKit by Fedify: A framework for creating ActivityPub bots\n// Copyright (C) 2025 Hong Minhee <https://hongminhee.org/>\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as\n// published by the Free Software Foundation, either version 3 of the\n// License, or (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\nimport {\n type Context,\n createFederation,\n type Federation,\n generateCryptoKeyPair,\n type InboxContext,\n type NodeInfo,\n type PageItems,\n type RequestContext,\n type Software,\n type UnverifiedActivityReason,\n} from \"@fedify/fedify\";\nimport {\n Accept,\n type Activity,\n type Actor,\n Announce,\n type Application,\n Article,\n ChatMessage,\n Create,\n Delete,\n Emoji as APEmoji,\n EmojiReact,\n Endpoints,\n Follow,\n Image,\n isActor,\n Like as RawLike,\n Link,\n Mention,\n Note,\n Object,\n PropertyValue,\n PUBLIC_COLLECTION,\n Question,\n type Recipient,\n Reject,\n Service,\n Undo,\n Update,\n} from \"@fedify/vocab\";\nimport { getLogger } from \"@logtape/logtape\";\nimport mimeDb from \"mime-db\";\nimport fs from \"node:fs/promises\";\nimport { getXForwardedRequest } from \"x-forwarded-fetch\";\nimport metadata from \"../deno.json\" with { type: \"json\" };\nimport type { Bot, CreateBotOptions, PagesOptions } from \"./bot.ts\";\nimport {\n type CustomEmoji,\n type DeferredCustomEmoji,\n type Emoji,\n isEmoji,\n} from \"./emoji.ts\";\nimport type {\n AcceptEventHandler,\n FollowEventHandler,\n LikeEventHandler,\n MentionEventHandler,\n MessageEventHandler,\n QuoteEventHandler,\n ReactionEventHandler,\n RejectEventHandler,\n ReplyEventHandler,\n SharedMessageEventHandler,\n UndoneReactionEventHandler,\n UnfollowEventHandler,\n UnlikeEventHandler,\n VoteEventHandler,\n} from \"./events.ts\";\nimport { FollowRequestImpl } from \"./follow-impl.ts\";\nimport {\n createMessage,\n getMessageVisibility,\n isMessageObject,\n isQuoteLink,\n messageClasses,\n} from \"./message-impl.ts\";\nimport type { Message, MessageClass, SharedMessage } from \"./message.ts\";\nimport { app } from \"./pages.tsx\";\nimport type { Vote } from \"./poll.ts\";\nimport type { Like, Reaction } from \"./reaction.ts\";\nimport { KvRepository, type Repository, type Uuid } from \"./repository.ts\";\nimport { SessionImpl } from \"./session-impl.ts\";\nimport type { Session } from \"./session.ts\";\nimport type { Text } from \"./text.ts\";\n\nexport interface BotImplOptions<TContextData>\n extends CreateBotOptions<TContextData> {\n collectionWindow?: number;\n}\n\nexport class BotImpl<TContextData> implements Bot<TContextData> {\n readonly identifier: string;\n readonly class: typeof Service | typeof Application;\n readonly username: string;\n readonly name?: string;\n readonly summary?: Text<\"block\", TContextData>;\n #summary: { text: string; tags: (Link | Object)[] } | null;\n readonly icon?: URL | Image;\n readonly image?: URL | Image;\n readonly properties: Record<string, Text<\"block\" | \"inline\", TContextData>>;\n #properties: { pairs: PropertyValue[]; tags: (Link | Object)[] } | null;\n readonly followerPolicy: \"accept\" | \"reject\" | \"manual\";\n readonly customEmojis: Record<string, CustomEmoji>;\n readonly repository: Repository;\n readonly software?: Software;\n readonly behindProxy: boolean;\n readonly pages: Required<PagesOptions>;\n readonly collectionWindow: number;\n readonly federation: Federation<TContextData>;\n\n onFollow?: FollowEventHandler<TContextData>;\n onUnfollow?: UnfollowEventHandler<TContextData>;\n onAcceptFollow?: AcceptEventHandler<TContextData>;\n onRejectFollow?: RejectEventHandler<TContextData>;\n onMention?: MentionEventHandler<TContextData>;\n onReply?: ReplyEventHandler<TContextData>;\n onQuote?: QuoteEventHandler<TContextData>;\n onMessage?: MessageEventHandler<TContextData>;\n onSharedMessage?: SharedMessageEventHandler<TContextData>;\n onLike?: LikeEventHandler<TContextData>;\n onUnlike?: UnlikeEventHandler<TContextData>;\n onReact?: ReactionEventHandler<TContextData>;\n onUnreact?: UndoneReactionEventHandler<TContextData>;\n onVote?: VoteEventHandler<TContextData>;\n\n constructor(options: BotImplOptions<TContextData>) {\n this.identifier = options.identifier ?? \"bot\";\n this.class = options.class ?? Service;\n this.username = options.username;\n this.name = options.name;\n this.summary = options.summary;\n this.#summary = null;\n this.icon = options.icon;\n this.image = options.image;\n this.properties = options.properties ?? {};\n this.#properties = null;\n this.followerPolicy = options.followerPolicy ?? \"accept\";\n this.customEmojis = {};\n this.repository = options.repository ?? new KvRepository(options.kv);\n this.software = options.software;\n this.pages = {\n color: \"green\",\n css: \"\",\n ...(options.pages ?? {}),\n };\n this.federation = createFederation<TContextData>({\n kv: options.kv,\n queue: options.queue,\n userAgent: {\n software: `BotKit/${metadata.version}`,\n },\n });\n this.behindProxy = options.behindProxy ?? false;\n this.collectionWindow = options.collectionWindow ?? 50;\n this.initialize();\n }\n\n initialize(): void {\n this.federation\n .setActorDispatcher(\n \"/ap/actor/{identifier}\",\n this.dispatchActor.bind(this),\n )\n .mapHandle(this.mapHandle.bind(this))\n .setKeyPairsDispatcher(this.dispatchActorKeyPairs.bind(this));\n this.federation\n .setFollowersDispatcher(\n \"/ap/actor/{identifier}/followers\",\n this.dispatchFollowers.bind(this),\n )\n .setFirstCursor(this.getFollowersFirstCursor.bind(this))\n .setCounter(this.countFollowers.bind(this));\n this.federation\n .setOutboxDispatcher(\n \"/ap/actor/{identifier}/outbox\",\n this.dispatchOutbox.bind(this),\n )\n .setFirstCursor(this.getOutboxFirstCursor.bind(this))\n .setCounter(this.countOutbox.bind(this));\n this.federation\n .setObjectDispatcher(\n Follow,\n \"/ap/follow/{id}\",\n this.dispatchFollow.bind(this),\n )\n .authorize(this.authorizeFollow.bind(this));\n this.federation.setObjectDispatcher(\n Create,\n \"/ap/create/{id}\",\n this.dispatchCreate.bind(this),\n );\n this.federation.setObjectDispatcher(\n Article,\n \"/ap/article/{id}\",\n (ctx, values) => this.dispatchMessage(Article, ctx, values.id),\n );\n this.federation.setObjectDispatcher(\n ChatMessage,\n \"/ap/chat-message/{id}\",\n (ctx, values) => this.dispatchMessage(ChatMessage, ctx, values.id),\n );\n this.federation.setObjectDispatcher(\n Note,\n \"/ap/note/{id}\",\n (ctx, values) => this.dispatchMessage(Note, ctx, values.id),\n );\n this.federation.setObjectDispatcher(\n Question,\n \"/ap/question/{id}\",\n (ctx, values) => this.dispatchMessage(Question, ctx, values.id),\n );\n this.federation.setObjectDispatcher(\n Announce,\n \"/ap/announce/{id}\",\n this.dispatchAnnounce.bind(this),\n );\n this.federation.setObjectDispatcher(\n APEmoji,\n \"/ap/emoji/{name}\",\n this.dispatchEmoji.bind(this),\n );\n this.federation\n .setInboxListeners(\"/ap/actor/{identifier}/inbox\", \"/ap/inbox\")\n .onUnverifiedActivity(this.onUnverifiedActivity.bind(this))\n .on(Follow, this.onFollowed.bind(this))\n .on(Undo, async (ctx, undo) => {\n const object = await undo.getObject(ctx);\n if (object instanceof Follow) await this.onUnfollowed(ctx, undo);\n else if (object instanceof RawLike) await this.onUnliked(ctx, undo);\n else {\n const logger = getLogger([\"botkit\", \"bot\", \"inbox\"]);\n logger.warn(\n \"The Undo object {undoId} is not about Follow or Like: {object}.\",\n { undoId: undo.id?.href, object },\n );\n }\n })\n .on(Accept, this.onFollowAccepted.bind(this))\n .on(Reject, this.onFollowRejected.bind(this))\n .on(Create, this.onCreated.bind(this))\n .on(Announce, this.onAnnounced.bind(this))\n .on(RawLike, this.onLiked.bind(this))\n .setSharedKeyDispatcher(this.dispatchSharedKey.bind(this));\n if (this.software != null) {\n this.federation.setNodeInfoDispatcher(\n \"/nodeinfo/2.1\",\n this.dispatchNodeInfo.bind(this),\n );\n }\n }\n\n async getActorSummary(\n session: Session<TContextData>,\n ): Promise<{ text: string; tags: (Link | Object)[] } | null> {\n if (this.summary == null) return null;\n if (this.#summary == null) {\n let summary = \"\";\n const tags: (Link | Object)[] = [];\n for await (const chunk of this.summary.getHtml(session)) {\n summary += chunk;\n }\n for await (const tag of this.summary.getTags(session)) {\n tags.push(tag);\n }\n return this.#summary = { text: summary, tags };\n }\n return this.#summary;\n }\n\n async getActorProperties(\n session: Session<TContextData>,\n ): Promise<{ pairs: PropertyValue[]; tags: (Link | Object)[] }> {\n if (this.#properties != null) return this.#properties;\n const pairs: PropertyValue[] = [];\n const tags: (Link | Object)[] = [];\n for (const name in this.properties) {\n const value = this.properties[name];\n const pair = new PropertyValue({\n name,\n value: (await Array.fromAsync(value.getHtml(session))).join(\"\"),\n });\n pairs.push(pair);\n for await (const tag of value.getTags(session)) {\n tags.push(tag);\n }\n }\n return this.#properties = { pairs, tags };\n }\n\n async dispatchActor(\n ctx: Context<TContextData>,\n identifier: string,\n ): Promise<Actor | null> {\n if (this.identifier !== identifier) return null;\n const session = this.getSession(ctx);\n const summary = await this.getActorSummary(session);\n const { pairs, tags } = await this.getActorProperties(session);\n const allTags = summary == null ? tags : [...tags, ...summary.tags];\n const keyPairs = await ctx.getActorKeyPairs(identifier);\n return new this.class({\n id: ctx.getActorUri(identifier),\n preferredUsername: this.username,\n name: this.name,\n summary: summary == null ? null : summary.text,\n attachments: pairs,\n tags: allTags.filter((tag, i) =>\n allTags.findIndex((t) =>\n t.name?.toString() === tag.name?.toString() &&\n (t instanceof Link\n ? tag instanceof Link && t.href?.href === tag.href?.href\n : tag instanceof Object && t.id?.href === tag.id?.href)\n ) === i\n ),\n icon: this.icon == null\n ? null\n : this.icon instanceof Image\n ? this.icon\n : new Image({ url: this.icon }),\n image: this.image == null\n ? null\n : this.image instanceof Image\n ? this.image\n : new Image({ url: this.image }),\n inbox: ctx.getInboxUri(identifier),\n endpoints: new Endpoints({\n sharedInbox: ctx.getInboxUri(),\n }),\n followers: ctx.getFollowersUri(identifier),\n outbox: ctx.getOutboxUri(identifier),\n publicKey: keyPairs[0].cryptographicKey,\n assertionMethods: keyPairs.map((pair) => pair.multikey),\n url: new URL(\"/\", ctx.origin),\n });\n }\n\n mapHandle(_ctx: Context<TContextData>, username: string): string | null {\n return username === this.username ? this.identifier : null;\n }\n\n async dispatchActorKeyPairs(\n _ctx: Context<TContextData>,\n identifier: string,\n ): Promise<CryptoKeyPair[]> {\n if (identifier !== this.identifier) return [];\n let keyPairs = await this.repository.getKeyPairs();\n if (keyPairs == null) {\n const rsa = await generateCryptoKeyPair(\"RSASSA-PKCS1-v1_5\");\n const ed25519 = await generateCryptoKeyPair(\"Ed25519\");\n keyPairs = [rsa, ed25519];\n await this.repository.setKeyPairs(keyPairs);\n }\n return keyPairs;\n }\n\n async dispatchFollowers(\n _ctx: Context<TContextData>,\n identifier: string,\n cursor: string | null,\n ): Promise<PageItems<Recipient> | null> {\n if (identifier !== this.identifier) return null;\n let followers: AsyncIterable<Actor>;\n let nextCursor: string | null;\n if (cursor == null) {\n followers = this.repository.getFollowers();\n nextCursor = null;\n } else {\n const offset = cursor.match(/^\\d+$/) ? parseInt(cursor) : 0;\n followers = this.repository.getFollowers({\n offset,\n limit: this.collectionWindow,\n });\n nextCursor = (offset + this.collectionWindow).toString();\n }\n const items: Recipient[] = [];\n let i = 0;\n for await (const follower of followers) {\n items.push(follower);\n i++;\n }\n if (i < this.collectionWindow) nextCursor = null;\n return { items, nextCursor };\n }\n\n getFollowersFirstCursor(\n _ctx: Context<TContextData>,\n identifier: string,\n ): string | null {\n if (identifier !== this.identifier) return null;\n return \"0\";\n }\n\n async countFollowers(\n _ctx: Context<TContextData>,\n identifier: string,\n ): Promise<number | null> {\n if (identifier !== this.identifier) return null;\n return await this.repository.countFollowers();\n }\n\n async getPermissionChecker(\n ctx: RequestContext<TContextData>,\n ): Promise<(object: Object) => boolean> {\n let owner: Actor | null;\n try {\n owner = await ctx.getSignedKeyOwner();\n } catch {\n owner = null;\n }\n let follower = false;\n const ownerUri = owner?.id;\n if (ownerUri != null) {\n follower = await this.repository.hasFollower(ownerUri);\n }\n const followersUri = ctx.getFollowersUri(this.identifier);\n return (object: Object): boolean => {\n const recipients = [...object.toIds, ...object.ccIds].map((u) => u.href);\n if (recipients.includes(PUBLIC_COLLECTION.href)) return true;\n if (recipients.includes(followersUri.href) && follower) return true;\n return ownerUri == null ? false : recipients.includes(ownerUri.href);\n };\n }\n\n async dispatchOutbox(\n ctx: RequestContext<TContextData>,\n identifier: string,\n cursor: string | null,\n ): Promise<PageItems<Activity> | null> {\n if (identifier !== this.identifier) return null;\n const activities = this.repository.getMessages({\n order: \"newest\",\n until: cursor == null || cursor === \"\"\n ? undefined\n : Temporal.Instant.from(cursor),\n limit: cursor == null ? undefined : this.collectionWindow + 1,\n });\n const items: Activity[] = [];\n const isVisible = await this.getPermissionChecker(ctx);\n let i = 0;\n let nextPublished: Temporal.Instant | null = null;\n for await (const activity of activities) {\n if (cursor != null && i >= this.collectionWindow) {\n nextPublished = activity.published ??\n (await activity.getObject())?.published ?? null;\n break;\n }\n if (isVisible(activity)) items.push(activity);\n i++;\n }\n return { items, nextCursor: nextPublished?.toString() ?? null };\n }\n\n getOutboxFirstCursor(\n _ctx: Context<TContextData>,\n identifier: string,\n ): string | null {\n if (identifier !== this.identifier) return null;\n return \"\";\n }\n\n async countOutbox(\n _ctx: Context<TContextData>,\n identifier: string,\n ): Promise<number | null> {\n if (identifier !== this.identifier) return null;\n return await this.repository.countMessages();\n }\n\n async dispatchFollow(\n _ctx: RequestContext<TContextData>,\n values: { id: string },\n ): Promise<Follow | null> {\n const id = values.id as Uuid;\n const follow = await this.repository.getSentFollow(id);\n return follow ?? null;\n }\n\n async authorizeFollow(\n ctx: RequestContext<TContextData>,\n values: { id: string },\n ): Promise<boolean> {\n const signedKeyOwner = await ctx.getSignedKeyOwner();\n if (signedKeyOwner == null || signedKeyOwner.id == null) return false;\n const id = values.id as Uuid;\n const follow = await this.repository.getSentFollow(id);\n if (follow == null) return false;\n return signedKeyOwner.id.href === follow.objectId?.href ||\n signedKeyOwner.id.href === follow.actorId?.href;\n }\n\n async dispatchCreate(\n ctx: RequestContext<TContextData>,\n values: { id: string },\n ): Promise<Create | null> {\n const activity = await this.repository.getMessage(values.id as Uuid);\n if (!(activity instanceof Create)) return null;\n const isVisible = await this.getPermissionChecker(ctx);\n return isVisible(activity) ? activity : null;\n }\n\n async dispatchMessage<T extends MessageClass>(\n // deno-lint-ignore no-explicit-any\n cls: new (values: any) => T,\n ctx: Context<TContextData> | RequestContext<TContextData>,\n id: string,\n ): Promise<T | null> {\n const activity = await this.repository.getMessage(id as Uuid);\n if (!(activity instanceof Create)) return null;\n if (\"request\" in ctx) {\n // TODO: Split this method into two\n const isVisible = await this.getPermissionChecker(ctx);\n if (!isVisible(activity)) return null;\n }\n const object = await activity.getObject(ctx);\n if (object == null || !(object instanceof cls)) return null;\n return object;\n }\n\n async dispatchAnnounce(\n ctx: RequestContext<TContextData>,\n values: { id: string },\n ): Promise<Announce | null> {\n const activity = await this.repository.getMessage(values.id as Uuid);\n if (!(activity instanceof Announce)) return null;\n const isVisible = await this.getPermissionChecker(ctx);\n return isVisible(activity) ? activity : null;\n }\n\n dispatchEmoji(\n ctx: Context<TContextData>,\n values: { name: string },\n ): APEmoji | null {\n const customEmoji = this.customEmojis[values.name];\n if (customEmoji == null) return null;\n return this.getEmoji(ctx, values.name, customEmoji);\n }\n\n dispatchSharedKey(_ctx: Context<TContextData>): { identifier: string } {\n return { identifier: this.identifier };\n }\n\n onUnverifiedActivity(\n _ctx: RequestContext<TContextData>,\n activity: Activity,\n reason: UnverifiedActivityReason,\n ): Response | void {\n if (\n activity instanceof Delete &&\n reason.type === \"keyFetchError\" &&\n \"status\" in reason.result &&\n reason.result.status === 410\n ) {\n return new Response(null, { status: 202 });\n }\n }\n\n async onFollowed(\n ctx: InboxContext<TContextData>,\n follow: Follow,\n ): Promise<void> {\n const botUri = ctx.getActorUri(this.identifier);\n if (\n follow.actorId?.href === botUri.href ||\n follow.objectId?.href !== botUri.href\n ) {\n return;\n }\n const follower = await follow.getActor({\n contextLoader: ctx.contextLoader,\n documentLoader: ctx.documentLoader,\n suppressError: true,\n });\n if (follower == null || follower.id == null) return;\n const session = this.getSession(ctx);\n const followRequest = new FollowRequestImpl<TContextData>(\n session,\n follow,\n follower,\n );\n await this.onFollow?.(session, followRequest);\n if (followRequest.state === \"pending\") {\n if (this.followerPolicy === \"accept\") await followRequest.accept();\n else if (this.followerPolicy === \"reject\") await followRequest.reject();\n }\n }\n\n async onUnfollowed(\n ctx: InboxContext<TContextData>,\n undo: Undo,\n ): Promise<void> {\n const followId = undo.objectId;\n if (followId == null || undo.actorId == null) return;\n const follower = await this.repository.removeFollower(\n followId,\n undo.actorId,\n );\n if (this.onUnfollow != null && follower != null) {\n const session = this.getSession(ctx);\n await this.onUnfollow(session, follower);\n }\n }\n\n async onFollowAccepted(\n ctx: InboxContext<TContextData>,\n accept: Accept,\n ): Promise<void> {\n const parsedObj = ctx.parseUri(accept.objectId);\n if (parsedObj?.type !== \"object\" || parsedObj.class !== Follow) return;\n const follow = await this.repository.getSentFollow(\n parsedObj.values.id as Uuid,\n );\n if (follow == null) return;\n const followee = await follow.getObject(ctx);\n if (\n !isActor(followee) || followee.id == null ||\n followee.id.href !== accept.actorId?.href\n ) {\n return;\n }\n await this.repository.addFollowee(followee.id, follow);\n if (this.onAcceptFollow != null) {\n const session = this.getSession(ctx);\n await this.onAcceptFollow(session, followee);\n }\n }\n\n async onFollowRejected(\n ctx: InboxContext<TContextData>,\n reject: Reject,\n ): Promise<void> {\n const parsedObj = ctx.parseUri(reject.objectId);\n if (parsedObj?.type !== \"object\" || parsedObj.class !== Follow) return;\n const id = parsedObj.values.id as Uuid;\n const follow = await this.repository.getSentFollow(id);\n if (follow == null) return;\n const followee = await follow.getObject(ctx);\n if (\n !isActor(followee) || followee.id == null ||\n followee.id.href !== reject.actorId?.href\n ) {\n return;\n }\n await this.repository.removeSentFollow(id);\n if (this.onRejectFollow != null) {\n const session = this.getSession(ctx);\n await this.onRejectFollow(session, followee);\n }\n }\n\n async onCreated(\n ctx: InboxContext<TContextData>,\n create: Create,\n ): Promise<void> {\n const object = await create.getObject(ctx);\n if (\n !(object instanceof Article || object instanceof ChatMessage ||\n object instanceof Note || object instanceof Question) ||\n object.attributionId?.href !== create.actorId?.href\n ) {\n return;\n }\n const session = this.getSession(ctx);\n let messageCache: Message<MessageClass, TContextData> | null = null;\n const getMessage = async () => {\n if (messageCache != null) return messageCache;\n return messageCache = await createMessage(object, session, {});\n };\n const replyTarget = ctx.parseUri(object.replyTargetId);\n if (\n this.onVote != null &&\n object instanceof Note && replyTarget?.type === \"object\" &&\n // @ts-ignore: replyTarget.class satisfies (typeof messageClasses)[number]\n messageClasses.includes(replyTarget.class) &&\n object.name != null\n ) {\n if (\n create.actorId == null || create.actorId.href === session.actorId.href\n ) {\n return;\n }\n const actorId = create.actorId;\n const actor = await create.getActor(ctx);\n if (actor == null) return;\n const messageId = replyTarget.values.id as Uuid;\n const pollMessage = await this.repository.getMessage(messageId);\n if (!(pollMessage instanceof Create)) return;\n const question = await pollMessage.getObject(ctx);\n if (\n !(question instanceof Question) || question.endTime == null ||\n Temporal.Instant.compare(question.endTime, Temporal.Now.instant()) < 0\n ) {\n return;\n }\n const optionNotes: Note[] = [];\n const options: string[] = [];\n for await (const note of question.getInclusiveOptions(ctx)) {\n if (!(note instanceof Note)) continue;\n optionNotes.push(note);\n if (note.name != null) options.push(note.name.toString());\n }\n const multiple = options.length > 0;\n for await (const note of question.getExclusiveOptions(ctx)) {\n if (!(note instanceof Note)) continue;\n optionNotes.push(note);\n if (note.name != null) options.push(note.name.toString());\n }\n const option = object.name.toString();\n if (!options.includes(option)) return;\n let updatedQuestion: Question = question;\n let updatedPollMessage = pollMessage;\n await this.repository.vote(messageId, actorId, option);\n await this.repository.updateMessage(\n replyTarget.values.id as Uuid,\n async () => {\n const votes = await this.repository.countVotes(messageId);\n const updatedOptionNotes: Note[] = [...optionNotes];\n let i = 0;\n for (const note of updatedOptionNotes) {\n if (note.name != null) {\n const replies = await note.getReplies(ctx);\n if (replies != null && replies.totalItems != null) {\n updatedOptionNotes[i] = note.clone({\n replies: replies.clone({\n totalItems: votes[note.name.toString()],\n }),\n });\n }\n }\n i++;\n }\n updatedQuestion = question.clone({\n inclusiveOptions: multiple ? updatedOptionNotes : [],\n exclusiveOptions: !multiple ? updatedOptionNotes : [],\n voters: await this.repository.countVoters(messageId),\n });\n return updatedPollMessage = pollMessage.clone({\n object: updatedQuestion,\n });\n },\n );\n const message = await createMessage(updatedQuestion, session, {});\n const vote: Vote<TContextData> = {\n raw: object,\n actor,\n message,\n poll: {\n multiple,\n options,\n endTime: question.endTime,\n },\n option,\n };\n await this.onVote(session, vote);\n const update = new Update({\n id: new URL(\n `#update-votes/${crypto.randomUUID()}`,\n updatedQuestion.id ?? ctx.origin,\n ),\n actor: ctx.getActorUri(this.identifier),\n object: updatedPollMessage.id,\n tos: updatedPollMessage.toIds,\n ccs: updatedPollMessage.ccIds,\n });\n if (message.visibility === \"direct\") {\n await ctx.forwardActivity(this, [...message.mentions], {\n skipIfUnsigned: true,\n excludeBaseUris: [new URL(ctx.origin)],\n });\n await ctx.sendActivity(\n this,\n [...message.mentions],\n update,\n { excludeBaseUris: [new URL(ctx.origin)] },\n );\n } else {\n await ctx.forwardActivity(this, \"followers\", {\n skipIfUnsigned: true,\n preferSharedInbox: true,\n excludeBaseUris: [new URL(ctx.origin)],\n });\n await ctx.sendActivity(\n this,\n \"followers\",\n update,\n {\n preferSharedInbox: true,\n excludeBaseUris: [new URL(ctx.origin)],\n },\n );\n }\n return;\n }\n if (\n this.onReply != null &&\n replyTarget?.type === \"object\" &&\n // @ts-ignore: replyTarget.class satisfies (typeof messageClasses)[number]\n messageClasses.includes(replyTarget.class)\n ) {\n const message = await getMessage();\n if (\n message.visibility === \"public\" || message.visibility === \"unlisted\"\n ) {\n await ctx.forwardActivity(this, \"followers\", {\n skipIfUnsigned: true,\n preferSharedInbox: true,\n excludeBaseUris: [new URL(ctx.origin)],\n });\n }\n await this.onReply(session, message);\n }\n let quoteUrl: URL | null = null;\n // FIXME: eliminate this duplication\n for await (const tag of object.getTags(ctx)) {\n if (tag instanceof Link && isQuoteLink(tag)) {\n quoteUrl = tag.href;\n break;\n }\n }\n if (quoteUrl == null) quoteUrl = object.quoteUrl;\n const quoteTarget = ctx.parseUri(quoteUrl);\n if (\n this.onQuote != null &&\n quoteTarget?.type === \"object\" &&\n // @ts-ignore: quoteTarget.class satisfies (typeof messageClasses)[number]\n messageClasses.includes(quoteTarget.class)\n ) {\n const message = await getMessage();\n if (\n message.visibility === \"public\" || message.visibility === \"unlisted\"\n ) {\n await ctx.forwardActivity(this, \"followers\", {\n skipIfUnsigned: true,\n preferSharedInbox: true,\n excludeBaseUris: [new URL(ctx.origin)],\n });\n }\n await this.onQuote(session, message);\n }\n for await (const tag of object.getTags(ctx)) {\n if (\n tag instanceof Mention && tag.href != null && this.onMention != null\n ) {\n const parsed = ctx.parseUri(tag.href);\n if (\n parsed?.type === \"actor\" && parsed.identifier === this.identifier\n ) {\n await this.onMention(session, await getMessage());\n break;\n }\n }\n }\n if (this.onMessage != null) {\n await this.onMessage(session, await getMessage());\n }\n }\n\n async onAnnounced(\n ctx: InboxContext<TContextData>,\n announce: Announce,\n ): Promise<void> {\n if (\n this.onSharedMessage == null || announce.id == null ||\n announce.actorId == null\n ) return;\n const objectUri = ctx.parseUri(announce.objectId);\n let object: Object | null = null;\n if (\n objectUri?.type === \"object\" &&\n // deno-lint-ignore no-explicit-any\n messageClasses.includes(objectUri.class as any)\n ) {\n const msg = await this.repository.getMessage(objectUri.values.id as Uuid);\n if (msg instanceof Create) object = await msg.getObject(ctx);\n } else {\n object = await announce.getObject(ctx);\n }\n if (!isMessageObject(object)) return;\n const session = this.getSession(ctx);\n const actor = announce.actorId.href == session.actorId.href\n ? await session.getActor()\n : await announce.getActor(ctx);\n if (actor == null) return;\n const original = await createMessage(object, session, {});\n const sharedMessage: SharedMessage<MessageClass, TContextData> = {\n raw: announce,\n id: announce.id,\n actor,\n visibility: getMessageVisibility(announce.toIds, announce.ccIds, actor),\n original,\n };\n await this.onSharedMessage(session, sharedMessage);\n }\n\n async #parseLike(\n ctx: InboxContext<TContextData>,\n like: RawLike,\n ): Promise<\n { session: Session<TContextData>; like: Like<TContextData> } | undefined\n > {\n if (like.id == null || like.actorId == null) return undefined;\n const objectUri = ctx.parseUri(like.objectId);\n let object: Object | null = null;\n if (\n objectUri?.type === \"object\" &&\n // deno-lint-ignore no-explicit-any\n messageClasses.includes(objectUri.class as any)\n ) {\n const msg = await this.repository.getMessage(objectUri.values.id as Uuid);\n if (msg instanceof Create) object = await msg.getObject(ctx);\n } else {\n object = await like.getObject(ctx);\n }\n if (!isMessageObject(object)) return undefined;\n const session = this.getSession(ctx);\n const actor = like.actorId.href == session.actorId.href\n ? await session.getActor()\n : await like.getActor(ctx);\n if (actor == null) return;\n const message = await createMessage(object, session, {});\n return {\n session,\n like: {\n raw: like,\n id: like.id,\n actor,\n message,\n },\n };\n }\n\n async onLiked(ctx: InboxContext<TContextData>, like: RawLike): Promise<void> {\n if (like.name != null) return this.onReacted(ctx, like);\n if (this.onLike == null) return;\n const sessionAndLike = await this.#parseLike(ctx, like);\n if (sessionAndLike == null) return;\n const { session, like: likeObject } = sessionAndLike;\n await this.onLike(session, likeObject);\n }\n\n async onUnliked(ctx: InboxContext<TContextData>, undo: Undo): Promise<void> {\n const like = await undo.getObject(ctx);\n if (!(like instanceof RawLike)) return;\n if (like.name != null) return this.onUnreacted(ctx, undo);\n if (this.onUnlike == null) return;\n if (undo.actorId?.href !== like.actorId?.href) return;\n const sessionAndLike = await this.#parseLike(ctx, like);\n if (sessionAndLike == null) return;\n const { session, like: likeObject } = sessionAndLike;\n await this.onUnlike(session, likeObject);\n }\n\n async #parseReaction(\n ctx: InboxContext<TContextData>,\n react: EmojiReact | RawLike,\n ): Promise<\n | { session: Session<TContextData>; reaction: Reaction<TContextData> }\n | undefined\n > {\n if (react.id == null || react.actorId == null || react.name == null) {\n return undefined;\n }\n let emoji: Emoji | APEmoji | undefined;\n if (isEmoji(react.name)) {\n emoji = react.name;\n } else if (\n typeof react.name === \"string\" && react.name.startsWith(\":\") &&\n react.name.endsWith(\":\")\n ) {\n for await (const tag of react.getTags(ctx)) {\n if (tag instanceof APEmoji && tag.name === react.name) {\n emoji = tag;\n break;\n }\n }\n }\n if (emoji == null) return undefined;\n const objectUri = ctx.parseUri(react.objectId);\n let object: Object | null = null;\n if (\n objectUri?.type === \"object\" &&\n // deno-lint-ignore no-explicit-any\n messageClasses.includes(objectUri.class as any)\n ) {\n const msg = await this.repository.getMessage(objectUri.values.id as Uuid);\n if (msg instanceof Create) object = await msg.getObject(ctx);\n } else {\n object = await react.getObject(ctx);\n }\n if (!isMessageObject(object)) return undefined;\n const session = this.getSession(ctx);\n const actor = react.actorId.href == session.actorId.href\n ? await session.getActor()\n : await react.getActor(ctx);\n if (actor == null) return;\n const message = await createMessage(object, session, {});\n return {\n session,\n reaction: {\n raw: react,\n id: react.id,\n actor,\n message,\n emoji,\n },\n };\n }\n\n async onReacted(\n ctx: InboxContext<TContextData>,\n react: EmojiReact | RawLike,\n ): Promise<void> {\n if (this.onReact == null) return;\n const sessionAndReaction = await this.#parseReaction(ctx, react);\n if (sessionAndReaction == null) return;\n const { session, reaction } = sessionAndReaction;\n await this.onReact(session, reaction);\n }\n\n async onUnreacted(\n ctx: InboxContext<TContextData>,\n undo: Undo,\n ): Promise<void> {\n if (this.onUnreact == null) return;\n const react = await undo.getObject(ctx);\n if (!(react instanceof EmojiReact || react instanceof RawLike)) return;\n if (undo.actorId?.href !== react.actorId?.href) return;\n const sessionAndReaction = await this.#parseReaction(ctx, react);\n if (sessionAndReaction == null) return;\n const { session, reaction } = sessionAndReaction;\n await this.onUnreact(session, reaction);\n }\n\n dispatchNodeInfo(_ctx: Context<TContextData>): NodeInfo {\n return {\n software: this.software!,\n protocols: [\"activitypub\"],\n services: {\n outbound: [\"atom1.0\"], // TODO\n },\n usage: {\n users: {\n total: 1,\n activeMonth: 1, // FIXME\n activeHalfyear: 1, // FIXME\n },\n localPosts: 0, // FIXME\n localComments: 0,\n },\n };\n }\n\n getSession(\n origin: string | URL,\n contextData: TContextData,\n ): SessionImpl<TContextData>;\n getSession(origin: string | URL): SessionImpl<TContextData>;\n getSession(context: Context<TContextData>): SessionImpl<TContextData>;\n\n getSession(\n origin: string | URL | Context<TContextData>,\n contextData?: TContextData,\n ): SessionImpl<TContextData> {\n const ctx = typeof origin === \"string\" || origin instanceof URL\n ? this.federation.createContext(new URL(origin), contextData!)\n : origin;\n return new SessionImpl(this, ctx);\n }\n\n async addCollectionInverseProperty(\n request: Request,\n contextData: TContextData,\n response: Response,\n ): Promise<Response> {\n if (!response.ok) return response;\n const ctx = this.federation.createContext(request, contextData);\n const parsed = ctx.parseUri(new URL(request.url));\n if (\n parsed == null ||\n (parsed.type !== \"outbox\" && parsed.type !== \"followers\") ||\n parsed.identifier == null\n ) {\n return response;\n }\n const contentType = response.headers.get(\"Content-Type\");\n if (\n contentType == null ||\n (\n !contentType.startsWith(\"application/activity+json\") &&\n !contentType.startsWith(\"application/ld+json\")\n )\n ) {\n return response;\n }\n const body = await response.json();\n if (typeof body !== \"object\" || body == null || Array.isArray(body)) {\n return new Response(JSON.stringify(body), {\n headers: response.headers,\n status: response.status,\n statusText: response.statusText,\n });\n }\n const property = parsed.type === \"outbox\" ? \"outboxOf\" : \"followersOf\";\n const actorUri = ctx.getActorUri(parsed.identifier).href;\n if (body[property] === actorUri) {\n return new Response(JSON.stringify(body), {\n headers: response.headers,\n status: response.status,\n statusText: response.statusText,\n });\n }\n const headers = new Headers(response.headers);\n headers.delete(\"Content-Length\");\n return new Response(JSON.stringify({ ...body, [property]: actorUri }), {\n headers,\n status: response.status,\n statusText: response.statusText,\n });\n }\n\n async fetch(request: Request, contextData: TContextData): Promise<Response> {\n if (this.behindProxy) {\n request = await getXForwardedRequest(request);\n }\n const url = new URL(request.url);\n if (\n url.pathname.startsWith(\"/.well-known/\") ||\n url.pathname.startsWith(\"/ap/\") ||\n url.pathname.startsWith(\"/nodeinfo/\")\n ) {\n const response = await this.federation.fetch(request, { contextData });\n return await this.addCollectionInverseProperty(\n request,\n contextData,\n response,\n );\n }\n const match = /^\\/emojis\\/([a-z0-9-_]+)(?:$|\\.)/.exec(url.pathname);\n if (match != null) {\n const customEmoji = this.customEmojis[match[1]];\n if (customEmoji == null || !(\"file\" in customEmoji)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n let file: fs.FileHandle;\n try {\n file = await fs.open(customEmoji.file, \"r\");\n } catch (error) {\n if (\n typeof error === \"object\" && error != null && \"code\" in error &&\n error.code === \"ENOENT\"\n ) {\n return new Response(\"Not Found\", { status: 404 });\n }\n throw error;\n }\n const fileInfo = await file.stat();\n return new Response(file.readableWebStream(), {\n headers: {\n \"Content-Type\": customEmoji.type,\n \"Content-Length\": fileInfo.size.toString(),\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n \"Last-Modified\": (fileInfo.mtime ?? new Date()).toUTCString(),\n \"ETag\": `\"${fileInfo.mtime?.getTime().toString(36)}${\n fileInfo.size.toString(36)\n }\"`,\n },\n });\n }\n return await app.fetch(request, { bot: this, contextData });\n }\n\n getEmoji(\n ctx: Context<TContextData>,\n name: string,\n data: CustomEmoji,\n ): APEmoji {\n let url: URL;\n if (\"url\" in data) {\n url = new URL(data.url);\n } else {\n // @ts-ignore: data.type satisfies keyof typeof mimeDb\n const t = mimeDb[data.type];\n url = new URL(\n `/emojis/${name}${\n t == null || t.extensions == null || t.extensions.length < 1\n ? \"\"\n : `.${t.extensions[0]}`\n }`,\n ctx.origin,\n );\n }\n return new APEmoji({\n id: ctx.getObjectUri(APEmoji, { name }),\n name: `:${name}:`,\n icon: new Image({\n mediaType: data.type,\n url,\n }),\n });\n }\n\n addCustomEmoji<TEmojiName extends string>(\n name: TEmojiName,\n data: CustomEmoji,\n ): DeferredCustomEmoji<TContextData> {\n if (!name.match(/^[a-z0-9-_]+$/i)) {\n throw new TypeError(\n `Invalid custom emoji name: ${name}. It must match /^[a-z0-9-_]+$/i.`,\n );\n } else if (name in this.customEmojis) {\n throw new TypeError(`Duplicate custom emoji name: ${name}`);\n } else if (!data.type.startsWith(\"image/\")) {\n throw new TypeError(`Unsupported media type: ${data.type}`);\n }\n this.customEmojis[name] = data;\n return (session: Session<TContextData>) =>\n this.getEmoji(\n session.context,\n name,\n data,\n );\n }\n\n addCustomEmojis<TEmojiName extends string>(\n emojis: Readonly<Record<TEmojiName, CustomEmoji>>,\n ): Readonly<Record<TEmojiName, DeferredCustomEmoji<TContextData>>> {\n const emojiMap = {} as Record<\n TEmojiName,\n DeferredCustomEmoji<TContextData>\n >;\n for (const name in emojis) {\n emojiMap[name] = this.addCustomEmoji(name, emojis[name]);\n }\n return emojiMap;\n }\n}\n"],"mappings":"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"}
|
package/dist/bot-impl.test.js
CHANGED
|
@@ -7,7 +7,7 @@ import { SessionImpl } from "./session-impl.js";
|
|
|
7
7
|
import { BotImpl } from "./bot-impl.js";
|
|
8
8
|
import { mention, strong, text } from "./text.js";
|
|
9
9
|
import { MemoryKvStore } from "@fedify/fedify/federation";
|
|
10
|
-
import { Accept, Announce, Article, Collection, Create, Emoji, EmojiReact, Follow, Image, Like, Mention, Note, PUBLIC_COLLECTION, Person, Place, PropertyValue, Question, Reject, Service, Undo, Update } from "@fedify/vocab";
|
|
10
|
+
import { Accept, Announce, Article, Collection, Create, Delete, Emoji, EmojiReact, Follow, Image, Like, Mention, Note, PUBLIC_COLLECTION, Person, Place, PropertyValue, Question, Reject, Service, Undo, Update } from "@fedify/vocab";
|
|
11
11
|
import assert from "node:assert";
|
|
12
12
|
import { describe, test } from "node:test";
|
|
13
13
|
|
|
@@ -683,6 +683,64 @@ test("BotImpl.dispatchSharedKey()", () => {
|
|
|
683
683
|
const ctx = bot.federation.createContext(new URL("https://example.com"));
|
|
684
684
|
assert.deepStrictEqual(bot.dispatchSharedKey(ctx), { identifier });
|
|
685
685
|
});
|
|
686
|
+
test("BotImpl.onUnverifiedActivity()", async (t) => {
|
|
687
|
+
const bot = new BotImpl({
|
|
688
|
+
kv: new MemoryKvStore(),
|
|
689
|
+
username: "bot"
|
|
690
|
+
});
|
|
691
|
+
const ctx = bot.federation.createContext(new Request("https://example.com/ap/inbox", { method: "POST" }), void 0);
|
|
692
|
+
const deleteActivity = new Delete({
|
|
693
|
+
id: new URL("https://remote.example/activities/delete"),
|
|
694
|
+
actor: new URL("https://remote.example/actors/deleted"),
|
|
695
|
+
object: new URL("https://remote.example/actors/deleted")
|
|
696
|
+
});
|
|
697
|
+
const createActivity = new Create({
|
|
698
|
+
id: new URL("https://remote.example/activities/create"),
|
|
699
|
+
actor: new URL("https://remote.example/actors/deleted"),
|
|
700
|
+
object: new Note({
|
|
701
|
+
id: new URL("https://remote.example/notes/1"),
|
|
702
|
+
attribution: new URL("https://remote.example/actors/deleted"),
|
|
703
|
+
content: "Hello, world!"
|
|
704
|
+
})
|
|
705
|
+
});
|
|
706
|
+
const keyFetch410 = {
|
|
707
|
+
type: "keyFetchError",
|
|
708
|
+
keyId: new URL("https://remote.example/actors/deleted#main-key"),
|
|
709
|
+
result: {
|
|
710
|
+
status: 410,
|
|
711
|
+
response: new Response(null, { status: 410 })
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
await t.test("acknowledges gone actor deletes", () => {
|
|
715
|
+
const response = bot.onUnverifiedActivity(ctx, deleteActivity, keyFetch410);
|
|
716
|
+
assert.ok(response instanceof Response);
|
|
717
|
+
assert.deepStrictEqual(response.status, 202);
|
|
718
|
+
});
|
|
719
|
+
await t.test("ignores non-410 key fetch failures", () => {
|
|
720
|
+
const reason = {
|
|
721
|
+
...keyFetch410,
|
|
722
|
+
result: {
|
|
723
|
+
status: 404,
|
|
724
|
+
response: new Response(null, { status: 404 })
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
const response = bot.onUnverifiedActivity(ctx, deleteActivity, reason);
|
|
728
|
+
assert.deepStrictEqual(response, void 0);
|
|
729
|
+
});
|
|
730
|
+
await t.test("ignores non-delete activities", () => {
|
|
731
|
+
const response = bot.onUnverifiedActivity(ctx, createActivity, keyFetch410);
|
|
732
|
+
assert.deepStrictEqual(response, void 0);
|
|
733
|
+
});
|
|
734
|
+
await t.test("ignores other verification failures", () => {
|
|
735
|
+
const noSignature = { type: "noSignature" };
|
|
736
|
+
const invalidSignature = {
|
|
737
|
+
type: "invalidSignature",
|
|
738
|
+
keyId: new URL("https://remote.example/actors/deleted#main-key")
|
|
739
|
+
};
|
|
740
|
+
assert.deepStrictEqual(bot.onUnverifiedActivity(ctx, deleteActivity, noSignature), void 0);
|
|
741
|
+
assert.deepStrictEqual(bot.onUnverifiedActivity(ctx, deleteActivity, invalidSignature), void 0);
|
|
742
|
+
});
|
|
743
|
+
});
|
|
686
744
|
for (const policy of [
|
|
687
745
|
"accept",
|
|
688
746
|
"reject",
|
|
@@ -1515,6 +1573,72 @@ test("BotImpl.fetch()", async () => {
|
|
|
1515
1573
|
const response2 = await botBehindProxy.fetch(request);
|
|
1516
1574
|
assert.deepStrictEqual(response2.status, 200);
|
|
1517
1575
|
});
|
|
1576
|
+
test("BotImpl.fetch() includes FEP-5711 inverse properties", async () => {
|
|
1577
|
+
const repository = new MemoryRepository();
|
|
1578
|
+
const bot = new BotImpl({
|
|
1579
|
+
kv: new MemoryKvStore(),
|
|
1580
|
+
repository,
|
|
1581
|
+
username: "bot",
|
|
1582
|
+
collectionWindow: 1
|
|
1583
|
+
});
|
|
1584
|
+
const actorId = new URL("https://example.com/ap/actor/bot");
|
|
1585
|
+
await repository.addFollower(new URL("https://example.com/actor/1#follow"), new Person({
|
|
1586
|
+
id: new URL("https://example.com/actor/1"),
|
|
1587
|
+
preferredUsername: "john",
|
|
1588
|
+
inbox: new URL("https://example.com/actor/1/inbox")
|
|
1589
|
+
}));
|
|
1590
|
+
await repository.addMessage("78acb1ea-4ac6-46b7-bcd4-3a8965d8126e", new Create({
|
|
1591
|
+
id: new URL("https://example.com/ap/actor/bot/create/78acb1ea-4ac6-46b7-bcd4-3a8965d8126e"),
|
|
1592
|
+
actor: actorId,
|
|
1593
|
+
to: PUBLIC_COLLECTION,
|
|
1594
|
+
cc: new URL("https://example.com/ap/actor/bot/followers"),
|
|
1595
|
+
object: new Note({
|
|
1596
|
+
id: new URL("https://example.com/ap/actor/bot/note/1"),
|
|
1597
|
+
attribution: actorId,
|
|
1598
|
+
to: PUBLIC_COLLECTION,
|
|
1599
|
+
cc: new URL("https://example.com/ap/actor/bot/followers"),
|
|
1600
|
+
content: "Hello, world!",
|
|
1601
|
+
published: Temporal.Instant.from("2025-01-01T00:00:00Z")
|
|
1602
|
+
}),
|
|
1603
|
+
published: Temporal.Instant.from("2025-01-01T00:00:00Z")
|
|
1604
|
+
}));
|
|
1605
|
+
const actorResponse = await bot.fetch(new Request("https://example.com/ap/actor/bot", { headers: { accept: "application/activity+json" } }));
|
|
1606
|
+
assert.deepStrictEqual(actorResponse.status, 200);
|
|
1607
|
+
const actorJson = await actorResponse.json();
|
|
1608
|
+
assert.deepStrictEqual(actorJson.id, actorId.href);
|
|
1609
|
+
assert.deepStrictEqual(actorJson.followers, "https://example.com/ap/actor/bot/followers");
|
|
1610
|
+
assert.deepStrictEqual(actorJson.outbox, "https://example.com/ap/actor/bot/outbox");
|
|
1611
|
+
const outboxResponse = await bot.fetch(new Request("https://example.com/ap/actor/bot/outbox", { headers: { accept: "application/activity+json" } }));
|
|
1612
|
+
assert.deepStrictEqual(outboxResponse.status, 200);
|
|
1613
|
+
const outboxJson = await outboxResponse.json();
|
|
1614
|
+
assert.deepStrictEqual(outboxJson.type, "OrderedCollection");
|
|
1615
|
+
assert.deepStrictEqual(outboxJson.id, "https://example.com/ap/actor/bot/outbox");
|
|
1616
|
+
assert.deepStrictEqual(outboxJson.totalItems, 1);
|
|
1617
|
+
assert.deepStrictEqual(outboxJson.first, "https://example.com/ap/actor/bot/outbox?cursor=");
|
|
1618
|
+
assert.deepStrictEqual(outboxJson.outboxOf, actorId.href);
|
|
1619
|
+
const outboxPageResponse = await bot.fetch(new Request("https://example.com/ap/actor/bot/outbox?cursor=", { headers: { accept: "application/activity+json" } }));
|
|
1620
|
+
assert.deepStrictEqual(outboxPageResponse.status, 200);
|
|
1621
|
+
const outboxPageJson = await outboxPageResponse.json();
|
|
1622
|
+
assert.deepStrictEqual(outboxPageJson.type, "OrderedCollectionPage");
|
|
1623
|
+
assert.deepStrictEqual(outboxPageJson.id, "https://example.com/ap/actor/bot/outbox?cursor=");
|
|
1624
|
+
assert.deepStrictEqual(outboxPageJson.partOf, "https://example.com/ap/actor/bot/outbox");
|
|
1625
|
+
assert.deepStrictEqual(outboxPageJson.outboxOf, actorId.href);
|
|
1626
|
+
const followersResponse = await bot.fetch(new Request("https://example.com/ap/actor/bot/followers", { headers: { accept: "application/activity+json" } }));
|
|
1627
|
+
assert.deepStrictEqual(followersResponse.status, 200);
|
|
1628
|
+
const followersJson = await followersResponse.json();
|
|
1629
|
+
assert.deepStrictEqual(followersJson.type, "OrderedCollection");
|
|
1630
|
+
assert.deepStrictEqual(followersJson.id, "https://example.com/ap/actor/bot/followers");
|
|
1631
|
+
assert.deepStrictEqual(followersJson.totalItems, 1);
|
|
1632
|
+
assert.deepStrictEqual(followersJson.first, "https://example.com/ap/actor/bot/followers?cursor=0");
|
|
1633
|
+
assert.deepStrictEqual(followersJson.followersOf, actorId.href);
|
|
1634
|
+
const followersPageResponse = await bot.fetch(new Request("https://example.com/ap/actor/bot/followers?cursor=0", { headers: { accept: "application/activity+json" } }));
|
|
1635
|
+
assert.deepStrictEqual(followersPageResponse.status, 200);
|
|
1636
|
+
const followersPageJson = await followersPageResponse.json();
|
|
1637
|
+
assert.deepStrictEqual(followersPageJson.type, "OrderedCollectionPage");
|
|
1638
|
+
assert.deepStrictEqual(followersPageJson.id, "https://example.com/ap/actor/bot/followers?cursor=0");
|
|
1639
|
+
assert.deepStrictEqual(followersPageJson.partOf, "https://example.com/ap/actor/bot/followers");
|
|
1640
|
+
assert.deepStrictEqual(followersPageJson.followersOf, actorId.href);
|
|
1641
|
+
});
|
|
1518
1642
|
describe("BotImpl.addCustomEmoji(), BotImpl.addCustomEmojis()", () => {
|
|
1519
1643
|
const bot = new BotImpl({
|
|
1520
1644
|
kv: new MemoryKvStore(),
|