Spaces:
Running on CPU Upgrade

File size: 9,184 Bytes
5a26019
 
65c7e49
24b8a56
8b23687
5a26019
 
 
 
65c7e49
5086fc0
5a26019
 
 
39a3bfb
5a26019
8b23687
24b8a56
65c7e49
5a26019
65c7e49
bfb4d01
5a26019
4d6295e
6386d92
5a26019
6386d92
5a26019
8b23687
fc1881a
5a26019
7586254
5a26019
42d451d
39a3bfb
2badc7b
5a26019
500d61b
c284eb8
65c7e49
421347b
24b8a56
 
 
 
 
 
 
 
a5603d3
7187a61
cf1e692
 
a5603d3
5a26019
 
d520393
 
 
 
e91740b
39a3bfb
 
0aa83ad
d0b459f
 
 
5a9fc8c
 
 
 
 
 
 
 
 
 
5a26019
bcfea73
5a9fc8c
 
 
 
 
5a26019
 
 
5a9fc8c
 
5a26019
4e807ea
 
 
bcfea73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5a26019
 
5a9fc8c
 
 
 
 
 
 
 
bcfea73
 
 
 
 
 
 
5a9fc8c
 
 
6b5ae25
 
bcfea73
5a9fc8c
bcfea73
 
5a9fc8c
bcfea73
 
e698139
bcfea73
 
 
 
5a9fc8c
bcfea73
 
 
 
 
 
 
 
 
 
 
 
0b7628c
 
66cef69
 
d0b459f
 
 
 
 
66cef69
459c6ce
 
4e807ea
 
 
 
 
 
 
 
 
bcfea73
4e807ea
bcfea73
 
 
 
 
 
 
 
 
4e807ea
bcfea73
 
 
 
4e807ea
 
bcfea73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4e807ea
d0b459f
 
4e807ea
 
 
 
4f0ae67
bfb3bca
22d3c95
 
f5d5dc6
f5010c4
f5d5dc6
 
5a26019
359b637
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42641d6
 
f5d5dc6
 
 
63e32ce
ca772d6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
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 = """<h1 align="center" id="space-title">🤗 Hugging Face Discord Verification</h1>"""
        gr.HTML(TITLE)
        with gr.Tabs(elem_classes="tab-buttons") as tabs:
            #------------------------------------------------------------------------------
            with gr.TabItem("✅ Discord Verification", elem_id="verify-tab", id=2):
                login_button = gr.LoginButton()
                m1 = gr.Markdown()
                demo.load(verify_button, inputs=None, outputs=m1)
    
                def check_login_status():
                    try:
                        return login_button.get_session().get("oauth_info", None)
                    except AttributeError:
                        return None
            
                def check_login_wrapper():
                    session = check_login_status()
                    if session is None:
                        return "Not logged in."
                    else:
                        return f"Logged in as {session.get('username', 'Unknown')}"
            
                login_button.click(check_login_wrapper, inputs=None, outputs=m1)    
            #------------------------------------------------------------------------------
         
            #with gr.TabItem("📈 Hub-only leaderboard", elem_id="hub-table", id=2):
    except Exception as e:
        print(f"gradio demo Error: {e}")
demo.queue().launch()