lunarflu's picture
lunarflu HF staff
Update app.py
c4bbe7e verified
raw
history blame
12.4 kB
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
("dataset",): ["<@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()