π©βπ« Tutorials#
In this page, we try to explain how to perform a number of actions with our Python library. Ideally, it is best to follow it step by step in order to understand the process, but if you feel comfortable, you can try to access directly to the section that interests you.
The basics#
OlvidClient#
To interact with the daemon, you will need to systematically create an instance of the OlvidClient class. This can be the original class or a subclass.
This class will automatically retrieve a client key using the environment or a .env file. (see Python Client Configuration)
Thanks to this client, you will be able to execute commands and set up notification listeners.
Hereβs what a basic main.py file looks like containing the creation of an Olvid client and executing a command (display current identity). This structure should be used in each of the code examples on this page, simply replace the content of the main function with your chosen code.
import asyncio
from olvid import OlvidClient, datatypes
async def main():
client = OlvidClient()
# code to replace
print(await client.identity_get())
asyncio.set_event_loop(asyncio.new_event_loop())
asyncio.get_event_loop().run_until_complete(main())
Command#
All command gRPC methods exposed by the daemon are easily accessible through methods of the OlvidClient class. For example, to send a message (gRPC method MessageSend), we will use the message_send method of our OlvidClient instance.
In the case where we know the identifier of the discussion in which to post, this would give:
from olvid import OlvidClient
client = OlvidClient()
client.message_send(discussion_id=1, body="Use Olvid !")
Notification#
The OlvidClient class also implements methods that allow you to easily listen for notifications emitted by the daemon. These methods all start with the on_ prefix and must be overridden in a subclass of OlvidClient.
For example, to display in the terminal when a message is received and when a reaction is added, you can do:
import asyncio
from olvid import OlvidClient, datatypes
class Bot(OlvidClient):
async def on_message_received(self, message: datatypes.Message):
print(f"Message received: {message.body}")
async def on_message_reaction_added(self, message: datatypes.Message, reaction: datatypes.MessageReaction):
print(f"Reaction added: {reaction.reaction}")
async def main():
bot = Bot()
await bot.run_forever()
asyncio.set_event_loop(asyncio.new_event_loop())
asyncio.get_event_loop().run_until_complete(main())
Good Practices#
Invitations#
To make it easier to connect with a bot, you can enable automatic acceptance of invitations. This can be done by modifying the configuration of the daemon using the enable_auto_invitation method of an Olvid client.
Note
Automatic acceptance of invitations can only accept introductions, group invitations, and personal conversation invitations. It cannot complete direct invitations with SAS code exchange.
Here is a program that launches a bot after setting up automatic acceptance of invitations.
import asyncio
from olvid import OlvidClient, datatypes
class Bot(OlvidClient):
async def on_message_received(self, message: datatypes.Message):
print(f"Message received: {message.body}")
async def main():
bot = Bot()
await bot.enable_auto_invitation(accept_all=True)
await bot.run_forever()
asyncio.set_event_loop(asyncio.new_event_loop())
asyncio.get_event_loop().run_until_complete(main())
Cleaning of Messages#
For performance and privacy reasons, we recommend enabling automatic message cleanup.
It is possible to limit the number of messages kept globally or per discussion (global_count and discussion_count) and/or define the maximum age of a message (existence_duration).
We also advise you to activate the deletion of messages in locked discussions.
from olvid import OlvidClient
async def main():
client = OlvidClient()
# keep messages up to 7 days,
# with a maximum of 100 messages and 20 messages per discussions,
# and deletes messages when a discussion is locked
await client.set_message_retention_policy(global_count=100, discussion_count=20,
existence_duration=60*60*24*7, clean_locked_discussions=True)
Docker#
Once your program is ready to be deployed, we recommend using our Docker image python-runner to run it. It is configured to install your project dependencies and execute your program.
Just make sure your project follows this directory structure. You can add as many files as needed, as long as your program launches from a main.py file.
| app
| | main.py
| | requirements.txt
All you need to do then is add a service to your docker-compose.yaml file.
bot:
image: olvid/bot-python-runner:{{docker_version}}
# pass client key to use in environment
environment:
- OLVID_CLIENT_KEY= # TODO set value
# mount your bot code as a volume
volumes:
- ./app:/app
depends_on:
- daemon
And finally, launch your bot.
# start bot
docker compose up -d bot
# show bot logs
docker compose logs -f bot
Various#
Send an ephemeral message#
The API entry points messageSend and messageSendWithAttachments allow you to specify the ephemerality of the message to be sent. To do this, use the olvid.datatypes.MessageEphemerality object.
Here is an example in Python. It is possible to specify the parameters read_once, visibility_duration, and existence_duration independently. The existence and visibility durations are in seconds.
import asyncio
from olvid import datatypes, OlvidClient
async def main():
client = OlvidClient()
async for discussion in client.discussion_list():
await client.message_send(
discussion_id=discussion.id,
body="Self-destruct message",
ephemerality=datatypes.MessageEphemerality(
visibility_duration=10,
existence_duration=60,
read_once=True
)
)
asyncio.run(main())
Advanced Usage#
Listener#
For a more granular implementation of notifications listening, it is possible to use the concept of Listener. A listener is a subscription of a callback function to a notification type. This function will be called each time a notification of this type is received.
Each type of notification has its own class in the module olvid.listeners.
In this example, the reply_to_message method will be called every time a message arrives.
import asyncio
from olvid import OlvidClient, datatypes, listeners
async def reply_to_message(message: datatypes.Message):
await message.reply(f"Reply to: {message.body}")
async def main():
client = OlvidClient()
listener = listeners.MessageReceivedListener(handler=reply_to_message)
client.add_listener(listener)
await client.run_forever()
asyncio.set_event_loop(asyncio.new_event_loop())
asyncio.get_event_loop().run_until_complete(main())
Listener: expiration#
By default, a listener listens to notifications indefinitely, but it is possible to use the count argument to only listen for a certain number of notifications. In this case, once count notifications have been processed, the listener is stopped.
In this example, we respond to the next received message and then the program stops.
import asyncio
from olvid import OlvidClient, datatypes, listeners
async def reply_to_message(message: datatypes.Message):
await message.reply(f"Reply to: {message.body}")
async def main():
client = OlvidClient()
# added count parameter set to 1
listener = listeners.MessageReceivedListener(handler=reply_to_message, count=1)
client.add_listener(listener)
# wait_for_listeners_end: returns when all listeners are finished
await client.wait_for_listeners_end()
print("Program end")
asyncio.set_event_loop(asyncio.new_event_loop())
asyncio.get_event_loop().run_until_complete(main())
Listener: Filtering#
Listeners also allow filtering of notifications to be processed. To do this, we can add one or more filter functions to our listener. The different forms of filtering depend on the notification type and are defined in the messages of the MessageReceivedNotificationSubscription type (cf protobuf description of the daemonβs API).
For example, we might want to process only messages sent by a specific contact.
import asyncio
from olvid import OlvidClient, datatypes, listeners
CONTACT_ID: int = 1
async def reply_to_message(message: datatypes.Message):
await message.reply(f"Reply to: {message.body}")
async def main():
client = OlvidClient()
# only notifications matching check_sender_id will be handled
listener = listeners.MessageReceivedListener(handler=reply_to_message, filter=datatypes.MessageFilter(sender_contact_id=CONTACT_ID))
client.add_listener(listener)
await client.wait_for_listeners_end()
asyncio.set_event_loop(asyncio.new_event_loop())
asyncio.get_event_loop().run_until_complete(main())
It is perfectly possible to combine filtering and expiration.
Here, it is used to perform an action and exit the program when the message that has just been sent arrives on the recipientβs phone.
import asyncio
from olvid import OlvidClient, datatypes, listeners
DISCUSSION_ID: int = 1
async def main():
client = OlvidClient()
# send a message
message = await client.message_send(discussion_id=DISCUSSION_ID, body="Hello there !")
# add a listener to do something when message had been delivered, then program will exit
listener = listeners.MessageDeliveredListener(
handler=lambda m: print("Message delivered"),
message_ids=[message.id],
count=1
)
client.add_listener(listener)
await client.wait_for_listeners_end()
print("Program end")
asyncio.set_event_loop(asyncio.new_event_loop())
asyncio.get_event_loop().run_until_complete(main())