import asyncio import csv import datetime import json import logging import os import os.path import random import re import threading import time import discord import gradio as gr import gradio_client import gspread import numpy as np import pandas as pd import requests import plotly.graph_objects as go from requests import HTTPError from apscheduler.executors.pool import ThreadPoolExecutor from apscheduler.schedulers.background import BackgroundScheduler from discord import Color, Embed from discord.ext import commands, tasks from gradio_client import Client from gspread_dataframe import get_as_dataframe, set_with_dataframe from gspread_formatting.dataframe import format_with_dataframe from huggingface_hub import HfApi, list_liked_repos, list_metrics, list_models from tabulate import tabulate from datetime import datetime, timedelta DISCORD_TOKEN = os.environ.get("DISCORD_TOKEN", None) intents = discord.Intents.all() bot = commands.Bot(command_prefix='!', intents=intents) """""" XP_PER_MESSAGE = 10 # 100k messages = 1M exp = lvl 100 """""" service_account = json.loads(os.environ.get('KEY')) file_path = 'service_account.json' with open(file_path, 'w') as json_file: json.dump(service_account, json_file) gspread_bot = gspread.service_account(filename='service_account.json') worksheet = gspread_bot.open("levelbot").sheet1 test_merge_worksheet = gspread_bot.open("test_merge").sheet1 """""" bot_ids = [1136614989411655780, 1166392942387265536, 1158038249835610123, 1130774761031610388, 1155489509518098565, 1155169841276260546, 1152238037355474964, 1154395078735953930] """""" api = HfApi() """""" global_df = pd.DataFrame() print(type(global_df)) community_global_df = pd.DataFrame() community_global_df_with_id = pd.DataFrame() community_global_df_gradio = pd.DataFrame() test_merge = pd.read_csv("https://docs.google.com/spreadsheets/d/1C8aLqgCqLYcMiIFf-P_Aosaa03C_WLIB_UyqvjSdWg8/export?format=csv&gid=0") @bot.event async def on_ready(): try: global global_df await asyncio.sleep(1.1) print(f'Logged in as {bot.user.name}') print(f"XP_PER_MESSAGE: {XP_PER_MESSAGE}") """import data from google sheets -> HF Space df (doesn't make API call this way, as it's read-only)""" global_df = test_merge print(f"csv successfully retrieved: \n {global_df}") # updates both leaderboards remove_huggingfolks.start() print("------------------------------------------------------------------------") except Exception as e: print(f"on_ready Error: {e}") def update_google_sheet(): """save data from HF Space -> google sheets (makes 1 API call)""" try: print("Updating google sheets...") print("------------------------------------------------------------------------") name = "test_merge_worksheet" set_with_dataframe(test_merge_worksheet, global_df) timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print("------------------------------------------------------------------------") print(f"Google sheet {name} {test_merge_worksheet} successfully updated at {timestamp}! \n{global_df}") print("------------------------------------------------------------------------") except Exception as e: print(f"update_google_sheet Error: {e}") #@tasks.loop(minutes=1) tasks.loop leads to heartbeat blocked issues (merging calculations too much with normal discord bot functions) def update_hub_stats(): try: global global_df print("Updating hub stats...") print("------------------------------------------------------------------------") for index, row in global_df.iterrows(): user = row['hf_user_name'] if pd.notna(user): #print(f"user: {user}") url = f"https://huggingface.co/api/users/{user}/overview" #print(f"url: {url}") response = requests.get(url) #print(f"response: {response}") if response.status_code == 200: data = response.json() #print(f"data: {data}") likes = data["numLikes"] models = data["numModels"] datasets = data["numDatasets"] spaces = data["numSpaces"] discussions = data["numDiscussions"] papers = data["numPapers"] upvotes = data["numUpvotes"] # recalculate level as well (no longer only depends on discord activity) try: global_df.loc[index, 'likes'] = likes global_df.loc[index, 'models'] = models global_df.loc[index, 'datasets'] = datasets global_df.loc[index, 'spaces'] = spaces global_df.loc[index, 'discussions'] = discussions global_df.loc[index, 'papers'] = papers global_df.loc[index, 'upvotes'] = upvotes total_hub_activity = likes + models + datasets + spaces + discussions + papers + upvotes total_hub_exp = total_hub_activity * 20 #2x better than discord total_hub_exp_string = "L" + str(total_hub_exp) + "L" # hub exp global_df.loc[index, 'hub_exp'] = total_hub_exp_string # total exp (discord + hub) discord_exp = row['discord_exp'] discord_exp_int = discord_exp.strip('L') total_exp = int(discord_exp_int) + int(total_hub_exp) total_exp_string = "L" + str(total_exp) + "L" global_df.loc[index, 'total_exp'] = total_exp_string # level level = calculate_level(total_exp) global_df.loc[index, 'discord_level'] = level except Exception as e: print(f"{e} error updating the dataframe") else: print(f"Failed to retrieve data for user {user}. Status code: {response.status_code}") except Exception as e: print(f"Failed to parse data for user {user}. {e}") timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print("------------------------------------------------------------------------") print(f"Hub stats successfully updated at {timestamp}! \n{global_df}") print("------------------------------------------------------------------------") executor = ThreadPoolExecutor(max_workers=2) scheduler = BackgroundScheduler(executors={'default': executor}) scheduler.add_job(update_google_sheet, trigger='interval', minutes=1, max_instances=2) scheduler.add_job(update_hub_stats, trigger='interval', minutes=1.5, max_instances=2) scheduler.start() def calculate_level(xp): return int(xp ** (1.0 / 3.0)) def calculate_xp(level): return (int(level ** 3)) async def add_exp(member_id): try: """Uses member_id to create new record or update old one [member_id_column] ... [member_exp_column]""" await asyncio.sleep(0.1) global global_df guild = bot.get_guild(879548962464493619) member = guild.get_member(member_id) # bot.get_user == User, guild.get_member == Member (not the same thing!) lvl1 = guild.get_role(1171861537699397733) lvl2 = guild.get_role(1171861595115245699) lvl3 = guild.get_role(1171861626715115591) lvl4 = guild.get_role(1171861657975259206) lvl5 = guild.get_role(1171861686580412497) lvl6 = guild.get_role(1171861900301172736) lvl7 = guild.get_role(1171861936258941018) lvl8 = guild.get_role(1171861968597024868) lvl9 = guild.get_role(1171862009982242836) lvl10 = guild.get_role(1164188093713223721) lvl11 = guild.get_role(1171524944354607104) lvl12 = guild.get_role(1171524990257082458) lvl13 = guild.get_role(1171525021928263791) lvl14 = guild.get_role(1171525062201966724) lvl15 = guild.get_role(1171525098465918996) lvl16 = guild.get_role(1176826165546201099) lvl17 = guild.get_role(1176826221301092392) lvl18 = guild.get_role(1176826260643659776) lvl19 = guild.get_role(1176826288816791693) lvl20 = guild.get_role(1176826319447801896) lvl21 = guild.get_role(1195030831174008902) lvl22 = guild.get_role(1195030883351150592) lvl23 = guild.get_role(1196055555006009445) lvl24 = guild.get_role(1196055640917938216) lvl25 = guild.get_role(1196055712506318869) lvl26 = guild.get_role(1196055775924195378) lvl27 = guild.get_role(1196055837018435664) lvl28 = guild.get_role(1196055908267081849) lvl29 = guild.get_role(1196055970804150352) lvl30 = guild.get_role(1196056027720847380) lvl31 = guild.get_role(1206542603261186078) lvl32 = guild.get_role(1206542673549205514) lvl33 = guild.get_role(1206542690939048007) lvl34 = guild.get_role(1206542707862806568) lvl35 = guild.get_role(1206542723633512468) lvl36 = guild.get_role(1206542738728681485) lvl37 = guild.get_role(1206542754625101866) lvl38 = guild.get_role(1206542771314364416) lvl39 = guild.get_role(1206542785973321758) lvl40 = guild.get_role(1206542802155208725) lvl41 = guild.get_role(1206568953221218354) lvl42 = guild.get_role(1206568979393413150) lvl43 = guild.get_role(1206568997374394368) lvl44 = guild.get_role(1206569014747463681) lvl45 = guild.get_role(1206569031650385921) lvl46 = guild.get_role(1206569047207182356) lvl47 = guild.get_role(1206569062851805254) lvl48 = guild.get_role(1206569077112315984) lvl49 = guild.get_role(1206569091826057306) lvl50 = guild.get_role(1206569107118493757) lvls = { 1: lvl1, 2: lvl2, 3: lvl3, 4: lvl4, 5: lvl5, 6: lvl6, 7: lvl7, 8: lvl8, 9: lvl9, 10: lvl10, 11: lvl11, 12: lvl12, 13: lvl13, 14: lvl14, 15: lvl15, 16: lvl16, 17: lvl17, 18: lvl18, 19: lvl19, 20: lvl20, 21: lvl21, 22: lvl22, 23: lvl23, 24: lvl24, 25: lvl25, 26: lvl26, 27: lvl27, 28: lvl28, 29: lvl29, 30: lvl30, 31: lvl31, 32: lvl32, 33: lvl33, 34: lvl34, 35: lvl35, 36: lvl36, 37: lvl37, 38: lvl38, 39: lvl39, 40: lvl40, 41: lvl41, 42: lvl42, 43: lvl43, 44: lvl44, 45: lvl45, 46: lvl46, 47: lvl47, 48: lvl48, 49: lvl49, 50: lvl50, } member_found = False print(f"Searching for member_id {member_id} in dataframe...") # discord_user_id column # iterate over items of first column (discord_user_id) for index, cell_value in global_df.iloc[:, 0].items(): # tldr; set_as_dataframe forces scientific notation which corrupts discord_user_id data. # set_as_dataframe is still highly efficient (1 API call), so we format numerical data as strings, # which results in efficient google sheet updating + data integrity cell_value = str(cell_value) if cell_value.startswith("L") and cell_value.endswith("L"): cell_value_clipped = cell_value[1:-1] if cell_value_clipped == str(member_id): # str(member_id) needed, it is int by default print("test4") # if found, update that row... member_found = True print(f"Record for {member} found at row {index + 1}, column 1") # increment the old experience value (better not to replace outright) old_xp = global_df.loc[index, 'discord_exp'] # remove L (write, so we replace) old_xp = str(old_xp) if old_xp.startswith("L") and old_xp.endswith("L"): old_xp = old_xp[1:-1] # str -> int temporarily for adding new_xp = int(old_xp) + XP_PER_MESSAGE total_exp = global_df.loc[index, 'total_exp'] hub_xp = global_df.loc[index, 'hub_exp'] total_exp = str(total_exp) hub_xp = str(hub_xp) if total_exp.startswith("L") and total_exp.endswith("L"): total_exp = total_exp[1:-1] if hub_xp.startswith("L") and hub_xp.endswith("L"): hub_xp = hub_xp[1:-1] # set old level; use this for more accurate logging and jumping multiple levels at once (for example, verifying) old_total_xp = int(total_exp) #old_level = calculate_level(old_total_xp) # check if hub exp not empty if hub_xp.strip(): total_exp = int(new_xp) + int(hub_xp) else: total_exp = int(new_xp) # total v current_level = calculate_level(total_exp) # convert back to string + google sheet proofing new_xp = str(new_xp) if not new_xp.startswith("L") and not new_xp.endswith("L"): new_xp = "L" + str(new_xp) + "L" global_df.loc[index, 'discord_exp'] = new_xp # do not change column name # after total_exp = str(total_exp) if not total_exp.startswith("L") and not total_exp.endswith("L"): total_exp = "L" + str(total_exp) + "L" # add back to dataframe in memory after checking redundantly; if total_exp.startswith("L") and total_exp.endswith("L"): print("test5") global_df.loc[index, 'total_exp'] = total_exp # do not change column name print(f"Record for {member} updated from {old_total_xp} to {global_df.loc[index, 'total_exp']} (+{XP_PER_MESSAGE}) ") # level up verified_role = guild.get_role(900063512829755413) print(f"Current_level for {member}: {current_level}") if current_level >= 2 and current_level <=50: print("test6") current_role = lvls[current_level] if current_role not in member.roles: # if we need to level up / update role print("test7") # finding leaderboard rank + excluding huggingfolks (still need exclusion) try: print("Calculating rank...") copy_df = global_df.copy() copy_df['discord_user_id'] = copy_df['discord_user_id'].str.strip('L').astype(str) copy_df['total_exp'] = copy_df['total_exp'].str.strip('L').astype(int) row = copy_df[copy_df['discord_user_id'] == str(member_id)] target_exp = row['total_exp'].values[0] rank = (copy_df['total_exp'] > target_exp).sum() + 1 print(f"The rank for {member} based on total_exp is: {rank}") except Exception as e: print(f"Discord ID {member} {member_id} not found in the DataFrame. {e}") rank = "πŸ€—" # if level 3 -> then send embed, remove some exp if current_level >= 3: # could change to 4 maybe if verified_role not in member.roles: # L12345L -> `12345` -> 12345 total_exp = total_exp[1:-1] total_exp = int(total_exp) if total_exp % 30 == 0: # staggers messages so we don't send one every time exp is earned # claim exp (-30 for level 3, but +100 as bonus exp. This scales infinitely until the member verifies, # so they can continue earning exp, it just won't translate to levels and the leaderboard. # This way they can claim at any time and get a big boost in levels! claim_exp = total_exp + 70 # send embed embed = Embed(color=Color.red()) embed.set_author(name=f"{member}", icon_url=member.avatar.url if member.avatar else bot.user.avatar.url) embed.title = f"⚠️Your account is not Verified! Unable to level up `πŸš€` -> `{current_level}` ❌" msg = f'πŸ€— Hey {member}! You can continue leveling up in the Hugging Face Discord server by Verifying your account, and claim `{claim_exp}` bonus exp points!' embed.description = f"{msg}" verification_link = "https://discord.com/channels/879548962464493619/900125909984624713" embed.add_field(name="Verify Here:", value=verification_link, inline=True) u_1 = "πŸ‘‘ Earn exp for activity on Discord and HF and climb the ⁠leaderboard !" u_2 = "🌎 Feature your content in weekly news and increase its visibility!" u_3 = "πŸš€ Early access to Beta features!" u_4 = "πŸ›‘οΈ Secure your progress, and restore if needed!" embed.add_field(name="You can Unlock:", value=f"{u_1}\n{u_2}\n{u_3}\n{u_4}", inline=True) embed.set_image(url='https://cdn.discordapp.com/attachments/1150399343912833024/1205537451242688573/download_1.png?ex=65d8bb3e&is=65c6463e&hm=042fe7dd3521887db0bd48eeb846de1cc7c75194f9e95215c23512ff61ea3475&') lunar = bot.get_user(811235357663297546) await member.send(embed=embed) await lunar.send(embed=embed) print("Sent verification cap embed to {member}") print("------------------------------------------------------------------------") return # increment the old level value (better to replace outright) # only increment level column if you are lvl2 or 3+ with verified role (this may make some members not appear) global_df.loc[index, 'discord_level'] = current_level # do not change column name # remove all level roles then add new role current_level_roles = [role for level, role in lvls.items() if role in member.roles] if current_level_roles: print(f"current_level_roles for {member}: {current_level_roles}") for removable_role in current_level_roles: await member.remove_roles(removable_role) print(f"Removed {removable_role} from {member}") removable_role_name = removable_role.name else: removable_role_name = "πŸš€" await member.add_roles(current_role) print(f"Level Up! Gave {member} {current_role}") if current_level == 2: # special embed with opt-out instructions for discord newcomers embed = Embed(color=Color.blue()) embed.set_author(name=f"{member}", icon_url=member.avatar.url if member.avatar else bot.user.avatar.url) embed.title = f"Level Up! `{removable_role_name}` -> `{current_level}`" msg = f'πŸ€— Congrats {member}! You just leveled up in the Hugging Face Discord server. To opt out of these notifications, you can right click -> "Block" me!' embed.description = f"{msg}." embed.add_field(name="Leaderboard Ranking:", value=f"πŸ‘‘ **{rank}**\n\nhttps://discord.com/channels/879548962464493619/1197143964994773023", inline=True) msg3 = "- Posting\n- Reacting / being reacted to\n- Being active on the Hugging Face Hub (verify to link your Hub + Discord accounts!)" embed.add_field(name="How to Level Up:", value=msg3, inline=True) verification_link = "https://discord.com/channels/879548962464493619/900125909984624713" embed.add_field(name="Verify Here:", value=verification_link, inline=True) lunar = bot.get_user(811235357663297546) await member.send(embed=embed) await lunar.send(embed=embed) print(f"Sent levelup embed to {member}") return if current_role in member.roles: # send embed embed = Embed(color=Color.blue()) embed.set_author(name=f"{member}", icon_url=member.avatar.url if member.avatar else bot.user.avatar.url) embed.title = f"Level Up! `{removable_role_name}` -> `{current_level}`" msg = f'πŸ€— Congrats {member}! You just leveled up in the Hugging Face Discord server' embed.description = f"{msg}." embed.add_field(name="Leaderboard Ranking:", value=f"πŸ‘‘ **{rank}**\n\nhttps://discord.com/channels/879548962464493619/1197143964994773023", inline=True) msg3 = "- Posting\n- Reacting / being reacted to\n- Being active on the Hugging Face Hub (verify to link your Hub + Discord accounts!)" embed.add_field(name="How to Level Up:", value=msg3, inline=True) verification_link = "https://discord.com/channels/879548962464493619/900125909984624713" embed.add_field(name="Verify Here:", value=verification_link, inline=True) lunar = bot.get_user(811235357663297546) await member.send(embed=embed) await lunar.send(embed=embed) print(f"Sent levelup embed to {member}") print("------------------------------------------------------------------------") if not member_found: # this only checks if discord_user_id (with L) is present in discord_user_id column, # if not, create new record print(f"Creating new record for {member}") xp = 10 # define somewhere else? current_level = calculate_level(xp) xp = str(xp) if not xp.startswith("L") and not xp.endswith("L"): xp = "L" + str(xp) + "L" member_id = str(member_id) if not member_id.startswith("L") and not member_id.endswith("L"): member_id = "L" + str(member_id) + "L" member_name = str(member.name) hf_user_name = "n/a" hub_exp = "L0L" total_exp = xp verified_date = "n/a" # need to initialize these when creating new record likes = 0 models = 0 datasets = 0 spaces = 0 discussions = 0 papers = 0 upvotes = 0 row_data = [member_id, member_name, xp, current_level, hf_user_name, hub_exp, total_exp, verified_date, likes, models, datasets, spaces, discussions, papers, upvotes] global_df.loc[len(global_df.index)] = row_data print("------------------------------------------------------------------------") except Exception as e: print(f"add_exp Error: {e}") @bot.event async def on_message(message): global global_df org_link = "https://huggingface.co/organizations/discord-community/share/wPKRAHYbAlaEaCxUxcqVyaaaeZcYagDvqc" invite_message = "Click to join our community org on the HF Hub!" try: if message.author.id not in bot_ids: # could change to if author does not have bot role (roleid) print(f"adding exp from message {message.author}") await asyncio.sleep(0.1) await add_exp(message.author.id) # add check for verification if message.content.find("!help") != -1: await message.channel.send( "To verify your πŸ€— account, message me '!auth ' using your API token found here: https://huggingface.co/settings/token" ) if message.content.startswith('!auth'): await asyncio.sleep(3) lunar = bot.get_user(811235357663297546) token = message.content.split()[-1].strip() try: user = HfApi().whoami(token) except HTTPError as e: await message.channel.send(f"Error occured when trying to authenticate. Likely invalid API Token. {e}") token = "abc" # reset right after we use to be safe if user['type'] == 'org': await message.channel.send( "Authentication failed because you tried to authenticate with an organization's API token. Please authenticate with your User API token instead." ) return try: server = bot.get_guild(879548962464493619) #role = discord.utils.get(server.roles, name="verified") role = server.get_role(900063512829755413) member = server.get_member(message.author.id) verified_date = datetime.now().strftime("%m/%d/%Y, %H:%M:%S") likes = 0 models = 0 datasets = 0 spaces = 0 discussions = 0 papers = 0 upvotes = 0 # important!----------------------------------------------------------------------------- # In order to add verification to a discord account, some important checks must ALL pass: # 1. Discord account should not already have verified role. # 2. hf_user_name should not already exist in the dataframe. # -----> If it does, it means we are trying to link 1 HF account to multiple discord accounts. 1->many is disallowed. # check if the member has the verified role (prevent duplicate entries in google sheet) # if users want to change discord / HF accounts, we can deal with that manually if role in member.roles: await message.channel.send(f"{member} already has has the '{role}' role and is already verified! To change discord accounts or HF accounts, contact <@811235357663297546> or adam@huggingface.co") return # check if hf_user_name in dataframe: if user['name'] in global_df['hf_user_name'].values: await message.channel.send(f"The HF account {user['name']} is already verified! To change discord accounts or HF accounts, contact <@811235357663297546> or adam@huggingface.co") return # check if discord_user_id in dataframe: altered_member_id = "L" + str(member.id) + "L" if altered_member_id in global_df['discord_user_id'].values: hf_user_name = global_df.loc[global_df['discord_user_id'] == altered_member_id, 'hf_user_name'].iloc[0] if pd.isnull(hf_user_name) or hf_user_name == 'n/a': # empty (no link created yet between discord_user_id and hf_user_name) so we can update global_df.loc[global_df['discord_user_id'] == altered_member_id, 'hf_user_name'] = user['name'] global_df.loc[global_df['discord_user_id'] == altered_member_id, 'verified_date'] = verified_date await member.add_roles(role) if role in member.roles: print(f"Updated hf_user_name for id {member.id} | discord_user_name {member} | hf_user_name {user['name']}") await message.channel.send(f"Verification successful! [{member} <---> {user['name']}] πŸ€— {org_link} {invite_message}") await lunar.send(f"Verification successful! [{member} <---> {user['name']}] \nπŸ€— {org_link} {invite_message}") print(f"Verification successful! [{member} <---> {user['name']}] πŸ€—") return else: await message.channel.send(f"The Discord account {member} is already verified! To change discord accounts or HF accounts, contact <@811235357663297546> or adam@huggingface.co") await lunar.send(f"The Discord account {member} is already verified! To change discord accounts or HF accounts, contact <@811235357663297546> or adam@huggingface.co") print(f"The Discord account {member} is already verified! To change discord accounts or HF accounts, contact <@811235357663297546> or adam@huggingface.co") return except Exception as e: print(f"verification Error: {e}") # ----------------------------------------------------------------------------------------- # If creating new record (edge case) xp = 10 discord_exp = "L10L" hub_exp = "L0L" total_exp = "L10L" current_level = calculate_level(xp) row_data = [altered_member_id, member.name, discord_exp, current_level, user['name'], "L0L", hub_exp, total_exp, verified_date, likes, models, datasets, spaces, discussions, papers, upvotes] global_df.loc[len(global_df.index)] = row_data await member.add_roles(role) if role in member.roles: print(f"New record created for {member}") print("------------------------------------------------------------------------") await bot.process_commands(message) except Exception as e: print(f"on_message Error: {e}") @bot.event async def on_reaction_add(reaction, user): try: if user.id not in bot_ids: print(f"adding exp from react {user}") await asyncio.sleep(0.1) if not isinstance(reaction.message.channel, discord.DMChannel): # can't earn exp from reacting in bot DMs, which is harder to track await add_exp(user.id) await asyncio.sleep(0.1) if reaction.message.author.id != user.id: # can't earn while self-reacting, which is abuseable await add_exp(reaction.message.author.id) except Exception as e: print(f"on_reaction_add Error: {e}") def format_hyperlink(username): return f'{username}' @tasks.loop(minutes=1) async def remove_huggingfolks(): try: # remove huggingfolks global community_global_df global community_global_df_with_id global community_global_df_gradio community_global_df = global_df.copy() guild = bot.get_guild(879548962464493619) role = discord.utils.get(guild.roles, id=897376942817419265) members_with_role = [member.id for member in guild.members if role in member.roles] # remove L formatting (doesn't affect main global_df) community_global_df['discord_user_id'] = community_global_df['discord_user_id'].str.strip('L').astype(str) for member_id in members_with_role: community_global_df = community_global_df[community_global_df.iloc[:, 0] != str(member_id)] # make a copy while discord id column still exists -> use for rank in discord embeds community_global_df_with_id = community_global_df_with_id.copy() # reorder (switching from discord_user_name to hf_user_name) reorder = ['discord_user_id', 'hf_user_name', 'discord_exp','discord_level', 'discord_user_name', 'hub_exp', 'total_exp', 'verified_date'] community_global_df = community_global_df[reorder] # drop first column (discord id -> this is so we can display the important stuff in the leaderboard) community_global_df.drop(community_global_df.columns[0], axis=1, inplace=True) community_global_df.drop(community_global_df.columns[1], axis=1, inplace=True) community_global_df.drop(community_global_df.columns[2], axis=1, inplace=True) community_global_df.drop(community_global_df.columns[2], axis=1, inplace=True) community_global_df.drop(community_global_df.columns[3], axis=1, inplace=True) # drop rows (records) where hf_user_name does not exist (we display only verified users) community_global_df = community_global_df.dropna(subset=['hf_user_name']) community_global_df['total_exp'] = community_global_df['total_exp'].str.strip('L').astype(int) community_global_df['total_exp'] = pd.to_numeric(community_global_df['total_exp'], errors='coerce').fillna(0).astype(int) community_global_df = community_global_df.nlargest(len(community_global_df), 'total_exp') # add profile hyperlinks to gradio demo community_global_df_gradio = community_global_df.copy() # must be a COPY, otherwise it adds a new name for the same dataframe. community_global_df_gradio['hf_user_name'] = community_global_df_gradio['hf_user_name'].apply(format_hyperlink) top_num = 30 top_num_exp = community_global_df.nlargest(top_num, 'total_exp') top_num_exp['D'] = ['πŸ₯‡','πŸ₯ˆ','πŸ₯‰','','','','','','','','','','','','','','','','','','','','','','','','','','',''] top_num_rows = top_num_exp.values.tolist() #print(top_30_rows) channel = bot.get_channel(1197143964994773023) message = await channel.fetch_message(1197148293164187678) # put into message / leaderboard new_table = tabulate(top_num_rows, headers=["Name", "Level", "Experience", "Rank"], tablefmt="plain") await message.edit(content=f"Updated Leaderboard:\n```\n{new_table}\n```") print("Updated discord leaderboard!") print("------------------------------------------------------------------------") except Exception as e: print(f"remove_huggingfolks Error: {e}") @bot.command(name='xp_help') async def xp_help(ctx): try: help_message = "How to earn Discord / Hub exp: Post messages, react, Like, discuss, create repos and papers" await ctx.author.send(help_message) except Exception as e: print(f"xp_help Error: {e}") @bot.command() async def count_users_with_role(ctx, role_id): role = discord.utils.get(ctx.guild.roles, id=int(role_id)) count = sum(1 for member in ctx.guild.members if role in member.roles) await ctx.send(f"Number of users with the role '{role.name}': {count}") @bot.command() async def send_hub_org_dm_retroactive(ctx, role_id): if ctx.author.id == 811235357663297546: lunar = bot.get_user(811235357663297546) role = discord.utils.get(ctx.guild.roles, id=int(role_id)) # for every person with this role # dm them message with link if role: for member in role.members: try: dm_message = f"Hey {member}! You've just been invited to join the Hugging Face Discord Community org! πŸ€—\n If you're interested in collaborating on open source projects, hanging out with the community or just enjoying your new badge, we're happy to have you! ❀️ https://huggingface.co/organizations/discord-community/share/wPKRAHYbAlaEaCxUxcqVyaaaeZcYagDvqc" await member.send(dm_message) await lunar.send(dm_message) await asyncio.sleep(5) except discord.Forbidden: print(f"Could not DM {member.name}") await lunar.send(f"Could not DM {member.name} a Hugging Face Org invite link") DISCORD_TOKEN = os.environ.get("DISCORD_TOKEN", None) def run_bot(): bot.run(DISCORD_TOKEN) threading.Thread(target=run_bot).start() def get_data(): try: return community_global_df_gradio except Exception as e: print(f"get_data Error: {e}") def get_data2(): try: display_data = { 'πŸ€— Hub (+30 exp)': ['Creating Repos', 'Papers', 'Likes/Upvotes', 'Discussions'], 'Discord (+10 exp)': ['Posting messages', 'Reacting', '', ''] } display_df = pd.DataFrame(display_data) return display_df except Exception as e: print(f"get_data2 Error: {e}") #------------------------------------------------------------------------------------------------------------------------------- # year->get all dates def get_dates_in_year(year): start_date = datetime(year, 1, 1) end_date = datetime(year + 1, 1, 1) delta = end_date - start_date return [start_date + timedelta(days=i) for i in range(delta.days)] # HF API->get activity def get_user_likes(username): url = f"https://huggingface.co/api/users/{username}/likes" response = requests.get(url) if response.status_code == 200: return response.json() else: print(f"Failed to fetch data: {response.status_code}") return [] # data->get dates def parse_activity_dates(activity_data): activity_dates = [] for item in activity_data: timestamp = item.get('createdAt') if timestamp: date = datetime.fromisoformat(timestamp[:-1]).date() activity_dates.append(date) return activity_dates def create_plot(username, year): dates_in_year = get_dates_in_year(year) dates_in_year = [d.date() for d in dates_in_year] activity_data = get_user_likes(username) activity_dates = parse_activity_dates(activity_data) activity_count = {date: 0 for date in dates_in_year} for date in activity_dates: if date in activity_count: activity_count[date] += 1 total_activities = sum(activity_count.values()) # activity->color def get_color(activity_count): if activity_count == 0: return 'white' elif activity_count < 5: return 'lightgreen' elif activity_count < 10: return 'green' else: return 'darkgreen' # data->Plotly num_weeks = len(dates_in_year) // 7 + (1 if len(dates_in_year) % 7 != 0 else 0) z = [[None for _ in range(num_weeks)] for _ in range(7)] hover_texts = [["" for _ in range(num_weeks)] for _ in range(7)] for idx, date in enumerate(dates_in_year): week = idx // 7 day = idx % 7 z[day][week] = activity_count[date] hover_texts[day][week] = f"{date}: {activity_count[date]} activities" # month labels month_labels = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"] month_positions = [0] # JAN for month in range(2, 13): first_day_of_month = datetime(year, month, 1).date() month_position = (first_day_of_month - datetime(year, 1, 1).date()).days // 7 month_positions.append(month_position) # heatmap fig = go.Figure(data=go.Heatmap( z=z, x=list(range(num_weeks)), y=list(range(7)), hoverinfo='text', text=hover_texts, showscale=False, colorscale=[ [0, 'white'], [0.25, 'lightgreen'], [0.5, 'green'], [1, 'darkgreen'] ], zmin=0, zmax=20 )) # lock the plot size and ensure cells are square cell_size = 20 fig.update_layout( title=f'{username} Activity in {year} | Total Contributions: {total_activities}', xaxis_nticks=52, yaxis_nticks=7, xaxis_title=' ', yaxis_title=' ', yaxis=dict( tickmode='array', tickvals=list(range(7)), ticktext=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], scaleanchor="x", # This ensures that the cells are square scaleratio=1 ), xaxis=dict( tickmode='array', tickvals=month_positions, ticktext=month_labels, scaleanchor="y", scaleratio=1, fixedrange=True, # disable zoom and pan ), autosize=False, width=num_weeks * cell_size, height=14 * cell_size, # halve height of each cell margin=dict( l=20, r=20, b=20, t=50, pad=2 ) ) fig.update_layout( xaxis=dict(fixedrange=True), # disable zoom and pan yaxis=dict(fixedrange=True) # disable zoom and pan ) return fig #------------------------------------------------------------------------------------------------------------------------------- demo = gr.Blocks() with demo: try: dataframe1 = pd.read_csv("https://docs.google.com/spreadsheets/d/1C8aLqgCqLYcMiIFf-P_Aosaa03C_WLIB_UyqvjSdWg8/export?format=csv&gid=0") # required for timing print(dataframe1) level_counts = dataframe1['discord_level'].value_counts().sort_index() dataframe2 = pd.DataFrame({ 'Levels': level_counts.index, 'Members': level_counts.values }) print(dataframe2) TITLE = """

