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¶
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,
)
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 |
|
|
|
|
Slack |
|
|
N/A |
N/A |
Symphony |
|
|
|
|
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¶
See Backend Examples for platform-specific code
Learn about Message Formatting
Explore Mentions for tagging users
Check the API Reference for complete details