Skip to content

How to Use Client

Setup

import { makeTgBotClient } from "@effect-ak/tg-bot-client"
const client = makeTgBotClient({
bot_token: "YOUR_BOT_TOKEN"
})

If you run a self-hosted Bot API server, pass a custom base URL:

const client = makeTgBotClient({
bot_token: "YOUR_BOT_TOKEN",
base_url: "https://your-custom-server.com"
})

All Telegram Bot API methods are available through client.execute(method, params). Method names match the official API in snake_case — send_message, send_photo, get_chat, and so on. TypeScript will autocomplete both the method name and its parameters.

Every call returns a ClientResult — a simple { ok, data } or { ok, error } object. Check result.ok to handle the response — see Errors below for all possible error types.

Message Effects

You can pass an emoji directly as message_effect_id — the client resolves it to the correct Telegram effect ID automatically:

const result = await client.execute("send_message", {
chat_id: "123456789",
text: "Message with fire effect!",
message_effect_id: "🔥"
})

Available free effects

These 6 effects are available for all bots without Telegram Premium:

EmojiDescription
🔥Fire
👍Thumbs up
👎Thumbs down
❤️Heart
🎉Party popper
💩Poop

Telegram Premium unlocks hundreds of additional effects, but their IDs can only be obtained dynamically via the MTProto API (messages.getAvailableEffects), not through the Bot API. If you have a Premium effect ID, you can still pass it as a raw string.

Examples

Messages

Basic Text Message

const result = await client.execute("send_message", {
chat_id: "123456789",
text: "Hello from TypeScript!"
})
if (!result.ok) {
console.error(result.error._tag, result.error)
return
}
console.log("Sent message:", result.data.message_id)

Message with Formatting

const result = await client.execute("send_message", {
chat_id: "123456789",
text: "*Bold* _italic_ `code`",
parse_mode: "Markdown"
})

Message with Inline Keyboard

const result = await client.execute("send_message", {
chat_id: "123456789",
text: "Choose an option:",
reply_markup: {
inline_keyboard: [
[
{ text: "Option 1", callback_data: "opt_1" },
{ text: "Option 2", callback_data: "opt_2" }
]
]
}
})

Files

Sending a Document

const result = await client.execute("send_document", {
chat_id: "123456789",
document: {
file_content: new TextEncoder().encode("Hello from file!"),
file_name: "hello.txt"
},
caption: "Simple text file"
})

Sending a Photo

const result = await client.execute("send_photo", {
chat_id: "123456789",
photo: {
file_content: photoBuffer,
file_name: "image.jpg"
},
caption: "Check out this photo!"
})

File Download

const result = await client.getFile({
fileId: "AgACAgIAAxkBAAI..."
})
if (result.ok) {
console.log(result.data.file_name)
const base64 = result.data.base64String()
}

Errors

Client methods never throw — they return a ClientResult<T> discriminated union:

type ClientResult<T> =
| { ok: true; data: T }
| { ok: false; error: ClientErrorReason }

Check result.error._tag to determine what went wrong:

if (!result.ok) {
switch (result.error._tag) {
case "NotOkResponse":
console.error("API error:", result.error.errorCode, result.error.details)
break
case "UnexpectedResponse":
console.error("Unexpected response:", result.error.response)
break
case "ClientInternalError":
console.error("Internal error:", result.error.cause)
break
case "UnableToGetFile":
console.error("File download error:", result.error.cause)
break
case "NotJsonResponse":
console.error("Invalid JSON response:", result.error.response)
break
}
return
}
// result.data is fully typed
console.log(result.data.message_id)

Error types

TagDescription
NotOkResponseTelegram API returned an error (e.g., invalid chat_id)
UnexpectedResponseResponse didn’t match expected format
ClientInternalErrorInternal error (network failure, etc.)
UnableToGetFileFile download failed
NotJsonResponseResponse was not valid JSON