lunarflu's picture
lunarflu HF staff
Update app.py
6a6eddc verified
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()