import os import re import csv import sys import json import time import random import asyncio import discord import logging import os.path import secrets import gspread import datetime import tempfile import requests import threading import traceback import gradio_client import numpy as np import pandas as pd import gradio as gr import plotly.graph_objects as go from tabulate import tabulate from requests import HTTPError from gradio_client import Client from discord import Color, Embed from huggingface_hub import HfApi from discord.ui import Button, View from discord.ext import commands, tasks from datetime import datetime, timedelta from urllib.parse import urlparse, parse_qs # starting to migrate to HF datasets and away from google sheets from huggingface_hub import HfApi from apscheduler.executors.pool import ThreadPoolExecutor from apscheduler.executors.asyncio import AsyncIOExecutor from apscheduler.schedulers.asyncio import AsyncIOScheduler from gspread_formatting.dataframe import format_with_dataframe discord.utils.setup_logging(level=logging.DEBUG) # discord.py built-in logging.getLogger("discord.http").setLevel(logging.DEBUG) # HTTP layer logging.basicConfig( level=logging.DEBUG, format="%(asctime)s %(levelname)s %(name)s: %(message)s", stream=sys.stdout, ) DISCORD_TOKEN = os.environ.get("DISCORD_TOKEN", None) intents = discord.Intents.all() bot = commands.Bot(command_prefix='!', intents=intents) GRADIO_APP_URL = "https://huggingface.co/spaces/discord-community/LevelBot" """""" bot_ids = [1136614989411655780, 1166392942387265536, 1158038249835610123, 1130774761031610388, 1155489509518098565, 1155169841276260546, 1152238037355474964, 1154395078735953930] """""" hf_token = os.environ.get("HF_TOKEN") discord_loop = None # global variable # new @bot.event async def on_interaction(interaction: discord.Interaction): # Fires for every interaction—great catch-all probe. cid = getattr(interaction.data, "get", lambda k, d=None: None)("custom_id") logging.debug(f"on_interaction custom_id={cid!r} responded={interaction.response.is_done()}") class DMButton(Button): def __init__(self): super().__init__( label="Verify Discord Account", style=discord.ButtonStyle.primary, custom_id="verify_discord_account_button" # <- stable ) async def callback(self, interaction: discord.Interaction): # await interaction.user.send(self.message) # this is for DMs, but users may have DMs disabled await interaction.response.defer(ephemeral=True) # to fix interaction issue user_id = interaction.user.id guild = interaction.guild # safer than cache: try: member = await guild.fetch_member(user_id) except discord.NotFound: await interaction.followup.send("Could not find your member record.", ephemeral=True) return pending_role = guild.get_role(1380908157475360899) if pending_role: try: await member.add_roles(pending_role, reason="Clicked verification button") logging.info("Assigned Pending to %s (%s)", member, member.id) except discord.Forbidden: logging.error("Missing permissions or role hierarchy below Pending.") except Exception: logging.exception("Failed to add Pending role") unique_link = f"<{GRADIO_APP_URL}?user_id={user_id}>" msg = ( "Login link generated! To complete verification:\n" "- 1 Visit this link,\n- 2 click '🤗 Sign in with Hugging Face'\n\n" f"{unique_link}" ) # since we deferred: await interaction.followup.send(msg, ephemeral=True) # new class PersistentVerifyView(discord.ui.View): def __init__(self): super().__init__(timeout=None) # <- persistent self.add_item(DMButton()) async def on_error(self, error: Exception, item, interaction: discord.Interaction): logging.exception("Component error") try: if interaction.response.is_done(): await interaction.followup.send("Something broke while handling that click.", ephemeral=True) else: await interaction.response.send_message("Something broke while handling that click.", ephemeral=True) except Exception: logging.exception("Failed to notify user after component error.") @bot.event async def on_ready(): logging.info("Logged in as %s (%s)", bot.user, bot.user.id) view = PersistentVerifyView() bot.add_view(view) # binds handler for the custom_id channel = bot.get_channel(900125909984624713) msg = await channel.fetch_message(1271145797433557023) # Step A: clear existing components await msg.edit(view=None) # Step B: attach the new persistent view (with stable custom_id) await msg.edit(view=view) # fetch and print the custom_ids currently on the message msg2 = await channel.fetch_message(msg.id) try: current_ids = [] for action_row in msg2.components: for comp in action_row.children: current_ids.append(getattr(comp, "custom_id", None)) logging.info("Message components now: %s", current_ids) except Exception: logging.exception("Could not introspect message components") logging.info("------------------------------------------------------------------------") DISCORD_TOKEN = os.environ.get("DISCORD_TOKEN", None) def run_bot(): global discord_loop discord_loop = asyncio.new_event_loop() asyncio.set_event_loop(discord_loop) discord_loop.create_task(bot.start(DISCORD_TOKEN)) discord_loop.run_forever() threading.Thread(target=run_bot).start() def verify_button(profile: gr.OAuthProfile | None, request: gr.Request) -> str: query_params = parse_qs(urlparse(str(request.url)).query) user_id = query_params.get('user_id', [None])[0] if not user_id: return "# ❌ Missing Discord user ID." if profile is None: return "# ❌ You're not logged in with Hugging Face." async def upgrade_role(): await bot.wait_until_ready() guild = bot.get_guild(879548962464493619) if not guild: logging.error("Guild not ready") return try: member = await guild.fetch_member(int(user_id)) except discord.NotFound: logging.error("User %s not found in guild", user_id) return pending_role = guild.get_role(1380908157475360899) verified_role = guild.get_role(900063512829755413) if not verified_role: logging.error("Verified role missing") return # remove pending if present if pending_role and pending_role in member.roles: try: await member.remove_roles(pending_role, reason="Verification complete") except Exception: logging.exception("Remove pending failed") # always try to add verified if verified_role not in member.roles: try: await member.add_roles(verified_role, reason=f"HF verified {profile.username}") logging.info("Added Verified to %s", member) except discord.Forbidden: logging.error("Forbidden adding Verified (perm/hierarchy)") except Exception: logging.exception("Add Verified failed") if discord_loop: asyncio.run_coroutine_threadsafe(upgrade_role(), discord_loop) return f"# ✅ Verification successful! Welcome, {profile.username} 🎉" demo = gr.Blocks() with demo: try: TITLE = """