-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscript.py
334 lines (283 loc) · 12.2 KB
/
script.py
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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
from dotenv import load_dotenv
import os
import re
import slack_sdk
import discord
import requests
from datetime import datetime, timezone
from discord.ext import tasks
import io
import logging
# Logger settings
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler("debug.log"),
logging.StreamHandler()
]
)
load_dotenv('.env.local')
load_dotenv()
SLACK_TOKEN = os.getenv("SLACK_API_TOKEN")
SLACK_CHANNEL_ID = os.getenv("SLACK_CHANNEL_ID")
DISCORD_TOKEN = os.getenv("DISCORD_BOT_TOKEN")
DISCORD_CHANNEL_ID = int(os.getenv("DISCORD_CHANNEL_ID", 0))
# settings for Discord
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
# settings for Slack
slack_client = slack_sdk.WebClient(token=SLACK_TOKEN)
user_cache = {}
def get_user_info(user_id):
"""Get user information from the cache or Slack API."""
if user_id not in user_cache:
try:
response = slack_client.users_info(user=user_id)
if response['ok']:
user = response['user']
user_cache[user_id] = {
'name': user.get('real_name', 'Unknown'),
'avatar': user.get('profile', {}).get('image_192', '')
}
logging.info(f"User data fetched: {user_cache[user_id]}")
else:
user_cache[user_id] = {'name': 'Unknown', 'avatar': ''}
logging.warning(f"An exception while fetching user info: {user_id}")
except Exception as e:
logging.error(f"An exception while fetching user info: {user_id}: {e}")
user_cache[user_id] = {'name': 'Unknown', 'avatar': ''}
return user_cache[user_id]
def fetch_message_files(message):
"""Download files associated with the message."""
files = []
if 'files' in message:
for file in message['files']:
file_url = file.get('url_private')
if file_url:
try:
headers = {'Authorization': f'Bearer {SLACK_TOKEN}'}
response = requests.get(file_url, headers=headers)
if response.status_code == 200:
if file['mimetype'].startswith('image/'):
files.append({
'type': 'image',
'content': response.content,
'filename': file.get('name', 'image.jpg')
})
else:
files.append({
'type': 'file',
'url': file_url,
'filename': file.get('name', 'file')
})
logging.info(f"FIle downloaded: {file['name']}")
except Exception as e:
logging.error(f"An exception on downloading file {file_url}: {e}")
return files
def fetch_thread_replies(channel_id, thread_ts):
"""Get the replies in the thread for the message given."""
try:
response = slack_client.conversations_replies(
channel=channel_id,
ts=thread_ts
)
replies = response.get("messages", [])
logging.info(f"Downloaded {len(replies)} replies in thread for ts={thread_ts}.")
return replies
except Exception as e:
logging.error(f"An exception while fetching response from thread {thread_ts}: {e}")
return []
async def send_message_with_files(channel, message_text, files, parent_message=None):
"""
Send a message with attachments on Discord, with advanced thread handling.
Prevents adding main messages to threads.
"""
if not message_text:
return None
# Prepare image files
image_files = [
discord.File(io.BytesIO(f['content']), filename=f['filename'])
for f in files if f['type'] == 'image'
]
try:
# If there's a parent message, create a thread for replies
if parent_message:
# Ensure thread exists
if not parent_message.thread:
thread = await parent_message.create_thread(name="Slack Thread")
else:
thread = parent_message.thread
# Send reply in the thread
sent_message = await thread.send(
content=message_text,
files=image_files or None
)
logging.info(f"Message sent in thread: {message_text[:50]}")
# Send as a regular message if no parent message
else:
sent_message = await channel.send(
content=message_text,
files=image_files or None
)
logging.info(f"Main message sent: {message_text[:50]}")
# Handle additional files (non-image)
for file in [f for f in files if f['type'] == 'file']:
if parent_message and parent_message.thread:
await parent_message.thread.send(f"File: {file['url']}")
else:
await channel.send(f"File: {file['url']}")
logging.info(f"Send File: {file['url']}")
return sent_message
except Exception as e:
logging.error(f"Exception while sending message: {e}")
return None
def extract_message_content(message):
# Extracting the message content
try:
if 'text' in message and message['text']:
return message['text']
if 'blocks' in message and message['blocks']:
return process_slack_blocks(message['blocks'])
return "[NO MESSAGE]"
except Exception as e:
print(f"An exception while message parsing: {e}")
return "Failed to process message"
def format_messages(messages):
"""It formats messages from the Slack channel, taking into account threads."""
formatted_messages = {} # Map for main message storage
for message in messages:
if not message or not isinstance(message, dict):
continue
# Logging messages for debugging
logging.info(f"Processing message: {message.get('ts', 'no ts')}, user: {message.get('user', 'no user')}")
# Get user data and format message
user_info = get_user_info(message.get('user', ''))
timestamp = float(message.get('ts', 0))
date_time = datetime.fromtimestamp(timestamp, tz=timezone.utc)
# Extract message content
message_content = extract_message_content(message)
message_files = fetch_message_files(message)
formatted_message = (
f"**{user_info['name']}** - {date_time.strftime('%Y-%m-%d %H:%M:%S')}\n"
f"{message_content}"
)
if 'thread_ts' in message: # The message is part of a thread
parent_ts = message['thread_ts']
# Get replies in the thread if this is the first message
if parent_ts not in formatted_messages:
# Find the original message (not a reply)
original_message = next(
(msg for msg in messages if msg.get('ts') == parent_ts),
None
)
if original_message:
orig_user_info = get_user_info(original_message.get('user', ''))
orig_timestamp = float(original_message.get('ts', 0))
orig_date_time = datetime.fromtimestamp(orig_timestamp, tz=timezone.utc)
orig_content = extract_message_content(original_message)
orig_files = fetch_message_files(original_message)
formatted_original = (
f"**{orig_user_info['name']}** - {orig_date_time.strftime('%Y-%m-%d %H:%M:%S')}\n"
f"{orig_content}"
)
formatted_messages[parent_ts] = {
'text': formatted_original,
'files': orig_files,
'replies': []
}
replies = fetch_thread_replies(SLACK_CHANNEL_ID, parent_ts)
for reply in replies:
# Skip the original message when processing replies
if reply.get('ts') == parent_ts:
continue
reply_content = extract_message_content(reply)
reply_files = fetch_message_files(reply)
reply_user_info = get_user_info(reply.get('user', ''))
reply_timestamp = float(reply.get('ts', 0))
reply_date_time = datetime.fromtimestamp(reply_timestamp, tz=timezone.utc)
reply_formatted = (
f"**{reply_user_info['name']}** - {reply_date_time.strftime('%Y-%m-%d %H:%M:%S')}\n"
f"{reply_content}"
)
formatted_messages[parent_ts]['replies'].append({
'text': reply_formatted,
'files': reply_files
})
else: # Main message
ts = message.get('ts')
formatted_messages[ts] = {
'text': formatted_message,
'files': message_files,
'replies': []
}
return formatted_messages
@client.event
async def on_ready():
"""
Main function to process and send Slack messages to Discord.
Handles messages with and without threads.
"""
print(f"Logged in discord as {client.user}")
logging.info(f"Logged in discord as {client.user}")
today = datetime.now(timezone.utc).date()
try:
# Fetch messages from Slack
response = slack_client.conversations_history(
channel=SLACK_CHANNEL_ID,
oldest=datetime.combine(today, datetime.min.time()).timestamp(),
latest=datetime.combine(today, datetime.max.time()).timestamp(),
inclusive=True,
limit=1000
)
messages = response.get("messages", [])
logging.info(f"Downloaded {len(messages)} messages from Slack channel.")
# Format messages
formatted_messages = format_messages(messages)
if not formatted_messages:
logging.info("No formatted messages to send.")
return
# Get Discord channel
discord_channel = client.get_channel(DISCORD_CHANNEL_ID)
sent_messages = {} # Dictionary to store sent messages
# Process messages sorted by timestamp
for msg_ts, msg_data in sorted(formatted_messages.items(), key=lambda x: float(x[0])):
# Prepare image files for main message
image_files = [
discord.File(io.BytesIO(f['content']), filename=f['filename'])
for f in msg_data.get('files', []) if f['type'] == 'image'
]
# Check if message has replies (thread)
if 'replies' in msg_data and msg_data['replies']:
# Send main message
main_message = await discord_channel.send(
content=msg_data['text'],
files=image_files or None
)
sent_messages[msg_ts] = main_message
# Create thread and send replies
thread = await main_message.create_thread(name="Slack Thread")
for reply in msg_data['replies']:
reply_image_files = [
discord.File(io.BytesIO(f['content']), filename=f['filename'])
for f in reply.get('files', []) if f['type'] == 'image'
]
await thread.send(
content=reply['text'],
files=reply_image_files or None
)
else:
# Send regular message without thread
await discord_channel.send(
content=msg_data['text'],
files=image_files or None
)
logging.info("Messages were sent on Discord.")
except Exception as e:
logging.error(f"Error while downloading or sending messages: {e}")
import traceback
traceback.print_exc()
await client.close()
if __name__ == "__main__":
client.run(DISCORD_TOKEN)