DiscordPY

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

Dans cet exemple, nous créons 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 appliquez vos propres récompenses :

    • donner un rôle,

    • ajouter de l’XP,

    • débloquer un salon, etc.


1. Pré-requis

  • Python 3.10+

  • Un bot Discord fonctionnel (token)

  • Un token API DiscordTop (api_token) associé à votre serveur

Packages à installer :

pip install -U discord.py aiohttp python-dotenv

2. Configuration du projet

Créez un fichier .env à la racine de votre projet :

DISCORD_TOKEN=VOTRE_TOKEN_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 correspond à 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. Bot Python complet : commande /vote + boutons + appel API DTOP

Créez un fichier bot.py avec le contenu suivant :

import os
import asyncio
from dotenv import load_dotenv

import aiohttp
import discord
from discord import app_commands
from discord.ext import commands

load_dotenv()

DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
DISCORD_GUILD_ID = int(os.getenv("DISCORD_GUILD_ID"))
DTOP_API_TOKEN = os.getenv("DTOP_API_TOKEN")
DTOP_GUILD_ID = os.getenv("DTOP_GUILD_ID")  # ID du serveur sur DiscordTop

# Exemple : rôle récompense (à remplacer par un ID réel si vous utilisez cette partie)
REWARD_ROLE_ID = 0  # ex: 123456789012345678

intents = discord.Intents.default()
intents.members = True  # nécessaire si vous attribuez des rôles

bot = commands.Bot(command_prefix="!", intents=intents)


def get_discordtop_vote_url() -> str:
    """Construit l'URL de vote DiscordTop pour votre serveur."""
    return f"https://discordtop.net/guild/{DTOP_GUILD_ID}/vote"


async def check_vote_on_discordtop(user_id: int):
    """
    Appelle l’API DiscordTop pour vérifier si l’utilisateur a voté.

    GET https://api.discordtop.net/v7/check-vote?discord_id=...

    Retourne (status, body_json | None).
    """
    url = "https://api.discordtop.net/v7/check-vote"
    params = {
        "discord_id": str(user_id),
        # "locale": "fr",  # décommentez pour forcer la langue si besoin
    }

    async with aiohttp.ClientSession() as session:
        async with session.get(
            url,
            params=params,
            headers={"Authorization": f"Bearer {DTOP_API_TOKEN}","Accept-Language": "fr-FR"},
        ) as resp:
            status = resp.status
            try:
                data = await resp.json()
            except Exception:
                data = None
            return status, data


