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
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)
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înehash: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]
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
silines
est0
, 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 sisync
vauttrue
) -
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 sisync
vauttrue
) -
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
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éthodesPOST
etPUT
) -
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, ounull
si la requête n’avait pas de corps -
request_id
(chaîne) : l’identifiant de la requête, ounull
si la requête n’avait pas d’identifiant -
body_type
(chaîne) : type des objets retournés dans le corps (voir ci-dessous), ounull
si la réponse n’a pas de corps -
body
(objet ou tableau) : le corps retourné, ounull
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)
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"
}
}
]
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 |
---|---|---|---|
|
id tampon |
|
tampon avec les lignes et pseudos |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
null |
null |
|
id tampon |
|
ligne de tampon |
|
id tampon |
|
ligne de tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
tampon |
|
id tampon |
|
groupe de pseudos |
|
id tampon |
|
groupe de pseudos |
|
id tampon |
|
groupe de pseudos |
|
id tampon |
|
pseudo |
|
id tampon |
|
pseudo |
|
id tampon |
|
pseudo |
|
-1 |
null |
null |
|
-1 |
null |
null |
|
-1 |
null |
null |
(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
}