Skip to main content

Unified message provider interface for Discord, Slack, Jira, and custom platforms with distributed relay support

Project description

Omni Message Provider

A unified Python interface for building chatbots and automated systems across multiple messaging platforms (Discord, Slack, Jira) with optional distributed relay support for scalable deployments.

Features

  • Unified Interface: Single MessageProvider interface for all platforms
  • Multiple Platforms: Discord, Slack, Jira, FastAPI (custom), and polling clients
  • Distributed Architecture: Optional WebSocket relay for microservices deployments
  • High Performance: MessagePack serialization, WebSocket transport
  • Production Ready: Kubernetes-ready, auto-reconnection, error handling

Installation

Basic Installation

pip install omni-message-provider

With Platform Support

# Discord only
pip install omni-message-provider[discord]

# Slack only
pip install omni-message-provider[slack]

# Jira only
pip install omni-message-provider[jira]

# All platforms
pip install omni-message-provider[all]

Quick Start

Discord Bot

import os
import discord
from message_provider import DiscordMessageProvider

# Configure Discord
intents = discord.Intents.default()
intents.message_content = True

# Create provider
provider = DiscordMessageProvider(
    bot_token=os.getenv("DISCORD_BOT_TOKEN"),
    client_id="discord:my-bot",
    intents=intents,
    trigger_mode="mention",
    command_prefixes=["!support", "!cq"]
)

# Handle messages
def message_handler(message):
    print(f"Received: {message['text']}")

    channel = message['channel']
    message_id = message['message_id']

    # Reply (threads the response)
    provider.send_message(
        message="Hello!",
        user_id=message['user_id'],
        channel=channel,
        previous_message_id=message_id
    )

    # React to the original message
    provider.send_reaction(message_id, "👋", channel=channel)

provider.register_message_listener(message_handler)
provider.start()

Slack Bot

import os
from message_provider import SlackMessageProvider

provider = SlackMessageProvider(
    bot_token=os.getenv("SLACK_BOT_TOKEN"),
    app_token=os.getenv("SLACK_APP_TOKEN"),
    use_socket_mode=True,
    trigger_mode="mention",
    allowed_channels=["#support", "C12345678"]
)

def message_handler(message):
    channel = message['channel']
    message_id = message['message_id']

    # Reply in thread (previous_message_id is used as thread_ts)
    provider.send_message(
        message="Got it!",
        user_id=message['user_id'],
        channel=channel,
        previous_message_id=message_id
    )

    # React to the original message
    provider.send_reaction(message_id, "eyes", channel=channel)

provider.register_message_listener(message_handler)
provider.start()

Jira Issue Monitor

import os
from message_provider import JiraMessageProvider

provider = JiraMessageProvider(
    server="https://company.atlassian.net",
    email=os.getenv("JIRA_EMAIL"),
    api_token=os.getenv("JIRA_API_TOKEN"),
    project_keys=["SUPPORT", "BUG"],
    client_id="jira:main",
    watch_labels=["bot-watching"],
    trigger_phrases=["@bot"]
)

def message_handler(message):
    if message['type'] == 'new_issue':
        # Add comment to ticket
        provider.send_message(
            message="We're on it!",
            channel=message['channel']  # Issue key
        )
        # Add label
        provider.send_reaction(message['channel'], "bot-acknowledged")
        # Change status
        provider.update_message(message['channel'], "In Progress")

provider.register_message_listener(message_handler)
provider.start()

Distributed Architecture

For scalable, Kubernetes-ready deployments:

Distributed Architecture

# Message Provider Pod (Discord/Slack/Jira)
from message_provider import DiscordMessageProvider, RelayClient

discord_provider = DiscordMessageProvider(...)
relay_client = RelayClient(
    local_provider=discord_provider,
    relay_hub_url="ws://relay-hub:8765",
    client_id="discord:guild-123"
)
relay_client.start_blocking()

# RelayHub Pod (Central Router)
from message_provider import RelayHub, FastAPIMessageProvider

mp_provider = FastAPIMessageProvider(...)
hub = RelayHub(local_provider=mp_provider, port=8765)
await hub.start()

# Orchestrator Pods (Multiple instances)
from message_provider import RelayMessageProvider

provider = RelayMessageProvider(websocket_url="ws://relay-hub:8765")
provider.register_message_listener(my_handler)
provider.start()

Unified Interface

All providers implement the same interface:

class MessageProvider:
    def send_message(message: str, user_id: str, channel: str = None, previous_message_id: str = None) -> dict:
        """Send a message. Use previous_message_id to reply in a thread."""

    def send_reaction(message_id: str, reaction: str, channel: str = None) -> dict:
        """Add a reaction/label. channel is required for Discord and Slack."""

    def update_message(message_id: str, new_text: str, channel: str = None) -> dict:
        """Update a message/status. channel is required for Discord and Slack."""

    def register_message_listener(callback: Callable) -> None:
        """Register callback for incoming messages"""

    def start() -> None:
        """Start the provider (blocking)"""

Providers are stateless -- they do not cache message or channel metadata internally. The application is responsible for tracking conversation state (e.g., which channel and thread a message belongs to) using the data provided in incoming messages.

Platform-Specific Mappings

Discord

  • send_message() → Send Discord message (uses previous_message_id as reply reference)
  • send_reaction(channel=...) → Add emoji reaction (channel required)
  • update_message(channel=...) → Edit message (channel required)
  • channel = Discord channel ID

