Skip to main content

Minimum viable chat components

Project description

chatom chatom

chatom

Framework-agnostic chat application models and utilities

Build Status codecov License PyPI

Overview

chatom provides a unified, framework-agnostic representation of chat applications. It offers:

  • Base models for users, channels, messages, attachments, embeds, reactions, and more
  • Backend-specific implementations for Discord, Slack, Symphony, and Telegram
  • Rich text formatting with nodes for bold, italic, code, tables, lists, and more
  • Format converters to render messages as plaintext, markdown, HTML, Slack mrkdwn, Discord markdown, or Symphony MessageML

Installation

pip install chatom

Quick Start

Basic Models

from chatom import User, Channel, Message, Emoji, Reaction

# Create a user
user = User(id="u123", name="Alice", email="alice@example.com")

# Create a channel
channel = Channel(id="c456", name="general", topic="General discussion")

# Create a message with reactions
emoji = Emoji(name="thumbsup", unicode="👍")
reaction = Reaction(emoji=emoji, count=5)

message = Message(
    id="m789",
    content="Hello, world!",
    author=user,
    channel=channel,
    reactions=[reaction],
)

Rich Text Formatting

from chatom import (
    Format,
    Text,
    Bold,
    Italic,
    Paragraph,
    Span,
    Table,
    FormattedMessage,
    MessageBuilder,
)

# Build a formatted message using nodes
msg = FormattedMessage(
    content=[
        Paragraph(children=[
            Text(content="Welcome to "),
            Bold(child=Text(content="chatom")),
            Text(content="!"),
        ]),
    ]
)

# Render to different formats
print(msg.render(Format.MARKDOWN))      # "Welcome to **chatom**!\n"
print(msg.render(Format.HTML))          # "<p>Welcome to <strong>chatom</strong>!</p>"
print(msg.render(Format.SLACK_MARKDOWN)) # "Welcome to *chatom*!\n"

Tables

from chatom import Table, Format

# Create a table from data
data = [
    ["Alice", "100", "Gold"],
    ["Bob", "85", "Silver"],
    ["Carol", "70", "Bronze"],
]
table = Table.from_data(data, headers=["Name", "Score", "Rank"])

# Render as markdown
print(table.render(Format.MARKDOWN))
# | Name | Score | Rank |
# |---|---|---|
# | Alice | 100 | Gold |
# | Bob | 85 | Silver |
# | Carol | 70 | Bronze |

# Render as HTML
print(table.render(Format.HTML))
# <table><thead>...</thead><tbody>...</tbody></table>

Backend-Specific Models

Each backend provides specialized models with platform-specific fields:

# Discord
from chatom.discord import (
    DiscordUser,
    DiscordChannel,
    DiscordChannelType,
    DiscordPresence,
    mention_user,
    mention_channel,
    mention_role,
    mention_everyone,
    mention_here,
)

user = DiscordUser(
    id="123456789",
    name="Alice",
    discriminator="1234",
    is_bot=False,
)
print(mention_user(user))  # "<@123456789>"

channel = DiscordChannel(
    id="987654321",
    name="general",
    channel_type=DiscordChannelType.GUILD_TEXT,
)
print(mention_channel(channel))  # "<#987654321>"

# Slack
from chatom.slack import (
    SlackUser,
    SlackChannel,
    SlackPresence,
    mention_user,
    mention_channel,
    mention_user_group,
    mention_here,
    mention_channel_all,
    mention_everyone,
)

user = SlackUser(
    id="U123456",
    name="alice",
    real_name="Alice Smith",
    team_id="T123",
)
print(mention_user(user))  # "<@U123456>"

# Symphony
from chatom.symphony import (
    SymphonyUser,
    SymphonyChannel,
    SymphonyStreamType,
    mention_user,
    format_hashtag,
    format_cashtag,
)

user = SymphonyUser(id="123", name="alice", user_id=12345)
print(mention_user(user))  # '<mention uid="12345"/>'
print(format_hashtag("python"))  # '<hash tag="python"/>'
print(format_cashtag("AAPL"))  # '<cash tag="AAPL"/>'

# Telegram
from chatom.telegram import (
    TelegramUser,
    TelegramChannel,
    TelegramChatType,
    TelegramPresence,
    mention_user,
    mention_channel,
)

user = TelegramUser(id="123456", name="Alice", username="alice")
print(mention_user(user))  # "@alice"

channel = TelegramChannel(id="-1001234567890", name="General")
print(mention_channel(channel))  # "#General"

Polymorphic Mentions

The mention_user and mention_channel functions use singledispatch to automatically route to the correct backend implementation:

