arcade-slack 0.4.6__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arcade_slack/constants.py +7 -2
- arcade_slack/conversation_retrieval.py +74 -0
- arcade_slack/custom_types.py +4 -4
- arcade_slack/exceptions.py +0 -20
- arcade_slack/message_retrieval.py +76 -0
- arcade_slack/models.py +165 -1
- arcade_slack/tools/chat.py +675 -538
- arcade_slack/tools/users.py +59 -47
- arcade_slack/user_retrieval.py +214 -0
- arcade_slack/utils.py +238 -79
- {arcade_slack-0.4.6.dist-info → arcade_slack-0.5.1.dist-info}/METADATA +1 -1
- arcade_slack-0.5.1.dist-info/RECORD +17 -0
- arcade_slack-0.4.6.dist-info/RECORD +0 -14
- {arcade_slack-0.4.6.dist-info → arcade_slack-0.5.1.dist-info}/WHEEL +0 -0
- {arcade_slack-0.4.6.dist-info → arcade_slack-0.5.1.dist-info}/licenses/LICENSE +0 -0
arcade_slack/utils.py
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from collections.abc import Callable, Sequence
|
|
3
5
|
from datetime import datetime, timezone
|
|
4
|
-
from typing import Any
|
|
6
|
+
from typing import Any, cast
|
|
5
7
|
|
|
6
8
|
from arcade_tdk import ToolContext
|
|
7
9
|
from arcade_tdk.errors import RetryableToolError
|
|
8
10
|
|
|
9
|
-
from arcade_slack.constants import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
UsernameNotFoundError,
|
|
11
|
+
from arcade_slack.constants import (
|
|
12
|
+
MAX_CONCURRENT_REQUESTS,
|
|
13
|
+
MAX_PAGINATION_SIZE_LIMIT,
|
|
14
|
+
MAX_PAGINATION_TIMEOUT_SECONDS,
|
|
14
15
|
)
|
|
16
|
+
from arcade_slack.custom_types import SlackPaginationNextCursor
|
|
17
|
+
from arcade_slack.exceptions import PaginationTimeoutError
|
|
15
18
|
from arcade_slack.models import (
|
|
19
|
+
AbstractConcurrencySafeCoroutineCaller,
|
|
16
20
|
BasicUserInfo,
|
|
17
21
|
ConversationMetadata,
|
|
18
22
|
ConversationType,
|
|
19
23
|
ConversationTypeSlackName,
|
|
20
24
|
Message,
|
|
25
|
+
PaginationSentinel,
|
|
21
26
|
SlackConversation,
|
|
22
27
|
SlackConversationPurpose,
|
|
23
28
|
SlackMessage,
|
|
@@ -75,7 +80,7 @@ def remove_none_values(params: dict) -> dict:
|
|
|
75
80
|
return {k: v for k, v in params.items() if v is not None}
|
|
76
81
|
|
|
77
82
|
|
|
78
|
-
def get_slack_conversation_type_as_str(channel: SlackConversation) -> str:
|
|
83
|
+
def get_slack_conversation_type_as_str(channel: SlackConversation) -> str | None:
|
|
79
84
|
"""Get the type of conversation from a Slack channel's dictionary.
|
|
80
85
|
|
|
81
86
|
Args:
|
|
@@ -92,19 +97,7 @@ def get_slack_conversation_type_as_str(channel: SlackConversation) -> str:
|
|
|
92
97
|
return ConversationTypeSlackName.IM.value
|
|
93
98
|
if channel.get("is_mpim"):
|
|
94
99
|
return ConversationTypeSlackName.MPIM.value
|
|
95
|
-
raise ValueError(f"Invalid conversation type in channel {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def get_user_by_username(username: str, users_list: list[dict]) -> SlackUser:
|
|
99
|
-
usernames_found = []
|
|
100
|
-
for user in users_list:
|
|
101
|
-
if isinstance(user.get("name"), str):
|
|
102
|
-
usernames_found.append(user["name"])
|
|
103
|
-
username_found = user.get("name") or ""
|
|
104
|
-
if username.lower() == username_found.lower():
|
|
105
|
-
return SlackUser(**user)
|
|
106
|
-
|
|
107
|
-
raise UsernameNotFoundError(usernames_found=usernames_found, username_not_found=username)
|
|
100
|
+
raise ValueError(f"Invalid conversation type in channel: {json.dumps(channel)}")
|
|
108
101
|
|
|
109
102
|
|
|
110
103
|
def convert_conversation_type_to_slack_name(
|
|
@@ -191,62 +184,6 @@ def extract_basic_user_info(user_info: SlackUser) -> BasicUserInfo:
|
|
|
191
184
|
)
|
|
192
185
|
|
|
193
186
|
|
|
194
|
-
async def associate_members_of_multiple_conversations(
|
|
195
|
-
get_members_in_conversation_func: Callable,
|
|
196
|
-
conversations: list[dict],
|
|
197
|
-
context: ToolContext,
|
|
198
|
-
) -> list[dict]:
|
|
199
|
-
"""Associate members to each conversation, returning the updated list."""
|
|
200
|
-
return await asyncio.gather(*[ # type: ignore[no-any-return]
|
|
201
|
-
associate_members_of_conversation(get_members_in_conversation_func, context, conv)
|
|
202
|
-
for conv in conversations
|
|
203
|
-
])
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
async def associate_members_of_conversation(
|
|
207
|
-
get_members_in_conversation_func: Callable,
|
|
208
|
-
context: ToolContext,
|
|
209
|
-
conversation: dict,
|
|
210
|
-
) -> dict:
|
|
211
|
-
response = await get_members_in_conversation_func(context, conversation["id"])
|
|
212
|
-
conversation["members"] = response["members"]
|
|
213
|
-
return conversation
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
async def retrieve_conversations_by_user_ids(
|
|
217
|
-
list_conversations_func: Callable,
|
|
218
|
-
get_members_in_conversation_func: Callable,
|
|
219
|
-
context: ToolContext,
|
|
220
|
-
conversation_types: list[ConversationType],
|
|
221
|
-
user_ids: list[str],
|
|
222
|
-
exact_match: bool = False,
|
|
223
|
-
limit: int | None = None,
|
|
224
|
-
next_cursor: str | None = None,
|
|
225
|
-
) -> list[dict]:
|
|
226
|
-
"""
|
|
227
|
-
Retrieve conversations filtered by the given user IDs. Includes pagination support
|
|
228
|
-
and optionally limits the number of returned conversations.
|
|
229
|
-
"""
|
|
230
|
-
conversations_found: list[dict] = []
|
|
231
|
-
|
|
232
|
-
response = await list_conversations_func(
|
|
233
|
-
context=context,
|
|
234
|
-
conversation_types=conversation_types,
|
|
235
|
-
next_cursor=next_cursor,
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
# Associate members to each conversation
|
|
239
|
-
conversations_with_members = await associate_members_of_multiple_conversations(
|
|
240
|
-
get_members_in_conversation_func, response["conversations"], context
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
conversations_found.extend(
|
|
244
|
-
filter_conversations_by_user_ids(conversations_with_members, user_ids, exact_match)
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
return conversations_found[:limit]
|
|
248
|
-
|
|
249
|
-
|
|
250
187
|
def filter_conversations_by_user_ids(
|
|
251
188
|
conversations: list[dict],
|
|
252
189
|
user_ids: list[str],
|
|
@@ -317,6 +254,7 @@ async def async_paginate(
|
|
|
317
254
|
limit: int | None = None,
|
|
318
255
|
next_cursor: SlackPaginationNextCursor | None = None,
|
|
319
256
|
max_pagination_timeout_seconds: int = MAX_PAGINATION_TIMEOUT_SECONDS,
|
|
257
|
+
sentinel: PaginationSentinel | None = None,
|
|
320
258
|
*args: Any,
|
|
321
259
|
**kwargs: Any,
|
|
322
260
|
) -> tuple[list, SlackPaginationNextCursor | None]:
|
|
@@ -332,6 +270,10 @@ async def async_paginate(
|
|
|
332
270
|
not provided, the entire response dictionary is used.
|
|
333
271
|
limit: The maximum number of items to retrieve (defaults to Slack's suggested limit).
|
|
334
272
|
next_cursor: The cursor to use for pagination (optional).
|
|
273
|
+
max_pagination_timeout_seconds: The maximum timeout for the pagination loop (defaults to
|
|
274
|
+
MAX_PAGINATION_TIMEOUT_SECONDS).
|
|
275
|
+
sentinel: Control whether the pagination should continue after each iteration (optional).
|
|
276
|
+
If provided, the pagination will stop when the sentinel function returns True.
|
|
335
277
|
*args: Positional arguments to pass to the Slack method.
|
|
336
278
|
**kwargs: Keyword arguments to pass to the Slack method.
|
|
337
279
|
|
|
@@ -358,13 +300,18 @@ async def async_paginate(
|
|
|
358
300
|
response = await func(*args, **iteration_kwargs)
|
|
359
301
|
|
|
360
302
|
try:
|
|
361
|
-
|
|
303
|
+
result = dict(response.data) if not response_key else response[response_key]
|
|
304
|
+
results.extend(result)
|
|
362
305
|
except KeyError:
|
|
363
306
|
raise ValueError(f"Response key {response_key} not found in Slack response")
|
|
364
307
|
|
|
365
308
|
next_cursor = response.get("response_metadata", {}).get("next_cursor")
|
|
366
309
|
|
|
367
|
-
if (
|
|
310
|
+
if (
|
|
311
|
+
(sentinel and sentinel(last_result=result))
|
|
312
|
+
or (limit and len(results) >= limit)
|
|
313
|
+
or not next_cursor
|
|
314
|
+
):
|
|
368
315
|
should_continue = False
|
|
369
316
|
|
|
370
317
|
return results
|
|
@@ -445,3 +392,215 @@ def convert_relative_datetime_to_unix_timestamp(
|
|
|
445
392
|
days, hours, minutes = map(int, relative_datetime.split(":"))
|
|
446
393
|
seconds = days * 86400 + hours * 3600 + minutes * 60
|
|
447
394
|
return int(current_unix_timestamp - seconds)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def short_user_info(user: dict) -> dict[str, str | None]:
|
|
398
|
+
data = {"id": user.get("id")}
|
|
399
|
+
if user.get("name"):
|
|
400
|
+
data["name"] = user["name"]
|
|
401
|
+
if isinstance(user.get("profile"), dict) and user["profile"].get("email"):
|
|
402
|
+
data["email"] = user["profile"]["email"]
|
|
403
|
+
elif user.get("email"):
|
|
404
|
+
data["email"] = user["email"]
|
|
405
|
+
return data
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def short_human_users_info(users: list[dict]) -> list[dict[str, str | None]]:
|
|
409
|
+
return [short_user_info(user) for user in users if not user.get("is_bot")]
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def is_valid_email(email: str) -> bool:
|
|
413
|
+
"""Validate an email address using regex.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
email: The email address to validate.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
True if the email is valid, False otherwise.
|
|
420
|
+
"""
|
|
421
|
+
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
|
|
422
|
+
return bool(re.match(email_pattern, email))
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
async def build_multiple_users_retrieval_response(
|
|
426
|
+
context: ToolContext,
|
|
427
|
+
users_responses: list[dict[str, Any]],
|
|
428
|
+
) -> list[dict[str, Any]]:
|
|
429
|
+
"""Builds response list for the get_multiple_users_by_usernames_or_emails function."""
|
|
430
|
+
await raise_for_users_not_found(context, users_responses)
|
|
431
|
+
|
|
432
|
+
users = []
|
|
433
|
+
|
|
434
|
+
for users_response in users_responses:
|
|
435
|
+
users.extend(users_response["users"])
|
|
436
|
+
|
|
437
|
+
return cast(list[dict[str, Any]], users)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
async def raise_for_users_not_found(
|
|
441
|
+
context: ToolContext, users_responses: list[dict[str, Any]]
|
|
442
|
+
) -> None:
|
|
443
|
+
"""Raise an error if any user was not found in the responses."""
|
|
444
|
+
users_not_found, available_users = collect_users_not_found_in_responses(users_responses)
|
|
445
|
+
|
|
446
|
+
if users_not_found:
|
|
447
|
+
not_found_message = ", ".join(users_not_found)
|
|
448
|
+
s = "" if len(users_not_found) == 1 else "s"
|
|
449
|
+
message = f"User{s} not found: {not_found_message}"
|
|
450
|
+
available_users_prompt = await get_available_users_prompt(context, available_users)
|
|
451
|
+
|
|
452
|
+
raise RetryableToolError(
|
|
453
|
+
message=message,
|
|
454
|
+
developer_message=message,
|
|
455
|
+
additional_prompt_content=available_users_prompt,
|
|
456
|
+
retry_after_ms=500,
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def collect_users_not_found_in_responses(
|
|
461
|
+
responses: list[dict[str, Any]],
|
|
462
|
+
) -> tuple[list[str], list[dict[str, Any]]]:
|
|
463
|
+
users_not_found = []
|
|
464
|
+
available_users = []
|
|
465
|
+
|
|
466
|
+
for response in responses:
|
|
467
|
+
if response.get("not_found"):
|
|
468
|
+
users_not_found.extend(response["not_found"])
|
|
469
|
+
if response.get("available_users"):
|
|
470
|
+
available_users = response["available_users"]
|
|
471
|
+
|
|
472
|
+
return users_not_found, available_users
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
async def get_available_users_prompt(
|
|
476
|
+
context: ToolContext,
|
|
477
|
+
available_users: list[dict] | None = None,
|
|
478
|
+
limit: int = 100,
|
|
479
|
+
) -> str:
|
|
480
|
+
try:
|
|
481
|
+
from arcade_slack.tools.users import list_users # Avoid circular import
|
|
482
|
+
|
|
483
|
+
if isinstance(available_users, list) and available_users:
|
|
484
|
+
available_users = [
|
|
485
|
+
user for user in available_users if not is_user_a_bot(SlackUser(**user))
|
|
486
|
+
]
|
|
487
|
+
available_users_str = json.dumps(short_human_users_info(available_users))
|
|
488
|
+
next_cursor = None
|
|
489
|
+
potentially_more_users = True
|
|
490
|
+
else:
|
|
491
|
+
users = await list_users(context, limit=limit, exclude_bots=True)
|
|
492
|
+
next_cursor = users["next_cursor"]
|
|
493
|
+
available_users_str = json.dumps(short_human_users_info(users["users"]))
|
|
494
|
+
potentially_more_users = bool(next_cursor)
|
|
495
|
+
|
|
496
|
+
if not potentially_more_users:
|
|
497
|
+
return f"The users available are: {available_users_str}"
|
|
498
|
+
else:
|
|
499
|
+
msg = (
|
|
500
|
+
f"Some of the available users are: {available_users_str}. Potentially more users "
|
|
501
|
+
f"can be retrieved by calling the 'Slack.{list_users.__tool_name__}' tool"
|
|
502
|
+
)
|
|
503
|
+
if next_cursor:
|
|
504
|
+
msg += f" using the next cursor: '{next_cursor}' to continue pagination."
|
|
505
|
+
return msg
|
|
506
|
+
except Exception as e:
|
|
507
|
+
return (
|
|
508
|
+
"The tool tried to retrieve a list of available users, but failed with error: "
|
|
509
|
+
f"{type(e).__name__}: {e!s}. Use the 'Slack.{list_users.__tool_name__}' tool "
|
|
510
|
+
"to get a list of users."
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
async def gather_with_concurrency_limit(
|
|
515
|
+
coroutine_callers: Sequence[AbstractConcurrencySafeCoroutineCaller],
|
|
516
|
+
semaphore: asyncio.Semaphore | None = None,
|
|
517
|
+
max_concurrent_requests: int = MAX_CONCURRENT_REQUESTS,
|
|
518
|
+
) -> list[Any]:
|
|
519
|
+
if not semaphore:
|
|
520
|
+
semaphore = asyncio.Semaphore(max_concurrent_requests)
|
|
521
|
+
|
|
522
|
+
return await asyncio.gather(*[caller(semaphore) for caller in coroutine_callers]) # type: ignore[no-any-return]
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def cast_user_dict(user: dict[str, Any]) -> dict[str, Any]:
|
|
526
|
+
slack_user = SlackUser(**cast(dict, user))
|
|
527
|
+
return dict(**extract_basic_user_info(slack_user))
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
async def populate_users_in_messages(auth_token: str, messages: list[dict]) -> list[dict]:
|
|
531
|
+
if not messages:
|
|
532
|
+
return messages
|
|
533
|
+
|
|
534
|
+
users = await get_users_from_messages(auth_token, messages)
|
|
535
|
+
users_by_id = {user["id"]: {"id": user["id"], "name": user["name"]} for user in users}
|
|
536
|
+
|
|
537
|
+
for message in messages:
|
|
538
|
+
if message.get("type") != "message":
|
|
539
|
+
continue
|
|
540
|
+
|
|
541
|
+
# Message author
|
|
542
|
+
message["user"] = users_by_id.get(
|
|
543
|
+
message.get("user"), {"id": message["user"], "name": None}
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
# User mentions in the message text
|
|
547
|
+
text_mentions = re.findall(r"<@([A-Z0-9]+)>", message.get("text", ""))
|
|
548
|
+
for user_id in text_mentions:
|
|
549
|
+
if user_id in users_by_id:
|
|
550
|
+
user = users_by_id.get(user_id, {"id": user_id, "name": None})
|
|
551
|
+
name = user.get("name")
|
|
552
|
+
message["text"] = message["text"].replace(
|
|
553
|
+
f"<@{user_id}>", f"<@{name} (id:{user_id})>" if name else f"<@{user_id}>"
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
# User mentions in reactions
|
|
557
|
+
reactions = message.get("reactions")
|
|
558
|
+
if isinstance(reactions, list):
|
|
559
|
+
for reaction in reactions:
|
|
560
|
+
reaction_users = []
|
|
561
|
+
for user_id in reaction.get("users", []):
|
|
562
|
+
reaction_users.append(users_by_id.get(user_id, {"id": user_id, "name": None}))
|
|
563
|
+
reaction["users"] = reaction_users
|
|
564
|
+
|
|
565
|
+
return messages
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
async def get_users_from_messages(auth_token: str, messages: list[dict]) -> list[dict[str, Any]]:
|
|
569
|
+
if not messages:
|
|
570
|
+
return []
|
|
571
|
+
|
|
572
|
+
from arcade_slack.user_retrieval import get_users_by_id # Avoid circular import
|
|
573
|
+
|
|
574
|
+
user_ids = get_user_ids_from_messages(messages)
|
|
575
|
+
response = await get_users_by_id(auth_token, user_ids)
|
|
576
|
+
print("\n\n\nresponse:", response, "\n\n\n")
|
|
577
|
+
return response["users"]
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def get_user_ids_from_messages(messages: list[dict]) -> list[str]:
|
|
581
|
+
if not messages:
|
|
582
|
+
return []
|
|
583
|
+
|
|
584
|
+
user_ids = []
|
|
585
|
+
|
|
586
|
+
for message in messages:
|
|
587
|
+
if message.get("type") != "message":
|
|
588
|
+
continue
|
|
589
|
+
|
|
590
|
+
# Message author
|
|
591
|
+
user = message.get("user")
|
|
592
|
+
if isinstance(user, str) and user:
|
|
593
|
+
user_ids.append(user)
|
|
594
|
+
|
|
595
|
+
# User mentions in the message text
|
|
596
|
+
text = message.get("text")
|
|
597
|
+
if isinstance(text, str) and text:
|
|
598
|
+
user_ids.extend(re.findall(r"<@([A-Z0-9]+)>", text))
|
|
599
|
+
|
|
600
|
+
# User mentions in reactions
|
|
601
|
+
reactions = message.get("reactions")
|
|
602
|
+
if isinstance(reactions, list):
|
|
603
|
+
for reaction in reactions:
|
|
604
|
+
user_ids.extend(reaction.get("users", []))
|
|
605
|
+
|
|
606
|
+
return user_ids
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
arcade_slack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
arcade_slack/constants.py,sha256=ANAZOmNSqrbZHDqEvQfau7_91if9EeKV-G5D0VBB9vo,524
|
|
3
|
+
arcade_slack/conversation_retrieval.py,sha256=jsd64-IDpgTpuq-4Hbg1317VatxKnIy8edWhHB6bN7E,2338
|
|
4
|
+
arcade_slack/critics.py,sha256=Sx0nOmvym4LFNX1gZ34ndHkAKAB--X1zRPePcbiVzDo,1209
|
|
5
|
+
arcade_slack/custom_types.py,sha256=-23JnfXjMQ-H8CyRL3CEv9opPMySox1KINH5rrPIOt0,928
|
|
6
|
+
arcade_slack/exceptions.py,sha256=YQ4CTa1LbR-G7sjnQM8LB7LruxZDqPzvo-cptFYW7E8,385
|
|
7
|
+
arcade_slack/message_retrieval.py,sha256=XdzWLJdKtc5DL7H_qQBei6qJ-rz2uGs-Q0HfYqx7EG4,2496
|
|
8
|
+
arcade_slack/models.py,sha256=-zlkAxaDNEcIkQqePYTZXAqcRX4gO5jocJIixeWpK7A,12213
|
|
9
|
+
arcade_slack/user_retrieval.py,sha256=cdfzBbm2fsVNCztt2XbIV2UKuCdHaotfFBdo10EpBcE,6774
|
|
10
|
+
arcade_slack/utils.py,sha256=nKfvpeVmLeIun0y9poPG27jLBji2WMH4h1oWgemPIQ8,21438
|
|
11
|
+
arcade_slack/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
arcade_slack/tools/chat.py,sha256=KWDalEiOhay4Z71EB97dVllm3Uo7sXxjXFfmcpvuWYE,38125
|
|
13
|
+
arcade_slack/tools/users.py,sha256=Kw3MWNOwuUY17X5HGf3JMAH_mfloLRdRzXSBC7Zo7ug,4233
|
|
14
|
+
arcade_slack-0.5.1.dist-info/METADATA,sha256=t__oFJOZyq4HWgW6xFZhKzBmGVc8mm55_QSSCgrUPF4,953
|
|
15
|
+
arcade_slack-0.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
arcade_slack-0.5.1.dist-info/licenses/LICENSE,sha256=f4Q0XUZJ2MqZBO1XsqqHhuZfSs2ar1cZEJ45150zERo,1067
|
|
17
|
+
arcade_slack-0.5.1.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
arcade_slack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
arcade_slack/constants.py,sha256=txtPR4G0eYWXu1ABMtNXjH084CwHB6FsrQ1Z7MI43X0,363
|
|
3
|
-
arcade_slack/critics.py,sha256=Sx0nOmvym4LFNX1gZ34ndHkAKAB--X1zRPePcbiVzDo,1209
|
|
4
|
-
arcade_slack/custom_types.py,sha256=_dWh7QuCrBsHj3U-e1-P5gMYFaip_a44eYnpccKG7Yo,896
|
|
5
|
-
arcade_slack/exceptions.py,sha256=4vBwQN_sBRTb0WsbM-ohHrdpUENV70Bu2zzP35L6j0A,1031
|
|
6
|
-
arcade_slack/models.py,sha256=hEQZCaz7f9lnzbIAgXWZkt2lebTTUK7R1YvpBjeCbSI,6274
|
|
7
|
-
arcade_slack/utils.py,sha256=W6-SSe9SDaJfRMCj3b70SyYHjrxstLdRG9SNwj6XRjE,15679
|
|
8
|
-
arcade_slack/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
arcade_slack/tools/chat.py,sha256=nIBqRnAw64azVbB_ewpcsVyP9mk2_2_FMQk_YQFjnnk,35023
|
|
10
|
-
arcade_slack/tools/users.py,sha256=tdexdc-nBBEvSaYKgnvrjWsQrtwJnBjcJdaicNhSoPU,2984
|
|
11
|
-
arcade_slack-0.4.6.dist-info/METADATA,sha256=_NFliyW3f0X_wTQq3y5GZqXfLUDJHlfTAuxmExvh6Ww,953
|
|
12
|
-
arcade_slack-0.4.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
-
arcade_slack-0.4.6.dist-info/licenses/LICENSE,sha256=f4Q0XUZJ2MqZBO1XsqqHhuZfSs2ar1cZEJ45150zERo,1067
|
|
14
|
-
arcade_slack-0.4.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|