DiscordJS

Intégrer la vérification des votes dans un bot Discord (JS)

Dans cet exemple, on crée une commande /vote qui :

  1. Envoie un message avec :

    • un bouton “Voter sur DiscordTop” → ouvre la page de vote,

    • un bouton “Vérifier mon vote” → appelle l’API DiscordTop.

  2. Si le vote est validé par l’API, vous pouvez appliquer vos propres récompenses :

    • donner un rôle,

    • ajouter de l’XP,

    • ouvrir l’accès à un salon privé, etc.


1. Pré-requis

  • Node.js 18+ (pour avoir fetch intégré)

  • Un bot Discord configuré (token)

  • Un token API DiscordTop (api_token) associé à votre serveur - Comment trouvé ma clé ?

Packages :

npm install discord.js dotenv

2. Configuration du projet

Créez un fichier .env à la racine :

DISCORD_TOKEN=VOTRE_TOKEN_BOT
DISCORD_CLIENT_ID=ID_CLIENT_DE_VOTRE_BOT
DISCORD_GUILD_ID=ID_DU_SERVEUR_DE_TEST
DTOP_API_TOKEN=VOTRE_CLE_API_DISCORDTOP
DTOP_GUILD_ID=ID_DU_SERVEUR_SUR_DTOP

DTOP_GUILD_ID est l’ID du serveur tel qu’il apparaît sur DiscordTop (en général, c’est le même que l’ID Discord).


3. Enregistrer la commande /vote

Créez un fichier deploy-commands.js :

import 'dotenv/config';
import { REST, Routes, SlashCommandBuilder } from 'discord.js';

const commands = [
  new SlashCommandBuilder()
    .setName('vote')
    .setDescription('Obtenir le lien de vote DiscordTop et vérifier votre vote.')
    .toJSON(),
];

const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);

async function main() {
  try {
    console.log('🔁 Mise à jour des commandes (guild)…');
    await rest.put(
      Routes.applicationGuildCommands(
        process.env.DISCORD_CLIENT_ID,
        process.env.DISCORD_GUILD_ID
      ),
      { body: commands }
    );
    console.log('✅ Commandes enregistrées avec succès.');
  } catch (error) {
    console.error('❌ Erreur lors de l’enregistrement des commandes :', error);
  }
}

main();

Lancer une fois :

node deploy-commands.js

4. Bot de base avec /vote + boutons

Créez un fichier index.js :

import 'dotenv/config';
import {
  Client,
  GatewayIntentBits,
  Partials,
  ButtonStyle,
  ActionRowBuilder,
  ButtonBuilder,
  Events,
} from 'discord.js';

const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
  partials: [Partials.GuildMember],
});

// URL de vote DTOP (adapter si besoin)
function getDiscordTopVoteUrl() {
  const guildId = process.env.DTOP_GUILD_ID;
  return `https://discordtop.net/guild/${guildId}/vote`;
}

