Skip to content

BloumeChat ​

La classe principale du SDK — le point d'entrée unique pour authentifier un bot, écouter les événements temps réel, et appeler l'API BloumeChat. Étend EventEmitter de Node.js (voir la liste complète des événements dans le guide Événements).

Notation des signatures

Sur cette page, les signatures de méthode (setActivity(activity: ActivityData | null): Promise<void>) sont écrites en notation TypeScript pour préciser exactement les types acceptés — mais tout s'appelle à l'identique en JavaScript, simplement sans les annotations. Voir Utiliser le SDK en JavaScript.

ts
import { BloumeChat } from "bloumechat";
const client = new BloumeChat();
js
const { BloumeChat } = require("bloumechat");
const client = new BloumeChat();

Le constructeur ne prend aucun argument : il initialise les managers (users, guilds, channels, members, voice), masque le futur token de manière non énumérable (pour qu'il n'apparaisse jamais dans console.log(client) ou JSON.stringify(client)), et vérifie que baseUrl/socketUrl pointent bien vers un hôte BloumeChat connu — un avertissement est loggé sinon (voir Sécurité de l'hôte).

Propriétés ​

PropriétéTypeDescription
userUser | nullLe profil du bot lui-même. null avant l'événement ready.
usersUserManagerCache de tous les utilisateurs rencontrés (auteurs de messages, membres consultés…).
guildsGuildManagerCache des serveurs où le bot est présent. Pré-rempli automatiquement après login().
channelsChannelManagerCache des salons connus (texte, vocal…), tous serveurs confondus.
membersMemberManagerCache des profils de membre (rôles, permissions par serveur).
voiceVoiceManager (2.1.0+)Gère la connexion vocale du bot — client.voice.connection, client.voice.leave(). Préférez channel.join() pour rejoindre un salon.
voiceStatesVoiceStateManager (4.1.0+)Cache de l'état vocal courant de chaque utilisateur connu (salon, muet, sourdine, en train de parler…), alimenté par les événements voice:*. Préférez member.voice si vous avez déjà un Member.
commandsCollection<string, Command> (4.2.0+)Commandes chargées via loadCommands(), indexées par nom. Vide tant que loadCommands() n'a pas été appelé au moins une fois.
readyAtDate | nullHorodatage de la première connexion réussie. null avant ready, ré-initialisé à null par destroy().
uptimenumber | null (getter)Millisecondes écoulées depuis readyAt. null si le client n'est pas encore prêt.
baseUrlstring (readonly)Base des requêtes REST : https://bloumechat.com/api/v2.
socketUrlstring (readonly)Hôte du WebSocket : https://api.bloumechat.com.

baseUrl / socketUrl sont en lecture seule au sens TypeScript uniquement

readonly est une garantie compile-time, pas runtime — un module malveillant pourrait techniquement réassigner ces champs sur l'instance. Le SDK s'en protège en loggant un avertissement ([BloumeChat SDK] baseUrl/socketUrl points to an unrecognized host...) si l'hôte détecté ne fait pas partie de la liste autorisée (bloumechat.com, api.bloumechat.com, localhost) au moment de la construction du client. Ne redéfinissez jamais ces valeurs vers un hôte tiers : le token du bot y serait envoyé en clair à chaque requête.

uptime ​

ts
get uptime(): number | null

Calcule Date.now() - readyAt.getTime(). Pratique pour une commande !uptime ou pour exposer un endpoint de health-check.

ts
client.on("messageCreate", (message) => {
  if (message.content === "!uptime") {
    const seconds = Math.floor((client.uptime ?? 0) / 1000);
    message.reply(`En ligne depuis ${seconds}s.`);
  }
});
js
client.on("messageCreate", (message) => {
  if (message.content === "!uptime") {
    const seconds = Math.floor((client.uptime || 0) / 1000);
    message.reply(`En ligne depuis ${seconds}s.`);
  }
});

Connexion ​

login(token) ​

ts
login(token: string): Promise<void>
ParamètreTypeRequisDéfautDescription
tokenstringOui—Le token de bot, généré depuis le Portail Développeurs.

Établit la connexion WebSocket authentifiée vers socketUrl, puis :

  1. Ouvre le socket avec transports: ["websocket"] et reconnexion automatique activée (reconnectionAttempts: Infinity, délai 1s → 30s en backoff).
  2. Dès connect, appelle en interne GET /auth/me pour charger client.user.
  3. Pré-charge tous les serveurs du bot (client.guilds.fetchAll()), ce qui peuple aussi client.members avec le membre du bot lui-même sur chaque serveur (nécessaire pour que ses permissions soient immédiatement disponibles).
  4. Émet ready, puis résout la promesse.

Erreurs possibles :

  • Rejette immédiatement avec une BloumeChatAuthError ("login() requires a non-empty bot token string.") si token est vide, null/undefined, ou n'est pas une chaîne.
  • Rejette si la connexion socket échoue avant la première connexion (connect_error) — un token invalide ou révoqué déclenche ce cas.
  • Une fois ready atteint, les coupures réseau suivantes ne rejettent plus rien : le socket retente indéfiniment en arrière-plan et émet reconnect/disconnect (voir Événements) au lieu de faire échouer la promesse déjà résolue.
ts
import { BloumeChat } from "bloumechat";

const client = new BloumeChat();

client.on("ready", () => {
  console.log(`Connecté en tant que ${client.user?.tagString}`);
});

try {
  await client.login(process.env.BOT_TOKEN!);
} catch (err) {
  console.error("Échec de connexion :", err);
  process.exit(1);
}
js
const { BloumeChat } = require("bloumechat");

const client = new BloumeChat();

client.on("ready", () => {
  console.log(`Connecté en tant que ${client.user?.tagString}`);
});

client
  .login(process.env.BOT_TOKEN)
  .catch((err) => {
    console.error("Échec de connexion :", err);
    process.exit(1);
  });

Pourquoi await client.login(...) plutôt que client.on("ready", ...) seul ?

La promesse retournée par login() se résout exactement au moment de ready — les deux approches sont équivalentes. Utilisez await en haut de votre script si le reste du code dépend d'un client prêt (par exemple pour enregistrer des commandes) ; utilisez l'écouteur ready si vous préférez une structure événementielle.

destroy() ​

ts
destroy(): void

Ferme proprement la connexion :

  • Déconnecte le socket (socket.disconnect()) et le libère (socket = null) — plus aucune reconnexion automatique n'aura lieu après cet appel.
  • Réinitialise readyAt à null (donc uptime redevient null).
  • Efface le token en mémoire.
  • Retire tous les écouteurs d'événements enregistrés sur le client (removeAllListeners()) — y compris les vôtres.

N'efface pas les caches (client.guilds.cache, client.channels.cache, etc.) : ces objets restent accessibles en lecture après destroy(), mais ne seront plus jamais mis à jour. Ne réutilisez pas la même instance après un destroy() — créez un nouveau BloumeChat() pour vous reconnecter.

ts
process.on("SIGINT", () => {
  console.log("Arrêt du bot...");
  client.destroy();
  process.exit(0);
});
js
process.on("SIGINT", () => {
  console.log("Arrêt du bot...");
  client.destroy();
  process.exit(0);
});

Requêtes API bas niveau ​

apiCall(path, options?) ​

ts
apiCall(path: string, options?: ApiCallOptions): Promise<any>
ParamètreTypeRequisDéfautDescription
pathstringOui—Chemin relatif à baseUrl, ex. "/servers/123/roles". Doit commencer par /.
optionsApiCallOptionsNon{}Étend RequestInit standard (method, body, headers…) avec timeoutMs et maxRetries.
ts
interface ApiCallOptions extends RequestInit {
  headers?: Record<string, string>;
  /** Temps max (ms) avant d'annuler la requête. Défaut 15000. */
  timeoutMs?: number;
  /** Nombre max de tentatives sur erreur réseau / 429 / 502 / 503 / 504. Défaut 3. */
  maxRetries?: number;
}

Appel REST authentifié brut vers ${baseUrl}${path}, utilisé en interne par toutes les méthodes de haut niveau du SDK (sendMessage, setStatus, tous les managers…). Vous n'en avez besoin directement que pour un endpoint non encore couvert par une méthode dédiée.

Comportement détaillé :

  • Ajoute automatiquement Authorization: Bearer <token>, Content-Type: application/json, X-Bloume-SDK: true et un User-Agent — vos headers personnalisés sont fusionnés par-dessus (ils ne peuvent pas écraser Authorization).
  • Limite la concurrence côté client à 5 requêtes simultanées maximum : au-delà, les appels supplémentaires sont mis en file d'attente (FIFO) et débloqués un par un dès qu'un slot se libère. Ceci protège votre bot contre un bannissement pour abus si une boucle envoie trop de requêtes d'un coup.
  • Sur timeout (timeoutMs dépassé) ou erreur réseau (TypeError de fetch), retente automatiquement avec un backoff exponentiel (500ms × 2^tentative, plafonné à 30s), jusqu'à maxRetries fois.
  • Sur réponse 429, 502, 503 ou 504, retente également — en respectant le header Retry-After de la réponse s'il est présent (en secondes), sinon le même backoff exponentiel.
  • Depuis la 1.5.0, un 429 enregistre aussi un délai de blocage partagé par bucket (méthode + type de ressource + ID « majeur ») : toute autre requête vers le même bucket patiente automatiquement, sans affecter les requêtes vers des ressources différentes — voir Gestion des erreurs — buckets de rate-limit.
  • Une réponse 204 No Content résout avec null.
  • Toute autre réponse non-OK (après épuisement des tentatives) rejette avec une BloumeChatAPIError (ou sa sous-classe RateLimitError pour un 429) exposant status/path/body.
  • Un timeout définitivement épuisé rejette avec une BloumeChatTimeoutError.

Voir aussi

La page Gestion des erreurs détaille toute la hiérarchie (BloumeChatError, BloumeChatAPIError, RateLimitError, BloumeChatAuthError, BloumeChatTimeoutError) avec des exemples instanceof.

ts
// Endpoint non encore couvert par une méthode dédiée du SDK
const stats = await client.apiCall(`/servers/${guildId}/stats`);
console.log(stats.memberCount);

// Avec options personnalisées
const data = await client.apiCall("/servers", {
  method: "POST",
  body: JSON.stringify({ name: "Mon serveur" }),
  timeoutMs: 5000,
  maxRetries: 1,
});
js
// Endpoint non encore couvert par une méthode dédiée du SDK
const stats = await client.apiCall(`/servers/${guildId}/stats`);
console.log(stats.memberCount);

// Avec options personnalisées
const data = await client.apiCall("/servers", {
  method: "POST",
  body: JSON.stringify({ name: "Mon serveur" }),
  timeoutMs: 5000,
  maxRetries: 1,
});

Body toujours en JSON string

apiCall ne sérialise pas options.body pour vous — passez toujours JSON.stringify(...) explicitement, comme dans l'exemple ci-dessus. Le SDK force déjà Content-Type: application/json.

Messagerie ​

sendMessage(channelId, options) ​

ts
sendMessage(
  channelId: string,
  options: string | EmbedBuilder | { content?: string; embeds?: Array<EmbedBuilder | EmbedPayload | Record<string, unknown>>; replyToId?: string }
): Promise<Message>
ParamètreTypeRequisDescription
channelIdstringOuiIdentifiant public du salon cible.
optionsstring | EmbedBuilder | objectOuiContenu du message — voir les trois formes acceptées ci-dessous.

Méthode bas niveau utilisée en interne par Channel.send() et Message.reply() — préférez ces raccourcis dans votre code, client.sendMessage() reste utile quand vous n'avez qu'un ID de salon sous la main (par exemple stocké en base de données) sans avoir fetché l'objet Channel.

Trois formes acceptées pour options :

FormeExempleRésultat
string"Bonjour !"Message texte simple.
EmbedBuildernew EmbedBuilder().setTitle("Titre")Message avec un seul embed, sans texte.
object{ content: "Voici :", embeds: [embed], replyToId: "msg_id" }Combine texte, embeds multiples, et réponse à un message existant.

Comportement :

  • Envoie via le WebSocket (message:send) avec un nonce aléatoire, puis attend l'écho message:new correspondant à ce nonce pour résoudre avec l'objet Message réellement créé côté serveur.
  • Rejette immédiatement avec une BloumeChatAuthError si le socket n'est pas connecté (avant login() ou après destroy()).
  • Rejette immédiatement avec une BloumeChatAuthError ("sendMessage requires non-empty content or at least one embed.") si content est vide et qu'aucun embed n'est fourni — évite d'attendre inutilement un accusé de réception qui n'arrivera jamais.
  • Rejette avec Error("sendMessage timeout after 10s") si aucun accusé n'est reçu dans les 10 secondes (salon inexistant, permissions insuffisantes côté serveur, etc.).
ts
// Texte simple
await client.sendMessage(channelId, "Bonjour tout le monde !");

// Embed seul
import { EmbedBuilder } from "bloumechat";
const embed = new EmbedBuilder().setTitle("Annonce").setColor("#5e72e4");
await client.sendMessage(channelId, embed);

// Texte + embed + réponse à un message
await client.sendMessage(channelId, {
  content: "Voici les détails :",
  embeds: [embed],
  replyToId: originalMessage.id,
});
js
// Texte simple
await client.sendMessage(channelId, "Bonjour tout le monde !");

// Embed seul
const { EmbedBuilder } = require("bloumechat");
const embed = new EmbedBuilder().setTitle("Annonce").setColor("#5e72e4");
await client.sendMessage(channelId, embed);

// Texte + embed + réponse à un message
await client.sendMessage(channelId, {
  content: "Voici les détails :",
  embeds: [embed],
  replyToId: originalMessage.id,
});

Voir aussi : EmbedBuilder, Message, Channel.

Présence (Rich Presence / RPC) ​

Trois méthodes pour contrôler le statut affiché du bot — voir aussi l'exemple Rich Presence (RPC).

ts
interface ActivityData {
  type: "using" | "browsing" | "listening" | "playing";
  name: string;
  details?: string;
  startedAt?: number;
}

interface PresenceData {
  status?: "online" | "idle" | "dnd" | "invisible";
  activity?: ActivityData | null;
}

setActivity(activity) ​

ts
setActivity(activity: ActivityData | null): Promise<void>
ParamètreTypeRequisDescription
activityActivityData | nullOuiDescription de l'activité. Passez null pour l'effacer.

Émet activity:update sur le socket. name est tronqué côté client à 128 caractères, details à 64 — pas d'erreur levée en cas de dépassement, la chaîne est simplement coupée. Si startedAt n'est pas fourni, il vaut Date.now() (utile pour afficher une durée écoulée côté client BloumeChat). Rejette avec une BloumeChatAuthError si appelé avant login().

ts
// Définir une activité
await client.setActivity({ type: "playing", name: "avec le BloumeChat SDK" });

// Avec des détails
await client.setActivity({
  type: "listening",
  name: "Radio BloumeChat",
  details: "Épisode 12",
});

// Effacer l'activité
await client.setActivity(null);
js
// Définir une activité
await client.setActivity({ type: "playing", name: "avec le BloumeChat SDK" });

// Avec des détails
await client.setActivity({
  type: "listening",
  name: "Radio BloumeChat",
  details: "Épisode 12",
});

// Effacer l'activité
await client.setActivity(null);

setStatus(status) ​

ts
setStatus(status: "online" | "idle" | "dnd" | "invisible"): Promise<void>
ParamètreTypeRequisDescription
status"online" | "idle" | "dnd" | "invisible"OuiLe nouveau statut de présence.

Émet presence:update sur le socket, met à jour client.user.status localement en optimiste, puis persiste le changement via PATCH /users/settings. Rejette avec une BloumeChatAuthError si le socket n'est pas connecté.

ts
await client.setStatus("dnd");
js
await client.setStatus("dnd");

setPresence(data) ​

ts
setPresence(data: PresenceData): Promise<void>
ParamètreTypeRequisDescription
dataPresenceDataOui{ status?, activity? } — les deux champs sont optionnels et indépendants.

Combine setStatus() et setActivity() en un seul appel pratique. N'appelle setStatus() que si data.status est fourni, et setActivity() que si data.activity !== undefined (donc passer explicitement activity: null efface bien l'activité, alors qu'omettre le champ la laisse inchangée).

ts
await client.setPresence({
  status: "online",
  activity: { type: "playing", name: "avec le BloumeChat SDK" },
});
js
await client.setPresence({
  status: "online",
  activity: { type: "playing", name: "avec le BloumeChat SDK" },
});

Messages privés ​

createDM(userId) ​

ts
createDM(userId: string): Promise<DMChannel>
ParamètreTypeRequisDescription
userIdstringOuiIdentifiant public de l'utilisateur destinataire.

Ouvre (ou récupère, si déjà existant) un salon de messages privés avec cet utilisateur via GET /channels/dm/:userId. Retourne un DMChannel, qui expose sa propre méthode .send().

ts
const dm = await client.createDM("USER_PUBLIC_ID");
await dm.send("Bonjour ! Je suis le bot de modération de ce serveur.");
js
const dm = await client.createDM("USER_PUBLIC_ID");
await dm.send("Bonjour ! Je suis le bot de modération de ce serveur.");

Voir aussi : DMChannel.

Invitations ​

fetchInvite(code) ​

ts
fetchInvite(code: string): Promise<Invite>
ParamètreTypeRequisDescription
codestringOuiLe code d'invitation (partie après bloumechat.com/invite/).

Résout une invitation en lecture seule — utile pour inspecter le serveur cible avant d'agir, ou pour obtenir un objet Invite que vous pourrez révoquer si le bot dispose de MANAGE_INVITES sur ce serveur.

Un bot ne peut pas rejoindre un serveur seul

Contrairement à un utilisateur humain, un bot ne peut pas "accepter" une invitation par lui-même — il doit être ajouté à un serveur par un utilisateur disposant des permissions nécessaires (via le portail OAuth2, par exemple). fetchInvite() sert uniquement à consulter ou révoquer, jamais à rejoindre.

ts
const invite = await client.fetchInvite("abc123");
console.log(`Invitation vers ${invite.guild?.name}, ${invite.uses} utilisations`);
js
const invite = await client.fetchInvite("abc123");
console.log(`Invitation vers ${invite.guild?.name}, ${invite.uses} utilisations`);

Voir aussi : Invite.

Recherche ​

searchMessages(channelId, query, options?) ​

ts
searchMessages(channelId: string, query: string, options?: { limit?: number }): Promise<Message[]>
ParamètreTypeRequisDéfautDescription
channelIdstringOui—Identifiant public du salon à fouiller.
querystringOui—Terme(s) de recherche en texte libre.
options.limitnumberNon(serveur)Nombre maximum de résultats. Omis de la requête si non fourni.

Recherche des messages par mot-clé dans un salon via GET /chat/:channelId/search. Retourne des instances Message du SDK (depuis la 1.5.0 — c'était auparavant du JSON brut) — [] si aucun résultat.

ts
const results = await client.searchMessages(channelId, "hello world", { limit: 20 });
console.log(`${results.length} résultat(s) trouvé(s)`);
js
const results = await client.searchMessages(channelId, "hello world", { limit: 20 });
console.log(`${results.length} résultat(s) trouvé(s)`);

Commandes ​

loadCommands(directoryPath) (4.2.0+) ​

ts
loadCommands(directoryPath: string): Promise<Collection<string, Command>>

interface Command {
  name: string;
  aliases?: string[];
  execute: (...args: any[]) => unknown | Promise<unknown>;
  [key: string]: unknown;
}
ParamètreTypeRequisDescription
directoryPathstringOuiDossier contenant les fichiers de commande (non récursif).

Charge chaque fichier .js/.cjs/.mjs/.ts du dossier et ajoute au client.commands celles dont l'export par défaut (ou nommé command) a un name (string) et un execute (fonction). Remplace le classique fs.readdirSync + new Map() que les bots réécrivaient tous à la main. Un fichier invalide ou en erreur est ignoré avec un console.warn — il n'interrompt pas le chargement des autres.

Utilise import() dynamique (pas require), donc compatible que le bot soit en CommonJS ou en ESM. Charger un fichier .ts directement suppose que le runtime du bot a déjà un loader TypeScript enregistré (tsx, ts-node/esm…) — sans ça, ces fichiers échouent au chargement (avec le même console.warn) plutôt que de planter le processus.

Retour : Promise<Collection<string, Command>> — le client.commands mis à jour (utile pour chaîner). Peut être appelé plusieurs fois (un dossier par plugin, par exemple) : chaque appel ajoute au lieu de remplacer.

Ne dispatche rien automatiquement

loadCommands() charge et stocke — il ne branche pas de préfixe, de cooldown, ni d'appel automatique sur messageCreate. Ces conventions varient trop d'un bot à l'autre pour être imposées par le SDK ; câblez le dispatch vous-même comme dans l'exemple ci-dessous.

ts
// commands/ping.ts
import type { Command } from "bloumechat";

export default {
  name: "ping",
  execute: (message) => message.reply("Pong !"),
} satisfies Command;

// index.ts
await client.loadCommands(path.join(__dirname, "commands"));
client.on("messageCreate", (message) => {
  if (!message.content.startsWith("!")) return;
  const name = message.content.slice(1).split(" ")[0];
  client.commands.get(name)?.execute(message, client);
});
js
// commands/ping.js
module.exports = {
  name: "ping",
  execute: (message) => message.reply("Pong !"),
};

// index.js
await client.loadCommands(path.join(__dirname, "commands"));
client.on("messageCreate", (message) => {
  if (!message.content.startsWith("!")) return;
  const name = message.content.slice(1).split(" ")[0];
  client.commands.get(name)?.execute(message, client);
});

Voir aussi : Collection.

Sécurité de l'hôte ​

Le constructeur vérifie que baseUrl et socketUrl pointent vers bloumechat.com, api.bloumechat.com, ou localhost (protocole https: obligatoire, sauf en local). Si ce n'est pas le cas — par exemple une sous-classe qui redéfinit ces champs — un avertissement est loggé sur console.warn à chaque construction d'instance :

[BloumeChat SDK] baseUrl/socketUrl points to an unrecognized host ("evil.example.com"). Your bot token will be sent to this host — make sure this is intentional.

Un second avertissement distinct est loggé si le protocole n'est pas https: (hors localhost/127.0.0.1), car le token serait alors transmis en clair. Ces avertissements sont informatifs uniquement — ils ne bloquent jamais l'exécution.

Sérialisation sûre ​

BloumeChat redéfinit son inspection Node.js (util.inspect) et toJSON() pour toujours remplacer le token interne par "[REDACTED]" (ou null s'il n'est pas encore défini). Concrètement :

ts
console.log(client); // le token n'apparaît jamais, même après login()
JSON.stringify(client); // idem
js
console.log(client); // le token n'apparaît jamais, même après login()
JSON.stringify(client); // idem

Ceci évite qu'un token de bot ne fuite accidentellement dans des logs applicatifs, un rapport de crash, ou une sortie CI.

Exemple complet ​

ts
import { BloumeChat, EmbedBuilder } from "bloumechat";

const client = new BloumeChat();

client.on("ready", async () => {
  console.log(`Connecté en tant que ${client.user?.tagString}`);
  await client.setActivity({ type: "playing", name: "avec le BloumeChat SDK" });
});

client.on("messageCreate", (msg) => {
  if (msg.author.bot) return;
  if (msg.content === "!ping") {
    msg.reply("🏓 Pong !");
  }
  if (msg.content === "!uptime") {
    msg.reply(`En ligne depuis ${Math.floor((client.uptime ?? 0) / 1000)}s`);
  }
});

client.on("disconnect", (reason) => console.warn("Déconnecté :", reason));
client.on("reconnect", () => console.log("Reconnecté !"));
client.on("error", (err) => console.error("Erreur socket :", err));

await client.login(process.env.BOT_TOKEN!);
js
const { BloumeChat, EmbedBuilder } = require("bloumechat");

const client = new BloumeChat();

client.on("ready", async () => {
  console.log(`Connecté en tant que ${client.user?.tagString}`);
  await client.setActivity({ type: "playing", name: "avec le BloumeChat SDK" });
});

client.on("messageCreate", (msg) => {
  if (msg.author.bot) return;
  if (msg.content === "!ping") {
    msg.reply("🏓 Pong !");
  }
  if (msg.content === "!uptime") {
    msg.reply(`En ligne depuis ${Math.floor((client.uptime || 0) / 1000)}s`);
  }
});

client.on("disconnect", (reason) => console.warn("Déconnecté :", reason));
client.on("reconnect", () => console.log("Reconnecté !"));
client.on("error", (err) => console.error("Erreur socket :", err));

client.login(process.env.BOT_TOKEN);

Voir aussi ​

SDK publié sous licence ISC.