import discord import threading import os import gradio as gr import time from discord.ext import commands from slack_sdk import WebClient from slack_sdk.errors import SlackApiError import aiojobs import asyncio import re from datetime import datetime, timedelta from apscheduler.executors.pool import ThreadPoolExecutor from apscheduler.schedulers.background import BackgroundScheduler DISCORD_TOKEN = os.getenv('DISCORD_TOKEN') SLACK_BOT_TOKEN = os.getenv('BOT_USER_OAUTH_TOKEN_HF') # real = os.getenv('SLACK_CHANNEL_ID_HF') # test = 'C07B4KNU5BQ' SLACK_CHANNEL_ID = os.getenv('SLACK_CHANNEL_ID_HF') SLACK_CHANNEL_ID_TEST = 'C07B4KNU5BQ' # 1259415803879751700 = test forum # 1019883044724822016 = ask for help ASK_FOR_HELP_CHANNEL_ID = 1019883044724822016 GRADIO_CHANNEL_ID = 1025174734427656283 ARGILLA_HELP_CHANNEL_ID = 1253640751481356330 DATA_DISCUSSIONS_CHANNEL_ID = 1217179426002047076 GIVE_HF_FEEDBACK_CHANNEL_ID = 897391062975385640 TRIGGERS = { ("discord bot",): ["<@U051DB2754M>"], # adam ("autotrain",): ["<@U01E3LEC2N7>"], # abhishek ("auto train",): ["<@U01E3LEC2N7>"], # abhishek ("competition",): ["<@U01E3LEC2N7>"], # abhishek ("competitions",): ["<@U01E3LEC2N7>"], # abhishek ("text to speech",): ["<@U039C2GANMV>"], # VB ("tts",): ["<@U039C2GANMV>"], # VB ("asr",): ["<@U039C2GANMV>"], # VB ("musicgen",): ["<@U039C2GANMV>"], # VB ("whisper",): ["<@U039C2GANMV>"], # VB ("speech recognition",): ["<@U039C2GANMV>"], # VB ("bark",): ["<@U039C2GANMV>"], # VB ("sentence-transformers",): ["<@U04E4DNPWG7>"], # tom aarsen ("sentence_transformers",): ["<@U04E4DNPWG7>"], # tom aarsen ("setfit",): ["<@U04E4DNPWG7>"], # tom aarsen ("sentence transformers",): ["<@U04E4DNPWG7>"], # tom aarsen ("argilla",): ["<@U076B8C7G3E>", "<@U0766H30T7F>", "<@U076MF65WEM>", "<@U0765RENPNZ>", "<@U0768QEN0LA>"], # david berenstein, natalia elvira, sara han diaz lorenzo, Gabriel Martín Blázquez ("distilabel",): ["<@U076B8C7G3E>", "<@U076MF65WEM>", "<@U0765RENPNZ>", "<@U0768QEN0LA>", "<@U076271MBUN>"], # david berenstein, sara han diaz lorenzo, Gabriel Martín Blázquez, Agustín Piqueres ("docs",): ["<@U02DATT4C5B>"], # steven liu ("documentation",): ["<@U02DATT4C5B>"], # steven liu ("gradio",): ["<@U02NMK75F1V>", "<@U04FLGQ26PQ>"], # abubakar abid, yuvraj sharma ("dataset", "feedback"): ["<@U0768RCHCRY>"], # ben burtenshaw ("git ",): ["<@U07F1NP5U0K>"], # ann huang ("lfs",): ["<@U07F1NP5U0K>"], # ann huang ("xet",): ["<@U07F1NP5U0K>"], # ann huang ("upload",): ["<@U07F1NP5U0K>"], # ann huang ("download",): ["<@U07F1NP5U0K>"], # ann huang ("stream",): ["<@U07F1NP5U0K>"], # ann huang } daily_pings = [] intents = discord.Intents.all() intents.messages = True bot = commands.Bot(command_prefix='!', intents=intents) slack_client = WebClient(token=SLACK_BOT_TOKEN) thread_mapping = {} @bot.event async def on_ready(): print(f'Logged in as {bot.user}') @bot.event async def on_message(message): if message.author == bot.user: return # notification bot print("on_message") huggingfolks_role = discord.utils.get(message.guild.roles, id=897376942817419265) bots_role = discord.utils.get(message.guild.roles, id=1258328471609016341) if huggingfolks_role not in message.author.roles: # no need for ping if we're already discussing if bots_role not in message.author.roles: # bots shouldn't trigger pings for this print(" not bot ") content = message.content.lower() for trigger, mentions in TRIGGERS.items(): if all(word in content for word in trigger): adjacent_words = extract_adjacent_words(message.content, trigger) for slack_mention in mentions: daily_pings.append({ 'author': str(message.author), 'content': adjacent_words, 'channel': message.channel.name, 'url': message.jump_url, 'mention': slack_mention, 'trigger': trigger }) print(f"daily pings:{daily_pings}") # Check if the message is in a thread if isinstance(message.channel, discord.Thread): discord_thread_id = message.channel.id # Check if there's an existing Slack thread for this Discord thread # (the only Slack threads created should be for forum channel threads, not just any thread) if discord_thread_id in thread_mapping: slack_thread_ts = thread_mapping[discord_thread_id] # post to slack only if thread already exists post_to_slack_forum_version(message, SLACK_CHANNEL_ID, message.content, message.author, thread_ts=slack_thread_ts) if message.channel.id == GIVE_HF_FEEDBACK_CHANNEL_ID: post_to_slack_general(message, SLACK_CHANNEL_ID) await bot.process_commands(message) def post_to_slack_general(message, channel): text = f"New post in `#give-hf-feedback` by {message.author}: {message.content}" # Handle attachments if any if message.attachments: for attachment in message.attachments: attachment_url = attachment.url text += f"\nAttachment: {attachment_url}" try: response = slack_client.chat_postMessage( channel=channel, text=text, ) return response['ts'] except SlackApiError as e: print(f"Error posting to Slack: {e.response['error']}") return None def extract_adjacent_words(content, trigger): words = content.split() pattern = r'\s*\b'.join(map(re.escape, trigger)) regex = re.compile(pattern, re.IGNORECASE) match = regex.search(content) if match: start, end = match.span() before = content[:start].split()[-5:] after = content[end:].split()[:5] print("--------------------------------------------------------------") print('...' + ' '.join(before + [match.group()] + after) + '...') return '...' + ' '.join(before + [match.group()] + after) + '...' @bot.event async def on_thread_create(thread): # (discord) must be the child thread of the CORRECT forum channel(s) (not just any thread, or any forum channel) if isinstance(thread.parent, discord.ForumChannel) and thread.parent.id in {ASK_FOR_HELP_CHANNEL_ID, GRADIO_CHANNEL_ID, ARGILLA_HELP_CHANNEL_ID, DATA_DISCUSSIONS_CHANNEL_ID}: discord_thread_id = thread.id slack_thread_ts = post_to_slack_create_thread( SLACK_CHANNEL_ID, f"New forum thread started in {thread.parent.name} by {thread.owner}: *{thread.name}*\n" f"{thread.jump_url}" ) if slack_thread_ts: thread_mapping[discord_thread_id] = slack_thread_ts def post_to_slack_forum_version(message, channel, text, author, thread_ts=None): if message.attachments: for attachment in message.attachments: attachment_url = attachment.url text += f"\nAttachment: {attachment_url}" text = f"{author}" + ": " + text try: response = slack_client.chat_postMessage( channel=channel, text=text, thread_ts=thread_ts ) return response['ts'] # Return the Slack message timestamp (thread ID) except SlackApiError as e: print(f"Error posting to Slack: {e.response['error']}") return None def post_to_slack_create_thread(channel, text, thread_ts=None): try: response = slack_client.chat_postMessage( channel=channel, text=text, thread_ts=thread_ts, unfurl_links=False, unfurl_media=False ) return response['ts'] # Return the Slack message timestamp (thread ID) except SlackApiError as e: print(f"Error posting to Slack: {e.response['error']}") return None @bot.command() async def list_tags(ctx, forum_channel_id: int): if ctx.author.id == 811235357663297546: forum_channel = bot.get_channel(forum_channel_id) if isinstance(forum_channel, discord.ForumChannel): tags = forum_channel.available_tags tag_list = [f"{tag.name} (ID: {tag.id})" for tag in tags] await ctx.send(f'Available tags: {", ".join(tag_list)}') # react with ✅ on slack if marked with solved tag on discord SOLVED_TAG_IDS = {1026743978026094664, 1025179659215847575, 1263095032328753174, 1253641354312155208} @bot.event async def on_thread_update(before, after): if isinstance(after.parent, discord.ForumChannel) and after.parent.id in {ASK_FOR_HELP_CHANNEL_ID, GRADIO_CHANNEL_ID, ARGILLA_HELP_CHANNEL_ID, DATA_DISCUSSIONS_CHANNEL_ID}: before_tag_ids = {tag.id for tag in before.applied_tags} after_tag_ids = {tag.id for tag in after.applied_tags} added_tags = after_tag_ids - before_tag_ids removed_tags = before_tag_ids - after_tag_ids discord_thread_id = after.id if discord_thread_id in thread_mapping: slack_thread_ts = thread_mapping[discord_thread_id] if any(tag_id in SOLVED_TAG_IDS for tag_id in added_tags): react_to_slack_message(slack_thread_ts, 'white_check_mark') if any(tag_id in SOLVED_TAG_IDS for tag_id in removed_tags): unreact_to_slack_message(slack_thread_ts, 'white_check_mark') def react_to_slack_message(thread_ts, emoji): try: response = slack_client.reactions_add( channel=SLACK_CHANNEL_ID, name=emoji, timestamp=thread_ts ) except SlackApiError as e: print(f"Error reacting to Slack message: {e.response['error']}") def unreact_to_slack_message(thread_ts, emoji): try: response = slack_client.reactions_remove( channel=SLACK_CHANNEL_ID, name=emoji, timestamp=thread_ts ) except SlackApiError as e: print(f"Error removing reaction from Slack message: {e.response['error']}") #---------------------------------------------------------------------------------------------- def send_daily_pings(): global daily_pings if daily_pings: print(f"sending daily pings...{daily_pings}") pings_by_mention = {} # group pings by who they are meant to notify for ping in daily_pings: mention = ping['mention'] if mention not in pings_by_mention: pings_by_mention[mention] = [] pings_by_mention[mention].append(ping) # send each group of pings in a separate thread for mention, pings in pings_by_mention.items(): main_message = slack_client.chat_postMessage( channel=SLACK_CHANNEL_ID, text=f"DAILY PINGS FOR {mention} ON {datetime.now().strftime('%d/%m/%Y')}", unfurl_links=False, unfurl_media=False ) time.sleep(2) # https://api.slack.com/apis/rate-limits main_ts = main_message['ts'] for ping in pings: slack_client.chat_postMessage( channel=SLACK_CHANNEL_ID, text=f"(for the keyword -> '{ping['trigger']}')\nFrom {ping['author']} in channel #{ping['channel']}: {ping['content']}\n{ping['url']}", thread_ts=main_ts, unfurl_links=False, unfurl_media=False ) time.sleep(2) # https://api.slack.com/apis/rate-limits daily_pings = [] # reset after posting # pings ------------------------------------------------------------------------------------------- executor = ThreadPoolExecutor(max_workers=1) scheduler = BackgroundScheduler(executors={'default': executor}) scheduler.add_job(send_daily_pings, trigger='interval', days=1) scheduler.start() # runs discord bot in thread = helps avoid blocking calls def run_bot(): bot.run(DISCORD_TOKEN) threading.Thread(target=run_bot).start() def greet(name): return "Hello " + name + "!" demo = gr.Interface(fn=greet, inputs="text", outputs="text") demo.launch()