|
|
# A reactive bot framework for Pleroma
|
|
|
# A reactive bot framework for Plerma
|
|
|
|
|
|
## What is this?
|
|
|
|
|
|
This is a modification of Pleroma to support reactive server-side bots. By _reactive_ I mean they react when you talk to them, they don't push content to you uninvited. Examples are my pixelbot which lets you paint pixels on a shared canvas, my pollbot, a work in progress to support fedivers polls, or a bot to translate content or to let you bookmark content. Counter-examples are e.g. a followbot or a timeline scraper bot, as these are not reacting to posts from users.
|
|
|
This is a modification of [Pleroma](https://git.pleroma.social) to support reactive server-side bots. By _reactive_ I mean they react when you talk to them, they don't push content to you uninvited. Examples are my pixelbot which lets you paint pixels on a shared canvas, my pollbot, a work in progress to support fediverse polls, or a bot to translate content or to let you bookmark content. Counter-examples are e.g. a followbot or a timeline scraper bot, as these are not reacting to posts from users.
|
|
|
|
|
|
## How does it work?
|
|
|
## How do I use it?
|
|
|
|
|
|
Every bot is implemented as an asynchronous worker process. It receives a message, parses it and reacts to it by modifying its own state and/or replying to the received message.
|
|
|
Each bot is also an ordinary user on the instance, so you will have to create an account for your bot on your instance.
|
|
|
|
|
|
## How do I use it?
|
|
|
You will need to write the functionality for your bot in [Elixir](https://elixir-lang.org/). To write a bot you need implement to functions: an optional initialisation function and the main bot function. I provide a simple API to help you getting the ActivityPub messages and posting status updates.
|
|
|
|
|
|
- I recommend that you put your main bot code in `lib/pleroma/bots/yourbot.ex` an helper code in `lib/pleroma/bots/yourbot/`, with the namespace `Pleroma.Bots.YourBot`.
|
|
|
- In `config/config.exs`, enable support for the bot as follows:
|
|
|
- In [`config/config.exs`](https://git.pleroma.social/andarna/pleroma/blob/develop/config/config.exs), enable support for the bot as follows:
|
|
|
|
|
|
```elixir
|
|
|
config :pleroma, bots: [ :pixebot, :yourbot ]
|
... | ... | @@ -27,7 +27,7 @@ config :pleroma, :pixelbot, |
|
|
|
|
|
## Example: hellobot
|
|
|
|
|
|
This is a trivial bot which replies to any message with "Hello" and the nickname of the sender.
|
|
|
This is a trivial bot which replies to any message with "Hello" and the nickname of the sender. The code is in [`hellobot.ex`](https://git.pleroma.social/andarna/pleroma/blob/develop/lib/pleroma/bots/hellobot.ex).
|
|
|
|
|
|
```elixir
|
|
|
# WV: server for hellobot.
|
... | ... | @@ -90,9 +90,6 @@ defmodule Pleroma.Bots.HelloBot do |
|
|
# This will return the visibility of the sender's post (direct, followers-only aka private, unlisted, public)
|
|
|
{_,visibility} = Activity.get_visibility(received_activity["object"])
|
|
|
|
|
|
# You can provide your own visibility but normally you would inherit the visibility from the sender
|
|
|
# The other fields in `status_details` are `attachment:`, discussed below,
|
|
|
# and `to:` and `cc:` which you can used instead of `visibility:`.
|
|
|
status_details=%{
|
|
|
content: content,
|
|
|
visibility: visibility
|
... | ... | @@ -102,8 +99,21 @@ defmodule Pleroma.Bots.HelloBot do |
|
|
|
|
|
end
|
|
|
```
|
|
|
## Status details
|
|
|
|
|
|
The call `Common.bot_post_status(received_activity,status_details)` (defined in [`bots/bots_common.ex`](https://git.pleroma.social/andarna/pleroma/blob/develop/lib/pleroma/bots/bots_common.ex) )takes as first argument the original received message and as second a map which I call `status_details`. This map contains following fields:
|
|
|
|
|
|
```elixir
|
|
|
status_details = %{
|
|
|
content: <a string, see below>
|
|
|
attachments: <a list of attachments, see below>
|
|
|
visibility: <one of a fixed set of keyword strings, see below>
|
|
|
to: < a list of urls >
|
|
|
cc: < a list of urls >
|
|
|
}
|
|
|
```
|
|
|
|
|
|
## Content
|
|
|
### Content
|
|
|
|
|
|
The content you provide can contain some limited HTML tags (e.g. `<br>` and `<a>`), but the HTML is sanitized by the Pleroma server so full HTML is not supported. For example, pixelbot has the following content:
|
|
|
|
... | ... | @@ -111,7 +121,7 @@ The content you provide can contain some limited HTML tags (e.g. `<br>` and `<a> |
|
|
content = "Canvas at "<>now<>"<br><a href=\"https://pynq.limited.systems/pixelbot/canvas_512x512.png\" class='attachment'>canvas.png</a>"
|
|
|
```
|
|
|
|
|
|
## Attachments
|
|
|
### Attachments
|
|
|
|
|
|
|
|
|
The structure of the attachment to be provided in the `attachment:` field of the status details to be posted is specified as follows:
|
... | ... | @@ -126,6 +136,11 @@ The structure of the attachment to be provided in the `attachment:` field of the |
|
|
]
|
|
|
```
|
|
|
|
|
|
### Visibility
|
|
|
|
|
|
The visibility of the sender's post can be one of `direct`, `private` aka followers-only, `unlisted` or `public`. You can provide your own visibility but normally you would inherit the visibility from the sender, as obtained using the `get_visibility()` call. Instead of using the visibility you can also directly specify the lists of urls for the `to` and `cc` fields of the ActivityPub message.
|
|
|
|
|
|
|
|
|
## Message parsing
|
|
|
|
|
|
I have provided two helper functions for parsing the incoming messages:
|
... | ... | @@ -135,13 +150,22 @@ I have provided two helper functions for parsing the incoming messages: |
|
|
|
|
|
## API Overview
|
|
|
|
|
|
In module `Pleroma.Bots.Common` (`bots_common.ex`), `bot_post_status` takes the original received message and the status details as discussed above, i.e. a map with fields `:attachment`, `:content` and `:visibility` or `:to` and `:cc`.
|
|
|
In module `Pleroma.Bots.Common` ([`bots_common.ex`](https://git.pleroma.social/andarna/pleroma/blob/develop/lib/pleroma/bots/bots_common.ex)), `bot_post_status` takes the original received message and the status details as discussed above, i.e. a map with fields `:attachment`, `:content` and `:visibility` or `:to` and `:cc`.
|
|
|
|
|
|
```elixir
|
|
|
bot_post_status(orig_msg,status_details)
|
|
|
```
|
|
|
|
|
|
Module Pleroma.Bots.Activity (`bots_activity.ex`) contains functions to get certain field from an ActivityPub message. Most of the names are self-explanitary. The _object_ refers to the internal ActivityPub object inside a message; _context_ and _conversation_ are urls relating to the origin of the received message, usually you just pass these on as-is to indicate that your message is part of a thread.
|
|
|
Module Pleroma.Bots.Activity ([`bots_activity.ex`](https://git.pleroma.social/andarna/pleroma/blob/develop/lib/pleroma/bots/bots_activity.ex)) contains functions to get certain field from an ActivityPub message. Most of the names are self-explanitary:
|
|
|
- _nickname_ is the name of the bot
|
|
|
- _content_ is the actual textual content of the ActivityPub message posted to the bot
|
|
|
- _attachments_ is the attachment part of the ActivityPub message posted to the bot (in the format described above).
|
|
|
- _attachment_urls_ is a list of the urls of the attachments
|
|
|
- _actor_ is the sender of the message, _sender_ is a synonym.
|
|
|
- _visibility_ is the visibility of the message as explained above
|
|
|
|
|
|
|
|
|
The _object_ refers to the internal ActivityPub object inside a message; _context_ and _conversation_ are urls relating to the origin of the received message, usually you just pass these on as-is to indicate that your message is part of a thread.
|
|
|
|
|
|
```elixir
|
|
|
get_nickname(params)
|
... | ... | @@ -157,7 +181,7 @@ Module Pleroma.Bots.Activity (`bots_activity.ex`) contains functions to get cert |
|
|
get_sender(activity)
|
|
|
```
|
|
|
|
|
|
Module Pleroma.Bots.Parser (`bots_parser.ex`) provides convenience functions for parsing the incoming message.
|
|
|
Module Pleroma.Bots.Parser ([`bots_parser.ex`](https://git.pleroma.social/andarna/pleroma/blob/develop/lib/pleroma/bots/bots_parser.ex)) provides convenience functions for parsing the incoming message.
|
|
|
|
|
|
```elixir
|
|
|
strip_tags(msg)
|
... | ... | @@ -165,4 +189,25 @@ Module Pleroma.Bots.Parser (`bots_parser.ex`) provides convenience functions for |
|
|
```
|
|
|
|
|
|
|
|
|
## How does it work?
|
|
|
|
|
|
This section is of interest only if you want to hack Pleroma, you don't need it to write a bot.
|
|
|
|
|
|
Every bot is implemented as an asynchronous worker process. It receives a message, parses it and reacts to it by modifying its own state and/or replying to the received message. I modified the Pleroma source in only three places:
|
|
|
|
|
|
[`lib/pleroma/application.ex`](https://git.pleroma.social/andarna/pleroma/blob/develop/lib/pleroma/application.ex), [`lib/pleroma/web/activity_pub/activity_pub_controller.ex`](https://git.pleroma.social/andarna/pleroma/blob/develop/lib/pleroma/web/activity_pub/activity_pub_controller.ex) and [`lib/pleroma/web/common_api/common_api.ex`](https://git.pleroma.social/andarna/pleroma/blob/develop/lib/pleroma/web/common_api/common_api.ex)
|
|
|
|
|
|
In the `Pleroma.Application` I added a line to add workers:
|
|
|
|
|
|
```elixir
|
|
|
++ if !bots_enabled(), do: [], else: Pleroma.Bots.Common.add_bot_workers()
|
|
|
```
|
|
|
|
|
|
the other changes are in the functions `Pleroma.Web.ActivityPub.inbox` and `Pleroma.Web.CommonAPI.post`, in both cases I added a call to feed the message to the bots.
|
|
|
|
|
|
```elixir
|
|
|
Pleroma.Bots.Common.feed_to_bot()
|
|
|
```
|
|
|
|
|
|
This call checks if the recipient is in the list of bots and if so, sends the message asynchronously to the bot worker process.
|
|
|
This function is called in two places to support both local and federated requests. |