Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

telegram - Telegram Bot API client

Import with import "telegram.j" as telegram;. Drive a Telegram bot over the Bot API: send messages and photos, identify the bot, and long-poll for incoming updates. Built on the http module + json, so it needs the default jennifer binary. Larger than the one-shot notifiers (slack / discord / gotify) - getUpdates drives a stateful receive loop. An API error ({"ok": false, ...}) throws Error{kind: "telegram"}.

import "telegram.j" as telegram;

def bot as telegram.Bot init telegram.bot("123456:ABC-DEF...");   # token from @BotFather
telegram.sendMessage($bot, 12345678, "hello from Jennifer");

def updates as list of telegram.Update init telegram.getUpdates($bot, 0, 30);

Runnable: examples/modules/telegram_demo.j.

The bot

def struct telegram.Bot { token as string, baseUrl as string };
CallReturns
telegram.bot(token)Bota bot against the public Telegram API
telegram.botWith(token, baseUrl)Bota bot against a custom API base (a self-hosted Bot API server, or a test endpoint)

The token is a secret - read it from the environment, never commit it. Every call POSTs application/x-www-form-urlencoded params to baseUrl/bot<token>/<method> and verifies the {"ok": true} envelope, throwing on an API error with the server’s description.

Sending

CallReturns
telegram.sendMessage(bot, chatId, text)Messagesend a text message
telegram.sendMessageWith(bot, chatId, text, parseMode)Messagewith a parse mode ("Markdown", "MarkdownV2", "HTML", or "")
telegram.sendPhoto(bot, chatId, photo, caption)Messagesend a photo by URL or file id, with an optional caption
telegram.sendChatAction(bot, chatId, action)boolshow activity ("typing", "upload_photo", …)
telegram.getMe(bot)Userthe bot’s own identity (a good token / connectivity check)

chatId is an integer (Telegram user, group, or channel id - channel ids are large and negative, which fits Jennifer’s 64-bit int). A returned Message carries the text-relevant fields:

def struct telegram.Message {
    messageId as int,   # the message id
    chatId as int,      # the chat it belongs to
    text as string,     # the text ("" for non-text messages)
    date as int         # send time as a Unix timestamp
};
def struct telegram.User {
    id as int, isBot as bool, firstName as string, username as string
};

Receiving (long-poll)

telegram.getUpdates(bot, offset, timeout) long-polls for pending updates. Pass offset as the last processed updateId + 1 (0 on the first call) and timeout as the wait in seconds; the HTTP read is bounded a few seconds beyond that. Returns a list of Update:

def struct telegram.Update {
    updateId as int,       # advance the next poll offset to this + 1
    hasMessage as bool,    # whether this update carries a text-message
    message as Message     # the message (zero-valued when hasMessage is false)
};

The receive-loop pattern - fetch, process, advance the offset past each update:

def offset as int init 0;
def updates as list of telegram.Update init telegram.getUpdates($bot, $offset, 30);
for (def u in $updates) {
    $offset = $u.updateId + 1;
    if ($u.hasMessage and len($u.message.text) > 0) {
        telegram.sendMessage($bot, $u.message.chatId, "echo: " + $u.message.text);
    }
}
# next loop: telegram.getUpdates($bot, $offset, 30)

Scope

  • Long-poll only, no webhook receiver (that needs a public HTTPS server; compose web / httpd yourself).
  • Text-centric updates. Update surfaces the message (text) shape; edited_message, channel_post, callback_query, and inline queries set hasMessage false - reach the raw JSON via a direct http call if you need them.
  • No multipart upload. sendPhoto takes a URL or file id, not a local file (multipart multipart/form-data upload is a follow-on).
  • No inline keyboards / reply markup, no message editing or deletion in this version.

See also