aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTrygve Aaberge <trygveaa@gmail.com>2024-01-18 22:45:03 +0100
committerTrygve Aaberge <trygveaa@gmail.com>2024-02-18 12:56:59 +0100
commit15cabf8ba1b9daa7e28475ea46cc5a8fd478e174 (patch)
treef65e557a115b0f83d13cb6d4b9943ddbd273268c
parentbb80ee68763233930a6f4169b07a029cd99b589d (diff)
downloadwee-slack-15cabf8ba1b9daa7e28475ea46cc5a8fd478e174.tar.gz
Support files in Slack Connect channels
Files uploaded in Slack Connect channels doesn't contain any information, so we have to make a request to files.info in order to get the information to render it. See https://api.slack.com/apis/channels-between-orgs#check_file_info for more info.
-rw-r--r--slack/slack_api.py9
-rw-r--r--slack/slack_message.py110
-rw-r--r--tests/test_render_attachments.py1
-rw-r--r--typings/slack_api/slack_conversations_history.pyi38
-rw-r--r--typings/slack_api/slack_files_info.pyi57
5 files changed, 131 insertions, 84 deletions
diff --git a/slack/slack_api.py b/slack/slack_api.py
index c54268f..1faf4a2 100644
--- a/slack/slack_api.py
+++ b/slack/slack_api.py
@@ -29,6 +29,7 @@ if TYPE_CHECKING:
from slack_api.slack_conversations_members import SlackConversationsMembersResponse
from slack_api.slack_conversations_replies import SlackConversationsRepliesResponse
from slack_api.slack_emoji import SlackEmojiListResponse
+ from slack_api.slack_files_info import SlackFilesInfoResponse
from slack_api.slack_profile import SlackSetProfile, SlackUsersProfileSetResponse
from slack_api.slack_rtm_connect import SlackRtmConnectResponse
from slack_api.slack_team_info import SlackTeamInfoResponse
@@ -321,6 +322,14 @@ class SlackApi(SlackApiCommon):
raise SlackApiError(self.workspace, method, response)
return response
+ async def fetch_files_info(self, file_id: str):
+ method = "files.info"
+ params: Params = {"file": file_id}
+ response: SlackFilesInfoResponse = await self._fetch(method, params)
+ if response["ok"] is False:
+ raise SlackApiError(self.workspace, method, response)
+ return response
+
async def fetch_emoji_list(self):
method = "emoji.list"
response: SlackEmojiListResponse = await self._fetch(method)
diff --git a/slack/slack_message.py b/slack/slack_message.py
index 3e1ea20..576629c 100644
--- a/slack/slack_message.py
+++ b/slack/slack_message.py
@@ -38,10 +38,10 @@ if TYPE_CHECKING:
SlackMessageBlockRichTextElement,
SlackMessageBlockRichTextList,
SlackMessageBlockRichTextSection,
- SlackMessageFile,
SlackMessageReaction,
SlackMessageSubtypeHuddleThreadRoom,
)
+ from slack_api.slack_files_info import SlackFile
from slack_rtm.slack_rtm_message import SlackThreadSubscription
from typing_extensions import Literal, assert_never
@@ -184,19 +184,21 @@ class PendingMessageItem:
self,
message: SlackMessage,
item_type: Literal[
- "conversation", "user", "usergroup", "broadcast", "message_nick"
+ "conversation", "user", "usergroup", "broadcast", "message_nick", "file"
],
item_id: str,
display_type: Literal["mention", "chat"] = "mention",
fallback_name: Optional[str] = None,
+ file: Optional[SlackFile] = None,
):
self.message = message
self.item_type: Literal[
- "conversation", "user", "usergroup", "broadcast", "message_nick"
+ "conversation", "user", "usergroup", "broadcast", "message_nick", "file"
] = item_type
self.item_id = item_id
self.display_type: Literal["mention", "chat"] = display_type
self.fallback_name = fallback_name
+ self.file = file
def __repr__(self):
return f"{self.__class__.__name__}({self.message}, {self.item_type}, {self.item_id}, {self.display_type})"
@@ -284,6 +286,49 @@ class PendingMessageItem:
nick = await self.message.nick()
return nick.format(colorize=True)
+ elif self.item_type == "file":
+ if self.file is None or self.file.get("file_access") == "check_file_info":
+ file_response = await self.message.workspace.api.fetch_files_info(
+ self.item_id
+ )
+ file = file_response["file"]
+ else:
+ file = self.file
+
+ if file.get("mode") == "tombstone":
+ return with_color(
+ shared.config.color.deleted_message.value,
+ "(This file was deleted)",
+ )
+ elif file.get("mode") == "hidden_by_limit":
+ return with_color(
+ shared.config.color.deleted_message.value,
+ "(This file is not available because the workspace has passed its storage limit)",
+ )
+ elif file.get("file_access") == "file_not_found":
+ return with_color(
+ shared.config.color.deleted_message.value,
+ "(This file was not found)",
+ )
+ elif (
+ file.get("mimetype") == "application/vnd.slack-docs"
+ and "permalink" in file
+ ):
+ url = f"{file['permalink']}?origin_team={self.message.workspace.id}&origin_channel={self.message.conversation.id}"
+ title = unhtmlescape(file.get("title", ""))
+ return format_url(url, title)
+ elif "url_private" in file:
+ title = unhtmlescape(file.get("title", ""))
+ return format_url(file["url_private"], title)
+ else:
+ error = SlackError(self.message.workspace, "Unsupported file", file)
+ uncaught_error = UncaughtError(error)
+ store_uncaught_error(uncaught_error)
+ return with_color(
+ shared.config.color.render_error.value,
+ f"<Unsupported file, error id: {uncaught_error.id}>",
+ )
+
else:
assert_never(self.item_type)
@@ -300,6 +345,8 @@ class PendingMessageItem:
return not only_personal
elif self.item_type == "message_nick":
return False
+ elif self.item_type == "file":
+ return False
else:
assert_never(self.item_type)
@@ -686,12 +733,9 @@ class SlackMessage:
for item in items
]
- files_text = self._render_files(self._message_json.get("files", []))
- if files_text:
- texts.extend(["\n", files_text])
-
+ files = self._render_files(self._message_json.get("files", []), bool(texts))
attachment_items = self._render_attachments(texts)
- self._parsed_message = texts + attachment_items
+ self._parsed_message = texts + files + attachment_items
return self._parsed_message
@@ -1107,44 +1151,14 @@ class SlackMessage:
else:
return "▪︎"
- def _render_files(self, files: List[SlackMessageFile]) -> str:
- lines: List[str] = []
- for file in files:
- if file.get("mode") == "tombstone":
- text = with_color(
- shared.config.color.deleted_message.value, "(This file was deleted)"
- )
- elif file.get("mode") == "hidden_by_limit":
- text = with_color(
- shared.config.color.deleted_message.value,
- "(This file is not available because the workspace has passed its storage limit)",
- )
- if file.get("file_access") == "file_not_found":
- text = with_color(
- shared.config.color.deleted_message.value,
- "(This file was not found)",
- )
- elif (
- file.get("mimetype") == "application/vnd.slack-docs"
- and "permalink" in file
- ):
- url = f"{file['permalink']}?origin_team={self.workspace.id}&origin_channel={self.conversation.id}"
- title = unhtmlescape(file.get("title", ""))
- text = format_url(url, title)
- elif "url_private" in file:
- title = unhtmlescape(file.get("title", ""))
- text = format_url(file["url_private"], title)
- else:
- error = SlackError(self.workspace, "Unsupported file", file)
- uncaught_error = UncaughtError(error)
- store_uncaught_error(uncaught_error)
- text = with_color(
- shared.config.color.render_error.value,
- f"<Unsupported file, error id: {uncaught_error.id}>",
- )
- lines.append(text)
-
- return "\n".join(lines)
+ def _render_files(
+ self, files: List[SlackFile], has_items_before: bool
+ ) -> List[Union[str, PendingMessageItem]]:
+ items = [
+ PendingMessageItem(self, "file", file["id"], file=file) for file in files
+ ]
+ before = ["\n"] if has_items_before and items else []
+ return before + intersperse(items, "\n")
# TODO: Check if mentions in attachments should highlight
def _render_attachments(
@@ -1232,9 +1246,9 @@ class SlackMessage:
[item for item in self._unfurl_and_unescape(line)] for line in lines
]
- files = self._render_files(attachment.get("files", []))
+ files = self._render_files(attachment.get("files", []), False)
if files:
- lines.append([files])
+ lines.append(files)
# TODO: Don't render both text and blocks
blocks_items = self._render_blocks(attachment.get("blocks", []))
diff --git a/tests/test_render_attachments.py b/tests/test_render_attachments.py
index 45b51c4..9e47449 100644
--- a/tests/test_render_attachments.py
+++ b/tests/test_render_attachments.py
@@ -341,6 +341,7 @@ cases: List[Case] = [
"text": "Original message",
"files": [
{
+ "id": "F12345678",
"title": "File",
"url_private": "http://link",
}
diff --git a/typings/slack_api/slack_conversations_history.pyi b/typings/slack_api/slack_conversations_history.pyi
index af1c97d..228d824 100644
--- a/typings/slack_api/slack_conversations_history.pyi
+++ b/typings/slack_api/slack_conversations_history.pyi
@@ -4,6 +4,7 @@ from typing import Dict, List
from slack_api.slack_common import SlackErrorResponse
from slack_api.slack_conversations_replies import SlackMessageThreadCommon
+from slack_api.slack_files_info import SlackFile
from slack_rtm.slack_rtm_message import SlackMessageRtm
from typing_extensions import Literal, NotRequired, TypedDict, final
@@ -269,41 +270,6 @@ class SlackMessageEdited(TypedDict):
ts: str
@final
-class SlackMessageFile(TypedDict):
- id: str
- created: int
- timestamp: int
- name: NotRequired[str]
- title: NotRequired[str]
- mimetype: NotRequired[str]
- filetype: str
- pretty_type: NotRequired[str]
- user: str
- user_team: NotRequired[str]
- editable: NotRequired[bool]
- size: NotRequired[int]
- mode: NotRequired[str]
- is_external: NotRequired[bool]
- external_type: NotRequired[str]
- is_public: NotRequired[bool]
- public_url_shared: NotRequired[bool]
- display_as_bot: NotRequired[bool]
- username: NotRequired[str]
- url_private: NotRequired[str]
- url_private_download: NotRequired[str]
- permalink: NotRequired[str]
- permalink_public: NotRequired[str]
- preview: NotRequired[str]
- editor: NotRequired[None]
- last_editor: NotRequired[str]
- non_owner_editable: NotRequired[bool]
- updated: NotRequired[int]
- is_starred: NotRequired[bool]
- has_rich_preview: NotRequired[bool]
- file_access: str
- media_progress: NotRequired[None]
-
-@final
class SlackMessageUserProfile(TypedDict):
avatar_hash: str
image_72: str
@@ -376,7 +342,7 @@ class SlackMessageThreadBroadcastFinal(SlackMessageThreadBroadcast):
class SlackMessageWithFiles(SlackMessageCommon):
user: NotRequired[str]
user_profile: NotRequired[SlackMessageUserProfile]
- files: List[SlackMessageFile]
+ files: List[SlackFile]
upload: bool
display_as_bot: bool
diff --git a/typings/slack_api/slack_files_info.pyi b/typings/slack_api/slack_files_info.pyi
new file mode 100644
index 0000000..119f0b9
--- /dev/null
+++ b/typings/slack_api/slack_files_info.pyi
@@ -0,0 +1,57 @@
+from __future__ import annotations
+
+from slack_api.slack_common import SlackErrorResponse
+from typing_extensions import Literal, NotRequired, TypedDict, final
+
+@final
+class SlackFile(TypedDict):
+ id: str
+ created: int
+ timestamp: int
+ name: NotRequired[str]
+ title: NotRequired[str]
+ mimetype: NotRequired[str]
+ filetype: str
+ pretty_type: NotRequired[str]
+ user: str
+ user_team: NotRequired[str]
+ editable: NotRequired[bool]
+ size: NotRequired[int]
+ mode: NotRequired[str]
+ is_external: NotRequired[bool]
+ external_type: NotRequired[str]
+ is_public: NotRequired[bool]
+ public_url_shared: NotRequired[bool]
+ display_as_bot: NotRequired[bool]
+ username: NotRequired[str]
+ url_private: NotRequired[str]
+ url_private_download: NotRequired[str]
+ permalink: NotRequired[str]
+ permalink_public: NotRequired[str]
+ preview: NotRequired[str]
+ editor: NotRequired[None]
+ last_editor: NotRequired[str]
+ non_owner_editable: NotRequired[bool]
+ updated: NotRequired[int]
+ is_starred: NotRequired[bool]
+ has_rich_preview: NotRequired[bool]
+ file_access: Literal["visible", "check_file_info", "file_not_found"]
+
+ # only from files.info, not in conversations.history:
+ # update_notification
+ # shares
+ # channels
+ # groups
+ # ims
+ # has_more_shares
+ # comments_count
+
+@final
+class SlackFilesInfoSuccessResponse(TypedDict):
+ ok: Literal[True]
+ content_html: str
+ file: SlackFile
+ # comments
+ comments_count: int
+
+SlackFilesInfoResponse = SlackFilesInfoSuccessResponse | SlackErrorResponse