Metadata-Version: 2.4
Name: slack-markdown-parser
Version: 2.2.4
Summary: Convert LLM Markdown into Slack Block Kit markdown/table messages
Author: darkgaldragon
License-Expression: MIT
Project-URL: Homepage, https://github.com/darkgaldragon/slack-markdown-parser
Project-URL: Source, https://github.com/darkgaldragon/slack-markdown-parser
Project-URL: Issues, https://github.com/darkgaldragon/slack-markdown-parser/issues
Keywords: slack,markdown,block-kit,table,llm
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: black>=24.0.0; extra == "dev"
Requires-Dist: build>=1.2.0; extra == "dev"
Requires-Dist: pip-audit>=2.7.0; extra == "dev"
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
Requires-Dist: ruff>=0.6.0; extra == "dev"
Requires-Dist: twine>=5.1.0; extra == "dev"
Dynamic: license-file

# slack-markdown-parser

`slack-markdown-parser` is a Python library that converts standard Markdown generated by LLMs into Slack Block Kit messages built from `markdown` and `table` blocks.

## Why this library exists

Many Slack AI bots have traditionally converted model output into Slack-specific `mrkdwn`, but that approach creates a few recurring problems:

- Conversion overhead: LLMs naturally generate standard Markdown, so `mrkdwn` usually requires extra transformation logic or prompt constraints.
- Unstable formatting in languages without word spacing: in Japanese and similar scripts, Slack can fail to interpret `*`, `~`, and related markers correctly, exposing the raw punctuation.
- No table syntax in `mrkdwn`: Markdown tables need custom fallback rendering if you stay in the old format.

## Design approach

This library leans on Slack Block Kit's `markdown` block for standard Markdown and `table` block for tables.

| Problem | Approach |
|---|---|
| Conversion overhead | Send standard Markdown through Slack `markdown` blocks without rewriting it into `mrkdwn`. |
| Formatting instability | Prefer zero-width spaces (ZWSP, U+200B) around formatting tokens, and fall back to locale-aware visible-space padding for CJK nested inline-code cases where Slack rendering still breaks. |
| No table syntax in `mrkdwn` | Detect Markdown tables and convert them into Slack `table` blocks, including repair of common LLM-generated table inconsistencies. |

The target is natural rendering on Slack, not full CommonMark or HTML fidelity.
If Slack itself does not support a construct in `markdown` blocks, this library prefers safe plain-text rendering or explicit `table` blocks over aggressive rewrites into old `mrkdwn`.

## Features

- Convert standard Markdown into Slack `markdown` blocks
- Convert Markdown tables into Slack `table` blocks
- Repair common LLM table issues such as missing outer pipes, missing separator rows, mismatched column counts, and empty cells
- Split output into multiple Slack messages when needed to satisfy Slack's "one table per message" constraint
- Sanitize ANSI/control characters and neutralize invalid Slack angle-bracket tokens before block generation
- Add ZWSP around inline formatting tokens to reduce rendering issues outside fenced code blocks
- Use locale-aware visible-space padding for nested inline-code emphasis in dense Japanese, Chinese, and Korean text when Slack requires stronger boundaries than ZWSP alone
- Support Markdown and Slack-style links inside table cells
- Build fallback text for `chat.postMessage.text` from generated blocks, normalizing synthetic ZWSP and any parser-inserted visible-space padding used only for rendering stability
- Accept raw LLM Markdown without tightly constraining the model prompt, using best-effort sanitize and table repair before Slack delivery

## Observed Slack behavior

The library is built around how Slack actually renders `markdown` and `table` blocks in practice.

Reliable in current Slack rendering:

- `**bold**`, `*italic*`, `~~strike~~`, inline code, and fenced code blocks
- Bare URLs, autolinks, Markdown links, reference-style links, and mailto links
- Bullet lists, ordered lists, task lists, and simple blockquotes
- Explicit Slack `table` blocks generated from Markdown tables

Known Slack-side limitations:

- Heading syntax (`#`, setext headings) renders as plain text rather than true heading levels
- Nested blockquotes are weak compared with full Markdown renderers
- Horizontal rules render more like line text than semantic separators
- Markdown image syntax does not become an embedded image in `markdown` blocks
- Math, raw HTML, HTML comments, `<details>`, admonition syntax, and Mermaid are rendered as plain text or code, not as rich features

What this library compensates for:

- Normalizes underscore emphasis (`_..._`, `__...__`) into Slack-friendly asterisk emphasis
- Wraps bare URLs into Slack-friendly autolink form before sending `markdown` blocks
- Repairs malformed LLM-generated tables before converting them into Slack `table` blocks
- Keeps table-like rows inside fenced code blocks out of table normalization
- Neutralizes invalid Slack angle-bracket tokens such as raw HTML-like tags

