← Basics
Publishedrole: extensionlicense: BSD-3-Clause

tom_chattools

tom_chattools · v1.0.2

A platform-agnostic chat abstraction for sending and receiving messages, with a unified message model and a Telegram implementation behind a single `ChatAPI` interface.

View repository → See License
Status
Published
LOC
688
Tests
6
Test LOC
43

Overview

From the module readme.md file:

What it enables

Enables Platform-agnostic messaging, Telegram bot integration, Streaming message updates.

Relationships

Standalone — no declared relationships.

Tom Chattools

> Part of the Tom framework by al-the-bear. > © 2024–2026 Peter Nicolai Alexis Kyaw — BSD-3-Clause, see LICENSE.

Unified chat API abstraction for Telegram, WhatsApp, Signal, and other messaging platforms.

---

Overview

tom_chattools gives you one API to send and receive chat messages, regardless of the platform behind it. You describe a connection with ChatSettings, call ChatApi.connect(...), and from then on the same sendMessage / getMessages / onMessage surface works no matter which messenger you target.

  • Platform-agnostic core. ChatApi, ChatMessage, ChatReceiver, and

ChatResponse carry no platform-specific types, so application code never imports a Telegram (or future WhatsApp/Signal) class directly. - Platform auto-detection. ChatApi.connect inspects the ChatSettings you pass and picks the implementation — provide a Telegram token and you get a Telegram connection; nothing else changes in your code. - Two ways to receive. Block-and-wait with getMessages (with minWait / maxWait / interval tuning) or subscribe to the live onMessage stream. - Telegram today. A complete Telegram Bot API implementation (via televerse) ships now; WhatsApp and Signal are reserved in the settings model for future implementations.

Pure Dart, no Flutter dependency — works in servers, bots, and CLI tools.

---

Installation

dependencies:
  tom_chattools: ^1.0.2

or from the command line:

dart pub add tom_chattools

Requires the Dart SDK ^3.10.4. Pulls in televerse for the Telegram backend. For Telegram you also need a bot token from @BotFather — see Telegram setup.

---

Features

Core abstraction

CapabilityAPINotes
Connect (auto-detect platform) ChatApi.connect(settings) Picks the impl from ChatSettings
Send text sendMessage(receiver, text, {parseMode}) parseMode: Markdown/MarkdownV2/HTML
Send a rich message send(receiver, ChatMessage) Text + attachments + formatting
Pull messages getMessages(receiver, {minWait, maxWait, interval, filter}) Returns a ChatResponse
Stream messages onMessage Live Stream<ChatMessage>
Look up a recipient getReceiverInfo(receiver) Returns ChatReceiverInfo?
Download an attachment downloadAttachment(attachment) Resolves platform file IDs to bytes
Disconnectdisconnect()Closes the connection

Addressing & messages

TypeConstructors / membersNotes
ChatReceiver .id(), .username(), .phone(), .group() Who to send to
ChatMessage .text(...) , text , sender , type , attachments A sent/received message
ChatMessageType text , image , video , audio , document , sticker , location , contact , system , unknown Message kind
ChatResponse messages , hasMessages , count , ofType() , fromSender() , textContent Result of getMessages
ChatMessageFilter from, types, after Narrow what getMessages returns

Configuration

TypePurpose
ChatSettingsPlatform-agnostic, auto-detected connection settings
ChatSettings.telegram(token, …)Convenience constructor for Telegram
TelegramChatConfig Telegram-specific config (created internally by connect)

---

Quick start

ChatApi.connect takes a ChatSettings; the convenience constructor ChatSettings.telegram(token) is the shortest path to a working bot. Provide a real token and the snippet below sends a message and waits for replies:

import 'package:tom_chattools/tom_chattools.dart';

void main() async {
  // Connect — the platform is detected from the settings.
  final api = await ChatApi.connect(ChatSettings.telegram('YOUR_BOT_TOKEN'));

  // Address the user/chat to talk to.
  final receiver = ChatReceiver.id('123456789');

  // Send a message.
  await api.sendMessage(receiver, 'Hello from Tom ChatTools!');

  // Wait up to 30s for replies (returning early once any arrive after 5s).
  final response = await api.getMessages(
    receiver,
    maxWait: const Duration(seconds: 30),
    minWait: const Duration(seconds: 5),
  );

  if (response.hasMessages) {
    for (final message in response.messages) {
      print('Received from ${message.sender.name}: ${message.text}');
    }
  } else {
    print('No messages received (status: ${response.status})');
  }

  await api.disconnect();
}

