File size: 12,430 Bytes
78dea1a
db21dc3
434f798
db21dc3
b0918e1
78dea1a
b7eca85
 
ad9d692
8dd1108
676f51e
ad9d692
cbe51b9
 
c6565e2
b7eca85
4b7b722
56509cf
d3ba82c
 
 
09d6ac2
ad9d692
09d6ac2
 
b8997d2
 
df5822e
 
51315e7
d3ba82c
b7eca85
623cc98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51315e7
 
 
 
 
 
 
b7eca85
 
ad9d692
 
78dea1a
 
 
b7eca85
75d2f81
b7eca85
b80bf81
09d6ac2
b80bf81
d3ba82c
78dea1a
 
 
b7eca85
78dea1a
 
 
 
b7eca85
9177836
4ba6ad1
5d8559e
e57e72d
b91f995
623cc98
 
b3ba3b3
 
623cc98
 
 
b3ba3b3
623cc98
 
 
 
 
 
 
 
 
b3ba3b3
568a224
b80bf81
 
 
 
d3ba82c
b80bf81
 
d3ba82c
b0cdaf1
90c7959
c4bbe7e
51315e7
 
be053da
 
90c7959
51315e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e3fa325
e4e85e6
8fec8ef
e4e85e6
e4f9732
 
8c2219a
e4e85e6
 
e4f9732
e4e85e6
 
5d8559e
 
b80bf81
 
d3ba82c
df5822e
b80bf81
952362b
b0cdaf1
5021c90
952362b
d3ba82c
b80bf81
 
0654423
90c7959
37b0b26
 
6fca786
 
 
d589312
3c830fa
b80bf81
 
952362b
b80bf81
3c830fa
b80bf81
3c830fa
b80bf81
 
0654423
952362b
 
 
 
 
 
df083d0
 
 
952362b
 
 
 
4eb50e7
 
 
 
 
 
 
 
 
3a5f6f2
4eb50e7
952362b
761cff7
 
ca7a70a
761cff7
 
 
0022591
 
 
 
 
 
 
 
 
 
 
 
761cff7
 
0022591
 
 
687dc78
 
 
 
b0cdaf1
687dc78
 
 
 
 
 
0022591
 
 
 
 
 
 
 
 
 
 
ad9d692
 
cbe51b9
ad9d692
 
6c538e5
765d86a
 
b3ba3b3
5d8559e
765d86a
 
 
 
 
 
 
 
b3ba3b3
765d86a
5d8559e
 
765d86a
b0918e1
765d86a
 
 
b3ba3b3
c472ab1
765d86a
 
 
 
b0918e1
cbe51b9
ad9d692
 
 
cbe51b9
 
b3ba3b3
c6565e2
 
 
d4ce55b
c6565e2
 
 
db21dc3
 
 
b3ba3b3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
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()