aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--slack/slack_message.py94
-rw-r--r--tests/conftest.py41
-rw-r--r--tests/test_render_attachments.py642
-rw-r--r--tests/test_render_blocks.py2
-rw-r--r--typings/slack_api/slack_conversations_history.pyi13
5 files changed, 755 insertions, 37 deletions
diff --git a/slack/slack_message.py b/slack/slack_message.py
index 4301560..ade737c 100644
--- a/slack/slack_message.py
+++ b/slack/slack_message.py
@@ -4,7 +4,7 @@ import re
from collections import OrderedDict
from datetime import date, datetime, timedelta
from enum import Enum
-from typing import TYPE_CHECKING, Generator, List, Match, Optional, Union
+from typing import TYPE_CHECKING, Generator, Iterable, List, Match, Optional, Union
import weechat
@@ -19,7 +19,7 @@ from slack.python_compatibility import removeprefix, removesuffix
from slack.shared import shared
from slack.slack_user import SlackBot, SlackUser, format_bot_nick, nick_color
from slack.task import gather
-from slack.util import intersperse, unhtmlescape, with_color
+from slack.util import htmlescape, intersperse, unhtmlescape, with_color
if TYPE_CHECKING:
from slack_api.slack_conversations_history import SlackMessage as SlackMessageDict
@@ -662,7 +662,9 @@ class SlackMessage:
uncaught_error = UncaughtError(e)
print_error(store_and_format_uncaught_error(uncaught_error))
text = f"<Error rendering message {self.ts}, error id: {uncaught_error.id}>"
- self._rendered_message = with_color(shared.config.color.render_error.value, text)
+ self._rendered_message = with_color(
+ shared.config.color.render_error.value, text
+ )
return self._rendered_message
@@ -716,6 +718,19 @@ class SlackMessage:
if i < len(message):
yield message[i:]
+ def _unfurl_and_unescape(
+ self, items: Iterable[Union[str, PendingMessageItem]]
+ ) -> Generator[Union[str, PendingMessageItem], None, None]:
+ for item in items:
+ if isinstance(item, str):
+ for sub_item in self._unfurl_refs(item):
+ if isinstance(sub_item, str):
+ yield unhtmlescape(sub_item)
+ else:
+ yield sub_item
+ else:
+ yield item
+
def _get_emoji(self, emoji_name: str, skin_tone: Optional[int] = None) -> str:
emoji_name_with_colons = f":{emoji_name}:"
if shared.config.look.render_emoji_as.value == "name":
@@ -1077,7 +1092,10 @@ class SlackMessage:
if "attachments" not in self._message_json:
return []
- attachments_texts: List[Union[str, PendingMessageItem]] = []
+ attachments: List[List[Union[str, PendingMessageItem]]] = []
+ if any(items_before):
+ attachments.append([])
+
for attachment in self._message_json["attachments"]:
# Attachments should be rendered roughly like:
#
@@ -1088,16 +1106,16 @@ class SlackMessage:
if (
attachment.get("is_app_unfurl")
- and shared.config.look.display_link_previews
+ and not shared.config.look.display_link_previews.value
):
continue
- items: List[Union[str, PendingMessageItem]] = []
+ lines: List[List[Union[str, PendingMessageItem]]] = []
prepend_title_text = ""
if "author_name" in attachment:
prepend_title_text = attachment["author_name"] + ": "
if "pretext" in attachment:
- items.append(attachment["pretext"])
+ lines.append([attachment["pretext"]])
link_shown = False
title = attachment.get("title")
title_link = attachment.get("title_link", "")
@@ -1107,10 +1125,12 @@ class SlackMessage:
title_link = ""
link_shown = True
if title and title_link:
- items.append(f"{prepend_title_text}{title} ({title_link})")
+ lines.append(
+ [f"{prepend_title_text}{title} ({htmlescape(title_link)})"]
+ )
prepend_title_text = ""
elif title and not title_link:
- items.append(f"{prepend_title_text}{title}")
+ lines.append([f"{prepend_title_text}{title}"])
prepend_title_text = ""
from_url = attachment.get("from_url", "")
if (
@@ -1119,20 +1139,16 @@ class SlackMessage:
)
and from_url != title_link
):
- items.append(from_url)
+ lines.append([htmlescape(from_url)])
elif from_url:
link_shown = True
atext = attachment.get("text")
if atext:
tx = re.sub(r" *\n[\n ]+", "\n", atext)
- items.append(prepend_title_text + tx)
+ lines.append([prepend_title_text + tx])
prepend_title_text = ""
- # TODO: Don't render both text and blocks
- blocks_items = self._render_blocks(attachment.get("blocks", []))
- items.extend(blocks_items)
-
image_url = attachment.get("image_url", "")
if (
not any(
@@ -1141,30 +1157,41 @@ class SlackMessage:
and image_url != from_url
and image_url != title_link
):
- items.append(image_url)
+ lines.append([htmlescape(image_url)])
elif image_url:
link_shown = True
for field in attachment.get("fields", []):
if field.get("title"):
- items.append(f"{field['title']}: {field['value']}")
+ lines.append([f"{field['title']}: {field['value']}"])
else:
- items.append(field["value"])
+ lines.append([field["value"]])
+
+ lines = [
+ [item for item in self._unfurl_and_unescape(line)] for line in lines
+ ]
files = self._render_files(attachment.get("files", []))
if files:
- items.append(files)
+ lines.append([files])
- if attachment.get("is_msg_unfurl"):
+ # TODO: Don't render both text and blocks
+ blocks_items = self._render_blocks(attachment.get("blocks", []))
+ if blocks_items:
+ lines.append(blocks_items)
+
+ if "is_msg_unfurl" in attachment and attachment["is_msg_unfurl"]:
channel_name = PendingMessageItem(
- self, "conversation", self.conversation.id
+ self, "conversation", attachment["channel_id"], "chat"
)
if attachment.get("is_reply_unfurl"):
footer = ["From a thread in ", channel_name]
else:
footer = ["Posted in ", channel_name]
elif attachment.get("footer"):
- footer = [attachment.get("footer")]
+ footer: List[Union[str, PendingMessageItem]] = [
+ attachment.get("footer")
+ ]
else:
footer = []
@@ -1181,20 +1208,23 @@ class SlackMessage:
if date.today() - date.fromtimestamp(ts_int) <= timedelta(days=1):
time_string = " at {time}"
timestamp_formatted = format_date(
- ts_int, "date_short_pretty" + time_string
+ ts_int, "{date_short_pretty}" + time_string
)
footer.append(f" | {timestamp_formatted.capitalize()}")
- items.extend(footer)
+
+ lines.append([item for item in self._unfurl_and_unescape(footer)])
fallback = attachment.get("fallback")
- if items == [] and fallback and not link_shown:
- items.append(fallback)
+ if not any(lines) and fallback and not link_shown:
+ lines.append([fallback])
+
+ items = [item for items in intersperse(lines, ["\n"]) for item in items]
texts_separate_newlines = [
item_separate_newline
for item in items
for item_separate_newline in (
- intersperse(item.strip().split("\n"), "\n")
+ intersperse(item.split("\n"), "\n")
if isinstance(item, str)
else [item]
)
@@ -1221,10 +1251,12 @@ class SlackMessage:
for item in texts_separate_newlines
]
+ attachment_texts: List[Union[str, PendingMessageItem]] = []
if line_color:
- attachments_texts.append(weechat.color(line_color))
- attachments_texts.extend(texts_with_prefix)
+ attachment_texts.append(weechat.color(line_color))
+ attachment_texts.extend(texts_with_prefix)
if line_color:
- attachments_texts.append(weechat.color("reset"))
+ attachment_texts.append(weechat.color("reset"))
+ attachments.append(attachment_texts)
- return attachments_texts
+ return [item for items in intersperse(attachments, ["\n"]) for item in items]
diff --git a/tests/conftest.py b/tests/conftest.py
index 33e44de..d1cf16a 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -4,7 +4,7 @@ import importlib
import importlib.machinery
import json
import sys
-from typing import TYPE_CHECKING, Union
+from typing import TYPE_CHECKING, Dict, Union
import pytest
@@ -49,6 +49,10 @@ from slack.slack_user import SlackUser
from slack.slack_workspace import SlackWorkspace
from slack.task import Future
+config_values: Dict[str, str] = {
+ "replace_space_in_nicks_with": "_",
+}
+
def config_new_option(
config_file: str,
@@ -69,15 +73,30 @@ def config_new_option(
callback_delete: str,
callback_delete_data: str,
) -> str:
+ if name not in config_values and default_value is not None:
+ config_values[name] = default_value
return name
+def config_option_set(option: str, value: str, run_callback: int) -> int:
+ # TODO: special values
+ old_value = config_values.get(option)
+ if value == old_value:
+ return weechat.WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE
+ config_values[option] = value
+ return weechat.WEECHAT_CONFIG_OPTION_SET_OK_CHANGED
+
+
+def config_boolean(option: str) -> int:
+ return config_values.get(option) == "True"
+
+
def config_integer(option: str) -> int:
- return 1
+ return int(config_values.get(option, 0))
def config_string(option: str) -> str:
- return "_"
+ return config_values.get(option, "")
def config_color(option: str) -> str:
@@ -88,11 +107,23 @@ def color(option: str) -> str:
return f"<[color:{option}]>"
+def info_get(info_name: str, arguments: str):
+ if info_name == "color_rgb2term":
+ return arguments
+ elif info_name == "weechat_data_dir":
+ return "."
+ else:
+ return ""
+
+
weechat.config_new_option = config_new_option
+weechat.config_option_set = config_option_set
+weechat.config_boolean = config_boolean
weechat.config_integer = config_integer
weechat.config_string = config_string
weechat.config_color = config_color
weechat.color = color
+weechat.info_get = info_get
shared.weechat_version = 0x03080000
shared.weechat_callbacks = {}
@@ -103,6 +134,8 @@ color_user_mention = "<[color:<[config_color:user_mention]>]>"
color_usergroup_mention = "<[color:<[config_color:usergroup_mention]>]>"
color_reset = "<[color:reset]>"
+workspace_id = "T0FC8BFQR"
+
with open("mock_data/slack_users_info_person.json") as f:
user_test1_info_response: SlackUserInfoSuccessResponse[SlackUserInfo] = json.loads(
f.read()
@@ -122,7 +155,7 @@ with open("mock_data/slack_conversations_info_channel_public.json") as f:
def workspace():
shared.config = SlackConfig()
w = SlackWorkspace("workspace_name")
- w.id = "T0FC8BFQR"
+ w.id = workspace_id
user_test1 = SlackUser(w, user_test1_info)
user_test1_future = Future[SlackUser]()
diff --git a/tests/test_render_attachments.py b/tests/test_render_attachments.py
new file mode 100644
index 0000000..4a23b5a
--- /dev/null
+++ b/tests/test_render_attachments.py
@@ -0,0 +1,642 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, List
+
+import pytest
+
+from slack.shared import shared
+from slack.slack_message import SlackMessage
+from tests.conftest import (
+ channel_public_id,
+ color_reset,
+ color_user_mention,
+ resolve_pending_message_item,
+ user_test1_id,
+ workspace_id,
+)
+
+if TYPE_CHECKING:
+ from typing_extensions import NotRequired, TypedDict
+else:
+ TypedDict = object
+
+
+class Case(TypedDict):
+ input_message: Any
+ input_text_before: str
+ output: str
+ link_previews: NotRequired[bool]
+
+
+cases: List[Case] = [
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "| Title",
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ }
+ ]
+ },
+ "input_text_before": "Text before",
+ "output": "\n".join(
+ [
+ "",
+ "| Title",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title1",
+ },
+ {
+ "title": "Title2",
+ },
+ ]
+ },
+ "input_text_before": "Text before",
+ "output": "\n".join(
+ [
+ "",
+ "| Title1",
+ "| Title2",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ "text": "Attachment text",
+ "title_link": "http://title.link",
+ "from_url": "http://from.url",
+ "fallback": "Fallback",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| Title (http://title.link)",
+ "| http://from.url",
+ "| Attachment text",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ "text": "Attachment text",
+ "title_link": "http://title.link",
+ "image_url": "http://image.url",
+ "fallback": "Fallback",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| Title (http://title.link)",
+ "| Attachment text",
+ "| http://image.url",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ "text": "Attachment text",
+ "title_link": "http://link1",
+ "from_url": "http://link2",
+ "image_url": "http://link3",
+ }
+ ]
+ },
+ "input_text_before": "http://link1 http://link2 http://link3",
+ "output": "\n".join(
+ [
+ "",
+ "| Title",
+ "| Attachment text",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ "text": "Attachment text",
+ "title_link": "http://link",
+ "from_url": "http://link",
+ "image_url": "http://link",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| Title (http://link)",
+ "| Attachment text",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ "text": "Attachment text",
+ "from_url": "http://link",
+ "image_url": "http://link",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| Title",
+ "| http://link",
+ "| Attachment text",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ "text": "Attachment text\n\n\nWith multiple lines",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| Title",
+ "| Attachment text",
+ "| With multiple lines",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ "author_name": "Author",
+ "pretext": "Pretext",
+ "text": "Attachment text",
+ "title_link": "http://title.link",
+ "from_url": "http://from.url",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| Pretext",
+ "| Author: Title (http://title.link)",
+ "| http://from.url",
+ "| Attachment text",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "author_name": "Author",
+ "text": "Attachment text",
+ "title_link": "http://title.link",
+ "from_url": "http://from.url",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| http://from.url",
+ "| Author: Attachment text",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "fallback": "Fallback",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "| Fallback",
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "fallback": "Fallback",
+ "title_link": "http://link",
+ }
+ ]
+ },
+ "input_text_before": "http://link",
+ "output": "",
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "fallback": "Fallback",
+ "from_url": "http://link",
+ }
+ ]
+ },
+ "input_text_before": "http://link",
+ "output": "",
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "fallback": "Fallback",
+ "image_url": "http://link",
+ }
+ ]
+ },
+ "input_text_before": "http://link",
+ "output": "",
+ },
+ {
+ "input_message": {
+ "attachments": [{"text": "Some message", "footer": "Thread in #general"}]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| Some message",
+ "| Thread in #general",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "ts": 1584986782,
+ "text": "Some message",
+ "footer": "Thread in #general",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| Some message",
+ "| Thread in #general | Mar 23, 2020",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "ts": "1584986782.261400",
+ "text": "Some message",
+ "footer": "Thread in #general",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| Some message",
+ "| Thread in #general | Mar 23, 2020",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "text": "Original message",
+ "files": [
+ {
+ "title": "File",
+ "url_private": "http://link",
+ }
+ ],
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| Original message",
+ "| http://link (File)",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ "fields": [
+ {
+ "title": "First field title",
+ "value": "First field value",
+ },
+ {
+ "title": "",
+ "value": "Second field value",
+ },
+ ],
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| Title",
+ "| First field title: First field value",
+ "| Second field value",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "First attachment title",
+ "text": "First attachment text",
+ "title_link": "http://title.link.1",
+ "from_url": "http://from.url.1",
+ },
+ {
+ "title": "Second attachment title",
+ "text": "Second attachment text",
+ "title_link": "http://title.link.2",
+ "from_url": "http://from.url.2",
+ },
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| First attachment title (http://title.link.1)",
+ "| http://from.url.1",
+ "| First attachment text",
+ "| Second attachment title (http://title.link.2)",
+ "| http://from.url.2",
+ "| Second attachment text",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ "color": "ff0000",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "<[color:16711680]>|<[color:reset]> Title",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "title": "Title",
+ "color": "#ff0000",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "<[color:16711680]>|<[color:reset]> Title",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [{"text": "Attachment text", "is_app_unfurl": True}]
+ },
+ "input_text_before": "",
+ "output": "| Attachment text",
+ "link_previews": True,
+ },
+ {
+ "input_message": {"attachments": [{"text": "Attachment text"}]},
+ "input_text_before": "",
+ "output": "| Attachment text",
+ "link_previews": False,
+ },
+ {
+ "input_message": {
+ "attachments": [{"text": "Attachment text", "is_app_unfurl": True}]
+ },
+ "input_text_before": "",
+ "output": "",
+ "link_previews": False,
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "id": 1,
+ "ts": 1697480778,
+ "fallback": "title &amp; &lt;asd&gt;",
+ "text": "text &amp; &lt;asd&gt;",
+ "pretext": "pretext &amp; &lt;asd&gt;",
+ "title": "title &amp; &lt;asd&gt;",
+ "title_link": "https://title.link/?x=<x>&z=z",
+ "author_name": "author_name &amp; &lt;asd&gt;",
+ "from_url": "https://from.url/?x=<x>&z=z",
+ "image_url": "https://image.url/?x=<x>&z=z",
+ "footer": "footer &amp; &lt;asd&gt;",
+ "fields": [
+ {
+ "value": "field value &amp; &lt;asd&gt;",
+ "title": "field title &amp; &lt;asd&gt;",
+ "short": False,
+ },
+ {
+ "value": f"field value mention <@{user_test1_id}>",
+ "title": f"field title mention &lt;@{user_test1_id}&gt;",
+ "short": False,
+ },
+ ],
+ },
+ {
+ "id": 2,
+ "blocks": [
+ {
+ "type": "rich_text",
+ "block_id": "IQm+Q",
+ "elements": [
+ {
+ "type": "rich_text_preformatted",
+ "elements": [
+ {
+ "type": "text",
+ "text": "block rich_text_preformatted & <asd>",
+ }
+ ],
+ }
+ ],
+ },
+ {
+ "type": "rich_text",
+ "block_id": "a5bVo",
+ "elements": [
+ {
+ "type": "rich_text_section",
+ "elements": [
+ {
+ "type": "text",
+ "text": "block rich_text_section & <asd> ",
+ },
+ {
+ "type": "link",
+ "url": "https://block.link?x=<x>&z=z",
+ "style": {"code": True},
+ },
+ ],
+ }
+ ],
+ },
+ {
+ "type": "rich_text",
+ "block_id": "FeChA",
+ "elements": [
+ {
+ "type": "rich_text_section",
+ "elements": [
+ {"type": "user", "user_id": user_test1_id},
+ {"type": "text", "text": ": <@ASD>"},
+ ],
+ }
+ ],
+ },
+ ],
+ "fallback": "[no preview available]",
+ },
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| pretext & <asd>",
+ "| author_name & <asd>: title & <asd> (https://title.link/?x=<x>&z=z)",
+ "| https://from.url/?x=<x>&z=z",
+ "| text & <asd>",
+ "| https://image.url/?x=<x>&z=z",
+ "| field title & <asd>: field value & <asd>",
+ f"| field title mention <@{user_test1_id}>: field value mention {color_user_mention}@Test_1{color_reset}",
+ "| footer & <asd> | Oct 16, 2023",
+ "| ```",
+ "| block rich_text_preformatted & <asd>",
+ "| ```",
+ "| block rich_text_section & <asd> `https://block.link?x=<x>&z=z`",
+ f"| {color_user_mention}@Test_1{color_reset}: <@ASD>",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "from_url": "https://from.url",
+ "ts": "1697393234.859799",
+ "author_id": user_test1_id,
+ "channel_id": channel_public_id,
+ "channel_team": workspace_id,
+ "is_msg_unfurl": True,
+ "id": 1,
+ "fallback": "[October 15th, 2023 11:07 AM] username: fallback text",
+ "text": "text",
+ "author_name": "Author name",
+ "author_link": f"https://wee-slack-test.slack.com/team/{user_test1_id}",
+ "mrkdwn_in": ["text"],
+ "footer": "Slack Conversation",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| https://from.url",
+ "| Author name: text",
+ "| Posted in <[color:chat_channel]>#channel1<[color:reset]> | Oct 15, 2023",
+ ]
+ ),
+ },
+ {
+ "input_message": {
+ "attachments": [
+ {
+ "from_url": "https://from.url",
+ "ts": "1697393234.859799",
+ "author_id": user_test1_id,
+ "channel_id": channel_public_id,
+ "channel_team": workspace_id,
+ "is_msg_unfurl": True,
+ "is_reply_unfurl": True,
+ "id": 1,
+ "fallback": "[October 15th, 2023 11:07 AM] username: fallback text",
+ "text": "text",
+ "author_name": "Author name",
+ "author_link": f"https://wee-slack-test.slack.com/team/{user_test1_id}",
+ "mrkdwn_in": ["text"],
+ "footer": "Thread in Slack Conversation",
+ }
+ ]
+ },
+ "input_text_before": "",
+ "output": "\n".join(
+ [
+ "| https://from.url",
+ "| Author name: text",
+ "| From a thread in <[color:chat_channel]>#channel1<[color:reset]> | Oct 15, 2023",
+ ]
+ ),
+ },
+]
+
+
+@pytest.mark.parametrize("case", cases)
+def test_render_attachments(case: Case, message1_in_channel_public: SlackMessage):
+ shared.config.look.display_link_previews.value = case.get("link_previews", True)
+ message1_in_channel_public.update_message_json(case["input_message"])
+ parsed = message1_in_channel_public._render_attachments( # pyright: ignore [reportPrivateUsage]
+ [case["input_text_before"]]
+ )
+ resolved = "".join(resolve_pending_message_item(item) for item in parsed)
+ assert resolved == case["output"]
diff --git a/tests/test_render_blocks.py b/tests/test_render_blocks.py
index fedbab6..cd5442e 100644
--- a/tests/test_render_blocks.py
+++ b/tests/test_render_blocks.py
@@ -138,7 +138,7 @@ cases: List[Case] = [
"block code",
"more code",
"```",
- f"{color_user_mention}@Test_1{color_reset}: :open_mouth:",
+ f"{color_user_mention}@Test_1{color_reset}: 😮",
],
},
{
diff --git a/typings/slack_api/slack_conversations_history.pyi b/typings/slack_api/slack_conversations_history.pyi
index 6799f02..4b3702a 100644
--- a/typings/slack_api/slack_conversations_history.pyi
+++ b/typings/slack_api/slack_conversations_history.pyi
@@ -224,7 +224,7 @@ SlackMessageBlock = (
)
@final
-class SlackMessageAttachment(TypedDict):
+class SlackMessageAttachmentStandard(TypedDict):
from_url: str
image_url: str
image_width: int
@@ -241,6 +241,17 @@ class SlackMessageAttachment(TypedDict):
footer: str
@final
+class SlackMessageAttachmentMsgUnfurl(TypedDict):
+ is_msg_unfurl: Literal[True]
+ channel_id: str
+ footer: str
+ # incomplete
+
+SlackMessageAttachment = (
+ SlackMessageAttachmentStandard | SlackMessageAttachmentMsgUnfurl
+)
+
+@final
class SlackMessageReaction(TypedDict):
name: str
users: List[str]