# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['bovine',
 'bovine.activitypub',
 'bovine.activitystreams',
 'bovine.activitystreams.activities',
 'bovine.activitystreams.common',
 'bovine.activitystreams.objects',
 'bovine.activitystreams.utils',
 'bovine.clients',
 'bovine.utils',
 'bovine.utils.crypto']

package_data = \
{'': ['*']}

install_requires = \
['aiohttp>=3.8.3,<4.0.0',
 'bleach>=6.0.0,<7.0.0',
 'cryptography>=39.0.0,<40.0.0',
 'multiformats>=0.2.1,<0.3.0',
 'python-dateutil>=2.8.2,<3.0.0',
 'tomli>=2.0.1,<3.0.0']

setup_kwargs = {
    'name': 'bovine',
    'version': '0.0.9',
    'description': 'Core functionality of bovine needed to build client to server applications',
    'long_description': '# Bovine\n\nThis package contains two essential parts of bovine. First\nit defines `BovineActor`, which contains all the necessities\nto write ActivityPub Clients. Furthermore, this package contains\nthe cryptographic routines to verify HTTP signatures.\n\nFurthermore, the folder `examples` contains a few examples on\nhow `BovineActor` can be used. The cryptographic routines\nare used in `bovine_fedi` to verify signatures.\n\n## Example: Make a post aka Faking at being a Server\n\nWhile [ActivityPub](https://www.w3.org/TR/activitypub/) specifies Server to Server and Client to Server, they really are just two sides of the same coin. In this example, we will work through how to use `BovineActor` to post a message.\n\nWithout having an ActivityPub Server supporting Client to Server, this will require a bit of setup. This setup will build a stub server that just allows other ActivityPub servers to associate us with a domain.\n\nThe stub server is given by the following snippet. One should note that it just answers with predefined json from a config file, that hasn\'t been generated yet. One could easily replace it with serving static files. See also [the Mastodon Blog](https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/) for a similar implementation.\n\n```python\nimport tomli\nimport json\nfrom quart import Quart\n\napp = Quart(__name__)\n\nwith open("server.toml", "rb") as fp:\n    config = tomli.load(fp)\n\n@app.get("/.well-known/webfinger")\nasync def webfinger():\n    return json.loads(config["webfinger"])\n\n\n@app.get("/actor")\nasync def actor():\n    return json.loads(config["actor"])\n\nif __name__ == "__main__":\n    app.run()\n```\n\nThe following script generates the config files. You will have to adapt the `hostname` variable and be able to serve the entire thing through https.\n\n```python\nimport bovine\nimport tomli_w\nimport json\n\nhostname = "bovine-demo.mymath.rocks"\n\npublic_key, private_key = bovine.utils.crypto.generate_public_private_key()\nactor_url = f"https://{hostname}/actor"\nactor = (\n    bovine.activitystreams.build_actor("actor")\n    .with_account_url(actor_url)\n    .with_public_key(public_key)\n)\n\nwebfinger = {\n    "subject": f"acct:actor@{hostname}",\n    "links": [\n        {\n            "href": actor_url,\n            "rel": "self",\n            "type": "application/activity+json",\n        }\n    ],\n}\n\nserver_config = {"actor": json.dumps(actor.build()), "webfinger": json.dumps(webfinger)}\n\nactor_config = {\n    "account_url": actor_url,\n    "public_key_url": f"{actor_url}#main-key",\n    "private_key": private_key,\n}\n\nwith open("server.toml", "wb") as fp:\n    tomli_w.dump(server_config, fp)\n\nwith open("bovine.toml", "wb") as fp:\n    tomli_w.dump(actor_config, fp)\n```\n\nYou can now access the urls, which are in my case [https://bovine-demo.mymath.rocks/actor](https://bovine-demo.mymath.rocks/actor) and [https://bovine-demo.mymath.rocks/.well-known/webfinger?resource=acct:actor@bovine-demo.mymath.rocks](https://bovine-demo.mymath.rocks/.well-known/webfinger?resource=acct:actor@bovine-demo.mymath.rocks). Using this, we can now lookup the fediverse handle `actor@bovine-demo.mymath.rocks` on most FediVerse applications.\n\nYou can now send a post via the following code snippet:\n\n```python\nimport asyncio\n\nfrom uuid import uuid4\nfrom bovine import BovineActor\n\ntarget_account = "https://mas.to/users/themilkman"\ntarget_inbox = "https://mas.to/users/themilkman/inbox"\n\n\nasync def run():\n    async with BovineActor.from_file("bovine.toml") as actor:\n        activity_factory, object_factory = actor.factories\n        note = (\n            object_factory.note("Hello")\n            .add_to(target_account)\n            .with_mention(target_account)\n            .build()\n        )\n        note["id"] = actor.actor_id + "/" + str(uuid4())\n        create = activity_factory.create(note).build()\n        create["id"] = actor.actor_id + "/" + str(uuid4())\n\n        await actor.post(target_inbox, create)\n\n\nasyncio.run(run())\n```\n\nA few comments are in order:\n\n- The id needs to be set on the Note and Create in order to be compatible with Mastodon. When using proper Client To Server as below, it is superfluous\n- The form of adding the `target_account` to both to and mention causes it to be a direct message.\n- Normally, you would look up the inbox from the target account.\n\n## Using BovineActor\n\nOne can import it via `from bovine import BovineActor`. Then one can either use it via:\n\n```python\nasync with BovineActor(config) as actor:\n    ...\n# or\nactor = BovineActor(config)\nawait actor.init()\n```\n\nHere the config object can be present in two variants. First it can contain the keys `host` and `private_key`, where `host` is the domain the ActivityPub Actor is on and `private_key` is a mutlicodec encoded Ed25519 key, whose corresponding did-key has been added to the Actor. In this case [Moo-Auth-1](https://blog.mymath.rocks/2023-03-15/BIN1_Moo_Authentication_and_Authoriation) will be used. The second variant is to use [HTTP Signatures](https://docs.joinmastodon.org/spec/security/#http), where the keys `account_url`, `public_key_url`, and `private_key` need to be present. Alternatively, to passing a config object, one can use `BovineActor.from_file(path_to_toml_file)`.\n\n### Making a post\n\nBovineActor contains two factories to create [ActivityStreams Objects](https://www.w3.org/TR/activitystreams-vocabulary/#object-types) and [ActivityStreams Activities](https://www.w3.org/TR/activitystreams-vocabulary/#activity-types). One can obtain them by running\n\n```python\nactivity_factory, object_factory = actor.factories\n```\n\nThe simplest usage example is a create wrapping a note, that looks like:\n\n```python\nactivity_factory, object_factory = actor.factories\nnote = object_factory.note("Hello").as_public().build()\ncreate = activity_factory.create(note).build()\n```\n\nThe result should be the something equivalent to the json\n\n```json\n{\n  "@context": "https://www.w3.org/ns/activitystreams",\n  "type": "Create",\n  "actor": "https://domain/actor",\n  "object": {\n    "attributedTo": "https://domain/actor",\n    "type": "Note",\n    "content": "Hello",\n    "published": "2023-03-25T08:12:32Z",\n    "to": "as:Public",\n    "cc": "https://domain/followers_collection"\n  },\n  "published": "2023-03-25T08:12:32Z",\n  "to": "as:Public",\n  "cc": "https://domain/followers_collection"\n}\n```\n\nThe details depend on the used actor and will likely contain superfluous elements until the creation process is improved. We can now send this activity to our outbox using\n\n```python\nawait actor.send_to_outbox(create)\n```\n\n__Note__: This is different from what we did in the first example, where we used `await actor.post(inbox, create)`. The difference is that in the first example, we faked being a server, now we are actually using Client To Server.\n\n### The inbox and outbox\n\nBy running\n\n```python\ninbox = await actor.inbox()\noutbox = await actor.outbox()\n```\n\none can obtain `CollectionHelper` objects. These are meant to make it easier to interact with collection objects. In the simplest use case, one can use\n\n```python\nawait inbox.next_item()\n```\n\nto get the items from the inbox one after the other. It is also possible to print a summary of all elements that have been fetched from the inbox using `await inbox.summary()`. Finally, it is possible to iterate over the inbox via\n\n```python\nasync for item in inbox.iterate(max_number=3):\n    do_something(item)\n```\n\n### Proxying elements\n\nWe have already seen the difference between using `post` directly to an inbox and posting to the actor\'s outbox using `send_to_outbox`. A similar pattern applies to fetching objects. Both of these commands often have a similar result\n\n```python\nawait actor.get(object_id)\nawait actor.proxy_element(object_id)\n```\n\nHowever, they do different things:\n\n- The first `actor.get` sends a webrequest to the server `object_id` is on and retrieves it\n- The second `actor.proxy_element` sends a request to the actor\'s server for the object. This request is then either answered from the server\'s object store or by the server fetching the object. The cache behavior is up to the server. Depending of the evolution of `proxyUrl` of an Actor, more options might be added here.\n\nAs most servers don\'t support Moo-Auth-1, using `proxy_element` is the only way to obtain foreign objects, when using it.\n\n### Event Source\n\nThe event source is demonstrated in `examples/sse.py`. First, the event source will be specified in a [FEP](https://codeberg.org/fediverse/fep) to come. It provides a way to receive updates from the server, whenever a new element is added to the inbox or outbox. The basic usage is\n\n```python\nevent_source = await actor.event_source()\nasync for event in event_source:\n    if event and event.data:\n        data = json.loads(event.data)\n        do_something(data)\n```\n\nIf you plan on writing long running applications, the event source does not automatically reconnect, so you will need to implement this. [mechanical_bull](https://codeberg.org/helge/mechanical_bull) uses the event source in this way.\n',
    'author': 'Helge',
    'author_email': 'helge.krueger@gmail.com',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://codeberg.org/helge/bovine',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.10,<4.0',
}


setup(**setup_kwargs)
