πŸ‘©β€πŸ« 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())

Tips and Tricks#

AutoInvitationBot#

To make it easier to connect with a bot, it is possible to set up a bot that will accept all received invitations.

Just create an AutoInvitationBot instance from the module olvid.tools. It will automatically register to receive new invitation notifications and accept them.

Note

An AutoInvitationBot can only accept introductions and group invitations. It cannot automatically accept direct invitations with SAS code exchange.

Here is a program that launches an instance of AutoInvitationBot in the background. It is possible to launch several bots in parallel.

import asyncio
from olvid import OlvidClient, datatypes, tools

class Bot(OlvidClient):
    async def on_message_received(self, message: datatypes.Message):
        print(f"Message received: {message.body}")

async def main():
    bot = Bot()
    tools.AutoInvitationBot()
    await bot.run_forever()

asyncio.set_event_loop(asyncio.new_event_loop())
asyncio.get_event_loop().run_until_complete(main())

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())

Advanced listener#

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

def get_message_id_checker(message_id: datatypes.MessageId):
  def checker(message: datatypes.Message):
      return message.sender_id == message_id
  return checker

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"),
        checkers=[get_message_id_checker(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())