Advanced Features

This guide covers advanced chatom features including reactions, attachments, embeds, threads, and presence management.

Reactions

Reactions are emoji responses to messages. Support varies by platform.

Adding Reactions

# Add a Unicode emoji reaction
await backend.add_reaction(
    channel_id="C123456",
    message_id="msg_id",
    emoji="👍",
)

# Add a custom emoji (Discord, Slack)
await backend.add_reaction(
    channel_id="C123456",
    message_id="msg_id",
    emoji="custom_emoji_name",
)

Removing Reactions

await backend.remove_reaction(
    channel_id="C123456",
    message_id="msg_id",
    emoji="👍",
)

Reading Reactions from Messages

messages = await backend.fetch_messages(channel_id)

for message in messages:
    for reaction in message.reactions:
        print(f"Emoji: {reaction.emoji.name}")
        print(f"Unicode: {reaction.emoji.unicode}")
        print(f"Count: {reaction.count}")
        print(f"Custom: {reaction.emoji.is_custom}")

Reaction Models

from chatom import Emoji, Reaction

# Create an emoji
emoji = Emoji(
    name="thumbsup",
    unicode="👍",
    is_custom=False,
)

# Create a reaction
reaction = Reaction(
    emoji=emoji,
    count=5,
    me=True,  # Did the current user react?
)

Platform Support

Backend

Unicode

Custom

Add

Remove

Discord

Slack

Symphony

Reaction Events

Handle reaction events (backend-specific):

# Example event handler pattern
async def on_reaction_add(event):
    print(f"User {event.user_id} added {event.emoji}")
    print(f"Message: {event.message_id}")
    print(f"Channel: {event.channel_id}")

# Reaction event model
from chatom import ReactionEvent, ReactionEventType

event = ReactionEvent(
    type=ReactionEventType.ADD,  # or REMOVE
    message_id="msg_123",
    channel_id="C123456",
    user_id="U789",
    emoji=Emoji(name="thumbsup", unicode="👍"),
)

Attachments

Attachments are files sent with messages.

Attachment Model

from chatom import Attachment, AttachmentType, File, Image

# Generic attachment
attachment = Attachment(
    id="att_123",
    filename="document.pdf",
    url="https://example.com/files/document.pdf",
    content_type="application/pdf",
    size=1024000,  # bytes
    attachment_type=AttachmentType.FILE,
)

# File attachment (with additional metadata)
file = File(
    id="file_123",
    filename="report.xlsx",
    url="https://example.com/files/report.xlsx",
    content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    size=512000,
)

# Image attachment
image = Image(
    id="img_123",
    filename="photo.png",
    url="https://example.com/images/photo.png",
    content_type="image/png",
    size=256000,
    width=800,
    height=600,
    thumbnail_url="https://example.com/images/photo_thumb.png",
)

Reading Attachments from Messages

messages = await backend.fetch_messages(channel_id)

for message in messages:
    for attachment in message.attachments:
        print(f"Filename: {attachment.filename}")
        print(f"Type: {attachment.content_type}")
        print(f"Size: {attachment.size} bytes")
        print(f"URL: {attachment.url}")

        # Check attachment type
        if attachment.attachment_type == AttachmentType.IMAGE:
            print(f"Dimensions: {attachment.width}x{attachment.height}")

Attachment Types

from chatom import AttachmentType

AttachmentType.FILE      # Generic file
AttachmentType.IMAGE     # Image (png, jpg, gif, etc.)
AttachmentType.VIDEO     # Video file
AttachmentType.AUDIO     # Audio file
AttachmentType.ARCHIVE   # Zip, tar, etc.
AttachmentType.DOCUMENT  # PDF, Word, etc.

Sending Attachments

Attachment sending is backend-specific:

# Discord
await backend.send_message(
    channel_id,
    "Check out this file!",
    files=["/path/to/file.pdf"],
)