Slack

  • send_message() → Post Slack message (uses previous_message_id as thread_ts)
  • send_reaction(channel=...) → Add reaction emoji (channel required)
  • update_message(channel=...) → Update message (channel required)
  • channel = Slack channel ID

Jira

  • send_message() → Add comment to ticket
  • send_reaction() → Add label to ticket
  • update_message() → Change ticket status
  • channel = Jira issue key (e.g., "SUPPORT-123")

Message Format

All providers return messages in standardized format:

{
    "type": "new_issue" | "new_comment" | "new_message",
    "message_id": "unique-id",
    "text": "message content",
    "user_id": "user-identifier",
    "channel": "channel-identifier",
    "metadata": {
        "client_id": "platform:instance",
        # Platform-specific fields
    }
}

Provider-Specific Fields

Provider-specific data is included under metadata. Use it only when needed.

{
    "type": "new_message",
    "message_id": "unique-id",
    "text": "message content",
    "user_id": "user-identifier",
    "channel": "channel-identifier",
    "metadata": {
        "client_id": "platform:instance",
        "provider": "slack",  # comment: "slack", "discord", "jira", "fastapi", "relay"
        "provider_data": {    # comment: provider-specific details (see below)
            # comment: Slack example
            "thread_ts": "1700000000.123456",
            "channel_type": "channel",
            "event_type": "app_mention"
        }
    }
}

Slack provider_data (examples):

{
    "thread_ts": "1700000000.123456",  # comment: parent thread timestamp
    "channel_type": "channel",         # comment: channel, group, im, mpim
    "event_type": "app_mention",       # comment: app_mention or message
    "user_email": "user@example.com"   # comment: user's email from Slack profile
}

Discord provider_data (examples):

{
    "author_name": "User#1234",        # comment: display name
    "author_discriminator": "1234",    # comment: discriminator (if available)
    "guild_id": "1234567890",          # comment: server ID
    "guild_name": "My Server",         # comment: server name
    "channel_name": "support",         # comment: channel name
    "is_thread": False,                # comment: thread flag
    "thread_id": None,                 # comment: thread ID if applicable
    "reference_message_id": None,      # comment: replied-to message ID
    "is_mention": False                # comment: whether the bot was mentioned
}

Jira provider_data (examples):

{
    "issue_key": "SUPPORT-123",        # comment: issue key
    "project": "SUPPORT",              # comment: project key
    "project_name": "Support",         # comment: project name
    "issue_type": "Bug",               # comment: issue type
    "priority": "High",                # comment: priority
    "status": "In Progress",           # comment: current status
    "labels": ["bot-watching"],        # comment: issue labels
    "reporter_name": "Jane Doe",       # comment: reporter display name
    "reporter_email": "jane@acme.com",  # comment: reporter email if available
    "created": "2024-01-01T00:00:00Z",  # comment: created timestamp
    "url": "https://.../browse/KEY"     # comment: issue URL
}

Configuration

All providers accept explicit parameters (no environment variables in library):

# User controls their own env var names
provider = DiscordMessageProvider(
    bot_token=os.getenv("MY_DISCORD_TOKEN"),  # Your choice
    client_id=os.getenv("MY_CLIENT_ID"),
    trigger_mode="both",  # "mention", "chat", "command", "both"
    command_prefixes=["!support", "!cq"]
)

Examples

See the src/message_provider/examples/ directory for complete working examples:

  • discord_example.py - Discord bot with reactions
  • slack_example.py - Slack bot with Socket Mode
  • jira_example.py - Jira issue monitor
  • relay_example.py - Distributed relay setup
  • polling_client_example.py - FastAPI polling client

Development

# Clone repository
git clone https://github.com/AgentSanchez/omni-message-provider
cd omni-message-provider

# Install with dev dependencies
pip install -e ".[dev,all]"

# Run tests
pytest

# Format code
black src/message_provider/
ruff check src/message_provider/

Requirements

  • Python 3.9+
  • Core: fastapi, uvicorn, websockets, msgpack
  • Optional: discord.py, slack-bolt, jira

License

MIT License - see LICENSE file

Support

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

omni_message_provider-0.2.3.tar.gz (47.5 kB view details)

Uploaded Source

Built Distribution

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

omni_message_provider-0.2.3-py3-none-any.whl (57.1 kB view details)

Uploaded Python 3

File details

Details for the file omni_message_provider-0.2.3.tar.gz.

File metadata

  • Download URL: omni_message_provider-0.2.3.tar.gz
  • Upload date:
  • Size: 47.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for omni_message_provider-0.2.3.tar.gz
Algorithm Hash digest
SHA256 4a88375e90b54e56855cacace3cc6d492d78e5764f8bba505749db9b3dd381f7
MD5 1a51f93f533376c278655535be14b86f
BLAKE2b-256 d76e1128d98a601d1c6debadceb6283560e5fbe4c39ff1561da2d0693fbbe1e4

See more details on using hashes here.

File details

Details for the file omni_message_provider-0.2.3-py3-none-any.whl.

File metadata

File hashes

Hashes for omni_message_provider-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 c5056324417c3d14104c6abd7a00e3bfff7459e36fc402f35d7be6c8ddec507e
MD5 9b7fb36d08d96577ee802404ba6e90e2
BLAKE2b-256 996b45f378a8c9c4a9bb7ba60cbfbc10b49497d421ba63a966073977f92baae3

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