Skip to content

Commit f7c7356

Browse files
committed
Session.getActor() method
1 parent 0faf0ba commit f7c7356

File tree

6 files changed

+149
-5
lines changed

6 files changed

+149
-5
lines changed

docs/concepts/session.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,14 @@ bot.onFollow = async (session, actor) => {
8282
);
8383
};
8484
~~~~
85+
86+
87+
Getting the bot's `Actor` object
88+
--------------------------------
89+
90+
The `Session` object has a `~Session.getActor()` method that returns the `Actor`
91+
object of the bot:
92+
93+
~~~~ typescript
94+
const actor: Actor = session.getActor();
95+
~~~~

src/message-impl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ export async function createMessage<T extends MessageClass, TContextData>(
294294
suppressError: true,
295295
};
296296
const actor = raw.attributionId?.href === session.actorId?.href
297-
? await session.bot.dispatchActor(session.context, session.bot.identifier)
297+
? await session.getActor()
298298
: await raw.getAttribution(options);
299299
if (actor == null) {
300300
throw new TypeError(`The raw.attributionId is required.`);

src/session-impl.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// You should have received a copy of the GNU Affero General Public License
1515
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1616
import {
17+
type Actor,
1718
type Context,
1819
Create,
1920
isActor,
@@ -65,6 +66,10 @@ export class SessionImpl<TContextData> implements Session<TContextData> {
6566
return `@${this.bot.username}@${this.context.host}` as const;
6667
}
6768

69+
async getActor(): Promise<Actor> {
70+
return (await this.bot.dispatchActor(this.context, this.bot.identifier))!;
71+
}
72+
6873
async publish(
6974
content: Text<"block", TContextData>,
7075
options?: SessionImplPublishOptions<TContextData>,

src/session.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// You should have received a copy of the GNU Affero General Public License
1515
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1616
import type {
17+
Actor,
1718
Article,
1819
ChatMessage,
1920
Context,
@@ -50,6 +51,12 @@ export interface Session<TContextData> {
5051
*/
5152
readonly actorHandle: `@${string}@${string}`;
5253

54+
/**
55+
* Gets the `Actor` object of the bot.
56+
* @returns The `Actor` object of the bot.
57+
*/
58+
getActor(): Promise<Actor>;
59+
5360
/**
5461
* Publishes a message attributed to the bot.
5562
* @param text The content of the note.

src/text.test.ts

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const keyPair: CryptoKeyPair = {
7373
privateKey: await importJwk({
7474
kty: "RSA",
7575
alg: "RS256",
76+
// cSpell: disable
7677
n: "15DAu9S9sROZ_NonfZm5S0PSeYuh2POeo0cpvvGnp_T9jQWjMuGdhwOPT7OOD9N-R1IY-2hXkk-RjfWNzbxMoNKvOpz_1MHRg18W5Lw60mIxZFztLKlNhOZS7rVrlJc--jj1wLETEfY5ocYCyRZm25UeT_Q1JSePdnhEGVXo4sSqoUcMV5Bgys5PlISfQj_bHDpcIi9snJ70hOzYTy7k7fNuCHHsK08DN4bZMG58qQNrPFtZW6fgpQiu0kgtUBFgQu-uUNn1h0io-7OhMU2dXeV8lwCILhIFRvrRV9vKQydBejbyYuzFY-Xq98biB9Aox8GD0jJE4tTVj6CpsmaN9yq6nI8ibWjnk87IKdU3jex5zB8chR0cm2tyIWr6dKhCTexmtYTF0pGW7PZ2Dnn31BU3cPkrkplu752D4eQ47BsAspMsHRxXE3NtqlmN02Y-6AIzt1tuPBPHldQHUpxtGrwFh9b9biC2mtb_w6F0oyxVyZsAZnuK2tN0-uX_iMPoC5VLnPrzgWjQiMArivg4u1cQ35hrvuFYFAdNf2WaBDyDNxaCoTD28z9bF1titlbQ48tDw0adZ1Zp8_x12JqA5HNpqDfmyT4KPU_6Ag7J7cGfGeO2vsjcc1oGhqZ-n6JlnSvVImHS5eKC6CDhW7ceuKC_oX-XPWWQQHhPniwM2RE",
7778
e: "AQAB",
7879
d: "eAD5ipdQUrfazcyUl3Nwl9nV3hxBqYlWEweW0dmtv-6_CDbPN5AqJfNxYKlQuLbAYevuRGc9-RGasjC1FIdzEUS4kCS-ty5--GeDUysGhABuBrVEw8wsf4PJP2J31WytfpcfGHp7Z1BvnQOioVd7Q1qsWU5WF60CTK1_G6ubzkI1yzrGQCj7-WsJGmEKV9M8o2ZJzC4ihL5o2WcQtGQixeTyqHjjROjjnZHQbwnTFDP3Cs6_3CqFANrol9_eeehyclED9bag3QMyL408ezn-FTugNF_zb9JQZcdTq1mMK_46kVLtdOzipk5klDN_uWHEkg_E1sttVemuShri3ZICDUSd70Y4VeQxNLUKJNBSYdSLmcVgfIHaXMmcrknmBuz23SrGR6JZR4DSJtr3sylR2tlOxpZhJAUZf17f8aZD7EnbR7qcNtjZmf8RyAKrEXLgL6f3jm6FfE_b027kcmLMXL7bJtlTBYnM9MrBnXSsJftHRrmB1Xe-0Hq24Q7ctrhRhFF3Ij8MjNRjdn6NWdIXzltreblLEO44iTJtlWeYtg-C9566F_yWZnjDZEQ8nvBhpCM3iXlRfzhlEebBoBbf-Mf-0hJRRUL3EtGuMueylzeyk4tTzvOfK2FUnVAi8bIhjz4m8RhN7kC4ubqUXZbKwzjeI1dhWyfDphrPiRtCetE",
@@ -84,21 +85,25 @@ const keyPair: CryptoKeyPair = {
8485
"MVKMpNXrlizeny-cn1zGyx3un3bukwwrZHENhE-DITuEvLVYPwAHIYgZJ_nBblbWzxJrRU1pVt4dguL7jOYqmmpg9gE4eD2zKfUFSY45wSf2eVIOfCnAvnN1y0Nwrgn0bdRi_sSw-6orP99eBcL8mVrNhmqzYDfnAe3o8DwPZBhzJBOi43iQNA9_Y84ONuzvYpCGozDhdRhbeeOt62Hg9BFWRSCU3srMo33l3DMgWv80Pb0os7_ApAckzu2rfwYOvQxAPb44DUBKivcANjHR_Mzs9ITtZP-720zI-4tQSVjeeuSuokp64J-nVCZL0MxNGtfB-S_tFeG56s5EoFZLHQ",
8586
qi:
8687
"1J7CfWYlg4Igsu2N7bhgLzbc1l6A3odyyOlM70uH8P41kCYgpRDdH8Ms8yOJE-F13ha5drICZqsD7IjgG0cZONJ_0xTeka0AYMvCwjuJZ_4CzVFYNICxSHFUI-sCu1p-zb70eXU6fiwOFgzoPbnrwywpbxcTV_8H0XszwPcI3fjrGk6N-hi23Ur1gIjhnri_-x8mzwmtPA1ID1G17U4X93mP7dlYCzGigq8ORbSdZthOKdjtHXITBOgpcTiuyTTwAEqh3xyXscfsgzi0X6olBevJCGeTzOrqQX026JmNVykaS1-o_ea_Y0cD0q6Nxd5TwLZMCLZi1M5PLHhGlJg9MQ",
88+
// cSpell: enable
8789
key_ops: ["sign"],
8890
ext: true,
8991
}, "private"),
9092
publicKey: await importJwk({
9193
kty: "RSA",
9294
alg: "RS256",
95+
// cSpell: disable
9396
n: "15DAu9S9sROZ_NonfZm5S0PSeYuh2POeo0cpvvGnp_T9jQWjMuGdhwOPT7OOD9N-R1IY-2hXkk-RjfWNzbxMoNKvOpz_1MHRg18W5Lw60mIxZFztLKlNhOZS7rVrlJc--jj1wLETEfY5ocYCyRZm25UeT_Q1JSePdnhEGVXo4sSqoUcMV5Bgys5PlISfQj_bHDpcIi9snJ70hOzYTy7k7fNuCHHsK08DN4bZMG58qQNrPFtZW6fgpQiu0kgtUBFgQu-uUNn1h0io-7OhMU2dXeV8lwCILhIFRvrRV9vKQydBejbyYuzFY-Xq98biB9Aox8GD0jJE4tTVj6CpsmaN9yq6nI8ibWjnk87IKdU3jex5zB8chR0cm2tyIWr6dKhCTexmtYTF0pGW7PZ2Dnn31BU3cPkrkplu752D4eQ47BsAspMsHRxXE3NtqlmN02Y-6AIzt1tuPBPHldQHUpxtGrwFh9b9biC2mtb_w6F0oyxVyZsAZnuK2tN0-uX_iMPoC5VLnPrzgWjQiMArivg4u1cQ35hrvuFYFAdNf2WaBDyDNxaCoTD28z9bF1titlbQ48tDw0adZ1Zp8_x12JqA5HNpqDfmyT4KPU_6Ag7J7cGfGeO2vsjcc1oGhqZ-n6JlnSvVImHS5eKC6CDhW7ceuKC_oX-XPWWQQHhPniwM2RE",
9497
e: "AQAB",
98+
// cSpell: enable
9599
key_ops: ["verify"],
96100
ext: true,
97101
}, "public"),
98102
};
99103

100-
federation.setActorDispatcher("/ap/actor/{identifier}", (_ctx, identifier) => {
104+
federation.setActorDispatcher("/ap/actor/{identifier}", (ctx, identifier) => {
101105
return new Person({
106+
id: ctx.getActorUri(identifier),
102107
preferredUsername: identifier,
103108
});
104109
}).setKeyPairsDispatcher((_ctx, _identifier) => {
@@ -117,6 +122,13 @@ const bot: BotWithVoidContextData = {
117122
context: ctx,
118123
actorId: ctx.getActorUri(bot.identifier),
119124
actorHandle: `@bot@${ctx.host}` as const,
125+
getActor() {
126+
const actor = new Person({
127+
id: ctx.getActorUri(bot.identifier),
128+
preferredUsername: "bot",
129+
});
130+
return Promise.resolve(actor);
131+
},
120132
publish(_content: unknown, _options: unknown) {
121133
throw new Error("Not implemented");
122134
},
@@ -366,6 +378,83 @@ Deno.test({
366378
assertEquals(cache5.length, 1);
367379
assertInstanceOf(cache5[0], Person);
368380
assertEquals(cache5[0].id, new URL("https://hollo.social/@fedify"));
381+
382+
const m6: Text<"inline", void> = mention("@bot@example.com");
383+
assertEquals(
384+
(await Array.fromAsync(m6.getHtml(session))).join(""),
385+
'<a href="https://example.com/ap/actor/bot" translate="no" ' +
386+
'class="h-card u-url mention" target="_blank">@<span>' +
387+
"bot@example.com</span></a>",
388+
);
389+
const tags6 = await Array.fromAsync(m6.getTags(session));
390+
assertEquals(tags6.length, 1);
391+
assertInstanceOf(tags6[0], Mention);
392+
assertEquals(tags6[0].name, "@bot@example.com");
393+
assertEquals(tags6[0].href, new URL("https://example.com/ap/actor/bot"));
394+
const cache6 = m6.getCachedObjects();
395+
assertEquals(cache6.length, 1);
396+
assertInstanceOf(cache6[0], Person);
397+
assertEquals(cache6[0].id, new URL("https://example.com/ap/actor/bot"));
398+
399+
const m7: Text<"inline", void> = mention(
400+
"Example",
401+
new URL("https://example.com/ap/actor/bot"),
402+
);
403+
assertEquals(
404+
(await Array.fromAsync(m7.getHtml(session))).join(""),
405+
'<a href="https://example.com/ap/actor/bot" translate="no" ' +
406+
'class="h-card u-url mention" target="_blank">Example</a>',
407+
);
408+
const tags7 = await Array.fromAsync(m7.getTags(session));
409+
assertEquals(tags7.length, 1);
410+
assertInstanceOf(tags7[0], Mention);
411+
assertEquals(tags7[0].name, "Example");
412+
assertEquals(tags7[0].href, new URL("https://example.com/ap/actor/bot"));
413+
const cache7 = m7.getCachedObjects();
414+
assertEquals(cache7.length, 1);
415+
assertInstanceOf(cache7[0], Person);
416+
assertEquals(cache7[0].id, new URL("https://example.com/ap/actor/bot"));
417+
418+
const m8: Text<"inline", void> = mention(
419+
new Person({
420+
id: new URL("https://example.com/ap/actor/bot"),
421+
preferredUsername: "bot",
422+
}),
423+
);
424+
assertEquals(
425+
(await Array.fromAsync(m8.getHtml(session))).join(""),
426+
'<a href="https://example.com/ap/actor/bot" translate="no" ' +
427+
'class="h-card u-url mention" target="_blank">@<span>' +
428+
"bot@example.com</span></a>",
429+
);
430+
const tags8 = await Array.fromAsync(m8.getTags(session));
431+
assertEquals(tags8.length, 1);
432+
assertInstanceOf(tags8[0], Mention);
433+
assertEquals(tags8[0].name, "@bot@example.com");
434+
assertEquals(tags8[0].href, new URL("https://example.com/ap/actor/bot"));
435+
const cache8 = m8.getCachedObjects();
436+
assertEquals(cache8.length, 1);
437+
assertInstanceOf(cache8[0], Person);
438+
assertEquals(cache8[0].id, new URL("https://example.com/ap/actor/bot"));
439+
440+
const m9: Text<"inline", void> = mention(
441+
new URL("https://example.com/ap/actor/bot"),
442+
);
443+
assertEquals(
444+
(await Array.fromAsync(m9.getHtml(session))).join(""),
445+
'<a href="https://example.com/ap/actor/bot" translate="no" ' +
446+
'class="h-card u-url mention" target="_blank">@<span>' +
447+
"bot@example.com</span></a>",
448+
);
449+
const tags9 = await Array.fromAsync(m9.getTags(session));
450+
assertEquals(tags9.length, 1);
451+
assertInstanceOf(tags9[0], Mention);
452+
assertEquals(tags9[0].name, "@bot@example.com");
453+
assertEquals(tags9[0].href, new URL("https://example.com/ap/actor/bot"));
454+
const cache9 = m9.getCachedObjects();
455+
assertEquals(cache9.length, 1);
456+
assertInstanceOf(cache9[0], Person);
457+
assertEquals(cache9[0].id, new URL("https://example.com/ap/actor/bot"));
369458
},
370459
});
371460

@@ -477,4 +566,21 @@ Deno.test("markdown()", async () => {
477566
);
478567
assertEquals(await Array.fromAsync(t2.getTags(session)), []);
479568
assertEquals(t2.getCachedObjects(), []);
569+
570+
const t3: Text<"block", void> = markdown("@bot@example.com");
571+
assertEquals(
572+
(await Array.fromAsync(t3.getHtml(session))).join(""),
573+
'<p><a translate="no" class="h-card u-url mention" target="_blank" href="https://example.com/ap/actor/bot">' +
574+
'<span class="at">@</span><span class="user">bot</span>' +
575+
'<span class="at">@</span><span class="domain">example.com</span></a></p>\n',
576+
);
577+
const tags3 = await Array.fromAsync(t3.getTags(session));
578+
assertEquals(tags3.length, 1);
579+
assertInstanceOf(tags3[0], Mention);
580+
assertEquals(tags3[0].name, "@bot@example.com");
581+
assertEquals(tags3[0].href, new URL("https://example.com/ap/actor/bot"));
582+
const cache3 = t3.getCachedObjects();
583+
assertEquals(cache3.length, 1);
584+
assertInstanceOf(cache3[0], Person);
585+
assertEquals(cache3[0].id, new URL("https://example.com/ap/actor/bot"));
480586
});

src/text.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,34 +397,47 @@ export function mention<TContextData>(
397397
b?: Actor | URL,
398398
): Text<"inline", TContextData> {
399399
if (b != null) {
400+
// (label: string, actor: Actor | URL)
400401
return new MentionText<TContextData>(
401402
a as string,
402403
isActor(b) ? b : async (session) => {
404+
if (session.actorId.href === b.href) return await session.getActor();
403405
const documentLoader = await session.context.getDocumentLoader(
404406
session.bot,
405407
);
406408
return await session.context.lookupObject(b, { documentLoader });
407409
},
408410
);
409411
} else if (typeof a === "string") {
412+
// (handle: string)
410413
return new MentionText<TContextData>(
411414
a,
412415
async (session) => {
416+
if (session.actorHandle === a) return await session.getActor();
413417
const documentLoader = await session.context.getDocumentLoader(
414418
session.bot,
415419
);
416420
return await session.context.lookupObject(a, { documentLoader });
417421
},
418422
);
419423
} else if (isActor(a)) {
424+
// (actor: Actor)
420425
return new MentionText<TContextData>(
421-
(session) => getActorHandle(a, session.context),
426+
(session) =>
427+
a.id?.href === session.actorId.href
428+
? Promise.resolve(session.actorHandle)
429+
: getActorHandle(a, session.context),
422430
a,
423431
);
424432
}
433+
// (actor: URL)
425434
return new MentionText<TContextData>(
426-
(session) => getActorHandle(a, session.context),
435+
(session) =>
436+
a.href === session.actorId.href
437+
? Promise.resolve(session.actorHandle)
438+
: getActorHandle(a, session.context),
427439
async (session) => {
440+
if (a.href === session.actorId.href) return await session.getActor();
428441
const documentLoader = await session.context.getDocumentLoader(
429442
session.bot,
430443
);
@@ -714,7 +727,9 @@ export class MarkdownText<TContextData> implements Text<"block", TContextData> {
714727
const documentLoader = await session.context.getDocumentLoader(session.bot);
715728
const objects = await Promise.all(
716729
this.#mentions.map((m) =>
717-
session.context.lookupObject(m, { documentLoader })
730+
m === session.actorHandle
731+
? session.getActor()
732+
: session.context.lookupObject(m, { documentLoader })
718733
),
719734
);
720735
const actors: Record<string, Object> = {};

0 commit comments

Comments
 (0)