# Slack
await backend.send_message(
    channel_id,
    "Here's the document",
    files=[{"file": "/path/to/file.pdf", "title": "Report"}],
)

# Symphony
await backend.send_message(
    stream_id,
    "<messageML>See attachment</messageML>",
    attachments=["/path/to/file.pdf"],
)

Platform Support

Backend

Upload

Download URL

Max Size

Discord

25MB (100MB Nitro)

Slack

Workspace dependent

Symphony

100MB


Embeds

Embeds are rich content blocks (cards) attached to messages.

Embed Model

from chatom import Embed, EmbedAuthor, EmbedField, EmbedFooter, EmbedMedia

# Create a rich embed
embed = Embed(
    title="Project Update",
    description="Here's the latest status on our project.",
    url="https://example.com/project",
    color="#3498db",  # Hex color
    timestamp=datetime.now(timezone.utc),
    author=EmbedAuthor(
        name="Alice",
        url="https://example.com/alice",
        icon_url="https://example.com/alice/avatar.png",
    ),
    thumbnail=EmbedMedia(
        url="https://example.com/project/logo.png",
        width=100,
        height=100,
    ),
    image=EmbedMedia(
        url="https://example.com/project/banner.png",
        width=800,
        height=400,
    ),
    fields=[
        EmbedField(name="Status", value="In Progress", inline=True),
        EmbedField(name="Priority", value="High", inline=True),
        EmbedField(name="Due Date", value="2024-12-31", inline=True),
        EmbedField(name="Description", value="Long description here...", inline=False),
    ],
    footer=EmbedFooter(
        text="Posted by Project Bot",
        icon_url="https://example.com/bot/icon.png",
    ),
)

Embed Components

EmbedAuthor

author = EmbedAuthor(
    name="Author Name",
    url="https://example.com/author",      # Optional link
    icon_url="https://example.com/icon.png",  # Optional icon
)

EmbedField

# Inline fields (displayed side by side)
field1 = EmbedField(name="Column 1", value="Value 1", inline=True)
field2 = EmbedField(name="Column 2", value="Value 2", inline=True)

# Full-width field
field3 = EmbedField(name="Full Width", value="This spans the full width", inline=False)

EmbedMedia

# Thumbnail (small, shown on right)
thumbnail = EmbedMedia(
    url="https://example.com/thumb.png",
    width=100,
    height=100,
)

# Main image (large, shown at bottom)
image = EmbedMedia(
    url="https://example.com/image.png",
    width=800,
    height=400,
)

EmbedFooter

footer = EmbedFooter(
    text="Footer text here",
    icon_url="https://example.com/footer-icon.png",
)

Reading Embeds from Messages

for message in messages:
    for embed in message.embeds:
        print(f"Title: {embed.title}")
        print(f"Description: {embed.description}")
        print(f"URL: {embed.url}")

        if embed.author:
            print(f"Author: {embed.author.name}")

        for field in embed.fields:
            print(f"  {field.name}: {field.value}")

Sending Embeds

# Discord - supports rich embeds
await backend.send_message(
    channel_id,
    "Check this out!",
    embeds=[embed.to_dict()],  # Convert to dict for API
)

# Slack - uses "attachments" format
slack_attachment = {
    "fallback": embed.title,
    "color": embed.color,
    "title": embed.title,
    "title_link": embed.url,
    "text": embed.description,
    "fields": [
        {"title": f.name, "value": f.value, "short": f.inline}
        for f in embed.fields
    ],
}
await backend.send_message(
    channel_id,
    "Check this out!",
    attachments=[slack_attachment],
)

Platform Support

Backend

Rich Embeds

Link Previews

Custom Colors

Discord

✅ Full

Slack

✅ (attachments)

Symphony

✅ (cards)


Threads

Threads are sub-conversations attached to a message.

Thread Model

from chatom import Thread, Message, Channel

parent_msg = Message(id="msg_456", content="Original message")
channel = Channel(id="C123456", name="general")

