diff --git a/.github/actions/prepare-telegram-release-message/action.yml b/.github/actions/prepare-telegram-release-message/action.yml index 0a589a2..87eb3e3 100644 --- a/.github/actions/prepare-telegram-release-message/action.yml +++ b/.github/actions/prepare-telegram-release-message/action.yml @@ -14,5 +14,6 @@ runs: TAG_NAME: ${{ github.event.release.tag_name }} RELEASE_NAME: ${{ github.event.release.name }} RELEASE_URL: ${{ github.event.release.html_url }} + RELEASE_BODY: ${{ github.event.release.body }} ACTOR: ${{ github.event.release.author.login }} run: python3 "${{ github.action_path }}/prepare.py" diff --git a/.github/actions/prepare-telegram-release-message/prepare.py b/.github/actions/prepare-telegram-release-message/prepare.py index faca63a..8f8be65 100644 --- a/.github/actions/prepare-telegram-release-message/prepare.py +++ b/.github/actions/prepare-telegram-release-message/prepare.py @@ -1,8 +1,11 @@ #!/usr/bin/env python3 import os +import re import uuid +RELEASE_DESCRIPTION_LIMIT = 3500 MARKDOWN_V2_SPECIAL_CHARACTERS = frozenset("_*[]()~`>#+-=|{}.!\\") +MARKDOWN_LINK = re.compile(r"\[([^]]+)]\([^)]*\)") def escape_markdown(value): @@ -13,6 +16,24 @@ def escape_link_url(value): return str(value).replace("\\", "\\\\").replace(")", "\\)") +def truncate(value, limit): + if len(value) <= limit: + return value + return value[: limit - 3].rstrip() + "..." + + +def release_description(value, limit=RELEASE_DESCRIPTION_LIMIT): + lines = [] + for source_line in value.strip().splitlines(): + line = re.sub(r"^#{1,6}\s+", "", source_line.strip()) + line = re.sub(r"^(?:[-+*]|\d+\.)\s+", "", line) + line = MARKDOWN_LINK.sub(r"\1", line) + if line or (lines and lines[-1]): + lines.append(line) + + return truncate("\n".join(lines).strip(), limit) + + def format_published(repository, release): tag = release["tag"] label = escape_markdown(f"release {tag}") @@ -24,7 +45,10 @@ def format_published(repository, release): parts.append(f"*{escape_markdown(name)}*") parts.append(escape_markdown(release.get("actor") or "ghost")) - return " · ".join(parts) + message = " · ".join(parts) + if description := release_description(release.get("body") or ""): + message += f"\n{escape_markdown(description)}" + return message def write_output(message): @@ -40,6 +64,7 @@ def main(): "tag": os.environ["TAG_NAME"], "name": os.environ.get("RELEASE_NAME") or "", "url": os.environ["RELEASE_URL"], + "body": os.environ.get("RELEASE_BODY") or "", "actor": os.environ.get("ACTOR") or "ghost", } write_output(format_published(os.environ["REPOSITORY"], release)) diff --git a/test/test_telegram_notifications.py b/test/test_telegram_notifications.py index 5c8dade..9d7cdbb 100644 --- a/test/test_telegram_notifications.py +++ b/test/test_telegram_notifications.py @@ -220,6 +220,38 @@ def test_formats_distinct_release_name_and_escapes_markdown(self): self.assertIn(r"*Summer \[release\]*", message) self.assertTrue(message.endswith(r"dependabot\[bot\]")) + def test_includes_release_please_description_as_plain_text(self): + release = { + "tag": "v0.16.0", + "name": "v0.16.0", + "url": "https://github.com/owner/repo/releases/tag/v0.16.0", + "body": ( + "## [0.16.0](https://github.com/owner/repo/compare/v0.15.0...v0.16.0) (2026-08-24)\n\n" + "### Features\n\n" + "* add Telegram release notifications " + "([#171](https://github.com/owner/repo/issues/171)) " + "([8d9e8c0](https://github.com/owner/repo/commit/8d9e8c0))" + ), + "actor": "octocat", + } + + message = RELEASE_TELEGRAM.format_published("owner/repo", release) + + self.assertTrue( + message.endswith( + "octocat\n0\\.16\\.0 \\(2026\\-08\\-24\\)\n\n" + "Features\n\nadd Telegram release notifications \\(\\#171\\) \\(8d9e8c0\\)" + ) + ) + + def test_truncates_long_release_description(self): + description = RELEASE_TELEGRAM.release_description( + "x" * (RELEASE_TELEGRAM.RELEASE_DESCRIPTION_LIMIT + 10) + ) + + self.assertEqual(len(description), RELEASE_TELEGRAM.RELEASE_DESCRIPTION_LIMIT) + self.assertTrue(description.endswith("...")) + class TelegramWorkflowTest(unittest.TestCase): def load_workflow(self, name):