This mirrors the runnable example/tom_chattools_example.dart.

---

Example projects

ExampleWhat it shows
tom_chattools_sample The unified chat API as an article: the ChatApi contract driven against an in-memory mock transport (no live tokens), covering send/receive/filtering/streaming — seven runnable, CI-safe examples.
example/tom_chattools_example.dart Connect, send, and pull replies from Telegram
Quick startSame flow, annotated
Streaming updates The onMessage live stream
Message types Switching on ChatMessageType
Send to different chats ChatReceiver variants

---

Telegram setup

To connect to Telegram you need a bot token from @BotFather.

Step 1: Create a bot

1. Open Telegram and search for @BotFather (the official Telegram bot). 2. Start a conversation and send /newbot. 3. Follow the prompts: - Choose a display name (e.g. "My Assistant"). - Choose a username (must end in bot, e.g. my_assistant_bot). 4. BotFather gives you an API token like:

   123456789:ABCdefGHIjklMNOpqrsTUVwxyz

5. Save this token securely — it grants full access to your bot.

Step 2: Get your chat ID

To send/receive messages from a specific chat, you need its chat ID:

For personal chats:

1. Message your bot first (search for its username in Telegram). 2. Run your bot with polling enabled (see below). 3. Send a message to your bot. 4. Check sender.id on the received message — that is your chat ID.

Using a helper bot:

1. Forward any message to @userinfobot. 2. It replies with your user ID (same as the chat ID for 1:1 chats).

For groups:

1. Add your bot to the group. 2. The group chat ID appears in incoming messages (usually a negative number).

Step 3: Connect and use

import 'package:tom_chattools/tom_chattools.dart';

void main() async {
  // Settings carry just the authentication + polling preferences.
  final api = await ChatApi.connect(
    ChatSettings.telegram('YOUR_BOT_TOKEN', usePolling: true),
  );

  // Define who to communicate with.
  final receiver = ChatReceiver.id('YOUR_CHAT_ID');

  // Send a message.
  await api.sendMessage(receiver, 'Hello from Dart!');

  // Listen for incoming messages and echo them back.
  api.onMessage.listen((message) {
    print('Received: ${message.text} from ${message.sender.name}');
    api.sendMessage(ChatReceiver.id(message.sender.id), 'You said: ${message.text}');
  });
}

Environment variables (recommended)

Keep your token and chat ID out of source control:

import 'dart:io';
import 'package:tom_chattools/tom_chattools.dart';

final token = Platform.environment['TELEGRAM_BOT_TOKEN']!;
final chatId = Platform.environment['TELEGRAM_CHAT_ID']!;

final api = await ChatApi.connect(ChatSettings.telegram(token, usePolling: true));
final receiver = ChatReceiver.id(chatId);

Set them in your shell:

export TELEGRAM_BOT_TOKEN="123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
export TELEGRAM_CHAT_ID="987654321"

---

Usage

Basic send / receive

getMessages is the block-and-wait path. It polls the platform until either maxWait elapses or messages arrive after minWait, then returns a ChatResponse.

final api = await ChatApi.connect(ChatSettings.telegram(token, usePolling: true));
final receiver = ChatReceiver.id(chatId);

await api.sendMessage(receiver, 'Hello!');

final response = await api.getMessages(
  receiver,
  maxWait: const Duration(seconds: 10),
);
for (final msg in response.messages) {
  print('${msg.sender.name}: ${msg.text}');
}

ChatResponse carries helpers beyond messages: hasMessages, count, first, last, ofType(type), fromSender(id), and textContent.

Streaming updates

When you want push-style delivery instead of polling, subscribe to onMessage:

api.onMessage.listen((message) {
  print('${message.sender.name}: ${message.text}');
});

Message types

Every ChatMessage carries a ChatMessageType. Switch on it to handle each kind:

