đŸ‘©â€đŸ’» DĂ©veloppement#

Nous allons essayer d’expliquer le fonctionnement et l’utilisation de notre paquet npm @olvid/bot-node. IdĂ©alement, il vaut mieux la suivre Ă©tape par Ă©tape afin de suivre le cheminement, mais si vous vous sentez Ă  l’aise, vous pouvez essayer d’accĂ©der directement Ă  la section qui vous intĂ©resse.

À propos de @olvid/bot-web

Le paquet @olvid/bot-web est trùs similaire dans son usage mais possùde certaines contraintes. (voir 🌐 Navigateur)

  • il n’utilise pas gRPC directement, il doit donc utiliser un proxy cf: Daemon et proxy

  • il n’a pas accĂšs Ă  l’environnement, il faut donc passer l’url du serveur et la clĂ© client lors de la crĂ©ation d’un OlvidClient (ou la passer par d’autre moyens)

  • l’envoi de fichier ne fonctionne pas donc certaines mĂ©thodes comme sendMessageWithAttachments ne sont pas implĂ©mentĂ©es.

Vous pouvez facilement adapter le code présent dans cette page, mais il ne fonctionnera pas tel quel.

Les bases#

OlvidClient#

Pour interagir avec le daemon, il vous faudra systĂ©matiquement crĂ©er une instance de la classe OlvidClient. Il peut s’agir de la classe d’origine ou d’une classe enfant.

En node cette classe va automatiquement rĂ©cupĂ©rer une clĂ© client en utilisant l’environnement ou un fichier .env. (cf. Configuration)

Grùce à ce client, on pourra notamment exécuter des commandes et réagir à des notifications.

Voici Ă  quoi ressemble un fichier main.ts de base contenant la crĂ©ation d’un client Olvid et l’exĂ©cution d’une commande (afficher l’identitĂ© courante). Cette structure est Ă  utiliser dans chacun des exemples de code de cette page, il suffit de remplacer le contenu de la fonction main par le code de votre choix.

import { OlvidClient, datatypes } from "@olvid/bot-node";

async function main() {
    const client = new OlvidClient();
    const identity: datatypes.Identity = await client.identityGet();
    console.log(identity)
}

main().then();

Commande#

Toutes les mĂ©thodes gRPC de commandes exposĂ©es par le daemon sont facilement accessibles grĂące Ă  des mĂ©thodes de la classe OlvidClient. Par exemple, pour envoyer un message (mĂ©thode MessageSend en gRPC), on utilisera la mĂ©thode messageSend de notre instance d’OlvidClient.

Dans le cas oĂč nous connaissons l’identifiant de la discussion dans laquelle poster, cela donnerait :

import { OlvidClient, datatypes } from "@olvid/bot-node";

async function main() {
    const client = new OlvidClient();
    await client.messageSend({discussionId: 1, body: "Use Olvid !"});
    console.log(identity)
}

main().then();

Notification#

La classe OlvidClient permet de facilement Ă©couter les notifications Ă©mises par le daemon. Ces mĂ©thodes commencent toutes par le prĂ©fixe on et permettent d’ajouter une fonction de rappel qui sera executĂ© Ă  chaque nouvelle notification.

Par exemple, pour afficher dans le terminal quand un message est reçu et lorsqu’une rĂ©action est ajoutĂ©e, on peut faire :

import { OlvidClient, datatypes } from "@olvid/bot-node";

async function main() {
    const client = new OlvidClient();
    client.onMessageReceived({
        callback: (message: datatypes.Message) => {
            console.log("Message received:", message.body);
        }
    })
    client.onMessageReactionAdded({
        callback: (message: datatypes.Message, reaction: datatypes.MessageReaction) => {
            console.log("Reaction added:", reaction.reaction);
        }
    })
    
    await client.runForever();
}

main().then();

Utilisation avancée#

Filtrage des éléments#

Pour accéder à des éléments précis de la base donnée on peut appliquer un filtre lors des commandes de type List.

