Skip to content

Commit a1068dd

Browse files
authored
Merge pull request #8 from fedify-dev/poll
2 parents 4397bd4 + 4eae51f commit a1068dd

23 files changed

+1568
-43
lines changed

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"Bash(awk:*)",
88
"Bash(sed:*)",
99
"Bash(deno task:*)",
10-
"Bash(deno test:*)"
10+
"Bash(deno test:*)",
11+
"Bash(gh issue view:*)"
1112
],
1213
"deny": []
1314
}

CHANGES.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ To be released.
99
- BotKit now supports Node.js alongside of Deno. The minimum required
1010
version of Node.js is 22.0.0.
1111

12+
- BotKit now supports publishing polls. [[#7], [#8]]
13+
14+
- Added `Poll` interface.
15+
- Added `Vote` interface.
16+
- Added an overload of the `Session.publish()` method that accepts
17+
`SessionPublishOptionsWithQuestion` as the second argument.
18+
- Added `SessionPublishOptionsWithQuestion` interface.
19+
- Added `Bot.onVote` event.
20+
- Added `VoteEventHandler` type.
21+
- Added `KvStoreRepositoryPrefixes.polls` option.
22+
1223
- Added `@fedify/botkit/repository` module that provides repository
1324
implementations for BotKit.
1425

@@ -22,7 +33,10 @@ To be released.
2233
- Added `Create` class.
2334
- Added `MemoryCachedRepository` class.
2435

25-
- Upgraded Fedify to 1.6.1.
36+
- Upgraded Fedify to 1.8.0.
37+
38+
[#7]: https://github.com/fedify-dev/botkit/issues/7
39+
[#8]: https://github.com/fedify-dev/botkit/pull/8
2640

2741

2842
Version 0.2.0

deno.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
"./events": "./src/events.ts",
1414
"./follow": "./src/follow.ts",
1515
"./message": "./src/message.ts",
16+
"./poll": "./src/poll.ts",
1617
"./reaction": "./src/reaction.ts",
1718
"./repository": "./src/repository.ts",
1819
"./session": "./src/session.ts",
1920
"./text": "./src/text.ts"
2021
},
2122
"imports": {
22-
"@fedify/fedify": "jsr:@fedify/fedify@^1.6.1",
23+
"@fedify/fedify": "jsr:@fedify/fedify@^1.8.0-dev.910+8a000b1c",
2324
"@fedify/markdown-it-hashtag": "jsr:@fedify/markdown-it-hashtag@^0.3.0",
2425
"@fedify/markdown-it-mention": "jsr:@fedify/markdown-it-mention@^0.3.0",
2526
"@logtape/logtape": "jsr:@logtape/logtape@^1.0.0",
@@ -54,7 +55,11 @@
5455
"test": "deno test --allow-env=NODE_V8_COVERAGE,JEST_WORKER_ID --allow-net=hollo.social --parallel",
5556
"test:node": "pnpm install && pnpm test",
5657
"test-all": {
57-
"dependencies": ["check", "test", "test:node"]
58+
"dependencies": [
59+
"check",
60+
"test",
61+
"test:node"
62+
]
5863
},
5964
"coverage": "deno task test --coverage --clean && deno coverage --html",
6065
"hooks:install": "deno run --allow-read=deno.json,.git/hooks/ --allow-write=.git/hooks/ jsr:@hongminhee/deno-task-hooks",

deno.lock

Lines changed: 15 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/concepts/events.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,3 +444,78 @@ bot.onUnreact = async (session, reaction) => {
444444
}
445445
};
446446
~~~~
447+
448+
449+
Vote
450+
----
451+
452+
*This API is available since BotKit 0.3.0.*
453+
454+
The `~Bot.onVote` event handler is called when someone votes on a poll created
455+
by your bot. It receives a `Vote` object, which represents the vote activity,
456+
as the second argument.
457+
458+
The following is an example of a vote event handler that sends a direct message
459+
when someone votes on your bot's poll:
460+
461+
~~~~ typescript twoslash
462+
import { type Bot, text } from "@fedify/botkit";
463+
const bot = {} as unknown as Bot<void>;
464+
// ---cut-before---
465+
bot.onVote = async (session, vote) => {
466+
await session.publish(
467+
text`Thanks for voting "${vote.option}" on my poll, ${vote.actor}!`,
468+
{ visibility: "direct" },
469+
);
470+
};
471+
~~~~
472+
473+
The `Vote` object contains the following properties:
474+
475+
`~Vote.actor`
476+
: The actor who voted.
477+
478+
`~Vote.option`
479+
: The option that was voted for (as a string).
480+
481+
`~Vote.poll`
482+
: Information about the poll including whether it allows `~Poll.multiple`
483+
choices, all available `~Poll.options`, and the `~Poll.endTime`.
484+
485+
`~Vote.message`
486+
: The poll message that was voted on.
487+
488+
> [!TIP]
489+
> You can check if a poll allows multiple selections by accessing the
490+
> `vote.poll.multiple` property:
491+
>
492+
> ~~~~ typescript twoslash
493+
> import { type Bot, text } from "@fedify/botkit";
494+
> const bot = {} as unknown as Bot<void>;
495+
> // ---cut-before---
496+
> bot.onVote = async (session, vote) => {
497+
> if (vote.poll.multiple) {
498+
> await vote.message.reply(
499+
> text`${vote.actor} selected "${vote.option}" in the multiple choice poll!`
500+
> );
501+
> } else {
502+
> await vote.message.reply(
503+
> text`${vote.actor} voted for "${vote.option}"!`
504+
> );
505+
> }
506+
> };
507+
> ~~~~
508+
509+
> [!NOTE]
510+
> The `~Bot.onVote` event handler is only called for votes on polls created by
511+
> your bot. Votes on polls created by others will not trigger this event.
512+
513+
> [!NOTE]
514+
> The bot author cannot vote on their own pollssuch votes are automatically
515+
> ignored and will not trigger the `~Bot.onVote` event handler.
516+
517+
> [!NOTE]
518+
> On a poll with multiple options, each selection creates a separate `Vote`
519+
> object, even if the same user selects multiple options. This means that if
520+
> a user selects multiple options in a multiple-choice poll, the `~Bot.onVote`
521+
> event handler will be called multiple times, once for each selected option.

docs/concepts/message.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,70 @@ bot.onMention = async (session, message) => {
257257
> while others like Mastodon might implement quotes differently or not support
258258
> them at all.
259259
260+
### Polls
261+
262+
*This API is available since BotKit 0.3.0.*
263+
264+
You can attach a poll to a message by providing
265+
the `~SessionPublishOptionsWithQuestion.poll` option along with
266+
the message class `Question`. The poll option allows users to vote on
267+
different choices. For example:
268+
269+
~~~~ typescript twoslash
270+
import { type Session, Question, text } from "@fedify/botkit";
271+
import { Temporal } from "@js-temporal/polyfill";
272+
const session = {} as unknown as Session<void>;
273+
// ---cut-before---
274+
await session.publish(text`What's your favorite color?`, {
275+
class: Question,
276+
poll: {
277+
multiple: false, // Single choice poll
278+
options: ["Red", "Blue", "Green"],
279+
endTime: Temporal.Now.instant().add({ hours: 24 }),
280+
},
281+
});
282+
~~~~
283+
284+
For multiple choice polls, set `~Poll.multiple` to `true`:
285+
286+
~~~~ typescript twoslash
287+
import { type Session, Question, text } from "@fedify/botkit";
288+
import { Temporal } from "@js-temporal/polyfill";
289+
const session = {} as unknown as Session<void>;
290+
// ---cut-before---
291+
await session.publish(text`Which programming languages do you know?`, {
292+
class: Question,
293+
poll: {
294+
multiple: true, // Multiple choice poll
295+
options: ["JavaScript", "TypeScript", "Python", "Rust"],
296+
endTime: Temporal.Now.instant().add({ hours: 24 * 7 }),
297+
},
298+
});
299+
~~~~
300+
301+
The poll configuration includes:
302+
303+
`~Poll.multiple`
304+
: Whether the poll allows multiple selections (`true` for multiple
305+
choice, `false` for single choice).
306+
307+
`~Poll.options`
308+
: An array of strings representing the poll options. Each option
309+
must be unique and non-empty.
310+
311+
`~Poll.endTime`
312+
: A [`Temporal.Instant`] representing when the poll closes.
313+
314+
> [!NOTE]
315+
> Polls are represented as ActivityPub `Question` objects. Not all ActivityPub
316+
> implementations support polls, and the behavior may vary between different
317+
> platforms.
318+
319+
> [!TIP]
320+
> When someone votes on your bot's poll, the `~Bot.onVote` event handler will
321+
> be called. See the [*Vote* section](./events.md#vote) in the *Events* concept
322+
> document for more information.
323+
260324

261325
Extracting information from a message
262326
-------------------------------------

docs/examples.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,59 @@ BotKit. The bot performs the following actions:
2525
:::
2626

2727

28+
One-time passcode authentication bot
29+
------------------------------------
30+
31+
This example demonstrates how to implement an emoji-based one-time passcode
32+
authentication system using BotKit's poll functionality. The bot provides
33+
a simple two-factor authentication mechanism through the fediverse.
34+
35+
The authentication flow works as follows:
36+
37+
1. *Initial setup*: The user visits the web interface and enters their fediverse
38+
handle (e.g., `@username@server.com`).
39+
40+
2. *Challenge generation*: The system generates a random set of emojis and sends
41+
a direct message containing a poll with all available emoji options to
42+
the user's fediverse account.
43+
44+
3. *Web interface display*: The correct emoji sequence is displayed on the
45+
web page.
46+
47+
4. *User response*: The user votes for the matching emojis in the poll they
48+
received via direct message.
49+
50+
5. *Verification*: The system verifies that the user selected exactly
51+
the same emojis shown on the web page.
52+
53+
6. *Authentication result*: If the emoji selection matches, authentication is
54+
successful.
55+
56+
Key features:
57+
58+
- Uses BotKit's [poll functionality](./concepts/message.md#polls) for secure
59+
voting
60+
- Implements a 15-minute expiration for both the challenge and authentication
61+
attempts
62+
- Provides a clean web interface using [Hono] framework and [Pico CSS]
63+
- Stores temporary data using [Deno KV] for session management
64+
- Supports both direct message delivery and real-time vote tracking
65+
66+
This example showcases how to combine ActivityPub's social features with web
67+
authentication, demonstrating BotKit's capability to bridge fediverse
68+
interactions with traditional web applications.
69+
70+
::: code-group
71+
72+
<<< @/../examples/otp.tsx [otp.tsx]
73+
74+
:::
75+
76+
[Hono]: https://hono.dev/
77+
[Pico CSS]: https://picocss.com/
78+
[Deno KV]: https://deno.com/kv
79+
80+
2881
FediChatBot
2982
-----------
3083

docs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"@fedify/fedify": "catalog:",
55
"@fedify/postgres": "^0.3.0",
66
"@fedify/redis": "^0.4.0",
7+
"@js-temporal/polyfill": "^0.5.1",
78
"@shikijs/vitepress-twoslash": "^3.7.0",
89
"@types/deno": "^2.3.0",
910
"@types/node": "^24.0.3",

0 commit comments

Comments
 (0)