// Appel à l’API DTOP pour vérifier le vote
async function checkVoteOnDiscordTop(userId) {
  const apiToken = process.env.DTOP_API_TOKEN;
  const url = new URL('https://api.discordtop.net/v7/check-vote');
  
  url.searchParams.set('discord_id', userId);

  // Vous pouvez aussi ajouter &locale=fr si vous voulez forcer la langue
  // url.searchParams.set('locale', 'fr');

  const res = await fetch(url.toString(), {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${apiToken}`,
      'Accept-Language': 'fr-FR',
    },
  });

  // On retourne la réponse brute + le JSON
  let body = null;
  try {
    body = await res.json();
  } catch {
    body = null;
  }

  return { status: res.status, body };
}

client.once(Events.ClientReady, (c) => {
  console.log(`✅ Connecté en tant que ${c.user.tag}`);
});

client.on(Events.InteractionCreate, async (interaction) => {
  // Commande /vote
  if (interaction.isChatInputCommand() && interaction.commandName === 'vote') {
    const voteUrl = getDiscordTopVoteUrl();

    const row = new ActionRowBuilder().addComponents(
      new ButtonBuilder()
        .setLabel('Voter sur DiscordTop')
        .setStyle(ButtonStyle.Link)
        .setURL(voteUrl),
      new ButtonBuilder()
        .setCustomId('dtop-check-vote')
        .setLabel('Vérifier mon vote')
        .setStyle(ButtonStyle.Primary)
    );

    await interaction.reply({
      content:
        'Merci de soutenir le serveur en votant sur DiscordTop !\nCliquez sur le bouton ci-dessous pour voter, puis utilisez **“Vérifier mon vote”** pour recevoir vos récompenses.',
      components: [row],
      ephemeral: true,
    });

    return;
  }

  // Bouton "Vérifier mon vote"
  if (interaction.isButton() && interaction.customId === 'dtop-check-vote') {
    await interaction.deferReply({ ephemeral: true });

    const userId = interaction.user.id;

    try {
      const { status, body } = await checkVoteOnDiscordTop(userId);

      // Gestion des statuts principaux
      if (status === 200) {
        const hasVoted = body?.has_voted ?? false;

        if (!hasVoted) {
          return interaction.editReply(
            "Il semble que vous n'ayez pas encore voté pour ce serveur. Essayez de voter puis réessayez dans quelques secondes."
          );
        }

        // 👉 C’est ici que vous appliquez VOS récompenses :
        // - donner un rôle
        // - ajouter de l’XP
        // - débloquer un salon, etc.

        // Exemple : donner un rôle (remplacez par votre ID de rôle)
        const rewardRoleId = 'ID_DU_ROLE_RECOMPENSE'; // à adapter

        const member =
          interaction.member ??
          (await interaction.guild.members.fetch(userId).catch(() => null));

        if (member && rewardRoleId !== 'ID_DU_ROLE_RECOMPENSE') {
          await member.roles.add(rewardRoleId).catch(() => null);
        }

        return interaction.editReply(
          "✅ Vote validé sur DiscordTop ! Vos récompenses ont été appliquées sur le serveur."
        );
      }

      if (status === 404) {
        return interaction.editReply(
          "Aucun vote récent n'a été trouvé pour votre compte. Assurez-vous d'avoir voté sur la bonne page et réessayez dans quelques instants."
        );
      }

      if (status === 429) {
        const retryAfter = body?.retry_after ?? 60;
        return interaction.editReply(
          `🚫 Vous effectuez trop de vérifications de vote. Merci de patienter **${retryAfter} secondes** avant de réessayer.`
        );
      }

      if (status === 401 || status === 403) {
        return interaction.editReply(
          "⚠️ La configuration de l'API DiscordTop semble incorrecte (clé invalide ou serveur non autorisé). Contactez un administrateur."
        );
      }

      // Autres erreurs (500, 400, etc.)
      console.error('Erreur API DTOP:', status, body);
      return interaction.editReply(
        "❌ Une erreur est survenue lors de la vérification du vote. Merci de réessayer plus tard."
      );
    } catch (err) {
      console.error('Erreur lors de l’appel à DTOP :', err);
      return interaction.editReply(
        "❌ Impossible de contacter l’API DiscordTop pour le moment."
      );
    }
  }
});

client.login(process.env.DISCORD_TOKEN);

5. Où brancher votre propre logique de récompenses ?

Dans l’exemple ci-dessus, le bloc important est ici :

if (status === 200) {
  const hasVoted = body?.has_voted ?? true;

  if (!hasVoted) {
    return interaction.editReply(
      "Il semble que vous n'ayez pas encore voté pour ce serveur. Essayez de voter puis réessayez dans quelques secondes."
    );
  }

  // 👉 VOTRE LOGIQUE DE RÉCOMPENSE
  // Exemple : donner un rôle, XP, etc.
}

C’est à cet endroit précis que vous pouvez :

  • incrémenter un champ XP dans votre base,

  • ajouter un rôle avec member.roles.add(...),

  • logger l’événement dans un salon staff,

  • comptabiliser les votes journaliers, etc.


6. Résumé du flux côté bot

  1. L’utilisateur tape /vote

  2. Le bot répond avec :

    • un bouton lien → page de vote DiscordTop,

    • un bouton “Vérifier mon vote”

  3. L’utilisateur clique “Vérifier mon vote”

  4. Le bot appelle :

GET https://api.discordtop.net/v7/check-vote?api_token=VOTRE_CLE_API&discord_id=USER_ID
  1. Selon la réponse :

    • ✅ 200 → vote valide → vos récompenses sont appliquées

    • ❌ 404 → pas de vote récent

    • 🚫 429 → trop de requêtes, respectez retry_after

    • 🔐 401/403 → problème de configuration API

    • 💥 500 → erreur côté DTOP (à réessayer plus tard)

Last updated