from chatom import mention_user
from chatom.discord import DiscordUser
from chatom.slack import SlackUser
from chatom.telegram import TelegramUser

discord_user = DiscordUser(id="123", name="alice")
slack_user = SlackUser(id="U123", name="alice")
telegram_user = TelegramUser(id="789", name="alice", username="alice")

print(mention_user(discord_user))  # "<@123>"
print(mention_user(slack_user))    # "<@U123>"
print(mention_user(telegram_user)) # "@alice"

Backend-Agnostic Mentions

Use mention_user_for_backend and mention_channel_for_backend when you have a base User or Channel object and want to format it for a specific backend:

from chatom import User, Channel, mention_user_for_backend, mention_channel_for_backend

user = User(id="123", name="Alice", email="alice@example.com")
channel = Channel(id="C456", name="general")

# Mention for different backends
print(mention_user_for_backend(user, "discord"))   # "<@123>"
print(mention_user_for_backend(user, "slack"))     # "<@123>"
print(mention_user_for_backend(user, "symphony"))  # '<mention uid="123"/>'
print(mention_user_for_backend(user, "telegram"))  # "@Alice"

print(mention_channel_for_backend(channel, "discord"))  # "<#C456>"
print(mention_channel_for_backend(channel, "slack"))    # "<#C456>"

Rendering Messages for a Backend

Use render_for to render a formatted message using the appropriate format for a backend:

from chatom import FormattedMessage, Paragraph, Text, Bold, get_format_for_backend

msg = FormattedMessage(
    content=[
        Paragraph(children=[
            Text(content="Hello, "),
            Bold(child=Text(content="world")),
            Text(content="!"),
        ]),
    ]
)

# Render for different backends
print(msg.render_for("slack"))     # "Hello, *world*!\n" (Slack mrkdwn)
print(msg.render_for("discord"))   # "Hello, **world**!\n" (Discord markdown)
print(msg.render_for("symphony"))  # "<p>Hello, <b>world</b>!</p>" (MessageML)
print(msg.render_for("telegram"))  # "Hello, <b>world</b>!" (HTML)

# Get the format for a backend
from chatom import BACKEND_FORMAT_MAP
print(BACKEND_FORMAT_MAP["slack"])  # Format.SLACK_MARKDOWN

Type Conversion

Convert between base types and backend-specific types with validation:

from chatom import (
    User,
    promote,
    demote,
    can_promote,
    validate_for_backend,
    DISCORD,
    SLACK,
)
from chatom.discord import DiscordUser

# Create a base user
user = User(id="123", name="Alice", handle="alice")

# Check if it can be promoted
if can_promote(user, DISCORD):
    # Promote to DiscordUser with extra fields
    discord_user = promote(user, DISCORD, discriminator="1234")
    print(discord_user.discriminator)  # "1234"
    print(type(discord_user))  # <class 'chatom.discord.user.DiscordUser'>

# Demote back to base User
base_user = demote(discord_user)
print(type(base_user))  # <class 'chatom.base.user.User'>

# Cross-backend conversion: Discord -> Slack
slack_user = promote(demote(discord_user), SLACK, team_id="T123")
print(slack_user.team_id)  # "T123"

Backend Capabilities

from chatom import (
    Capability,
    DISCORD_CAPABILITIES,
    SLACK_CAPABILITIES,
    SYMPHONY_CAPABILITIES,
    TELEGRAM_CAPABILITIES,
)

# Check what a backend supports
print(DISCORD_CAPABILITIES.supports(Capability.THREADS))        # True
print(DISCORD_CAPABILITIES.supports(Capability.VOICE_CHAT))     # True
print(DISCORD_CAPABILITIES.supports(Capability.EMOJI_REACTIONS)) # True

Presence and Status

from chatom import Presence, PresenceStatus

presence = Presence(
    status=PresenceStatus.ONLINE,
    status_text="Working on chatom",
    activity="Coding",
)

print(presence.is_available)  # True
print(presence.is_online)     # True

Connections and Registries

The Connection base class provides a foundation for backend connections, along with UserRegistry and ChannelRegistry for managing and looking up users and channels:

from chatom import Connection, UserRegistry, ChannelRegistry, User, Channel, LookupError

# Create registries for managing users and channels
users = [
    User(id="u1", name="Alice", email="alice@example.com", handle="alice"),
    User(id="u2", name="Bob", email="bob@example.com", handle="bob"),
]
user_registry = UserRegistry(users=users)

# Look up users by different fields
print(user_registry.id_to_user("u1").name)         # "Alice"
print(user_registry.email_to_user("bob@example.com").name)  # "Bob"
print(user_registry.name_to_user("Alice").id)      # "u1"
print(user_registry.handle_to_user("alice").email) # "alice@example.com"

