Créer un bot Discord avec Haskell
Voir aussi : video youtube - video peertube - code source
Discord est un système de messagerie instantanée et de VoIP très connu. Il propose une API permettant de coder des bots, c’est-à-dire des programmes informatiques qui interagissent avec le système et ses utilisateurs. Un bot peut, par exemple, muter tous les participants dans un salon audio, répondre automatiquement à des messages textes, dialoguer avec un humain grâce à une IA (chatbot)…
Cet article explique comment créer un bot Discord codé en Haskell, de la mise en place sur un serveur Discord jusqu’à l’implémentation d’une interface vers un programme Hoogle (le moteur de recherche de fonctions Haskell). Il s’inspire en partie du tutoriel Créer un Bot Discord avec Python.
Création d’un bot sur Discord
Concrètement, un bot est un programme qui tourne en permanence sur une machine quelconque (serveur, ordinateur personnel, etc) et qui communique avec un serveur Discord via l’API.
Avant de lancer ce programme, il faut donc créer un serveur Discord ou être administrateur sur un serveur Discord existant. Pour ajouter un bot, il faut alors :
- se connecter au Discord Developer Portal
- dans l’onglet
Applications
, cliquerNew Application
, entrer un nom puis cliquerCreate
- dans l’onglet
Bot
, cliquerAdd Bot
puisYes, do it
- dans l’onglet
OAuth2
, mettreBOT PERMISSIONS
àAdministrator
(ou détailler au strict nécessaire), mettreSCOPES
àbot
, puis copier l’URL d’autorisation
Cette URL est de la forme
https://discordapp.com/oauth2/authorize?client_id=...&scope=bot&permissions=...
.
Elle permet d’ajouter le bot que l’on vient de créer à notre serveur Discord.
Pour cela, il suffit d’accéder à l’URL avec le navigateur web puis d’indiquer le serveur Discord qui doit accueillir le bot.
On peut désormais exécuter un programme de Bot, qui interagira alors avec ce
serveur. Pour cela, il faut également récupérer le token
d’accès sur le
Developer Portal
(onglet Bot
) et, par exemple, l’écrire dans un fichier
token.txt
sur la machine exécutant le programme de Bot.
Écriture d’un bot “d’écoute”
Haskell propose différentes bibliothèques pour coder des bots Discord. Ici, on
utilisera discord-haskell. Avec
Nix, on peut écrire le fichier shell.nix
suivant, qui récupère le code source
de la bibliothèque et crée un environnement suffisant.
let
discord-haskell-src = fetchTarball {
url = https://github.com/aquarial/discord-haskell/archive/8e1988e.tar.gz;
sha256 = sha256:1m074p7xi016qg6wv08zqzz4jhywgm65k1amk2hqji0xm1wr1d76;
};
config = {
allowBroken = true;
packageOverrides = pkgs: rec {
haskellPackages = pkgs.haskellPackages.override {
overrides = self: super: rec {
discord-haskell = self.callCabal2nix "discord-haskell" discord-haskell-src {};
};
};
};
};
pkgs = import <nixpkgs> { inherit config; };
in with pkgs; mkShell {
buildInputs = [
(haskellPackages.ghcWithHoogle (ps: with ps; [
discord-haskell]))
];
shellHook = ''
export PATH="$PATH:${pkgs.haskellPackages.hoogle}/bin"
'';
}
On peut alors exécuter un fichier de code Haskell très simplement, avec
nix-shell
et runghc
. Par exemple, le fichier listen.hs
suivant :
import Control.Monad (when, void, unless)
import Data.List (isPrefixOf)
import Data.Text (pack, unpack)
import System.Process (readProcess)
import UnliftIO (liftIO)
import qualified Discord as D
import qualified Discord.Types as D
import qualified Discord.Requests as D
main :: IO ()
= do
main putStrLn "running..."
<- pack <$> readFile "token.txt"
token let opts = D.def { D.discordToken = token, D.discordOnEvent = eventHandler }
>>= print
D.runDiscord opts
eventHandler :: D.Event -> D.DiscordHandler ()
D.MessageCreate m) = liftIO $ print m -- affiche les messages reçus
eventHandler (= pure () eventHandler _
Ce premier bot est très simple : on lance une application Discord avec le token
lu dans le fichier token.txt
et le gestionnaire d’événements eventHandler
.
Ce gestionnaire récupère tous les messages de notre serveur Discord et les
affiche sur le terminal de la machine exécutant le bot.
Pour exécuter le bot, on peut lancer la commande suivante :
$ nix-shell --run "runghc listen.hs" running...
Le bot écoute alors les événements sur le serveur Discord, et si quelqu’un envoit un message :
alors le bot reçoit l’événement et l’affiche sur son terminal local :
Message {messageId = 793422045328179233, ... messageText = "hello from discord" ...
Écriture d’un bot “echo”
Ainsi, la bibliothèque discord-haskell
est une classique interface vers l’API
originelle, avec un système d’événements (voir la
documentation et les
exemples).
Pour implémenter un bot d’echo (qui répète chaque message envoyé sur le serveur Discord), on vérifie que l’auteur du message n’est pas un bot, on récupère le texte du message et son canal, et on envoie un nouveau message avec ce même texte et sur ce même canal.
eventHandler :: D.Event -> D.DiscordHandler ()
D.MessageCreate m) =
eventHandler ($ D.messageAuthor m) $ do -- si le message ne vient pas d'un bot
unless (D.userIsBot let txt = D.messageText m -- récupère le texte du message
= D.messageChannel m -- et son canal
chan $ D.restCall (D.CreateMessage chan txt) -- et envoie un nouveau message
void = pure () eventHandler _
Écriture d’un bot “hoogle”
Pour terminer, voyons un bot un peu plus interessant, qui interroge le
programme hoogle
. Hoogle est un moteur permettant de recherche des fonctions
Haskell d’après leur nom ou leur signature. Il est disponible en ligne mais
également en local :
$ hoogle --count=5 putStrLn
Distribution.Compat.Prelude.Internal putStrLn :: String -> IO ()
Prelude putStrLn :: String -> IO ()
System.IO putStrLn :: String -> IO ()
Data.ByteString putStrLn :: ByteString -> IO ()
Data.ByteString.Char8 putStrLn :: ByteString -> IO () -- plus more results not shown, pass --count=15 to see more
On peut donc implémenter un bot qui écoute les messages sur le serveur Discord,
regarde si leur contenu commence par le préfix “hoogle” et, le cas échéant,
lance hoogle
localement avec la suite du message puis envoie le résultat sur
le serveur Discord.
eventHandler :: D.Event -> D.DiscordHandler ()
D.MessageCreate m) = do
eventHandler (let str = unpack $ D.messageText m -- récupère le texte du message
= checkHoogle str -- requête hoogle ?
hoogleReq = D.messageChannel m
chan /= "") $ do
when (hoogleReq -- exécute hoogle
<- liftIO $ readProcess "hoogle" ["--count=5", hoogleReq] ""
hoogleRes -- récupère et formate le résultat
let txt = pack $ "```hs\n" <> hoogleRes <> "\n```"
-- envoie le résultat sur Discord
$ D.restCall (D.CreateMessage chan txt)
void = pure ()
eventHandler _
-- Vérifie si c'est une requête pour hoogle. Retourne la requête ou une chaine vide.
checkHoogle :: String -> String
= if "hoogle " `isPrefixOf ` str then drop 7 str else "" checkHoogle str
Conclusion
Grâce à son API, Discord permet d’écrire des bots, c’est-à-dire des programmes qui interagissent avec un serveur Discord, par exemple pour répondre automatiquement à des messages, fournir un service particulier, générer des statisques d’utilisation, etc.
Dans cet article, on a vu comment écrire quelques bots simples en Haskell, avec la bibliothèque discord-haskell. Cette dernière est basée sur un gestionnaire d’événements auquel on fournit des fonctions de rappel. Ce système est très naturel dans un langage fonctionnel comme Haskell, d’où la simplicité des codes sources présentés ici.
Il existe également d’autres bibliothèques Haskell pour écrire des bots Discord, notamment calamity. Cette dernière utilise des notions plus avancées de Haskell mais est plus évoluée. Elle propose notamment un DSL pour définir les commandes d’un bot, ainsi qu’un gestionnaire d’effets pour préciser les fonctionnalités du bot (cache, métriques, logs…).