thread = Thread(
    id="thread_123",
    parent_message=parent_msg,
    parent_channel=channel,
    name="Discussion about feature X",
    message_count=15,
)

Sending Messages to Threads

# Reply in a thread
await backend.send_message(
    channel=channel,
    content="This is a thread reply",
    thread=thread,  # or parent message
)

Fetching Thread Messages

# Fetch messages from a thread
thread_messages = await backend.fetch_messages(
    channel=channel,
    thread=thread,
    limit=50,
)

Creating a Thread

# Start a new thread from a message (Discord)
await backend.send_message(
    channel=channel,
    content="Let's discuss this further",
    thread=parent_msg,  # Reply to start thread
)

Platform-Specific Thread Behavior

Discord

# Discord threads are first-class channels
# Thread ID is the channel ID of the thread
await backend.send_message(
    thread_id,  # Use thread ID as channel
    "Reply in Discord thread",
)

Slack

# Slack threads use thread_ts (parent message timestamp)
await backend.send_message(
    channel_id="C123456",
    content="Reply in Slack thread",
    thread_ts="1234567890.123456",  # Parent message ts
)

Platform Support

Backend

Native Threads

Reply-to

Thread Names

Discord

Slack

Symphony


Presence

Presence indicates a user’s online status and activity.

Presence Model

from chatom import Presence, PresenceStatus, Activity, ActivityType

# Basic presence
presence = Presence(
    user_id="U123456",
    status=PresenceStatus.ONLINE,
    status_text="Working on chatom",
)

# With activity
presence = Presence(
    user_id="U123456",
    status=PresenceStatus.ONLINE,
    activity=Activity(
        type=ActivityType.PLAYING,
        name="Visual Studio Code",
    ),
)

Presence Status Values

from chatom import PresenceStatus

PresenceStatus.ONLINE      # Available, active
PresenceStatus.IDLE        # Away, inactive
PresenceStatus.DND         # Do Not Disturb
PresenceStatus.OFFLINE     # Not connected
PresenceStatus.INVISIBLE   # Hidden (appears offline)
PresenceStatus.AWAY        # Explicitly away
PresenceStatus.BUSY        # Busy (Symphony)
PresenceStatus.UNKNOWN     # Status not available

Getting User Presence

# Get a user's presence
presence = await backend.get_presence("U123456")

if presence:
    print(f"Status: {presence.status}")
    print(f"Status text: {presence.status_text}")

    if presence.activity:
        print(f"Activity: {presence.activity.type} {presence.activity.name}")

    # Check convenience properties
    if presence.is_online:
        print("User is online!")
    elif presence.is_idle:
        print("User is idle")
    elif presence.is_offline:
        print("User is offline")

Setting Your Presence

# Set simple status
await backend.set_presence(
    status="online",
    status_text="Available for questions",
)

# Platform-specific options
await backend.set_presence(
    status="dnd",
    status_text="In a meeting",
    status_emoji=":calendar:",  # Slack
)

Platform-Specific Status Values

Backend

Online

Away/Idle

DND

Invisible

Discord

online

idle

dnd

invisible

Slack

auto

away

N/A

N/A

Symphony

AVAILABLE

AWAY

BUSY

OFF_WORK

Presence Heartbeat

For long-running bots, use the presence heartbeat:

# Start automatic presence updates
backend.start_presence_heartbeat(
    interval_seconds=60,  # Update every 60 seconds
    status="online",
    status_text="Bot is running",
)

# ... bot runs ...

# Stop when done
backend.stop_presence_heartbeat()

Activity Types

from chatom import ActivityType

ActivityType.PLAYING     # Playing a game
ActivityType.STREAMING   # Streaming content
ActivityType.LISTENING   # Listening to music
ActivityType.WATCHING    # Watching something
ActivityType.CUSTOM      # Custom status
ActivityType.COMPETING   # Competing in something

