import os import io import sys import asyncio import discord import aiohttp import pandas as pd import gradio as gr import logging from discord.ext import commands # Set up logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S" ) logger = logging.getLogger(__name__) DISCORD_TOKEN = os.environ.get("DISCORD_TOKEN", None) if not DISCORD_TOKEN: logger.error("DISCORD_TOKEN not set. Exiting.") sys.exit(1) intents = discord.Intents.all() bot = commands.Bot(command_prefix="!", intents=intents) # Cache guild/role globally GUILD_ID = 879548962464493619 ROLE_ID = 900063512829755413 LUNAR_ID = 811235357663297546 async def process_row(row, guild, role): hf_user_name = row["hf_user_name"] if pd.notna(hf_user_name) and hf_user_name.lower() != "n/a": discord_id = row["discord_user_id"].strip("L") try: member = guild.get_member(int(discord_id)) except Exception as e: logger.error(f"Error converting Discord ID {discord_id}: {e}") return if not member: return if role not in member.roles: try: await member.add_roles(role) logger.info(f"Role added to member: {member}") # Send confirmation to Lunar (optional) lunar = bot.get_user(LUNAR_ID) if lunar: try: await lunar.send(f"Verified role given to {member}!") except discord.Forbidden: logger.warning("Could not DM Lunar.") # DM the user (optional) try: await member.send( f"Verification successful! [{member} <---> {row['discord_user_name']}]" ) except discord.Forbidden: logger.warning(f"Cannot DM {member} — DMs likely off.") except Exception as e: logger.error(f"Error processing member {member}: {e}") async def give_verified_roles(): guild = bot.get_guild(GUILD_ID) role = guild.get_role(ROLE_ID) if not guild or not role: logger.error("Guild or role not found. Exiting loop.") return while True: try: async with aiohttp.ClientSession() as session: try: async with session.get( "https://docs.google.com/spreadsheets/d/1C8aLqgCqLYcMiIFf-P_Aosaa03C_WLIB_UyqvjSdWg8/export?format=csv&gid=0", timeout=10, ) as response: if response.status != 200: logger.error(f"Failed to fetch CSV: HTTP {response.status}") await asyncio.sleep(30) continue csv_data = await response.text() global_df = await asyncio.to_thread(pd.read_csv, io.StringIO(csv_data)) except asyncio.TimeoutError: logger.error("CSV fetch timed out.") await asyncio.sleep(30) continue except Exception as e: logger.error(f"Error fetching CSV: {e}") await asyncio.sleep(30) continue # Throttled loop to avoid hitting rate limits for _, row in global_df.iterrows(): await process_row(row, guild, role) await asyncio.sleep(1.5) # Delay between role assignments except Exception as e: logger.error(f"Error in give_verified_roles loop: {e}") await asyncio.sleep(30) @bot.event async def on_ready(): logger.info(f"We have logged in as {bot.user}") guild = bot.get_guild(GUILD_ID) if guild: await guild.chunk() # Cache members once on ready logger.info("Guild member cache initialized.") else: logger.warning("Guild not found on startup.") bot.loop.create_task(give_verified_roles()) bot.loop.create_task(heartbeat()) async def heartbeat(): while True: logger.info("Heartbeat: Bot is active.") await asyncio.sleep(60) # Gradio setup def greet(name): return "Hello " + name + "!" demo = gr.Interface(fn=greet, inputs="text", outputs="text") async def main(): gradio_thread = asyncio.to_thread(demo.launch, share=False) await asyncio.gather( bot.start(DISCORD_TOKEN), gradio_thread ) if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: logger.info("Shutting down...")