đ©âđ» 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();