Skip to main content

Convert between Slack message formats (Mrkdwn, Rich Text) and GitHub Flavored Markdown with AST manipulation

Project description

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 \n characters 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:

  1. Bold - *bold* (mrkdwn) or {"style": {"bold": true}} (rich text)
  2. Italic - _italic_ (mrkdwn) or {"style": {"italic": true}} (rich text)
  3. Strikethrough - ~strike~ (mrkdwn) or {"style": {"strike": true}} (rich text)
  4. Inline Code - `code` (mrkdwn) or {"style": {"code": true}} (rich text)
  5. Code Block - ```code``` (mrkdwn) or {"type": "rich_text_preformatted"} (rich text)
  6. Links - <url|text> (mrkdwn) or {"type": "link"} (rich text)
  7. Quotes - &gt; quote (mrkdwn) or {"type": "rich_text_quote"} (rich text)
  8. Bullet Lists - • item (mrkdwn) or {"type": "rich_text_list", "style": "bullet"} (rich text)
  9. 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

slack_gfm-0.2.1.tar.gz (157.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

slack_gfm-0.2.1-py3-none-any.whl (41.5 kB view details)

Uploaded Python 3

File details

Details for the file slack_gfm-0.2.1.tar.gz.

File metadata

  • Download URL: slack_gfm-0.2.1.tar.gz
  • Upload date:
  • Size: 157.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for slack_gfm-0.2.1.tar.gz
Algorithm Hash digest
SHA256 ee92a2a6b4259bf7f0bc9afd108b54058817fa2f80df5029a8ed0d2482c53fda
MD5 08414ded58c78a0cccdd4757c406879e
BLAKE2b-256 2bfc6307370d52966d1b9bd5385ec3be189159ac00ce075e2ed365ae1334ddc5

See more details on using hashes here.

File details

Details for the file slack_gfm-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: slack_gfm-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 41.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for slack_gfm-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8ef09c2c629547ba10f7057bb3b5aa2657411d24c874af505107ade21d895abb
MD5 7d601872123592b0232d608a27cf0241
BLAKE2b-256 dbe3abc0495108f613ce0ece82b24f825d715e32aad9711f83b0a4ea467c0e1c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page