diff options
-rw-r--r-- | mock_data/slack_conversations_history_channel_public.json | 298 | ||||
-rw-r--r-- | mock_data/slack_conversations_history_im.json | 85 | ||||
-rwxr-xr-x | scripts/update_mocks.sh | 3 | ||||
-rw-r--r-- | slack/slack_api.py | 7 | ||||
-rw-r--r-- | slack/slack_conversation.py | 3 | ||||
-rw-r--r-- | slack/slack_message.py | 6 | ||||
-rw-r--r-- | typings/slack_api/slack_conversations_history.pyi | 153 |
7 files changed, 551 insertions, 4 deletions
diff --git a/mock_data/slack_conversations_history_channel_public.json b/mock_data/slack_conversations_history_channel_public.json new file mode 100644 index 0000000..15ef66a --- /dev/null +++ b/mock_data/slack_conversations_history_channel_public.json @@ -0,0 +1,298 @@ +{ + "ok": true, + "messages": [ + { + "type": "message", + "text": "d", + "user": "U7JNGMGEB", + "ts": "1673707855.715349", + "blocks": [ + { + "type": "rich_text", + "block_id": "AJO9", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "d" + } + ] + } + ] + } + ], + "team": "T0FC8BFQR" + }, + { + "client_msg_id": "e77ce1a6-325c-4987-af9c-924fddd79bd4", + "type": "message", + "text": "c", + "user": "U9NJX9J83", + "ts": "1672950604.204009", + "blocks": [ + { + "type": "rich_text", + "block_id": "WB=v", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "c" + } + ] + } + ] + } + ], + "team": "T0FC8BFQR", + "thread_ts": "1672950604.204009", + "reply_count": 3, + "reply_users_count": 2, + "latest_reply": "1673707723.647229", + "reply_users": [ + "U9NJX9J83", + "U7JNGMGEB" + ], + "is_locked": false, + "subscribed": true, + "last_read": "1673707723.647229" + }, + { + "client_msg_id": "e8ea92f9-ebaf-40ef-88b8-f1a829591bb1", + "type": "message", + "text": "b", + "user": "U9NJX9J83", + "ts": "1668031072.607969", + "blocks": [ + { + "type": "rich_text", + "block_id": "6Hyuz", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "b" + } + ] + } + ] + } + ], + "team": "T0FC8BFQR", + "thread_ts": "1668031072.607969", + "reply_count": 2, + "reply_users_count": 1, + "latest_reply": "1672950600.835739", + "reply_users": [ + "U9NJX9J83" + ], + "is_locked": false, + "subscribed": false + }, + { + "client_msg_id": "0101c0b1-f56d-41e3-a99a-071391071441", + "type": "message", + "text": "1", + "user": "U9NJX9J83", + "ts": "1667398172.542849", + "blocks": [ + { + "type": "rich_text", + "block_id": "Ce6", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "1" + } + ] + } + ] + } + ], + "team": "T0FC8BFQR", + "thread_ts": "1667398172.542849", + "reply_count": 5, + "reply_users_count": 1, + "latest_reply": "1672950594.369459", + "reply_users": [ + "U9NJX9J83" + ], + "is_locked": false, + "subscribed": true, + "last_read": "1672950594.369459" + }, + { + "type": "message", + "text": "a", + "user": "U7JNGMGEB", + "ts": "1667127924.150389", + "blocks": [ + { + "type": "rich_text", + "block_id": "03jR", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "a" + } + ] + } + ] + } + ], + "team": "T0FC8BFQR" + }, + { + "type": "message", + "text": "", + "files": [ + { + "id": "F049CK2SPAL", + "created": 1667059799, + "timestamp": 1667059811, + "name": "Some_post_title", + "title": "Some post title", + "mimetype": "application/vnd.slack-docs", + "filetype": "docs", + "pretty_type": "Arugula", + "user": "U9NJX9J83", + "user_team": "T0FC8BFQR", + "editable": true, + "size": 19, + "mode": "docs", + "is_external": false, + "external_type": "", + "is_public": true, + "public_url_shared": false, + "display_as_bot": false, + "username": "", + "url_private": "https://files.slack.com/files-pri/T0FC8BFQR-F049CK2SPAL/some_post_title", + "url_private_download": "https://files.slack.com/files-pri/T0FC8BFQR-F049CK2SPAL/download/some_post_title", + "permalink": "https://wee-slack-test.slack.com/files/T0FC8BFQR/F049CK2SPAL", + "permalink_public": "https://slack-files.com/T0FC8BFQR-F049CK2SPAL-b2c5d3567b", + "preview": "<p>Some content</p>", + "editor": null, + "last_editor": "U9NJX9J83", + "non_owner_editable": false, + "updated": 1667059811, + "is_starred": false, + "has_rich_preview": false, + "file_access": "visible", + "media_progress": null + } + ], + "upload": false, + "user": "U9NJX9J83", + "display_as_bot": false, + "ts": "1667059824.317919" + }, + { + "type": "message", + "text": "<https://vg.no>", + "user": "U7JNGMGEB", + "ts": "1667057550.980779", + "blocks": [ + { + "type": "rich_text", + "block_id": "CbQrF", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "link", + "url": "https://vg.no" + } + ] + } + ] + } + ], + "team": "T0FC8BFQR", + "attachments": [ + { + "from_url": "https://vg.no/", + "image_url": "https://1.vgc.no/vgnett-prod/img/vgLogoSquare.png?28042014-1", + "image_width": 476, + "image_height": 250, + "image_bytes": 8471, + "service_icon": "https://www.vg.no/gfx/icons/apple-touch-icon-114-precomposed.png", + "id": 1, + "original_url": "https://vg.no", + "fallback": "VG: Nyheter fra Norges mest leste nettavis – VG", + "text": "Norges største nettsted. Oppdateres minutt for minutt på siste nytt innen sport, innenriks, utenriks, og underholdning.", + "title": "Nyheter fra Norges mest leste nettavis – VG", + "title_link": "https://vg.no/", + "service_name": "VG" + } + ] + }, + { + "type": "message", + "text": "<https://github.com/golang/go/issues/45624>", + "user": "U7JNGMGEB", + "ts": "1667057502.831129", + "blocks": [ + { + "type": "rich_text", + "block_id": "VuZO", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "link", + "url": "https://github.com/golang/go/issues/45624" + } + ] + } + ] + } + ], + "team": "T0FC8BFQR", + "attachments": [ + { + "from_url": "https://github.com/golang/go/issues/45624", + "image_url": "https://opengraph.githubassets.com/b7dc26526af66d71ff9883293c16e50754be7a056dbc0b52ad734491ba60707a/golang/go/issues/45624", + "image_width": 500, + "image_height": 250, + "image_bytes": 98252, + "service_icon": "https://a.slack-edge.com/80588/img/unfurl_icons/github.png", + "id": 1, + "original_url": "https://github.com/golang/go/issues/45624", + "fallback": "GitHub: proposal: expression to create pointer to simple types · Issue #45624 · golang/go", + "text": "This notion was addressed in #9097, which was shut down rather summarily. Rather than reopen it, let me take another approach. When &S{} was added to the language as a way to construct a pointe...", + "title": "proposal: expression to create pointer to simple types · Issue #45624 · golang/go", + "title_link": "https://github.com/golang/go/issues/45624", + "service_name": "GitHub" + } + ], + "reactions": [ + { + "name": "+1", + "users": [ + "U7JNGMGEB" + ], + "count": 1 + } + ] + } + ], + "has_more": false, + "is_limited": true, + "pin_count": 0, + "channel_actions_ts": null, + "channel_actions_count": 0 +} diff --git a/mock_data/slack_conversations_history_im.json b/mock_data/slack_conversations_history_im.json new file mode 100644 index 0000000..83d5a42 --- /dev/null +++ b/mock_data/slack_conversations_history_im.json @@ -0,0 +1,85 @@ +{ + "ok": true, + "messages": [ + { + "type": "message", + "text": "<mailto:name@example.com|name@example.com>", + "user": "U7JNGMGEB", + "ts": "1585253989.000200", + "blocks": [ + { + "type": "rich_text", + "block_id": "plZ", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "link", + "url": "mailto:name@example.com", + "text": "name@example.com" + } + ] + } + ] + } + ], + "team": "T0FC8BFQR" + }, + { + "type": "message", + "text": "_italic", + "user": "U7JNGMGEB", + "ts": "1584106682.000100", + "blocks": [ + { + "type": "rich_text", + "block_id": "Moc9", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "_italic" + } + ] + } + ] + } + ], + "team": "T0FC8BFQR" + }, + { + "type": "message", + "subtype": "bot_remove", + "text": "removed an integration from this channel: <https://wee-slack-test.slack.com/services/BBG98PXA6|incoming-webhook>", + "user": "U7JNGMGEB", + "bot_id": "BBG98PXA6", + "bot_link": "<https://wee-slack-test.slack.com/services/BBG98PXA6|incoming-webhook>", + "ts": "1530267481.000186" + }, + { + "type": "message", + "subtype": "bot_add", + "text": "added an integration to this channel: <https://wee-slack-test.slack.com/services/BBG98PXA6|incoming-webhook>", + "user": "U7JNGMGEB", + "bot_id": "BBG98PXA6", + "bot_link": "<https://wee-slack-test.slack.com/services/BBG98PXA6|incoming-webhook>", + "ts": "1530267459.000121", + "reactions": [ + { + "name": "smile", + "users": [ + "U7JNGMGEB" + ], + "count": 1 + } + ] + } + ], + "has_more": false, + "pin_count": 0, + "channel_actions_ts": null, + "channel_actions_count": 0 +} diff --git a/scripts/update_mocks.sh b/scripts/update_mocks.sh index 18e9092..2ac5887 100755 --- a/scripts/update_mocks.sh +++ b/scripts/update_mocks.sh @@ -23,3 +23,6 @@ curl_slack "$api_base/conversations.info?channel=D9N2KD0V6" | jq . > mock_data/s curl_slack "$api_base/users.info?user=U017V7T2D40" | jq . > mock_data/slack_users_info_person.json curl_slack "$api_base/users.info?user=UU6635U31" | jq . > mock_data/slack_users_info_bot.json + +curl_slack "$api_base/conversations.history?channel=CK4M8EWJE" | jq . > mock_data/slack_conversations_history_channel_public.json +curl_slack "$api_base/conversations.history?channel=D7HHQR467" | jq . > mock_data/slack_conversations_history_im.json diff --git a/slack/slack_api.py b/slack/slack_api.py index ef522d0..3bd75c4 100644 --- a/slack/slack_api.py +++ b/slack/slack_api.py @@ -1,13 +1,14 @@ from __future__ import annotations import json -from typing import TYPE_CHECKING, Any, Dict, Union +from typing import TYPE_CHECKING, Dict, Union from urllib.parse import urlencode from slack.http import http_request from slack.shared import shared if TYPE_CHECKING: + from slack_api.slack_conversations_history import SlackConversationsHistoryResponse from slack_api.slack_conversations_info import SlackConversationsInfoResponse from slack_api.slack_users_conversations import SlackUsersConversationsResponse from slack_api.slack_users_info import SlackUsersInfoResponse @@ -53,7 +54,9 @@ class SlackApi: return response return response - async def fetch_conversations_history(self, conversation: SlackConversation) -> Any: + async def fetch_conversations_history( + self, conversation: SlackConversation + ) -> SlackConversationsHistoryResponse: return await self._fetch("conversations.history", {"channel": conversation.id}) async def fetch_conversations_info( diff --git a/slack/slack_conversation.py b/slack/slack_conversation.py index 21b953b..49b7a2a 100644 --- a/slack/slack_conversation.py +++ b/slack/slack_conversation.py @@ -84,6 +84,9 @@ class SlackConversation: self.history_pending = True history = await self.api.fetch_conversations_history(self) + if history["ok"] is False: + # TODO: Handle error + return start = time.time() messages = [SlackMessage(self, message) for message in history["messages"]] diff --git a/slack/slack_message.py b/slack/slack_message.py index 0a7d9d2..abfa403 100644 --- a/slack/slack_message.py +++ b/slack/slack_message.py @@ -1,17 +1,19 @@ from __future__ import annotations import re -from typing import TYPE_CHECKING, Any, List +from typing import TYPE_CHECKING, List from slack.task import gather if TYPE_CHECKING: + from slack_api.slack_conversations_history import SlackMessage as SlackMessageDict + from slack.slack_conversation import SlackConversation from slack.slack_workspace import SlackApi, SlackWorkspace class SlackMessage: - def __init__(self, conversation: SlackConversation, message_json: Any): + def __init__(self, conversation: SlackConversation, message_json: SlackMessageDict): self.conversation = conversation self.ts = message_json["ts"] self.message_json = message_json diff --git a/typings/slack_api/slack_conversations_history.pyi b/typings/slack_api/slack_conversations_history.pyi new file mode 100644 index 0000000..0c486ed --- /dev/null +++ b/typings/slack_api/slack_conversations_history.pyi @@ -0,0 +1,153 @@ +from __future__ import annotations + +from typing import List, Literal, NotRequired, TypedDict, final + +@final +class SlackMessageBlockElement(TypedDict): + type: str + url: NotRequired[str] + text: str + +@final +class SlackMessageBlockElementParent(TypedDict): + type: str + elements: List[SlackMessageBlockElement] + +@final +class SlackMessageBlock(TypedDict): + type: str + block_id: str + elements: List[SlackMessageBlockElementParent] + +@final +class SlackMessageAttachment(TypedDict): + from_url: str + image_url: str + image_width: int + image_height: int + image_bytes: int + service_icon: str + id: int + original_url: str + fallback: str + text: str + title: str + title_link: str + service_name: str + +@final +class SlackMessageReaction(TypedDict): + name: str + users: List[str] + count: int + +@final +class SlackMessageFile(TypedDict): + id: str + created: int + timestamp: int + name: str + title: str + mimetype: str + filetype: str + pretty_type: str + user: str + user_team: str + editable: bool + size: int + mode: str + is_external: bool + external_type: str + is_public: bool + public_url_shared: bool + display_as_bot: bool + username: str + url_private: str + url_private_download: str + permalink: str + permalink_public: str + preview: str + editor: None + last_editor: str + non_owner_editable: bool + updated: int + is_starred: bool + has_rich_preview: bool + file_access: str + media_progress: None + +class SlackMessageCommon(TypedDict): + type: Literal["message"] + text: str + user: str + ts: str + reactions: List[SlackMessageReaction] + +class SlackMessageStandard(SlackMessageCommon): + client_msg_id: NotRequired[str] + blocks: List[SlackMessageBlock] + attachments: List[SlackMessageAttachment] + team: str + +class SlackMessageThreadParentCommon(SlackMessageStandard): + thread_ts: str + reply_count: int + reply_users_count: int + latest_reply: str + reply_users: List[str] + is_locked: bool + +@final +class SlackMessageThreadParentNotSubscribed(SlackMessageThreadParentCommon): + subscribed: Literal[False] + +@final +class SlackMessageThreadParentSubscribed(SlackMessageThreadParentCommon): + subscribed: Literal[True] + last_read: str + +class SlackMessageWithFiles(SlackMessageCommon): + files: List[SlackMessageFile] + upload: bool + display_as_bot: bool + +# TODO: Add other subtypes +@final +class SlackMessageSubtypeBotRemove(SlackMessageCommon): + subtype: Literal["bot_remove"] + bot_id: str + bot_link: str + +@final +class SlackMessageSubtypeBotAdd(SlackMessageCommon): + subtype: Literal["bot_add"] + bot_id: str + bot_link: str + +@final +class SlackConversationsHistoryErrorResponse(TypedDict): + ok: Literal[False] + error: str + +SlackMessage = ( + SlackMessageStandard + | SlackMessageThreadParentNotSubscribed + | SlackMessageThreadParentSubscribed + | SlackMessageWithFiles + | SlackMessageSubtypeBotRemove + | SlackMessageSubtypeBotAdd +) + +@final +class SlackConversationsHistorySuccessResponse(TypedDict): + ok: Literal[True] + messages: List[SlackMessage] + has_more: bool + is_limited: NotRequired[bool] + pin_count: int + channel_actions_ts: None + channel_actions_count: int + +SlackConversationsHistoryResponse = ( + SlackConversationsHistorySuccessResponse | SlackConversationsHistoryErrorResponse +) |