diff options
-rw-r--r-- | _pytest/test_render_blocks.py | 247 | ||||
-rw-r--r-- | wee_slack.py | 160 |
2 files changed, 397 insertions, 10 deletions
diff --git a/_pytest/test_render_blocks.py b/_pytest/test_render_blocks.py new file mode 100644 index 0000000..258fc49 --- /dev/null +++ b/_pytest/test_render_blocks.py @@ -0,0 +1,247 @@ +from __future__ import print_function, unicode_literals + +import pytest + +import wee_slack + + +@pytest.mark.parametrize( + "case", + [ + { + "blocks": [ + { + "type": "rich_text", + "block_id": "dhGA", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + {"type": "text", "text": "normal "}, + { + "type": "text", + "text": "bold", + "style": {"bold": True}, + }, + {"type": "text", "text": " "}, + { + "type": "text", + "text": "italic", + "style": {"italic": True}, + }, + {"type": "text", "text": " "}, + { + "type": "text", + "text": "strikethrough", + "style": {"strike": True}, + }, + {"type": "text", "text": " "}, + { + "type": "text", + "text": "bold-italic-strikethrough", + "style": { + "bold": True, + "italic": True, + "strike": True, + }, + }, + {"type": "text", "text": " "}, + { + "type": "link", + "url": "https://vg.no", + "text": "link", + }, + {"type": "text", "text": "\n"}, + ], + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "number"}], + }, + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "list"}], + }, + ], + "style": "ordered", + "indent": 0, + "border": 0, + }, + { + "type": "rich_text_quote", + "elements": [ + {"type": "text", "text": "some quote\nmore quote"} + ], + }, + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "inline code", + "style": {"code": True}, + }, + {"type": "text", "text": "\n"}, + ], + }, + { + "type": "rich_text_preformatted", + "elements": [ + {"type": "text", "text": "block code\nmore code"} + ], + "border": 0, + }, + { + "type": "rich_text_section", + "elements": [ + {"type": "user", "user_id": "U407ABLLW"}, + {"type": "text", "text": ": "}, + { + "type": "emoji", + "name": "open_mouth", + "unicode": "1f62e", + }, + ], + }, + ], + } + ], + "rendered": [ + "normal <[color bold]>*bold*<[color -bold]> <[color italic]>_italic_<[color -italic]> ~strikethrough~ <[color bold]><[color italic]>*_~bold-italic-strikethrough~_*<[color -bold]><[color -italic]> https://vg.no (link)", + "1. number", + "2. list", + "> some quote", + "> more quote", + "`inline code`", + "```\nblock code\nmore code\n```", + "@alice: :open_mouth:", + ], + }, + { + "blocks": [ + { + "type": "rich_text", + "block_id": "D=pkP", + "elements": [ + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "number"}], + } + ], + "style": "ordered", + "indent": 0, + "border": 0, + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "list"}], + } + ], + "style": "ordered", + "indent": 1, + "border": 0, + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "third"}], + } + ], + "style": "ordered", + "indent": 2, + "border": 0, + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + {"type": "text", "text": "end number list"} + ], + } + ], + "style": "ordered", + "indent": 0, + "offset": 1, + "border": 0, + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "bullet"}], + } + ], + "style": "bullet", + "indent": 0, + "border": 0, + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "list"}], + } + ], + "style": "bullet", + "indent": 1, + "border": 0, + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "third"}], + } + ], + "style": "bullet", + "indent": 2, + "border": 0, + }, + { + "type": "rich_text_list", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + {"type": "text", "text": "end bullet list"} + ], + } + ], + "style": "bullet", + "indent": 0, + "border": 0, + }, + ], + } + ], + "rendered": [ + "1. number", + " a. list", + " i. third", + "2. end number list", + "• bullet", + " ◦ list", + " ▪︎ third", + "• end bullet list", + ], + }, + ], +) +def test_render_blocks(case): + assert wee_slack.unfurl_blocks(case["blocks"]) == case["rendered"] diff --git a/wee_slack.py b/wee_slack.py index ff794f9..c87ea78 100644 --- a/wee_slack.py +++ b/wee_slack.py @@ -3394,14 +3394,7 @@ class SlackMessage(object): blocks = self.message_json.get("blocks", []) blocks_rendered = "\n".join(unfurl_blocks(blocks)) - has_rich_text = any(block["type"] == "rich_text" for block in blocks) - if has_rich_text: - text = self.message_json.get("text", "") - if blocks_rendered: - if text: - text += "\n" - text += blocks_rendered - elif blocks_rendered: + if blocks_rendered: text = blocks_rendered else: text = self.message_json.get("text", "") @@ -4605,7 +4598,47 @@ def unfurl_blocks(blocks): block_text.append(unfurl_block_element(block["title"])) block_text.append(unfurl_block_element(block)) elif block["type"] == "rich_text": - continue + for element in block.get("elements", []): + if element["type"] == "rich_text_section": + rendered = unfurl_rich_text_section(element) + if rendered: + block_text.append(rendered) + elif element["type"] == "rich_text_list": + rendered = [ + "{}{} {}".format( + " " * element.get("indent", 0), + block_list_prefix( + element, element.get("offset", 0) + i + ), + unfurl_rich_text_section(e), + ) + for i, e in enumerate(element["elements"]) + ] + block_text.extend(rendered) + elif element["type"] == "rich_text_quote": + lines = [ + "> {}".format(line) + for e in element["elements"] + for line in unfurl_block_element(e).split("\n") + ] + block_text.extend(lines) + elif element["type"] == "rich_text_preformatted": + texts = [ + e.get("text", e.get("url", "")) for e in element["elements"] + ] + if texts: + block_text.append("```\n{}\n```".format("".join(texts))) + else: + text = '<<Unsupported rich_text type "{}">>'.format( + element["type"] + ) + block_text.append(colorize_string(config.color_deleted, text)) + dbg( + "Unsupported rich_text element: '{}'".format( + json.dumps(element) + ), + level=4, + ) else: block_text.append( colorize_string( @@ -4613,7 +4646,7 @@ def unfurl_blocks(blocks): '<<Unsupported block type "{}">>'.format(block["type"]), ) ) - dbg('Unsupported block: "{}"'.format(json.dumps(block)), level=4) + dbg("Unsupported block: '{}'".format(json.dumps(block)), level=4) except Exception as e: dbg( "Failed to unfurl block ({}): {}".format(repr(e), json.dumps(block)), @@ -4622,9 +4655,92 @@ def unfurl_blocks(blocks): return block_text +def convert_int_to_letter(num): + letter = "" + while num > 0: + num -= 1 + letter = chr((num % 26) + 97) + letter + num //= 26 + return letter + + +def convert_int_to_roman(num): + roman_numerals = { + 1000: "m", + 900: "cm", + 500: "d", + 400: "cd", + 100: "c", + 90: "xc", + 50: "l", + 40: "xl", + 10: "x", + 9: "ix", + 5: "v", + 4: "iv", + 1: "i", + } + roman_numeral = "" + for value, symbol in roman_numerals.items(): + while num >= value: + roman_numeral += symbol + num -= value + return roman_numeral + + +def block_list_prefix(element, index): + if element["style"] == "ordered": + if element["indent"] == 0 or element["indent"] == 3: + return "{}.".format(index + 1) + elif element["indent"] == 1 or element["indent"] == 4: + return "{}.".format(convert_int_to_letter(index + 1)) + else: + return "{}.".format(convert_int_to_roman(index + 1)) + else: + if element["indent"] == 0 or element["indent"] == 3: + return "•" + elif element["indent"] == 1 or element["indent"] == 4: + return "◦" + else: + return "▪︎" + + +def unfurl_rich_text_section(block): + text = "".join( + unfurl_block_element(sub_element) for sub_element in block["elements"] + ) + if text.endswith("\n"): + return text[:-1] + else: + return text + + def unfurl_block_element(text): if text["type"] == "mrkdwn": return render_formatting(text["text"]) + elif text["type"] == "text": + colors = [] + characters = [] + if text.get("style", {}).get("bold"): + colors.append(config.render_bold_as) + characters.append("*") + if text.get("style", {}).get("italic"): + colors.append(config.render_italic_as) + characters.append("_") + if text.get("style", {}).get("strike"): + characters.append("~") + if text.get("style", {}).get("code"): + characters.append("`") + + colors_start = "".join(w.color(color) for color in colors) + colors_end = "".join(w.color("-" + color) for color in colors) + return ( + colors_start + + "".join(characters) + + text["text"] + + "".join(reversed(characters)) + + colors_end + ) elif text["type"] == "plain_text": return text["text"] elif text["type"] == "image": @@ -4632,6 +4748,30 @@ def unfurl_block_element(text): return "{} ({})".format(text["image_url"], text["alt_text"]) else: return text["image_url"] + elif text["type"] == "link": + if text.get("text"): + if text.get("style", {}).get("code"): + return text["text"] + else: + return "{} ({})".format(text["url"], text["text"]) + else: + return text["url"] + elif text["type"] == "emoji": + return replace_string_with_emoji(":{}:".format(text["name"])) + elif text["type"] == "user": + return resolve_ref("@{}".format(text["user_id"])) + elif text["type"] == "usergroup": + return resolve_ref("!subteam^{}".format(text["usergroup_id"])) + elif text["type"] == "broadcast": + return resolve_ref("@{}".format(text["range"])) + elif text["type"] == "channel": + return resolve_ref("#{}".format(text["channel_id"])) + else: + dbg("Unsupported block element: '{}'".format(json.dumps(text)), level=4) + return colorize_string( + config.color_deleted, + '<<Unsupported block element type "{}">>'.format(text["type"]), + ) def unfurl_refs(text): |