Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"Bash(awk:*)",
"Bash(sed:*)",
"Bash(deno task:*)",
"Bash(deno test:*)"
"Bash(deno test:*)",
"Bash(gh issue view:*)"
],
"deny": []
}
Expand Down
15 changes: 14 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ To be released.
- BotKit now supports Node.js alongside of Deno. The minimum required
version of Node.js is 22.0.0.

- BotKit now supports publishing polls. [[#7], [#8]]

- Added `Poll` interface.
- Added `Vote` interface.
- Added an overload of the `Session.publish()` method that accepts
`SessionPublishOptionsWithQuestion` as the second argument.
- Added `SessionPublishOptionsWithQuestion` interface.
- Added `Bot.onVote` event.
- Added `VoteEventHandler` type.

- Added `@fedify/botkit/repository` module that provides repository
implementations for BotKit.

Expand All @@ -22,7 +32,10 @@ To be released.
- Added `Create` class.
- Added `MemoryCachedRepository` class.

- Upgraded Fedify to 1.6.1.
- Upgraded Fedify to 1.7.1.

[#7]: https://github.com/fedify-dev/botkit/issues/7
[#8]: https://github.com/fedify-dev/botkit/pull/8


Version 0.2.0
Expand Down
9 changes: 7 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
"./events": "./src/events.ts",
"./follow": "./src/follow.ts",
"./message": "./src/message.ts",
"./poll": "./src/poll.ts",
"./reaction": "./src/reaction.ts",
"./repository": "./src/repository.ts",
"./session": "./src/session.ts",
"./text": "./src/text.ts"
},
"imports": {
"@fedify/fedify": "jsr:@fedify/fedify@^1.6.1",
"@fedify/fedify": "jsr:@fedify/fedify@^1.7.1",
"@fedify/markdown-it-hashtag": "jsr:@fedify/markdown-it-hashtag@^0.3.0",
"@fedify/markdown-it-mention": "jsr:@fedify/markdown-it-mention@^0.3.0",
"@logtape/logtape": "jsr:@logtape/logtape@^1.0.0",
Expand Down Expand Up @@ -54,7 +55,11 @@
"test": "deno test --allow-env=NODE_V8_COVERAGE,JEST_WORKER_ID --allow-net=hollo.social --parallel",
"test:node": "pnpm install && pnpm test",
"test-all": {
"dependencies": ["check", "test", "test:node"]
"dependencies": [
"check",
"test",
"test:node"
]
},
"coverage": "deno task test --coverage --clean && deno coverage --html",
"hooks:install": "deno run --allow-read=deno.json,.git/hooks/ --allow-write=.git/hooks/ jsr:@hongminhee/deno-task-hooks",
Expand Down
31 changes: 15 additions & 16 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 78 additions & 0 deletions docs/concepts/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,3 +444,81 @@ bot.onUnreact = async (session, reaction) => {
}
};
~~~~


Vote
----

*This API is available since BotKit 0.3.0.*

The `~Bot.onVote` event handler is called when someone votes on a poll created
by your bot. It receives a `Vote` object, which represents the vote activity,
as the second argument.

The following is an example of a vote event handler that sends a direct message
when someone votes on your bot's poll:

~~~~ typescript twoslash
import { type Bot, text } from "@fedify/botkit";
const bot = {} as unknown as Bot<void>;
// ---cut-before---
bot.onVote = async (session, vote) => {
await session.publish(
text`Thanks for voting "${vote.option}" on my poll, ${vote.actor}!`,
{ visibility: "direct" },
);
};
~~~~

The `Vote` object contains the following properties:

`~Vote.actor`
: The actor who voted.

`~Vote.option`
: The option that was voted for (as a string).

`~Vote.poll`
: Information about the poll including whether it allows `~Poll.multiple`
choices, all available `~Poll.options`, and the `~Poll.endTime`.

`~Vote.message`
: The poll message that was voted on.

Note that for multiple choice polls, each option selection creates a separate
`Vote` object, even if the same user selects multiple options.

> [!TIP]
> You can check if a poll allows multiple selections by accessing the
> `vote.poll.multiple` property:
>
> ~~~~ typescript twoslash
> import { type Bot, text } from "@fedify/botkit";
> const bot = {} as unknown as Bot<void>;
> // ---cut-before---
> bot.onVote = async (session, vote) => {
> if (vote.poll.multiple) {
> await vote.message.reply(
> text`${vote.actor} selected "${vote.option}" in the multiple choice poll!`
> );
> } else {
> await vote.message.reply(
> text`${vote.actor} voted for "${vote.option}"!`
> );
> }
> };
> ~~~~

> [!NOTE]
> The `~Bot.onVote` event handler is only called for votes on polls created by
> your bot. Votes on polls created by others will not trigger this event.

> [!NOTE]
> The bot author cannot vote on their own polls—such votes are automatically
> ignored and will not trigger the `~Bot.onVote` event handler.

> [!NOTE]
> On a poll with multiple options, each selection creates a separate `Vote`
> object, even if the same user selects multiple options. This means that if
> a user selects multiple options in a multiple-choice poll, the `~Bot.onVote`
> event handler will be called multiple times, once for each selected option.
64 changes: 64 additions & 0 deletions docs/concepts/message.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,70 @@ bot.onMention = async (session, message) => {
> while others like Mastodon might implement quotes differently or not support
> them at all.

### Polls

*This API is available since BotKit 0.3.0.*

You can attach a poll to a message by providing
the `~SessionPublishOptionsWithQuestion.poll` option along with
the message class `Question`. The poll option allows users to vote on
different choices. For example:

~~~~ typescript twoslash
import { type Session, Question, text } from "@fedify/botkit";
import { Temporal } from "@js-temporal/polyfill";
const session = {} as unknown as Session<void>;
// ---cut-before---
await session.publish(text`What's your favorite color?`, {
class: Question,
poll: {
multiple: false, // Single choice poll
options: ["Red", "Blue", "Green"],
endTime: Temporal.Now.instant().add({ hours: 24 }),
},
});
~~~~

For multiple choice polls, set `~Poll.multiple` to `true`:

~~~~ typescript twoslash
import { type Session, Question, text } from "@fedify/botkit";
import { Temporal } from "@js-temporal/polyfill";
const session = {} as unknown as Session<void>;
// ---cut-before---
await session.publish(text`Which programming languages do you know?`, {
class: Question,
poll: {
multiple: true, // Multiple choice poll
options: ["JavaScript", "TypeScript", "Python", "Rust"],
endTime: Temporal.Now.instant().add({ hours: 24 * 7 }),
},
});
~~~~

The poll configuration includes:

`~Poll.multiple`
: Whether the poll allows multiple selections (`true` for multiple
choice, `false` for single choice).

`~Poll.options`
: An array of strings representing the poll options. Each option
must be unique and non-empty.

`~Poll.endTime`
: A [`Temporal.Instant`] representing when the poll closes.

> [!NOTE]
> Polls are represented as ActivityPub `Question` objects. Not all ActivityPub
> implementations support polls, and the behavior may vary between different
> platforms.

> [!TIP]
> When someone votes on your bot's poll, the `~Bot.onVote` event handler will
> be called. See the [*Vote* section](./events.md#vote) in the *Events* concept
> document for more information.


Extracting information from a message
-------------------------------------
Expand Down
53 changes: 53 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,59 @@ BotKit. The bot performs the following actions:
:::


One-time passcode authentication bot
------------------------------------

This example demonstrates how to implement an emoji-based one-time passcode
authentication system using BotKit's poll functionality. The bot provides
a simple two-factor authentication mechanism through the fediverse.

The authentication flow works as follows:

1. *Initial setup*: The user visits the web interface and enters their fediverse
handle (e.g., `@username@server.com`).

2. *Challenge generation*: The system generates a random set of emojis and sends
a direct message containing a poll with all available emoji options to
the user's fediverse account.

3. *Web interface display*: The correct emoji sequence is displayed on the
web page.

4. *User response*: The user votes for the matching emojis in the poll they
received via direct message.

5. *Verification*: The system verifies that the user selected exactly
the same emojis shown on the web page.

6. *Authentication result*: If the emoji selection matches, authentication is
successful.

Key features:

- Uses BotKit's [poll functionality](./concepts/message.md#polls) for secure
voting
- Implements a 15-minute expiration for both the challenge and authentication
attempts
- Provides a clean web interface using [Hono] framework and [Pico CSS]
- Stores temporary data using [Deno KV] for session management
- Supports both direct message delivery and real-time vote tracking

This example showcases how to combine ActivityPub's social features with web
authentication, demonstrating BotKit's capability to bridge fediverse
interactions with traditional web applications.

::: code-group

<<< @/../examples/otp.tsx [otp.tsx]

:::

[Hono]: https://hono.dev/
[Pico CSS]: https://picocss.com/
[Deno KV]: https://deno.com/kv


FediChatBot
-----------

Expand Down
1 change: 1 addition & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"@fedify/fedify": "catalog:",
"@fedify/postgres": "^0.3.0",
"@fedify/redis": "^0.4.0",
"@js-temporal/polyfill": "^0.5.1",
"@shikijs/vitepress-twoslash": "^3.7.0",
"@types/deno": "^2.3.0",
"@types/node": "^24.0.3",
Expand Down
Loading
Loading