Skip to content

Commit 9e5f41a

Browse files
committed
Bot should respond only to toots where the bot is first mention
1 parent 8f5ca3d commit 9e5f41a

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

apps/mastodon-bot/src/basic.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,17 @@ export function htmlToBasic(content: string): string {
4242

4343
return text.replace(/[ \t]+$/gm, '').trim();
4444
}
45+
46+
/**
47+
* Return the leading run of @mention handles from a status, lowercased and
48+
* without the leading '@'. Mirrors the strip in htmlToBasic so the two agree on
49+
* what counts as a leading mention. Tags are dropped (not spaced) so the anchor
50+
* "@<span>bot</span>" rejoins into "@bot"; the rendered text is just the bare
51+
* "@username", with the domain living in the (now-stripped) href.
52+
*/
53+
export function leadingMentions(content: string): string[] {
54+
const text = decodeEntities(content.replace(/<[^>]+>/g, '')).replace(/ /g, ' ');
55+
const match = text.match(/^\s*((?:@\S+[ \t]*)+)/);
56+
if (!match) return [];
57+
return match[1].trim().split(/\s+/).map((handle) => handle.slice(1).toLowerCase());
58+
}

apps/mastodon-bot/src/index.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { config, assertRuntimeConfig } from './config.js';
2-
import { htmlToBasic } from './basic.js';
2+
import { htmlToBasic, leadingMentions } from './basic.js';
33
import { extractProjectRef } from './project.js';
44
import { parseDirectives } from './directives.js';
55
import { allowRequest } from './ratelimit.js';
@@ -28,11 +28,31 @@ function truncate(text: string, max: number): string {
2828
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
2929
}
3030

31+
// True when the bot's own handle is the first @mention in the toot, i.e. the
32+
// post is addressed to the bot rather than merely referencing it in passing.
33+
// The rendered mention text is the bare "@username"; we also accept a
34+
// "username@domain" form defensively in case an instance renders the full acct.
35+
function addressesBot(content: string, self: MastodonAccount): boolean {
36+
const first = leadingMentions(content)[0];
37+
if (!first) return false;
38+
const username = self.username.toLowerCase();
39+
return first === username || first === self.acct.toLowerCase() || first.startsWith(`${username}@`);
40+
}
41+
3142
async function handleMention(self: MastodonAccount, n: MastodonNotification): Promise<void> {
3243
const status = n.status;
3344
if (!status) return;
3445
if (status.account.id === self.id) return; // never answer ourselves
3546

47+
// Only act when the toot is addressed to the bot, i.e. our handle is the
48+
// first thing in it. A mid-sentence mention ("love what @bot does") is
49+
// conversation, not a submission, so ignore it rather than parse the
50+
// chatter as a program and reply with a compile error.
51+
if (!addressesBot(status.content, self)) {
52+
console.log(`Mention ${n.id} from @${status.account.acct}: not addressed to the bot, skipping`);
53+
return;
54+
}
55+
3656
// A project link takes precedence over inline source: the program already
3757
// lives on the site, so render it directly rather than parsing the toot.
3858
// Directives (#128/#48 machine, #asm language) are read from the toot text

0 commit comments

Comments
 (0)