Voici des exemples de filtres possibles, les diffĂ©rents filtres sont dĂ©finis dans la description protobuf de l’API du daemon (ici).

import {datatypes, OlvidClient} from "@olvid/bot-node";

async function main() {
    const client = new OlvidClient();

    /*
     ** list one to one discussions
     */
    let otoDiscussionFilter = new datatypes.DiscussionFilter({
        type: datatypes.DiscussionFilter_Type.OTO
    });
    for await (const discussion of client.discussionList({filter: otoDiscussionFilter})) {
        console.log(`OneToOne discussion: ${discussion.id} - ${discussion.title}`)
    }
    
    /*
    ** list messages in a specific discussion with reactions 
     */
    const messageFilter = new datatypes.MessageFilter({
        discussionId: 1n,
        hasReaction: datatypes.MessageFilter_Reaction.HAS
    })
    const filteredMessages = await Array.fromAsync(client.messageList({filter: messageFilter}));
    console.log("Message with reactions in discussion", filteredMessages)
}

main().then();

Filtrage des notifications#

Pour une implĂ©mentation plus fine de l’écoute des notifications, on peut utiliser un systĂšme de filtrage. Les diffĂ©rentes formes de filtrage dĂ©pendent du type de notification et sont dĂ©finis dans les messages du type MessageReceivedNotificationSubscription (cf description protobuf de l’API du daemon).

count#

Toutes les notifications peuvent ĂȘtre programmĂ©es pour n’ĂȘtre reçu qu’un nombre dĂ©fini de fois Ă  l’aide de l’argument count.

filter#

La plupart des notifications possĂšdent un ou plusieurs attributs filter. Si un filtre est spĂ©cifiĂ© une notification doit correspondre Ă  l’ensemble des critĂšres renseignĂ©s pour ĂȘtre envoyĂ©e.

Exemple#

Par exemple, on peut rĂ©pondre uniquement au prochain message qui contient hello puis quitter le programme. Pour cela, on construit un objet de type MessageFilter qui n’acceptera que les messages contenant le terme « hello » (bodySearch), et on spĂ©cifie qu’on ne veut traiter qu’une seule notification (count Ă  1).

import { OlvidClient, datatypes, tools } from "@olvid/bot-node";

async function main() {
    const client = new OlvidClient();

    client.onMessageReceived({
        callback: async (message: datatypes.Message) => {
            await client.messageSend({
                discussionId: message.discussionId,
                body: "Hello World !",
            })
        },
        filter: new datatypes.MessageFilter({bodySearch: "hello"}),
        count: 1n
    })
    await client.waitForCallbacksEnd();
}

main().then();

Astuce

Dans les filtres, les champs de type string se terminant en search sont interprétés par un moteur de regexp pour une plus grande polyvalence.

Raccourcis#

Les classes de base du module datatypes (Message, Discussion, Attachment, 
) implémentent des méthodes de raccourci pour avoir un code plus condensé et agréable à écrire.

Voici des exemples de méthodes, la liste exhaustive est définie dans le code source des classes, ou est accessible ici.

import { OlvidClient, datatypes, tools } from "@olvid/bot-node";

async function main() {
    const client = new OlvidClient();

    client.onMessageReceived({
        callback: async (message: datatypes.Message) => {
            // easily reply to a message, and access to it's sender details
            const senderContact: datatypes.Contact = (await message.getSenderContact(client))
            await message.reply(
                client,
                `Hello ${senderContact.displayName} ! 👋`
            );
            
            // save attachments on disk if there are some
            if (message.attachmentsCount) {
                (await message.getAttachments(client)).forEach((attachment) => {
                    attachment.save(client, "./attachments")
                })
            }
        }
    })

    // post a message in a discussion and wait for message to be uploaded on server
    const discussion = (await Array.fromAsync(client.discussionList()))[0]
    const sentMessage = await discussion.postMessage(client, "Hello there");
    await sentMessage.waitFor.messageToBeUploaded(client);
    
    await client.runForever();
}

