đŸ‘©â€đŸ’» 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#

AutoInvitationBot#

Pour rendre plus facile la mise en relation avec un bot, il est possible de mettre en place un autre bot, déjà écrit, qui acceptera toutes les invitations reçues.

Il suffit de crĂ©er un bot AutoInvitationBot du module tools. Il va automatiquement s’enregistrer pour recevoir les notifications de nouvelles invitations et les accepter.

Note

Un AutoInvitationBot ne peut accepter que les présentations et les invitations de groupe. Il ne peut pas accepter automatiquement les invitations directes avec échange de SAS code.

Voici un programme qui lance une instance de l’AutoInvitationBot en tñche de fond. Il est tout à fait possible de lancer plusieurs instances de bots en parallùle.

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);
        }
    })

    const invitationBot = new tools.AutoInvitationBot();

    await client.runForever();
}

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