πŸ€— Hugging Face Level Leaderboard

""" gr.HTML(TITLE) with gr.Tabs(elem_classes="tab-buttons") as tabs: with gr.TabItem("πŸ… Level leaderboard", elem_id="level-table", id=0): #gr.Markdown("# πŸ“ˆ Experience Leaderboard") with gr.Row(): with gr.Column(): gr.DataFrame(get_data, every=5, height=500, datatype=["markdown"], interactive=False, col_count=(3, "fixed"), column_widths=["100px","100px","100px"]) with gr.Column(): gr.BarPlot( value=dataframe2, x="Levels", y="Members", title="Level Distribution", height=450, width=450, interactive=False ) with gr.Row(): gr.Markdown("# πŸ“ˆ How to earn Experience!") with gr.Row(): gr.DataFrame(get_data2, every=5, interactive=False) with gr.TabItem("πŸ“ˆ Activity Heatmap", elem_id="activity-heatmap", id=1): with gr.Row(): username_input = gr.Textbox(label="Enter Username") with gr.Row(): year_buttons = [gr.Button(str(year)) for year in range(2021, 2025)] with gr.Row(): plot = gr.Plot(label="Activity Heatmap") # buttons for updating the plot for button in year_buttons: button.click( fn=create_plot, inputs=[username_input, gr.State(int(button.value))], outputs=plot ) #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()