slack-gfm
Convert between Slack message formats (Mrkdwn, Rich Text) and GitHub Flavored Markdown with AST manipulation.
TL;DR - Quick Start
from slack_gfm import rich_text_to_gfm, gfm_to_rich_text, mrkdwn_to_gfm
# Convert Slack Rich Text to GitHub Flavored Markdown
rich_text = {
"type": "rich_text",
"elements": [{
"type": "rich_text_section",
"elements": [
{"type": "text", "text": "Hello "},
{"type": "user", "user_id": "U123ABC"}
]
}]
}
gfm = rich_text_to_gfm(rich_text)
# Result: "Hello [@U123ABC](slack://user?id=U123ABC)"
# Convert back to Rich Text
rich_text = gfm_to_rich_text(gfm)
# Migrate legacy mrkdwn to GFM
mrkdwn = "*Hello* <@U123ABC|john>"
gfm = mrkdwn_to_gfm(mrkdwn)
# Result: "**Hello** [@john](slack://user?id=U123ABC&name=john)"
Installation
pip install --user slack-gfm
Or with uv:
uv add slack-gfm
Features
- Rich Text <-> GFM: Bidirectional conversion with full round-trip support
- Mrkdwn -> GFM: Migrate legacy Slack messages
- ID Mapping: Map Slack user/channel IDs to display names
- AST Manipulation: Transform messages for ML/MCP or custom processing
- Type Safe: Full type hints for Python 3.12+
Usage
Basic Conversions
from slack_gfm import rich_text_to_gfm, gfm_to_rich_text
# Rich Text -> GFM (most common use case)
gfm_text = rich_text_to_gfm(rich_text_data)
# GFM -> Rich Text (for creating Slack messages)
rich_text = gfm_to_rich_text(gfm_text)
ID Mapping
Map Slack IDs to human-readable names:
from slack_gfm import rich_text_to_gfm
gfm = rich_text_to_gfm(
rich_text_data,
user_map={"U123ABC": "john", "U456DEF": "jane"},
channel_map={"C789GHI": "general"}
)
# User mentions become: [@john](slack://user?id=U123ABC&name=john)
# Channel mentions become: [#general](slack://channel?id=C789GHI&name=general)
Advanced: AST Manipulation
For ML training, MCP context, or custom transformations:
from slack_gfm import parse_rich_text, render_gfm
from slack_gfm.ast import NodeVisitor, transform_ast, UserMention, CodeBlock
# Define custom visitor for ML feature extraction
class MLFeatureExtractor(NodeVisitor):
def __init__(self):
self.features = {"users": [], "code_blocks": []}
def visit_usermention(self, node: UserMention):
self.features["users"].append(node.user_id)
return node
def visit_codeblock(self, node: CodeBlock):
# Detect JSON in code blocks
if node.content.strip().startswith("{"):
self.features["code_blocks"].append({
"type": "json",
"content": node.content
})
return node
# Parse and extract features
ast = parse_rich_text(rich_text_data)
extractor = MLFeatureExtractor()
ast = transform_ast(ast, extractor)
# Use features for ML
print(extractor.features)
# {"users": ["U123", "U456"], "code_blocks": [{"type": "json", ...}]}
# Still render to GFM
gfm = render_gfm(ast)
See docs/user-guide.md for more examples and docs/api-reference.md for complete API documentation.
How It Works
slack-gfm uses a common Abstract Syntax Tree (AST) to represent formatted text:
Slack Rich Text -> Parser -> AST -> Renderer -> GFM
^ |
| v
+-------- Renderer <- AST <- Parser <- GFM
Slack Mrkdwn -> Parser -> AST -> Renderer -> GFM
Slack-specific features (user mentions, channel mentions, broadcasts) are preserved using custom slack:// URLs in GFM, enabling perfect round-trip conversion.
Format Differences and Limitations
Rich Text vs Mrkdwn Whitespace Handling
Slack's two formats handle whitespace differently when converting to GFM:
- Rich Text: Preserves literal
\ncharacters within paragraphs as they appear in the JSON structure - Mrkdwn: Follows Markdown conventions, converting single newlines to spaces (only double newlines create new paragraphs)
Example:
# Rich text with embedded newline
rich_text = {
"type": "rich_text",
"elements": [{
"type": "rich_text_section",
"elements": [{"type": "text", "text": "Line 1\nLine 2"}]
}]
}
# Mrkdwn equivalent
mrkdwn = "Line 1\nLine 2"
# Converting to GFM produces slightly different results:
rich_text_to_gfm(rich_text) # "Line 1\nLine 2" (preserves newline)
mrkdwn_to_gfm(mrkdwn) # "Line 1 Line 2" (converts to space)
Both render identically in most Markdown viewers (single newlines don't create line breaks), but the raw GFM strings differ. This is expected behavior, not a bug.
Slack Compose Bar Elements
This library has been tested against real Slack messages using all 9 compose bar formatting elements:
- Bold -
*bold*(mrkdwn) or{"style": {"bold": true}}(rich text) - Italic -
_italic_(mrkdwn) or{"style": {"italic": true}}(rich text) - Strikethrough -
~strike~(mrkdwn) or{"style": {"strike": true}}(rich text) - Inline Code -
`code`(mrkdwn) or{"style": {"code": true}}(rich text) - Code Block -
```code```(mrkdwn) or{"type": "rich_text_preformatted"}(rich text) - Links -
<url|text>(mrkdwn) or{"type": "link"}(rich text) - Quotes -
> quote(mrkdwn) or{"type": "rich_text_quote"}(rich text) - Bullet Lists -
• item(mrkdwn) or{"type": "rich_text_list", "style": "bullet"}(rich text) - Numbered Lists -
1. item(mrkdwn) or{"type": "rich_text_list", "style": "ordered"}(rich text)
All elements convert correctly to GFM and support round-trip conversion.
Development
# Clone the repository
git clone https://github.com/retcheverry/slack-gfm.git
cd slack-gfm
# Install dependencies
uv sync
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=slack_gfm --cov-report=html
Requirements
- Python 3.12+
- markdown-it-py >= 3.0.0
License
AGPL-3.0-or-later
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
Links
Metadata
Release files for slack-gfm 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| slack_gfm-0.2.1.tar.gz | 157.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| slack_gfm-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 199.1 kB
Release files / slack_gfm-0.2.1.tar.gz
| Download URL | slack_gfm-0.2.1.tar.gz |
|---|---|
| Size | 157.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ee92a2a6b4259bf7f0bc9afd108b54058817fa2f80df5029a8ed0d2482c53fda
|
|
BLAKE2b-256 checksum How to use checksums |
2bfc6307370d52966d1b9bd5385ec3be189159ac00ce075e2ed365ae1334ddc5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|
Release files / slack_gfm-0.2.1-py3-none-any.whl
| Download URL | slack_gfm-0.2.1-py3-none-any.whl |
|---|---|
| Size | 41.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8ef09c2c629547ba10f7057bb3b5aa2657411d24c874af505107ade21d895abb
|
|
BLAKE2b-256 checksum How to use checksums |
dbe3abc0495108f613ce0ece82b24f825d715e32aad9711f83b0a4ea467c0e1c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|