# Reverse lookups
print(user_registry.user_to_id(users[0]))          # "u1"
print(user_registry.user_to_email(users[0]))       # "alice@example.com"

# Generic lookup - searches all fields
print(user_registry.lookup("alice").id)            # "u1" (matches handle)
print(user_registry.lookup("bob@example.com").id)  # "u2" (matches email)

# Check if a user exists
print(user_registry.get_user("u1") is not None)    # True
print(user_registry.get_user("unknown"))           # None (no exception)

# Same pattern for channels
channels = [
    Channel(id="c1", name="general", topic="General discussion"),
    Channel(id="c2", name="random"),
]
channel_registry = ChannelRegistry(channels=channels)

print(channel_registry.id_to_channel("c1").name)   # "general"
print(channel_registry.name_to_channel("random").id) # "c2"
print(channel_registry.lookup("general").topic)    # "General discussion"

Subclass Connection to implement backend-specific connection logic:

from chatom import Connection, UserRegistry, ChannelRegistry

class MyBackendConnection(Connection):
    """Custom connection implementation."""

    async def connect(self):
        # Establish connection to your backend
        pass

    async def disconnect(self):
        # Clean up connection
        pass

    async def fetch_users(self) -> UserRegistry:
        # Fetch and return users from the backend
        users = await self._fetch_users_from_api()
        return UserRegistry(users=users)

    async def fetch_channels(self) -> ChannelRegistry:
        # Fetch and return channels from the backend
        channels = await self._fetch_channels_from_api()
        return ChannelRegistry(channels=channels)

Supported Backends

Backend User Model Channel Model Mention Support Presence
Discord DiscordUser DiscordChannel DiscordPresence
Slack SlackUser SlackChannel SlackPresence
Symphony SymphonyUser SymphonyChannel SymphonyPresence
Telegram TelegramUser TelegramChannel TelegramPresence

Output Formats

Format Enum Value Description
Plaintext Format.PLAINTEXT Plain text with no formatting
Markdown Format.MARKDOWN Standard Markdown
Slack mrkdwn Format.SLACK_MARKDOWN Slack's mrkdwn format
Discord Markdown Format.DISCORD_MARKDOWN Discord-flavored Markdown
HTML Format.HTML Standard HTML
Symphony MessageML Format.SYMPHONY_MESSAGEML Symphony's XML-based format

Text Node Types

Node Description Example Output (Markdown)
Text Plain text Hello
Bold Bold text **Hello**
Italic Italic text *Hello*
Strikethrough Strikethrough ~~Hello~~
Underline Underlined text <u>Hello</u> (HTML only)
Code Inline code `code`
CodeBlock Code block ```python\ncode\n```
Link Hyperlink [text](url)
Quote Block quote > quoted text
Heading Heading (h1-h6) # Heading
Paragraph Paragraph Content with newline
UnorderedList Bullet list - item
OrderedList Numbered list 1. item
Table Data table Markdown/HTML table
UserMention User mention @user or <@id>
ChannelMention Channel mention #channel

Development

# Clone the repository
git clone https://github.com/1kbgz/chatom.git
cd chatom

# Install development dependencies
make develop

# Run tests
make test

# Run linting
make lint

License

Apache License 2.0 - see LICENSE for details.

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

chatom-0.1.4.tar.gz (347.7 kB view details)

Uploaded Source

Built Distribution

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

chatom-0.1.4-py3-none-any.whl (424.0 kB view details)

Uploaded Python 3

File details

Details for the file chatom-0.1.4.tar.gz.

File metadata

  • Download URL: chatom-0.1.4.tar.gz
  • Upload date:
  • Size: 347.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for chatom-0.1.4.tar.gz
Algorithm Hash digest
SHA256 47642366513bc766824acdc64f1a3d01741f622cb295cfdb9a9329d9f67c88c1
MD5 f2fd54fefb82561b6cd00dc997caf139
BLAKE2b-256 a061e05fd2b2c20bc6935fd80e4fc3111776b414014e19dbd6d0848df904ca4d

See more details on using hashes here.

File details

Details for the file chatom-0.1.4-py3-none-any.whl.

File metadata

  • Download URL: chatom-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 424.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for chatom-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 dba8a61f4d4a93ae0f5b41cc65445b3e8c8ebb7dbc6ccabdbc5b182628f10959
MD5 4d9c9fdf169a4fcf604e984a8eb3705a
BLAKE2b-256 527c377f47cf0ff7a0d38a46791944157812aa42231c7e2f0e48c3be6decd765

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