api.onMessage.listen((msg) {
  switch (msg.type) {
    case ChatMessageType.text:
      print('Text: ${msg.text}');
    case ChatMessageType.image:
      print('Received an image');
    case ChatMessageType.document:
      print('Received a document');
    default:
      print('Other: ${msg.type}');
  }
});

To pull only certain kinds in a getMessages call, pass a ChatMessageFilter:

final response = await api.getMessages(
  receiver,
  filter: const ChatMessageFilter(types: [ChatMessageType.text]),
);

Send to different chats

A ChatReceiver can address a user by ID, by username, or a whole group:

// Send to a user by ID.
await api.sendMessage(ChatReceiver.id('123456789'), 'Hello user!');

// Send to a user by username.
await api.sendMessage(ChatReceiver.username('johndoe'), 'Hi John!');

// Send to a group.
await api.sendMessage(ChatReceiver.group('-100123456789'), 'Hello group!');

---

Architecture

package:tom_chattools/tom_chattools.dart   (single export surface)
│
├── api/chat/                  ← platform-agnostic core
│   ├── ChatApi (abstract)         connect() factory + send/receive contract
│   ├── ChatSettings               auto-detected connection settings
│   ├── ChatConfig (abstract)      platform config → createApi()
│   ├── ChatReceiver               who to address (id/username/phone/group)
│   ├── ChatMessage / ChatSender   message + author
│   ├── ChatResponse               result of getMessages()
│   └── ChatMessageFilter          narrow getMessages results
│
└── telegram/                  ← Telegram implementation
    ├── TelegramChatConfig         token + polling options
    └── TelegramChat               ChatApi over televerse
                                   (future: WhatsApp, Signal)

ChatApi.connect(settings) reads the ChatSettings, builds the matching ChatConfig (today: TelegramChatConfig), and calls its createApi + initialize. Application code only ever touches the abstract core types.

TypeRole
ChatApiAbstract send/receive contract + connect factory
ChatSettingsPlatform-agnostic, auto-detected connection settings
ChatConfigBase class a platform config extends
ChatReceiverAddresses a user, username, phone, or group
ChatReceiverInfoProfile details for a receiver
ChatMessageA sent or received message
ChatSenderThe author of a message
ChatMessageTypeEnum of message kinds
ChatAttachment / ChatAttachmentType Files, images, etc. on a message
ChatResponseResult of getMessages, with query helpers
ChatResponseStatus Outcome enum (ok, timeout, authError, …)
ChatMessageFilterFilter for getMessages
TelegramChatConfigTelegram token + polling configuration

---

Troubleshooting

"Conflict: terminated by other getUpdates request"

Only one polling connection can be active per bot token. Make sure you don't have another instance running and that previous bot instances were stopped cleanly.

Bot not receiving messages

1. Message the bot first — bots cannot initiate chats. 2. Confirm polling is enabled: ChatSettings.telegram(token, usePolling: true). 3. Verify the token is correct.

Getting chat / user IDs

Print incoming message details:

api.onMessage.listen((msg) {
  print('Chat ID: ${msg.sender.id}');
  print('Message ID: ${msg.platformMessageId}');
});

Bot privacy settings (groups)

By default, bots in groups only see messages that start with /, reply to the bot, or mention it. To see all messages, disable privacy mode:

1. Go to @BotFather. 2. Send /setprivacy. 3. Choose your bot. 4. Select "Disable".

---

Ecosystem

tom_chattools is one of the foundational packages under tom_ai/basics/. All tom_ai/basics/ packages share a single repository, tom_basics.

It underpins higher-level Tom features that talk to users over chat — for example the Tom Telegram bot integration (assistant chat, reminders, and build notifications) builds on this abstraction so the same code can target other messengers as their implementations land.

---

Further documentation

---

Status

Stable (1.0.2). Telegram is fully implemented; ChatSettings reserves WhatsApp and Signal slots for future backends. The public API is platform-agnostic, so adding a backend does not change application code. dart analyze is clean.

License
BSD 3-Clause License

Copyright (c) 2024-2026, Peter Nicolai Alexis Kyaw
Find me on LinkedIn under Alexis Kyaw
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.