## Requirements

- Your Slack integration must support Block Kit payloads with `markdown` and `table` blocks.
- This library does not help when your delivery path only accepts plain `text` or `mrkdwn` strings.

## Installation

```bash
pip install slack-markdown-parser
```

## Quick start

```python
from slack_markdown_parser import (
    convert_markdown_to_slack_payloads,
)

markdown = """
# Weekly Report

| Team | Status |
|---|---|
| API | **On track** |
| UI | *In progress* |
"""

for payload in convert_markdown_to_slack_payloads(markdown):
    print(payload)
```

`convert_markdown_to_slack_messages` automatically splits output into multiple messages when the input contains multiple tables.

## Rendering example

Example input:

````markdown
# Weekly Product Update

This week we worked on **search performance** and *UI polish*. The old flow is ~~scheduled for removal~~.
The detailed log ID is `run-20260305-02`.
Reference: https://example.com/changelog

- Improved **API response time**
  - Increased *cache hit rate*
  - Adjusted timeout settings
- Stabilized batch processing
  - Unified retry counts
- Updated documentation

Category | Status | Owner
API | **In progress** | Team A
UI | *Under review* | Team B
QA | ~~On hold~~ | Team C

> Note: production rollout is scheduled for 2026-03-08 10:00 JST

1. Finalize release notes
   1. Unify change labels
   2. Add impact notes
2. Tune monitoring alert thresholds
   1. Update the `warning` threshold
3. Re-check QA

```bash
./deploy.sh production
```
````

Example Slack bot rendering (`markdown` + `table` blocks):

![Slack BOT rendering example](Example_en.png)

## Public API

### Main functions

| Function | Description |
|---|---|
| `convert_markdown_to_slack_messages(markdown_text) -> list[list[dict]]` | Convert Markdown into Slack messages already split around table blocks. |
| `convert_markdown_to_slack_payloads(markdown_text) -> list[dict]` | Convert Markdown into Slack-ready payloads with both `blocks` and fallback `text`. |
| `convert_markdown_to_slack_blocks(markdown_text) -> list[dict]` | Convert Markdown into a flat Block Kit block list. |
| `build_fallback_text_from_blocks(blocks) -> str` | Build fallback text suitable for `chat.postMessage.text`. |
| `blocks_to_plain_text(blocks) -> str` | Convert blocks into plain text. |

### Utility functions

| Function | Description |
|---|---|
| `normalize_markdown_tables(markdown_text) -> str` | Normalize Markdown table syntax before conversion. |
| `add_zero_width_spaces_to_markdown(text) -> str` | Insert ZWSP around formatting tokens where Slack needs stronger boundaries. |
| `decode_html_entities(text) -> str` | Decode HTML entities before parsing. |
| `sanitize_slack_text(text) -> str` | Remove ANSI/control noise and neutralize invalid Slack angle-bracket tokens. |
| `strip_zero_width_spaces(text) -> str` | Remove ZWSP (`U+200B`) and BOM (`U+FEFF`) while preserving join-control characters such as ZWJ. |

### Lower-level exported helpers

These are also part of the public package surface:

- `add_zero_width_spaces`
- `convert_markdown_text_to_blocks`
- `extract_plain_text_from_table_cell`
- `markdown_table_to_slack_table`
- `parse_markdown_table`
- `split_blocks_by_table`
- `split_markdown_into_segments`

## Specification and scope

- Behavior spec: [docs/spec.md](docs/spec.md)
- Japanese behavior spec: [docs/spec-ja.md](docs/spec-ja.md)
- Slack render-test workflow: [docs/slack-render-test-workflow.md](docs/slack-render-test-workflow.md)
- Nested-modifier findings: [docs/slack-nested-modifier-findings.md](docs/slack-nested-modifier-findings.md)
- Desktop/mobile manual checklist: [docs/slack-client-manual-checklist.md](docs/slack-client-manual-checklist.md)
- Non-goals:
  - Generating Slack `mrkdwn` strings
  - Supporting clients or MCP tools that can only send `mrkdwn`

## Contributing

Contributions, bug reports, and documentation improvements are welcome.
Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening an issue or pull request.

## Changelog

Version history is maintained in [CHANGELOG.md](CHANGELOG.md).

## Contact

- GitHub Issues / Pull Requests
- X: [@darkgaldragon](https://x.com/darkgaldragon)

## License

MIT
