Skip to content

πŸ› οΈ Effectful library for crafting Telegram bots.

License

Notifications You must be signed in to change notification settings

grom-dev/effect-tg

Repository files navigation

effect-tg

Effectful npm codecov

Effectful library for crafting Telegram bots.

Features

  • Modular design to build with Effect.
  • Complete type definitions for Bot API methods and types.

Installation

# Install the library
npm install @grom.js/effect-tg

# Install Effect dependencies
npm install effect @effect/platform

Working with Bot API

Calling methods

BotApi service provides access to Telegram's Bot API. Each method on BotApi corresponds to the Bot API method with typed parameters and results. Methods return an Effect that succeeds with the method result or fails with BotApiError (see "Error handling").

Example: Calling Bot API methods using BotApi.

import { BotApi } from '@grom.js/effect-tg'
import { Effect } from 'effect'

const program = Effect.gen(function* () {
  const api = yield* BotApi.BotApi
  const me = yield* api.getMe()
  yield* api.sendMessage({
    chat_id: 123456789,
    text: `Hello from ${me.username}!`,
  })
})

Alternatively, you can use BotApi.callMethod function to call any method by name.

Example: Calling Bot API methods using BotApi.callMethod.

import { BotApi } from '@grom.js/effect-tg'
import { Effect } from 'effect'

const program = Effect.gen(function* () {
  const me = yield* BotApi.callMethod('getMe')
  yield* BotApi.callMethod('sendMessage', {
    chat_id: 123456789,
    text: `Hello from ${me.username}!`,
  })
})

Configuration

BotApi has a layered architecture:

β”Œβ€’ BotApi β€” typed interface that delegates calls to BotApiTransport.
└─┬‒ BotApiTransport β€” serializes parameters, sends HTTP requests, parses responses.
  β”œβ”€β”€β€’ BotApiUrl β€” constructs endpoint URLs to methods and files.
  └──‒ HttpClient β€” performs HTTP requests.

This design enables:

  • Extensibility: Extend BotApiTransport to implement logging, retrying, etc.
  • Testability: Mock implementation of BotApiTransport or HttpClient to test your bot.
  • Portability: Provide different BotApiUrl to run a bot on test environment or with local Bot API server.

Example: Constructing BotApi layer with method call tracing.

import { FetchHttpClient } from '@effect/platform'
import { BotApi } from '@grom.js/effect-tg'
import { Config, Effect, Layer } from 'effect'

const BotApiLive = Layer.provide(
  BotApi.layerConfig({
    token: Config.redacted('BOT_TOKEN'),
    environment: 'prod',
    transformTransport: transport => ({
      sendRequest: (method, params) =>
        transport.sendRequest(method, params).pipe(
          Effect.withSpan(method),
        ),
    }),
  }),
  FetchHttpClient.layer
)

Error handling

Failed BotApi method calls result in BotApiError, which is a union of tagged errors with additional information:

  • TransportError β€” HTTP or network failure.
    • cause β€” original error from HttpClient.
  • RateLimited β€” bot has exceeded the flood limit.
    • retryAfter β€” duration to wait before the next attempt.
  • GroupUpgraded β€” group has been migrated to a supergroup.
    • supergroup β€” object containing the ID of the new supergroup.
  • MethodFailed β€” response was unsuccessful, but the exact reason could not be determined.
    • possibleReason β€” string literal representing one of the common failure reasons. It is determined by the error code and description of the Bot API response, which are subject to change.
  • InternalServerError β€” Bot API server failed with a 5xx error code.

All errors except TransportError also have response property that contains the original response from Bot API.

Example: Handling Bot API failures.

import { BotApi } from '@grom.js/effect-tg'
import { Duration, Effect, Match } from 'effect'

const program = BotApi.callMethod('doSomething').pipe(
  Effect.matchEffect({
    onSuccess: result => Effect.logInfo('Got result:', result),
    onFailure: e => Match.value(e).pipe(
      Match.tagsExhaustive({
        TransportError: ({ message }) =>
          Effect.logError(`Probably network issue: ${message}`),
        RateLimited: ({ retryAfter }) =>
          Effect.logError(`Try again in ${Duration.format(retryAfter)}`),
        GroupUpgraded: ({ supergroup }) =>
          Effect.logError(`Group now has a new ID: ${supergroup.id}`),
        MethodFailed: ({ possibleReason, response }) =>
          Match.value(possibleReason).pipe(
            Match.when('BotBlockedByUser', () =>
              Effect.logError('I was blocked...')),
            Match.orElse(() =>
              Effect.logError(`Unsuccessful response: ${response.description}`)),
          ),
        InternalServerError: () =>
          Effect.logError('Not much we can do about it.'),
      }),
    ),
  }),
)

Types

BotApi module exports type definitions for all Bot API types, method parameters and results.

Example: Creating custom types from Bot API types.

import { BotApi, BotApiError } from '@grom.js/effect-tg'
import { Effect } from 'effect'

// Union of all possible updates
type UpdateType = Exclude<keyof BotApi.Types.Update, 'update_id'>

// Function to get gifts of multiple chats
type GiftsCollector = (
  chatIds: Array<BotApi.MethodParams['getChatGifts']['chat_id']>,
  params: Omit<BotApi.MethodParams['getChatGifts'], 'chat_id'>,
) => Effect.Effect<
  Array<BotApi.MethodResults['getChatGifts']>,
  BotApiError.BotApiError,
  BotApi.BotApi
>

About

πŸ› οΈ Effectful library for crafting Telegram bots.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •