1. Introduction

Ce document est une spécification du protocole relay api : le protocole utilisé pour relayer les données de WeeChat aux clients avec une API REST HTTP.

1.1. Terminologie

Les termes suivants sont utilisés dans ce document :

  • relay : il s’agit de l’extension "relay" de WeeChat, qui agit comme un "serveur" et autorise les clients à se connecter

  • client : il s’agit d’un logiciel connecté au relay via une connexion réseau (WeeChat lui-même ou une interface distante).

1.2. Diagramme réseau

Les clients sont connectés au relay comme dans le diagramme ci-dessous :

                                              ┌──────────┐ Station de travail
 ┌────────┐                               ┌───┤ client 1 │ (Linux, Windows,
 │  irc   │◄──┐  ╔═══════════╤═══════╗    │   └──────────┘ BSD, macOS, …)
 └────────┘   └──╢           │       ║◄───┘   ┌──────────┐
   ......        ║  WeeChat  │ Relay ║◄───────┤ client 2 │ Appareil mobile
 ┌────────┐   ┌──╢           │       ║◄───┐   └──────────┘ (Android, iPhone, …)
 │ jabber │◄──┘  ╚═══════════╧═══════╝    │      ......
 └────────┘                               │   ┌──────────┐
   ......                                 └───┤ client N │ Autres appareils
                                              └──────────┘


└────────────┘   └───────────────────┘╘══════╛└────────────────────────────────┘
   serveurs        interface ncurses    relay        interfaces distantes
Note
Tous les clients ici utilisent le protocole api dans l’extension relay.
L’extension relay autorise aussi les protocoles irc et weechat (non décrits dans ce document).

2. Généralités sur le protocole

  • Les connexions du client vers relay sont faites avec des sockets TCP sur l’IP/port utilisé par relay pour écouter les nouvelles connexions.

  • Le nombre de clients est limité par l’option relay.network.max_clients.

  • Chaque client est indépendant des autres clients.

  • Le relai api est une API REST HTTP utilisant le format JSON pour les entrées/sorties.

  • Les messages sont automatiquement compressés (deflate, gzip, zstd et permessage-deflate pour le protocole websocket).

  • WeeChat peut être utilisé comme client de ce relai.

2.1. Version de l’API

La version de l’API suit une version sémantique  « pragmatique », comme WeeChat, sur 3 chiffres X.Y.Z, où :

  • X est la version majeure

  • Y est la version mineure

  • Z est la version de patch.

Exemple : la version 2.0.0 apporte des changements incompatibles avec la version 1.2.3.

La version de l’API est retournée par la ressource version.

2.2. Schéma d’API

Vous pouvez parcourir et tester l’API en ligne : API relay WeeChat .

2.3. Codes réponse

Les codes réponse HTTP suivants peuvent être retournés au client :

  • 200 OK : réponse OK avec un corps (JSON)

  • 204 No Content : réponse OK sans corps

  • 400 Bad Request : requête invalide reçue

  • 401 Unauthorized : informations d’identification manquantes ou invalides

  • 403 Forbidden : permissions insuffisantes

  • 404 Not Found : ressource non trouvée

  • 500 Internal Server Error : erreur interne du serveur

  • 503 Service Unavailable : service non disponible

Lors d’une connexion via le protocole websocket, un code de réponse supplémentaire est envoyé quand WeeChat pousse des données vers le client sur des évènements :

  • 0 Event : évènement poussé au client, si la synchronisation est activée par la ressource sync.

2.4. Format de date

Le format de date est ISO 8601 , en utilisant le fuseau horaire UTC (cela est donc différent de l’affichage par WeeChat qui utilise le fuseau horaire local).
Les dates sont retournées avec le maximum de précision : jusqu’aux microsecondes si cela est possible ou bien les millisecondes, ou juste secondes.

Exemples :

2023-12-05T19:46:03.847625Z
2023-12-05T19:46:03.847Z
2023-12-05T19:46:03Z

3. Authentification

Le mot de passe doit être envoyé dans l’en-tête Authorization avec le schéma d’authentification Basic.

Le mot de passe peut être envoyé en clair ou haché, avec l’un des formats suivants pour l’utilisateur et le mot de passe :

  • plain:<mot_de_passe>

  • hash:sha256:<horodatage>:<hash>

  • hash:sha512:<horodatage>:<hash>

  • hash:pbkdf2+sha256:<horodatage>:<itérations>:<hash>

  • hash:pbkdf2+sha512:<horodatage>:<itérations>:<hash>

Où :

  • <mot_de_passe> est le mot de passe en clair

  • <horodatage> est l’horodatage courant sous forme d’entier (nombre de secondes depuis l’époque Unix) ; il est utilisé pour prévenir les attaques par rejeu

  • <itérations> est le nombre d’itérations (pour l’algorithme PBKDF2 seulement)

  • <hash> est la valeur hachée de l’horodatage + mot de passe (en hexadécimal)

Note
Le nombre maximal de secondes autorisé avant et après l’heure reçue (lorsque le mot de passe est haché) est configurable avec l’option relay.network.time_window.

Exemple :

  • l’horodatage courant est 1706431066

  • le mot de passe est secret_password

  • l’algorithme de hachage est sha256

  • le résultat du "hash" est le SHA256 de la chaîne 1706431066secret_password qui est en hexadécimal : dfa1db3f6bb6445d18d9ec7427c10f6421274e3a4751e6c1ffc7dd28c94eadf6

  • l’en-tête Authorization est la valeur encodée en base64 de la chaîne hash:sha256:1706431066:dfa1db3f6bb6445d18d9ec7427c10f6421274e3a4751e6c1ffc7dd28c94eadf6 : aGFzaDpzaGEyNTY6MTcwNjQzMTA2NjpkZmExZGIzZjZiYjY0NDVkMThkOWVjNzQyN2MxMGY2NDIxMjc0ZTNhNDc1MWU2YzFmZmM3ZGQyOGM5NGVhZGY2.

L’en-tête Authorization est autorisé dans la première requête avec le protocole websocket ou toute requête HTTP dans les autres cas.

Exemple de requête avec un mot de passe en clair :

curl -L -u 'plain:secret_password' 'https://localhost:9000/api/version'

Exemple de requête avec un mot de passe haché (SHA256) :

curl -L -u 'hash:sha256:1706431066:dfa1db3f6bb6445d18d9ec7427c10f6421274e3a4751e6c1ffc7dd28c94eadf6' 'https://localhost:9000/api/version'

Si TOTP (« Time-based One-Time Password » : mot de passe à usage unique basé sur le temps) est activé dans WeeChat/relay (option relay.network.totp_secret définie), vous devez envoyer la valeur TOTP dans l’en-tête x-weechat-totp comme ceci :

curl -L -u 'hash:sha256:1706431066:dfa1db3f6bb6445d18d9ec7427c10f6421274e3a4751e6c1ffc7dd28c94eadf6' -H "x-weechat-totp: 123456" 'https://localhost:9000/api/version'

En cas d’erreur, une réponse 401 Unauthorized est retournée avec un champ error dans les données JSON qui décrit l’erreur.

Réponse : mot de passe manquant :

HTTP/1.1 401 Unauthorized
{
    "error": "Missing password"
}

Réponse : mot de passe invalide :

HTTP/1.1 401 Unauthorized
{
    "error": "Invalid password"
}

Réponse : algorithme de hachage invalide :

HTTP/1.1 401 Unauthorized
{
    "error": "Invalid hash algorithm (not found or not supported)"
}

Réponse : horodatage invalide :

HTTP/1.1 401 Unauthorized
{
    "error": "Invalid timestamp"
}

Réponse : nombre d’itérations invalide :

HTTP/1.1 401 Unauthorized
{
    "error": "Invalid number of iterations"
}

Réponse : TOTP manquant :

HTTP/1.1 401 Unauthorized
{
    "error": "Missing TOTP"
}

Réponse : TOTP invalide :

HTTP/1.1 401 Unauthorized
{
    "error": "Invalid TOTP"
}

4. Compression

La compression du corps de la réponse est automatique et basée sur l’en-tête Accept-Encoding envoyé par le client.

Les formats de compression supportés sont :

  • deflate (zlib)

  • gzip

  • zstd

Exemple de requête :

curl -L -u 'plain:secret_password' -H "Accept-Encoding: gzip" 'https://localhost:9000/api/version'

Réponse :

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Content-Length: 77
[77 octets de données]
Note
Avec le protocole websocket, l’extension "permessage-deflate" autorise la compression des messages avec zlib.

5. Ressources

5.1. Requête « preflight »

La requête « preflight » avec la méthode HTTP OPTIONS est utilisée par les navigateurs web pour vérifier que le serveur (WeeChat) autorisera la requête proprement dite.

Exemple de requête : vérifier que la requête GET /api/version est autorisée :

OPTIONS /api/version HTTP/1.1
Host: localhost:9000
Connection: keep-alive
Accept: */*
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Origin: https://localhost
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Sec-Fetch-Dest: empty
Referer: https://localhost/
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9,fr;q=0.8

Réponse :

HTTP/1.1 204 No Content
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: origin, content-type, accept, authorization
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
Content-Length: 0

5.2. Poignée de main

Effectuer une poignée de main entre le client et WeeChat.

Cette ressource est accessible sans authentification.

Point de terminaison :

POST /api/handshake

Paramètres du corps :

  • password_hash_algo (tableau de chaînes, facultatif) : liste des algorithmes de hachage supportés par le client, chaque chaîne peut être :

    • plain : mot de passe en clair (pas de hachage)

    • sha256 : hash SHA256

    • sha512 : hash SHA512

    • pbkdf2+sha256 : hash PBKDF2 avec SHA256

    • pbkdf2+sha512 : hash PBKDF2 avec SHA512

La réponse a les champs suivants :

  • password_hash_algo (chaîne) : l’algorithme de hachage à utiliser (null si aucun algorithme n’est compatible)

  • password_hash_iterations (entier) : le nombre d’itérations à utiliser si le hachage PBKDF2 est utilisé

  • totp (booléen) : true si le TOTP est activé dans WeeChat (le client doit alors envoyer le TOTP dans un en-tête spécifique), false sinon

Exemple de requête :

curl -L -X POST -d '{"password_hash_algo": ["plain", "sha256", "sha512"]}' 'https://localhost:9000/api/handshake'

Réponse :

HTTP/1.1 200 OK
{
    "password_hash_algo": "sha512",
    "password_hash_iterations": 100000,
    "totp": false
}

5.3. Version

Retourner la version de WeeChat et du relai API.

Point de terminaison :

GET /api/version

Exemple de requête :

curl -L -u 'plain:secret_password' 'https://localhost:9000/api/version'

Réponse :

HTTP/1.1 200 OK
{
    "weechat_version": "4.2.0-dev",
    "weechat_version_git": "v4.1.0-143-g0b1cda1c4",
    "weechat_version_number": 67239936,
    "relay_api_version": "0.0.1",
    "relay_api_version_number": 1
}

5.4. Tampons

Retourner les tampons, lignes et pseudos.

Points de terminaison :

GET /api/buffers
GET /api/buffers/{buffer_id}
GET /api/buffers/{buffer_name}

Paramètres du chemin :

  • buffer_id (entier, facultatif) : identifiant unique du tampon (à ne pas confondre avec le numéro du tampon, qui est différent)

  • buffer_name (chaîne, facultatif) : nom de tampon

Paramètres de la requête :

  • lines (entier, facultatif, par défaut : 0) : nombre de lignes à retourner dans les tampons avec contenu formaté :

    • nombre négatif : retourner N lignes depuis la fin du tampon (lignes les plus récentes)

    • 0 : ne retourner aucune ligne

    • nombre positif : retourner N lignes depuis le début du tampon (lignes les plus anciennes)

  • lines_free (entier, facultatif, par défaut : 0 si lines est 0, sinon toutes les lignes) : nombre de lignes à retourner avec un contenu libre :

    • nombre négatif : retourner N lignes depuis la fin du tampon

    • 0 : ne retourner aucune ligne

    • nombre positif : retourner N lignes depuis le début du tampon

  • nicks (booléen, facultatif, par défaut : false) : retourner les pseudos du tampon

  • colors (chaîne, facultatif, par défaut : ansi) : comment les chaînes avec des couleurs sont retournées :

    • ansi : retourner les codes couleur ANSI

    • weechat : retourner les codes couleur internes WeeChat

    • strip : supprimer les couleurs

Exemple de requête : obtenir tous les tampons sans les lignes :

curl -L -u 'plain:secret_password' 'https://localhost:9000/api/buffers'

Réponse :

HTTP/1.1 200 OK
[
    {
        "id": 1709932823238637,
        "name": "core.weechat",
        "short_name": "weechat",
        "number": 1,
        "type": "formatted",
        "title": "WeeChat 4.2.0-dev (C) 2003-2023 - https://weechat.org/",
        "modes": "",
        "input_prompt": "",
        "input": "",
        "input_position": 0,
        "input_multiline": false,
        "nicklist": false,
        "nicklist_case_sensitive": false,
        "nicklist_display_groups": true,
        "time_displayed": true,
        "local_variables": {
            "plugin": "core",
            "name": "weechat"
        },
        "keys": []
    },
    {
        "id": 1709932823423765,
        "name": "irc.server.libera",
        "short_name": "libera",
        "number": 2,
        "type": "formatted",
        "title": "IRC: irc.libera.chat/6697 (2001:4b7a:a008::6667)",
        "modes": "",
        "input_prompt": "",
        "input": "",
        "input_position": 0,
        "input_multiline": false,
        "nicklist": false,
        "nicklist_case_sensitive": false,
        "nicklist_display_groups": true,
        "time_displayed": true,
        "local_variables": {
            "plugin": "irc",
            "name": "server.libera",
            "type": "server",
            "server": "libera",
            "channel": "libera",
            "charset_modifier": "irc.libera",
            "nick": "alice",
            "tls_version": "TLS1.3",
            "host": "~alice@example.com"
        },
        "keys": []
    },
    {
        "id": 1709932823649069,
        "name": "irc.libera.#weechat",
        "short_name": "#weechat",
        "number": 3,
        "type": "formatted",
        "title": "Welcome to the WeeChat official support channel",
        "modes": "+nt",
        "input_prompt": "\u001b[92m@\u001b[96malice\u001b[48;5;22m(\u001b[39mi\u001b[48;5;22m)",
        "input": "",
        "input_position": 0,
        "input_multiline": false,
        "nicklist": true,
        "nicklist_case_sensitive": false,
        "nicklist_display_groups": false,
        "time_displayed": true,
        "local_variables": {
            "plugin": "irc",
            "name": "libera.#weechat",
            "type": "channel",
            "server": "libera",
            "channel": "#weechat",
            "nick": "alice",
            "host": "~alice@example.com"
        },
        "keys": []
    }
]

Exemple de requête : obtenir le tampon WeeChat "core" avec la dernière ligne seulement et aucun code couleur :

curl -L -u 'plain:secret_password' 'https://localhost:9000/api/buffers/core.weechat?lines=-1&colors=strip'

Réponse :

HTTP/1.1 200 OK
{
    "id": 1709932823238637,
    "name": "core.weechat",
    "short_name": "weechat",
    "number": 1,
    "type": "formatted",
    "title": "WeeChat 4.2.0-dev (C) 2003-2023 - https://weechat.org/",
    "modes": "",
    "input_prompt": "",
    "input": "",
    "input_position": 0,
    "input_multiline": false,
    "nicklist": false,
    "nicklist_case_sensitive": false,
    "nicklist_display_groups": true,
    "time_displayed": true,
    "local_variables": {
        "plugin": "core",
        "name": "weechat"
    },
    "keys": [],
    "lines": [
        {
            "id": 10,
            "y": -1,
            "date": "2023-12-24T08:17:20.786538Z",
            "date_printed": "2023-12-24T08:17:20.786538Z",
            "displayed": true,
            "highlight": false,
            "notify_level": 0,
            "prefix": "",
            "message": "Plugins loaded: alias, buflist, charset, exec, fifo, fset, guile, irc, javascript, logger, lua, perl, php, python, relay, ruby, script, spell, tcl, trigger, typing, xfer",
            "tags": []
        }
    ]
}

Exemple de requête : obtenir un tampon de canal IRC avec les pseudos :

curl -L -u 'plain:secret_password' 'https://localhost:9000/api/buffers/irc.libera.%23weechat?nicks=true'

Réponse :

HTTP/1.1 200 OK
{
    "id": 1709932823649069,
    "name": "irc.libera.#weechat",
    "short_name": "#weechat",
    "number": 3,
    "type": "formatted",
    "title": "Welcome to the WeeChat official support channel",
    "modes": "+nt",
    "input_prompt": "\u001b[92m@\u001b[96malice\u001b[48;5;22m(\u001b[39mi\u001b[48;5;22m)",
    "input": "",
    "input_position": 0,
    "input_multiline": false,
    "nicklist": true,
    "nicklist_case_sensitive": false,
    "nicklist_display_groups": false,
    "time_displayed": true,
    "local_variables": {
        "plugin": "irc",
        "name": "libera.#weechat",
        "type": "channel",
        "server": "libera",
        "channel": "#weechat",
        "nick": "alice",
        "host": "~alice@example.com"
    },
    "keys": [],
    "nicklist_root": {
        "id": 0,
        "parent_group_id": -1,
        "name": "root",
        "color_name": "",
        "color": "",
        "visible": false,
        "groups": [
            {
                "id": 1709932823649181,
                "parent_group_id": 0,
                "name": "000|o",
                "color_name": "weechat.color.nicklist_group",
                "color": "\u001b[32m",
                "visible": true,
                "groups": [],
                "nicks": [
                    {
                        "id": 1709932823649184,
                        "parent_group_id": 1709932823649181,
                        "prefix": "@",
                        "prefix_color_name": "lightgreen",
                        "prefix_color": "\u001b[92m",
                        "name": "alice",
                        "color_name": "bar_fg",
                        "color": "",
                        "visible": true
                    }
                ]
            },
            {
                "id": 1709932823649189,
                "parent_group_id": 0,
                "name": "001|h",
                "color_name": "weechat.color.nicklist_group",
                "color": "\u001b[32m",
                "visible": true,
                "groups": [],
                "nicks": []
            },
            {
                "id": 1709932823649203,
                "parent_group_id": 0,
                "name": "002|v",
                "color_name": "weechat.color.nicklist_group",
                "color": "\u001b[32m",
                "visible": true,
                "groups": [],
                "nicks": []
            },
            {
                "id": 1709932823649210,
                "parent_group_id": 0,
                "name": "999|...",
                "color_name": "weechat.color.nicklist_group",
                "color": "\u001b[32m",
                "visible": true,
                "groups": [],
                "nicks": []
            }
        ],
        "nicks": []
    }
}

Exemple de requête : obtenir le tampon fset :

curl -L -u 'plain:secret_password' 'https://localhost:9000/api/buffers/fset.fset'

Réponse :

HTTP/1.1 200 OK
{
    "id": 1709932823897200,
    "name": "fset.fset",
    "short_name": "",
    "number": 4,
    "type": "free",
    "title": "\u001b[96m1/\u001b[36m3565 | Filter: \u001b[93m* | Sort: \u001b[97m~name | Key(input): alt+space=toggle boolean, alt+'-'(-)=subtract 1 or set, alt+'+'(+)=add 1 or append, alt+f,alt+r(r)=reset, alt+f,alt+u(u)=unset, alt+enter(s)=set, alt+f,alt+n(n)=set new value, alt+f,alt+a(a)=append, alt+','=mark/unmark, shift+down=mark and move down, shift+up=move up and mark, ($)=refresh, ($$)=unmark/refresh, (m)=mark matching options, (u)=unmark matching options, alt+p(p)=toggle plugins desc, alt+v(v)=toggle help bar, ctrl+x(x)=switch format, (q)=close buffer",
    "modes": "",
    "input_prompt": "",
    "input": "",
    "input_position": 0,
    "input_multiline": false,
    "nicklist": false,
    "nicklist_case_sensitive": false,
    "nicklist_display_groups": true,
    "time_displayed": true,
    "local_variables": {
        "plugin": "fset",
        "name": "fset",
        "type": "option",
        "filter": "*"
    },
    "keys": [
        {
            "key": "ctrl-l",
            "command": "/fset -refresh"
        },
        {
            "key": "ctrl-n",
            "command": "/eval ${if:${weechat.bar.buflist.hidden}?/fset -down:/buffer +1}"
        },
        {
            "key": "ctrl-x",
            "command": "/fset -format"
        },
        {
            "key": "down",
            "command": "/fset -down"
        },
        {
            "key": "f11",
            "command": "/fset -left"
        },
        {
            "key": "f12",
            "command": "/fset -right"
        },
        {
            "key": "meta-+",
            "command": "/fset -add 1"
        },
        {
            "key": "meta--",
            "command": "/fset -add -1"
        },
        {
            "key": "meta-comma",
            "command": "/fset -mark"
        },
        {
            "key": "meta-end",
            "command": "/fset -go end"
        },
        {
            "key": "meta-f,meta-a",
            "command": "/fset -append"
        },
        {
            "key": "meta-f,meta-n",
            "command": "/fset -setnew"
        },
        {
            "key": "meta-f,meta-r",
            "command": "/fset -reset"
        },
        {
            "key": "meta-f,meta-u",
            "command": "/fset -unset"
        },
        {
            "key": "meta-home",
            "command": "/fset -go 0"
        },
        {
            "key": "meta-p",
            "command": "/mute /set fset.look.show_plugins_desc toggle"
        },
        {
            "key": "meta-q",
            "command": "/input insert meta-q fset ${property}"
        },
        {
            "key": "meta-return",
            "command": "/fset -set"
        },
        {
            "key": "meta-space",
            "command": "/fset -toggle"
        },
        {
            "key": "meta-v",
            "command": "/bar toggle fset"
        },
        {
            "key": "shift-down",
            "command": "/fset -mark; /fset -down"
        },
        {
            "key": "shift-up",
            "command": "/fset -up; /fset -mark"
        },
        {
            "key": "up",
            "command": "/fset -up"
        }
    ]
}

Lignes

Retourner les lignes d’un tampon.

Points de terminaison :

GET /api/buffers/{buffer_id}/lines
GET /api/buffers/{buffer_id}/lines/{line_id}
GET /api/buffers/{buffer_name}/lines
GET /api/buffers/{buffer_name}/lines/{line_id}

Paramètres de chemin :

  • buffer_id (entier, obligatoire) : identifiant unique de tampon (à ne pas confondre avec le numéro du tampon, qui est différent)

  • buffer_name (chaîne, obligatoire) : nom du tampon

  • line_id (entier, facultatif) : retourner une seule ligne avec cet identifiant

Paramètres de requête :

  • lines (entier, facultatif, par défaut : toutes les lignes) : nombre de lignes à retourner :

    • nombre négatif : retourner N lignes depuis la fin du tampon (lignes les plus récentes)

    • 0 : ne retourner aucune ligne (autorisé mais ne fait pas vraiment de sens avec cette ressource)

    • nombre positif : retourner N lignes depuis le début du tampon (lignes les plus anciennes)

  • colors (chaîne, facultatif, par défaut : ansi) : comment les chaînes avec des couleurs sont retournées :

    • ansi : retourner les codes couleur ANSI

    • weechat : retourner les codes couleur internes WeeChat

    • strip : supprimer les couleurs

Exemple de requête : obtenir les 1000 dernières lignes d’un tampon, sans codes couleur :

curl -L -u 'plain:secret_password' 'https://localhost:9000/api/buffers/irc.libera.%23weechat/lines?lines=-1000&colors=strip'

Réponse :

HTTP/1.1 200 OK
[
    {
        "id": 0,
        "y": -1,
        "date": "2023-12-05T19:46:03.847625Z",
        "date_printed": "2023-12-05T19:46:03.847625Z",
        "displayed": true,
        "highlight": false,
        "notify_level": 0,
        "prefix": "-->",
        "message": "alice (~alice@example.com) has joined #test",
        "tags": [
            "irc_join",
            "irc_tag_account=alice",
            "irc_tag_time=2023-12-05T19:46:03.847Z",
            "nick_alice",
            "host_~alice@example.com",
            "log4"
        ]
    },
    {
        "id": 1,
        "y": -1,
        "date": "2023-12-05T19:46:03.986543Z",
        "date_printed": "2023-12-05T19:46:03.986543Z",
        "displayed": true,
        "highlight": false,
        "notify_level": 0,
        "prefix": "--",
        "message": "Mode #test [+Cnst] by zirconium.libera.chat",
        "tags": [
            "irc_mode",
            "irc_tag_time=2023-12-05T19:46:03.986Z",
            "nick_zirconium.libera.chat",
            "log3"
        ]
    },
    {
        "id": 2,
        "y": -1,
        "date": "2023-12-05T19:46:04.287546Z",
        "date_printed": "2023-12-05T19:46:04.287546Z",
        "displayed": true,
        "highlight": false,
        "notify_level": 0,
        "prefix": "--",
        "message": "Channel #test: 1 nick (1 op, 0 voiced, 0 regular)",
        "tags": [
            "irc_366",
            "irc_numeric",
            "irc_tag_time=2023-12-05T19:46:04.287Z",
            "nick_zirconium.libera.chat",
            "log3"
        ]
    }
]

Pseudos

Retourner les pseudos d’un tampon.

Points de terminaison :

GET /api/buffers/{buffer_id}/nicks
GET /api/buffers/{buffer_name}/nicks

Paramètres de chemin :

  • buffer_id (entier, obligatoire) : identifiant unique de tampon (à ne pas confondre avec le numéro du tampon, qui est différent)

  • buffer_name (chaîne, obligatoire) : nom du tampon

Exemple de requête : obtenir les pseudos d’un tampon :

curl -L -u 'plain:secret_password' 'https://localhost:9000/api/buffers/irc.libera.%23weechat/nicks'

Réponse :

HTTP/1.1 200 OK
{
    "id": 0,
    "parent_group_id": -1,
    "name": "root",
    "color_name": "",
    "color": "",
    "visible": false,
    "groups": [
        {
            "id": 1709932823649181,
            "parent_group_id": 0,
            "name": "000|o",
            "color_name": "weechat.color.nicklist_group",
            "color": "\u001b[32m",
            "visible": true,
            "groups": [],
            "nicks": [
                {
                    "id": 1709932823649184,
                    "parent_group_id": 1709932823649181,
                    "prefix": "@",
                    "prefix_color_name": "lightgreen",
                    "prefix_color": "\u001b[92m",
                    "name": "alice",
                    "color_name": "bar_fg",
                    "color": "",
                    "visible": true
                }
            ]
        },
        {
            "id": 1709932823649189,
            "parent_group_id": 0,
            "name": "001|h",
            "color_name": "weechat.color.nicklist_group",
            "color": "\u001b[32m",
            "visible": true,
            "groups": [],
            "nicks": []
        },
        {
            "id": 1709932823649203,
            "parent_group_id": 0,
            "name": "002|v",
            "color_name": "weechat.color.nicklist_group",
            "color": "\u001b[32m",
            "visible": true,
            "groups": [],
            "nicks": []
        },
        {
            "id": 1709932823649210,
            "parent_group_id": 0,
            "name": "999|...",
            "color_name": "weechat.color.nicklist_group",
            "color": "\u001b[32m",
            "visible": true,
            "groups": [],
            "nicks": []
        }
    ],
    "nicks": []
}

5.5. Hotlist

Retourner la "hotlist" (notification d’activité sur les tampons).

Point de terminaison :

GET /api/hotlist

Exemple de requête :

curl -L -u 'plain:secret_password' 'https://localhost:9000/api/hotlist'

Réponse :

HTTP/1.1 200 OK
[
    {
        "priority": 0,
        "date": "2024-03-17T16:38:51.572834Z",
        "buffer_id": 1710693531508204,
        "count": [
            44,
            0,
            0,
            0
        ]
    },
    {
        "priority": 0,
        "date": "2024-03-17T16:38:51.573028Z",
        "buffer_id": 1710693530395959,
        "count": [
            14,
            0,
            0,
            0
        ]
    },
    {
        "priority": 0,
        "date": "2024-03-17T16:38:51.611617Z",
        "buffer_id": 1710693531529248,
        "count": [
            4,
            0,
            0,
            0
        ]
    }
]

5.6. Entrée

Envoyer une commande ou du texte à un tampon.

Point de terminaison :

POST /api/input

Paramètres du corps :

  • buffer_id (entier, facultatif) : identifiant unique du tampon (à ne pas confondre avec le numéro du tampon, qui est différent)

  • buffer_name (chaîne, facultatif) : nom de tampon

  • command (chaîne, obligatoire) : commande ou texte à envoyer au tampon

Exemple de requête : dire "hello!" sur le canal #weechat :

curl -L -u 'plain:secret_password' -X POST \
  -d '{"buffer": "irc.libera.#weechat", "command": "hello!"}' \
  'https://localhost:9000/api/input'

Réponse :

HTTP/1.1 204 No content

Exemple de requête : quitter et fermer le canal #weechat (commande exécutée sur le tampon "core" WeeChat) :

curl -L -u 'plain:secret_password' -X POST \
  -d '{"command": "/buffer close irc.libera.#weechat"}' \
  'https://localhost:9000/api/input'

Réponse :

HTTP/1.1 204 No content

5.7. Ping

Envoyer une requête "ping".

Point de terminaison :

POST /api/ping

Paramètres du corps :

  • data (chaîne, facultatif) : chaîne qui est retournée dans la réponse

Exemple de requête : pas de corps :

curl -L -u 'plain:secret_password' -X POST 'https://localhost:9000/api/ping'

Réponse :

HTTP/1.1 204 No content

Exemple de requête : avec des données :

curl -L -u 'plain:secret_password' -X POST \
  -d '{"data": "1702835741"}' \
  'https://localhost:9000/api/ping'

Réponse :

HTTP/1.1 200 OK
{
    "data": "1702835741"
}

5.8. Sync

Démarrer ou arrêter la synchronisation des données avec WeeChat.

Cette ressource ne peut être utilisée que si le client est connecté avec le protocole websocket, pour que WeeChat puisse pousser des messages au client à tout moment.

Si cette ressource est utilisée sans connexion websocket, une erreur 403 (Forbidden) est retournée.

Point de terminaison :

POST /api/sync

Paramètres du corps :

  • sync (booléen, facultatif, par défaut : true) : true pour activer la synchronisation avec WeeChat

  • nicks (booléen, facultatif, par défaut : true) : true pour recevoir les mises à jour de pseudos dans les tampons (utilisé seulement si sync vaut true)

  • input (booléen, facultatif, par défaut : true) : true pour synchroniser l’entrée de tampon depuis le relai distant vers le client local (utilisé seulement si sync vaut true)

  • colors (chaîne, facultatif, par défaut : ansi) : comment les chaînes avec des couleurs sont retournées :

    • ansi : retourner les codes couleur ANSI

    • weechat : retourner les codes couleur internes WeeChat

    • strip : supprimer les couleurs

Exemple de requête avec le protocole websocket :

{
    "request": "POST /api/sync",
    "body": {
        "nicks": false
    }
}

Réponse :

{
    "code": 204,
    "message": "No Content",
    "request": "POST /api/sync",
    "request_body": {
        "nicks": false
    },
    "body_type": null,
    "body": null
}

Exemple de requête dans le protocole websocket (non autorisé) :

curl -L -u 'plain:secret_password' -X POST \
  -d '{"nicks": false}' \
  'https://localhost:9000/api/sync'

Réponse :

HTTP/1.1 403 Forbidden
{
    "error": "Sync resource is available only with a websocket connection"
}

6. Websocket

Le protocole websocket est utilisé pour effectuer une connexion persistante entre le client et WeeChat et recevoir les évènements en temps réel si la synchronisation est activée avec la ressource sync.

L’authentification doit être effectuée seulement une fois lorsque le protocole websocket est utilisé (voir authentification).

6.1. Poignée de main websocket

Pour établir la connexion, une poignée de main est effectuée sur le point de terminaison /api et ressemble à ceci :

GET /api HTTP/1.1
Host: localhost:9000
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Upgrade: websocket
Origin: https://example.com
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,fr;q=0.8
Sec-WebSocket-Key: 2XE8VAJktqi3Tpw5QnfxVQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

WeeChat retourne sa réponse à la poignée de main pour confirmer que le protocole websocket est bien supporté (et que l’authentification est réussie) :

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: PaY9vRflWeOKuD0/F7e5gD9At9U=
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Note
La valeur Sec-WebSocket-Accept retournée est le hachage SHA-1 de la valeur reçue, concaténée au GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 (le hachage SHA-1 est encodé en base64).
Dans l’exemple ci-dessus, le hachage SHA-1 de 2XE8VAJktqi3Tpw5QnfxVQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11 est PaY9vRflWeOKuD0/F7e5gD9At9U= (en base64).

6.2. Frames

Lorsque le client est connecté via le protocole websocket :

  • les requêtes et réponses sont en JSON, à l’intérieur des "frames" websocket

  • si la synchronisation est activée avec la ressource sync, WeeChat peut envoyer des "frames" au client à tout moment.

Les requêtes envoyées vers WeeChat sont faites avec un objet JSON qui contient les champs suivants :

  • request (chaîne) : la méthode HTTP et le chemin (exemple : GET /api/buffers?lines=-100)

  • body (objet ou tableau) : le corps (facultatif, pour les méthodes POST et PUT)

  • request_id (chaîne) : identifiant renvoyé dans la réponse

Plusieurs requêtes peuvent être envoyées simultanément avec un tableau d’objets, chaque objet étant une requête séparée.
Les requêtes sont exécutées dans l’ordre reçu (voir l’exemple ci-dessous).

Les réponses vers le client sont faites avec un objet JSON qui contient les champs suivants :

  • code (entier) : code réponse HTTP (exemple : 200)

  • message (chaîne) : message pour le code (exemple : OK)

  • request (chaîne) : la requête (exemple : GET /api/buffers?lines=-100)

  • request_body (objet) : le corps de la requête, ou null si la requête n’avait pas de corps

  • request_id (chaîne) : l’identifiant de la requête, ou null si la requête n’avait pas d’identifiant

  • body_type (chaîne) : type des objets retournés dans le corps (voir ci-dessous), ou null si la réponse n’a pas de corps

  • body (objet ou tableau) : le corps retourné, ou null si la réponse n’a pas de corps

Les types de corps qui peuvent être retournés :

  • handshake (objet)

  • version (objet)

  • buffers (tableau)

  • buffer (objet)

  • lines (tableau)

  • line (objet)

  • nick_group (objet)

  • nick (objet)

  • hotlist (objet)

  • ping (objet)

Astuce
Vous pouvez parcourir les schémas en ligne : API relay WeeChat .

Exemple de requête : obtenir la version :

{
    "request": "GET /api/version",
    "request_id": "get_version"
}

Réponse :

{
    "code": 200,
    "message": "OK",
    "request": "GET /api/version",
    "request_body": null,
    "request_id": "get_version",
    "body_type": "version",
    "body": {
        "weechat_version": "4.2.0-dev",
        "weechat_version_git": "v4.1.0-143-g0b1cda1c4",
        "weechat_version_number": 67239936,
        "relay_api_version": "0.0.1",
        "relay_api_version_number": 1
    }
}

Exemple de requête : dire "hello!" sur le canal #weechat :

{
    "request": "POST /api/input",
    "body": {
        "buffer_name": "irc.libera.#weechat",
        "command": "hello!"
    }
}

Réponse :

{
    "code": 204,
    "message": "No Content",
    "request": "POST /api/input",
    "request_body": {
        "buffer_name": "irc.libera.#weechat",
        "command": "hello!"
    },
    "request_id": null,
    "body_type": null,
    "body": null
}

Exemple de requêtes : envoyer deux requêtes en même temps : obtenir la liste des tampons avec les lignes et les pseudos, puis se synchroniser avec le relay distant :

[
    {
        "request": "GET /api/buffers?lines=-1000&nicks=true&colors=weechat",
        "request_id": "initial_sync"
    },
    {
        "request": "POST /api/sync",
        "body": {
            "colors": "weechat"
        }
    }
]
Note
Il est recommandé d’envoyer la requête de synchronisation en même temps que la première requête qui récupère les données, afin qu’aucun évènement ne soit manqué.

Première réponse (le "body" avec les tampons est tronqué pour la lisibilité) :

{
    "code": 200,
    "message": "OK",
    "request": "GET /api/buffers?lines=-1000&nicks=true&colors=weechat",
    "request_body": null,
    "request_id": "initial_sync",
    "body_type": "buffers",
    "body": [
        {
            "id": 1709932823238637,
            "name": "core.weechat",
            "short_name": "weechat",
            "number": 1,
            "type": "formatted"
        }
    ]
}

Seconde réponse :

{
    "code": 204,
    "message": "No Content",
    "request": "POST /api/sync",
    "request_body": {
        "colors": "weechat"
    },
    "request_id": null,
    "body_type": null,
    "body": null
}

WeeChat pousse des données au client à tout moment sur des évènements : lorsque des lignes sont affichées, des tampons ajoutés/supprimés/changés, des pseudos ajoutés/supprimés/changés, etc.

Les messages envoyés au client ont les champs suivants :

  • code : 0

  • message : Event

  • event_name (chaîne) : le nom de l’évènement (nom du signal ou hsignal)

  • buffer_id (entier) : l’identifiant unique du tampon, défini seulement pour les sous-objets, -1 dans les autres cas

Les évènements suivants sont envoyés au client, selon les options de synchronisation :

Nom d’évènement Id tampon Type de corps Corps

buffer_opened

id tampon

buffer

tampon avec les lignes et pseudos

buffer_type_changed

id tampon

buffer

tampon

buffer_moved

id tampon

buffer

tampon

buffer_merged

id tampon

buffer

tampon

buffer_unmerged

id tampon

buffer

tampon

buffer_hidden

id tampon

buffer

tampon

buffer_unhidden

id tampon

buffer

tampon

buffer_renamed

id tampon

buffer

tampon

buffer_title_changed

id tampon

buffer

tampon

buffer_time_for_each_line_changed

id tampon

buffer

tampon

buffer_localvar_added

id tampon

buffer

tampon

buffer_localvar_changed

id tampon

buffer

tampon

buffer_localvar_removed

id tampon

buffer

tampon

buffer_cleared

id tampon

buffer

tampon

buffer_closing

id tampon

buffer

tampon

buffer_closed

id tampon

null

null

buffer_line_added

id tampon

line

ligne de tampon

buffer_line_data_changed

id tampon

line

ligne de tampon

input_text_changed

id tampon

buffer

tampon

input_text_cursor_moved

id tampon

buffer

tampon

nicklist_group_changed

id tampon

nick_group

groupe de pseudos

nicklist_group_added

id tampon

nick_group

groupe de pseudos

nicklist_group_removing

id tampon

nick_group

groupe de pseudos

nicklist_nick_added

id tampon

nick

pseudo

nicklist_nick_removing

id tampon

nick

pseudo

nicklist_nick_changed

id tampon

nick

pseudo

upgrade (1)

-1

null

null

upgrade_ended (1)

-1

null

null

quit

-1

null

null

Note
(1) Les évènements upgrade et upgrade_ended sont envoyés seulement si le client est connecté sans chiffrement (pas de TLS), car avec TLS le client est déconnecté avant que la mise à jour soit faire (la mise à jour des connexions TLS n’est pas supportée).

Exemple : nouveau tampon : le canal #weechat a été rejoint :

{
    "code": 0,
    "message": "Event",
    "event_name": "buffer_opened",
    "buffer_id": 1709932823649069,
    "body_type": "buffer",
    "body": {
        "id": 1709932823649069,
        "name": "irc.libera.#test",
        "short_name": "",
        "number": 4,
        "type": "formatted",
        "title": "",
        "modes": "+nt",
        "input_prompt": "\u001b[92m@\u001b[96malice\u001b[48;5;22m(\u001b[39mi\u001b[48;5;22m)",
        "input": "",
        "input_position": 0,
        "input_multiline": false,
        "nicklist": true,
        "nicklist_case_sensitive": false,
        "nicklist_display_groups": false,
        "time_displayed": true,
        "local_variables": {
            "plugin": "irc",
            "name": "libera.#test",
            "type": "channel",
            "nick": "alice",
            "host": "~alice@example.com",
            "server": "libera",
            "channel": "#test"
        },
        "keys": [],
        "lines": []
    }
}

Exemple : nouvelle ligne affichée sur le canal #weechat :

{
    "code": 0,
    "message": "Event",
    "event_name": "buffer_line_added",
    "buffer_id": 1709932823649069,
    "body_type": "line",
    "body": {
        "id": 5,
        "index": -1,
        "date": "2024-01-07T08:54:00.179483Z",
        "date_printed": "2024-01-07T08:54:00.179483Z",
        "displayed": true,
        "highlight": false,
        "notify_level": 0,
        "prefix": "alice",
        "message": "hello!",
        "tags": [
            "irc_privmsg",
            "self_msg",
            "notify_none",
            "no_highlight",
            "prefix_nick_white",
            "nick_alice",
            "log1"
        ]
    }
}

Exemple : le pseudo bob est ajouté avec le statut opérateur sur le canal #weechat :

{
    "code": 0,
    "message": "Event",
    "event_name": "nicklist_nick_added",
    "buffer_id": 1709932823649069,
    "body_type": "nick",
    "body": {
        "id": 1709932823649902,
        "parent_group_id": 1709932823649181,
        "prefix": "@",
        "prefix_color_name": "lightgreen",
        "prefix_color": "\u001b[92m",
        "name": "bob",
        "color_name": "bar_fg",
        "color": "",
        "visible": true
    }
}

Exemple : le tampon du canal #weechat a été fermé :

{
    "code": 0,
    "message": "Event",
    "event_name": "buffer_closed",
    "buffer_id": 1709932823649069,
    "body_type": null,
    "body": null
}

Exemple : WeeChat est en cours de mise à jour :

{
    "code": 0,
    "message": "Event",
    "event_name": "upgrade",
    "buffer_id": -1,
    "body_type": null,
    "body": null
}

Exemple : la mise à jour de WeeChat est terminée :

{
    "code": 0,
    "message": "Event",
    "event_name": "upgrade_ended",
    "buffer_id": -1,
    "body_type": null,
    "body": null
}