class VoteView(discord.ui.View):
    """Vue avec les boutons de vote."""

    def __init__(self):
        super().__init__(timeout=60 * 5)  # 5 minutes
        # Bouton lien vers la page de vote DTOP
        self.add_item(
            discord.ui.Button(
                label="Voter sur DiscordTop",
                style=discord.ButtonStyle.link,
                url=get_discordtop_vote_url(),
            )
        )

    @discord.ui.button(
        label="Vérifier mon vote",
        style=discord.ButtonStyle.primary,
        custom_id="dtop-check-vote",
    )
    async def check_vote_button(
        self,
        interaction: discord.Interaction,
        button: discord.ui.Button,
    ):
        """Callback du bouton 'Vérifier mon vote'."""
        await interaction.response.defer(ephemeral=True)

        user_id = interaction.user.id

        try:
            status, body = await check_vote_on_discordtop(user_id)
        except Exception as exc:
            print("Erreur lors de l'appel à l'API DiscordTop :", exc)
            return await interaction.edit_original_response(
                content="❌ Impossible de contacter l’API DiscordTop pour le moment."
            )

        # Gestion des principaux statuts HTTP
        if status == 200:
        
            if not isinstance(body, dict):
                return await interaction.edit_original_response(
                    content="❌ Réponse inattendue de l’API DiscordTop."
                )
            # Selon la structure de la réponse, vous pouvez vérifier un champ comme `has_voted`

            has_voted = bool(body.get("has_voted", False))
            
            if not has_voted:
                return await interaction.edit_original_response(
                    content=(
                        "❌ Vous n'avez pas encore voté sur DiscordTop !\n"
                        "Cliquez sur « Voter sur DiscordTop », votez, puis réessayez."
                    )
                )
                
            # 👉 C'est ici que vous appliquez VOS récompenses :
            # - ajout de rôle
            # - ajout d'XP
            # - accès à un salon, etc.
            
            if REWARD_ROLE_ID:
                try:
                    member = interaction.user
                    if not isinstance(member, discord.Member):
                        member = await interaction.guild.fetch_member(user_id)

                    role = interaction.guild.get_role(REWARD_ROLE_ID)
                    if role:
                        await member.add_roles(
                            role, reason="Récompense de vote DiscordTop"
                        )
                except Exception as exc:
                    print("Erreur lors de l'attribution du rôle :", exc)

            return await interaction.edit_original_response(
                content="✅ Vote validé sur DiscordTop ! Vos récompenses ont été appliquées."
            )

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

        if status == 429:
            retry_after = 60
            if isinstance(body, dict):
                retry_after = int(body.get("retry_after", retry_after))

            return await interaction.edit_original_response(
                content=(
                    f"🚫 Vous effectuez trop de vérifications de vote.\n"
                    f"Merci de patienter **{retry_after} secondes** avant de réessayer."
                )
            )

        if status in (401, 403):
            return await interaction.edit_original_response(
                content=(
                    "⚠️ La configuration de l'API DiscordTop semble incorrecte "
                    "(clé invalide ou serveur non autorisé). Contactez un administrateur."
                )
            )

        # Autres erreurs (400, 500, etc.)
        print("Erreur API DiscordTop :", status, body)
        return await interaction.edit_original_response(
            content=(
                "❌ Une erreur est survenue lors de la vérification du vote.\n"
                "Merci de réessayer plus tard."
            )
        )


@bot.event
async def on_ready():
    print(f"✅ Connecté en tant que {bot.user} (ID: {bot.user.id})")

    # Synchronisation des commandes slash sur une guilde précise (plus rapide pour les tests)
    guild = discord.Object(id=DISCORD_GUILD_ID)
    bot.tree.copy_global_to(guild=guild)
    await bot.tree.sync(guild=guild)
    print(f"🧵 Commandes synchronisées sur la guilde {DISCORD_GUILD_ID}")


@bot.tree.command(
    name="vote",
    description="Lien de vote DiscordTop + vérification du vote.",
)
async def vote_command(interaction: discord.Interaction):
    """Commande /vote : envoie les boutons."""
    view = VoteView()
    await interaction.response.send_message(
        (
            "Merci de soutenir le serveur en votant sur DiscordTop !\n"
            "Cliquez sur **Voter sur DiscordTop**, puis utilisez **Vérifier mon vote** "
            "pour recevoir vos récompenses."
        ),
        view=view,
        ephemeral=True,
    )


def main():
    bot.run(DISCORD_TOKEN)


if __name__ == "__main__":
    asyncio.run(main())

4. Où appliquer vos propres récompenses ?

Le bloc à modifier est ici :

if status == 200:
    # 👉 C'est ici que vous appliquez VOS récompenses :
    # - ajout de rôle
    # - ajout d'XP
    # - accès à un salon, etc.

À cet endroit, vous pouvez :

  • incrémenter un champ XP dans votre base de données,

  • donner un rôle temporaire ou permanent,

  • ouvrir l’accès à un salon réservé aux voteurs,

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


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

  1. L’utilisateur exécute la commande /vote.

  2. Le bot envoie un message avec :

    • un bouton lien → page de vote DiscordTop,

    • un bouton « Vérifier mon vote ».

  3. L’utilisateur clique sur « Vérifier mon vote ».

  4. Le bot appelle :

GET https://api.discordtop.net/v7/check-vote?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