slack: use slackify-markdown for proper mrkdwn formatting
Replace the regex-based Markdown-to-Slack converter with the slackify-markdown library, which uses a proper Markdown parser (markdown-it-py, already a dependency) to correctly handle headings, bold/italic, code blocks, links, bullet lists, and strikethrough. The regex approach didn't handle headings (###), bullet lists (* ), or code block protection, causing raw Markdown to leak into Slack messages. Net -40 lines. Assisted-by: Claude 4.6 Opus (Anthropic)
This commit is contained in:
parent
c28e6771a9
commit
ed5593bbe0
@ -10,6 +10,8 @@ from slack_sdk.socket_mode.request import SocketModeRequest
|
|||||||
from slack_sdk.socket_mode.response import SocketModeResponse
|
from slack_sdk.socket_mode.response import SocketModeResponse
|
||||||
from slack_sdk.web.async_client import AsyncWebClient
|
from slack_sdk.web.async_client import AsyncWebClient
|
||||||
|
|
||||||
|
from slackify_markdown import slackify_markdown
|
||||||
|
|
||||||
from nanobot.bus.events import OutboundMessage
|
from nanobot.bus.events import OutboundMessage
|
||||||
from nanobot.bus.queue import MessageBus
|
from nanobot.bus.queue import MessageBus
|
||||||
from nanobot.channels.base import BaseChannel
|
from nanobot.channels.base import BaseChannel
|
||||||
@ -84,7 +86,7 @@ class SlackChannel(BaseChannel):
|
|||||||
use_thread = thread_ts and channel_type != "im"
|
use_thread = thread_ts and channel_type != "im"
|
||||||
await self._web_client.chat_postMessage(
|
await self._web_client.chat_postMessage(
|
||||||
channel=msg.chat_id,
|
channel=msg.chat_id,
|
||||||
text=self._convert_markdown(msg.content) or "",
|
text=slackify_markdown(msg.content) if msg.content else "",
|
||||||
thread_ts=thread_ts if use_thread else None,
|
thread_ts=thread_ts if use_thread else None,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -204,46 +206,3 @@ class SlackChannel(BaseChannel):
|
|||||||
return text
|
return text
|
||||||
return re.sub(rf"<@{re.escape(self._bot_user_id)}>\s*", "", text).strip()
|
return re.sub(rf"<@{re.escape(self._bot_user_id)}>\s*", "", text).strip()
|
||||||
|
|
||||||
# Markdown → Slack mrkdwn formatting rules (order matters: longest markers first)
|
|
||||||
_MD_TO_SLACK = (
|
|
||||||
(r'(?m)(^|[^\*])\*\*\*(.+?)\*\*\*([^\*]|$)', r'\1*_\2_*\3'), # ***bold italic***
|
|
||||||
(r'(?m)(^|[^_])___(.+?)___([^_]|$)', r'\1*_\2_*\3'), # ___bold italic___
|
|
||||||
(r'(?m)(^|[^\*])\*\*(.+?)\*\*([^\*]|$)', r'\1*\2*\3'), # **bold**
|
|
||||||
(r'(?m)(^|[^_])__(.+?)__([^_]|$)', r'\1*\2*\3'), # __bold__
|
|
||||||
(r'(?m)(^|[^\*])\*(.+?)\*([^\*]|$)', r'\1_\2_\3'), # *italic*
|
|
||||||
(r'(?m)(^|[^~])~~(.+?)~~([^~]|$)', r'\1~\2~\3'), # ~~strike~~
|
|
||||||
(r'(?m)(^|[^!])\[(.+?)\]\((http.+?)\)', r'\1<\3|\2>'), # [text](url)
|
|
||||||
(r'!\[.+?\]\((http.+?)(?:\s".*?")?\)', r'<\1>'), # 
|
|
||||||
)
|
|
||||||
_TABLE_RE = re.compile(r'(?m)^\|.*?\|$(?:\n(?:\|\:?-{3,}\:?)*?\|$)(?:\n\|.*?\|$)*')
|
|
||||||
|
|
||||||
def _convert_markdown(self, text: str) -> str:
|
|
||||||
"""Convert standard Markdown to Slack mrkdwn format."""
|
|
||||||
if not text:
|
|
||||||
return text
|
|
||||||
for pattern, repl in self._MD_TO_SLACK:
|
|
||||||
text = re.sub(pattern, repl, text)
|
|
||||||
return self._TABLE_RE.sub(self._convert_table, text)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _convert_table(match: re.Match) -> str:
|
|
||||||
"""Convert Markdown table to Slack quote + bullet format."""
|
|
||||||
lines = [l.strip() for l in match.group(0).strip().split('\n') if l.strip()]
|
|
||||||
if len(lines) < 2:
|
|
||||||
return match.group(0)
|
|
||||||
|
|
||||||
headers = [h.strip() for h in lines[0].strip('|').split('|')]
|
|
||||||
start = 2 if not re.search(r'[^|\-\s:]', lines[1]) else 1
|
|
||||||
|
|
||||||
result: list[str] = []
|
|
||||||
for line in lines[start:]:
|
|
||||||
cells = [c.strip() for c in line.strip('|').split('|')]
|
|
||||||
cells = (cells + [''] * len(headers))[:len(headers)]
|
|
||||||
if not any(cells):
|
|
||||||
continue
|
|
||||||
result.append(f"> *{headers[0]}*: {cells[0] or '--'}")
|
|
||||||
for i, cell in enumerate(cells[1:], 1):
|
|
||||||
if cell and i < len(headers):
|
|
||||||
result.append(f" \u2022 *{headers[i]}*: {cell}")
|
|
||||||
result.append("")
|
|
||||||
return '\n'.join(result).rstrip()
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@ dependencies = [
|
|||||||
"python-socketio>=5.11.0",
|
"python-socketio>=5.11.0",
|
||||||
"msgpack>=1.0.8",
|
"msgpack>=1.0.8",
|
||||||
"slack-sdk>=3.26.0",
|
"slack-sdk>=3.26.0",
|
||||||
|
"slackify-markdown>=0.2.0",
|
||||||
"qq-botpy>=1.0.0",
|
"qq-botpy>=1.0.0",
|
||||||
"python-socks[asyncio]>=2.4.0",
|
"python-socks[asyncio]>=2.4.0",
|
||||||
"prompt-toolkit>=3.0.0",
|
"prompt-toolkit>=3.0.0",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user