Skip to content


The source for bovine can be on codeberg. As bovine is meant as a general purpose Fediverse library, the actual source code for we are running is included in the docker files.

Furthermore, our bovine setup includes an automated actor called MooCow that replies to every post that mentions them with moo.

You can install the Fediverse pasture via

git clone
cd funfedidev/fediverse-pasture

Start bovine via

docker compose --file bovine.yml up

Access Data

Property Value
MooCow handle moocow@bovine
Your user handle vanilla@bovine
secret z3u2Yxcowsarethebestcowsarethebestcowsarethebest


Due to bovine’s architecture, actor URIs are not stable, but generated randomly on each container build. The same goes for the public / private key. It unfortunately, seems to be necessary to purge databases of some other Fediverse applications in order for them to recognize the bovine actors. This can be achieved via rm -rf volumes.

bovine repl

By running

docker compose --file bovine.yml --profile repl run bovine_repl

one can drop into a python REPL to interact as the user vanilla.

Messaging moocow

In order to message moocow, we will first look her up using webfinger. Then build a note that mentions her, finally send it to her then check our inbox.

>>> from bovine.clients import lookup_uri_with_webfinger
>>> remote, _ = await lookup_uri_with_webfinger(session=client.session, uri="acct:moocow@bovine", domain="http://bovine")
>>> mention = (await object_factory.mention_for_actor_uri(remote)).build()
>>> note = object_factory.note(to={remote}, tag=[mention], content="moo").build()
>>> await client.send_to_outbox(activity_factory.create(note).build())
>>> await client.inbox().__anext__()

The result is

    "@context": "about:bovine",
    "actor": {
        "id": "http://bovine/endpoints/tJSD4he3EVY3epL5P6R0gV-JfWTXP0MSsgecw_hgLp8",
        "inbox": "http://bovine/endpoints/MRZi0ilmfD7ZdD6Nxfte2XBrcVPyCFFMe6U7RuOnQMg",
        "name": "moocow",
        "outbox": "http://bovine/endpoints/eI-EJJIQFvOyNSkUGJcbKk7C_QFVsAoGiZIGqm5UyBE",
        "preferredUsername": "moocow",
        "publicKey": "http://bovine/endpoints/tJSD4he3EVY3epL5P6R0gV-JfWTXP0MSsgecw_hgLp8#serverKey",
        "type": "Person",
    "cc": ["http://bovine/endpoints/_l28Gx9jXnHHeMgua7_LiHRgHf6169GARwTGug6ZKYY"],
    "id": "http://bovine/objects/226e81b5-9266-41d4-889b-eb4c23fab6ef",
    "object": {
        "attributedTo": "http://bovine/endpoints/tJSD4he3EVY3epL5P6R0gV-JfWTXP0MSsgecw_hgLp8",
        "cc": ["http://bovine/endpoints/_l28Gx9jXnHHeMgua7_LiHRgHf6169GARwTGug6ZKYY"],
        "content": "mooooo",
        "id": "http://bovine/objects/f3ad7979-03a3-43b3-bfdf-37f2a92313be",
        "inReplyTo": "http://bovine/objects/1d092228-b508-4779-98a0-998d48c289e8",
        "likes": "http://bovine/objects/f3ad7979-03a3-43b3-bfdf-37f2a92313be/likes",
        "replies": "http://bovine/objects/f3ad7979-03a3-43b3-bfdf-37f2a92313be/replies",
        "shares": "http://bovine/objects/f3ad7979-03a3-43b3-bfdf-37f2a92313be/shares",
        "tag": [{"href": "http://bovine/endpoints/ITx52m5Cb4gxLfEzLRwVTtX25kd8NMVGM3EsWwn6gOo", "name": "vanilla@bovine", "type": "Mention"}],
        "to": ["http://bovine/endpoints/ITx52m5Cb4gxLfEzLRwVTtX25kd8NMVGM3EsWwn6gOo", "as:Public"],
        "type": "Note",
    "to": ["http://bovine/endpoints/ITx52m5Cb4gxLfEzLRwVTtX25kd8NMVGM3EsWwn6gOo", "as:Public"],
    "type": "Create",


To view the inbox, run

>>> await client.inbox().as_collection()
>>> items = _["items"]
>>> first_post = items[0]


Assuming, you declared first_post as in the previous section, you can now compose a reply via

>>> mention = (await object_factory.mention_for_actor_uri(first_post["actor"]["id"])).build()
>>> reply = object_factory.reply(first_post["object"], content="I'm replying to you", tag=[mention]).build()
>>> await client.send_to_outbox(activity_factory.create(reply).build())

What is available here is described in the documentation for