main().then();

Décorateurs#

Nous avons mis en place des dĂ©corateurs qui permettent de facilement Ă©couter des notifications et mettre en place des commandes pour ChatBot. Ils permettent d’écrire du code plus joli pour les programmes de type Bot qui tournent Ă  l’infini.

Pour les utiliser, il faut sous-classer la classe OlvidClient, comme dans l’exemple suivant.

import { OlvidClient, datatypes, onDiscussionNew, command } from "@olvid/bot-node";

class MyBot extends OlvidClient {
    @onDiscussionNew()
    async discussionNew(discussion: datatypes.Discussion) {
        await discussion.postMessage(this, `Hello ${discussion.title}} !`)
        // post help message when a new discussion is created
        await discussion.postMessage(this, this.getHelpMessage())
    }
    
    @command("!help")
    async help(message: datatypes.Message) {
        await message.reply(this, this.getHelpMessage())
    }

    @command("!ping")
    async ping(message: datatypes.Message) {
        await message.reply(this, "pong")
    }

    getHelpMessage() {
        return "### Available commands\n- *!help*\n- *!ping*"
    }
}

async function main() {
    const bot = new MyBot();
    await bot.runForever();
}

main().then();

Conseils et astuces#

Invitations#

Pour rendre plus facile la mise en relation avec un bot, il est possible d’activer l’acceptation automatique des invitations. Il suffit de modifier la configuration du daemon Ă  l’aide la mĂ©thode enableAutoInvitation d’un client Olvid.

Note

L’acceptation automatique des invitations ne peut accepter que les prĂ©sentations, les invitations de groupe et les invitations Ă  une discussion personnelle. Il ne peut pas aller au bout des invitations directes avec Ă©change de SAS code.

Voici un programme qui lance un bot aprĂšs avoir configurĂ© l’acceptation automatique des invitations.

import { OlvidClient, datatypes, tools } from "@olvid/bot-node";

async function main() {
    const client = new OlvidClient();
    client.onMessageReceived({
        callback: (message: datatypes.Message) => {
            console.log("Message received:", message.body);
        }
    })

    await client.enableAutoInvitation({acceptAll: true});

    await client.runForever();
}

main().then();

Nettoyage des messages#

Pour des raisons de performances et de confidentialitĂ© nous vous conseillons d’activer le nettoyage automatique des messages.

Il est possible de limiter le nombre de messages conservĂ©s globalement ou par discussion (global_count et discussion_count) et/ou de dĂ©finir l’ñge maximal d’un message (existence_duration).

Nous vous conseillons aussi d’activer la suppression des messages dans les discussions fermĂ©es.

import { OlvidClient, datatypes, tools } from "@olvid/bot-node";

async function main() {
    const client = new 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.setMessageRetentionPolicy({
        globalCount: 100n, discussionCount: 20n,
        existenceDuration: 60n*60n*24n*7n, cleanLockedDiscussions: true
    });
}

main().then();

Divers#

Envoyer un message éphémÚre#

Les points d’entrĂ©e API messageSend et messageSendWithAttachments permettent de spĂ©cifier l’éphĂ©mĂ©ralitĂ© du message Ă  envoyer. On utilisera pour cela l’objet datatypes.MessageEphemerality.

Voici un exemple en typescript. Il est possible de spĂ©cifier les paramĂštres read_once, visibility_duration et existence_duration de maniĂšre indĂ©pendante. Les durĂ©es d’existence et de visibilitĂ© sont en secondes.

import { OlvidClient, datatypes, tools } from "@olvid/bot-node";

async function main() {
    const client = new OlvidClient();
    
    for await (const discussion of client.discussionList()) {
        await client.messageSend({
            discussionId: discussion.id,
            body: "Self destruct-message",
            ephemerality: new datatypes.MessageEphemerality({
                visibilityDuration: 10n,
                existenceDuration: 60n,
                readOnce: true
            })
        })
    }
}

main().then();