Setting Activity (Discord)

# Discord supports rich activity
await backend.set_presence(
    status="online",
    activity_type="playing",
    activity_name="with the chatom API",
)

Platform Support

Backend

Get

Set

Status Text

Activity

Discord

Slack

Symphony


User and Channel Lookup

Fetching Users

# Fetch by ID
user = await backend.fetch_user("U123456")

# Lookup with flexible criteria
user = await backend.lookup_user(
    id="U123456",        # By ID
    # or
    name="Alice",        # By name
    # or
    email="alice@example.com",  # By email
    # or
    handle="alice",      # By username/handle
)

Fetching Channels

# Fetch by ID
channel = await backend.fetch_channel("C123456")

# Lookup with flexible criteria
channel = await backend.lookup_channel(
    id="C123456",   # By ID
    # or
    name="general", # By name
)

User Caching

Backends cache fetched users automatically:

# First call fetches from API
user1 = await backend.fetch_user("U123")

# Second call returns cached copy
user2 = await backend.fetch_user("U123")

# Access the cache directly
cached = backend.users.get_by_id("U123")
all_users = list(backend.users)

# Clear cache if needed
backend.users.clear()

Channel Caching

# Channels are also cached
channel = await backend.fetch_channel("C123")

# Access cache
cached = backend.channels.get_by_id("C123")
by_name = backend.channels.get_by_name("general")
all_channels = list(backend.channels)

Complete Example: Feature-Rich Bot

A bot demonstrating multiple features:

import asyncio
from datetime import datetime, timezone
from chatom import (
    Embed, EmbedField, EmbedFooter,
    Emoji, Reaction,
    PresenceStatus,
    parse_mentions,
)
from chatom.slack import SlackBackend, SlackConfig

async def run_feature_bot():
    config = SlackConfig(bot_token="xoxb-your-token")
    backend = SlackBackend(config=config)

    await backend.connect()

    # Set bot presence
    await backend.set_presence(
        status="auto",
        status_text="Feature Bot Online",
    )

    # Start presence heartbeat
    backend.start_presence_heartbeat(
        interval_seconds=300,
        status="auto",
        status_text="Feature Bot Running",
    )

    channel_id = "C123456"

    # Fetch recent messages
    messages = await backend.fetch_messages(channel_id, limit=10)

    for message in messages:
        content = message.content or ""

        # Check for commands
        if content.startswith("!status"):
            # Show user presence
            mentions = parse_mentions(content, "slack")
            if mentions:
                user_id = mentions[0].id
                presence = await backend.get_presence(user_id)
                if presence:
                    await backend.send_message(
                        channel_id,
                        f"User status: {presence.status.value}",
                    )

        elif content.startswith("!info"):
            # Send a rich embed (Slack attachment)
            attachment = {
                "fallback": "Bot Information",
                "color": "#36a64f",
                "title": "Feature Bot Info",
                "text": "I'm a demonstration bot for chatom features.",
                "fields": [
                    {"title": "Version", "value": "1.0.0", "short": True},
                    {"title": "Uptime", "value": "2 hours", "short": True},
                    {"title": "Commands", "value": "!status, !info, !react", "short": False},
                ],
                "footer": "chatom Feature Bot",
                "ts": int(datetime.now().timestamp()),
            }
            await backend.send_message(
                channel_id,
                "Here's my info:",
                attachments=[attachment],
            )

        elif content.startswith("!react"):
            # Add a reaction to the command message
            await backend.add_reaction(
                channel_id,
                message.id,
                "thumbsup",
            )

        # Check for attachments
        if message.attachments:
            for att in message.attachments:
                await backend.send_message(
                    channel_id,
                    f"I see you uploaded: {att.filename} ({att.size} bytes)",
                )

    # Cleanup
    backend.stop_presence_heartbeat()
    await backend.disconnect()

asyncio.run(run_feature_bot())

Next Steps