tdl icon indicating copy to clipboard operation
tdl copied to clipboard

Node.js bindings to TDLib 🥒

tdl

npm CI

A JavaScript wrapper for TDLib (Telegram Database library), a library to create Telegram clients or bots.
TDLib version 1.5.0 or newer is required.

Table of Contents

  • Installation
  • Requirements
  • API
  • Examples
  • Log in as a bot
  • Options
  • Typings
  • WebAssembly
  • Architecture notes
  • Can I create multiple clients?
  • Contributing
  • Windows
  • UPDATE_APP_TO_LOGIN

Installation

  1. Build TDLib (https://github.com/tdlib/td#building)
  2. npm install tdl tdl-tdlib-addon  (install both)
  3. npm install --save-dev tdlib-types if you use TypeScript or Flow  (recommended)

You can also use third-party pre-built binaries:


Requirements

  • Node.js v10+
  • A C++ compiler and Python installed
  • The tdjson shared library (libtdjson.so on Linux, libtdjson.dylib on macOS, tdjson.dll on Windows)

Note that Node.js exports OpenSSL symbols. If libtdjson is linked dynamically against openssl, it will use openssl symbols from the Node.js binary, not from your system. Therefore libtdjson's openssl version should be compatible with the openssl version that Node.js is statically linked against (process.versions.openssl).
If you get segmentation faults, it's most likely due to the incompatibility of openssl versions.

If you have already built TDLib with your system OpenSSL, a possible option is to rebuild Node.js from source, dynamically linking it against the same system OpenSSL.
For example, using nvm, you can install Node.js v12 from source on GNU/Linux via this command:

$ nvm install -s 12 --shared-openssl --shared-openssl-includes=/usr/include/ --shared-openssl-libpath=/usr/lib/x86_64-linux-gnu/

Another option is to build TDLib with the same OpenSSL version that the Node.js binary on your system includes.

This doesn't apply to Electron, since it doesn't export openssl symbols.


API

new Client(tdlibInstance, options) => Client

// Example in Node.js:
const { Client } = require('tdl')
const { TDLib } = require('tdl-tdlib-addon')

const client = new Client(new TDLib(), {
  apiId: 2222, // Your api_id
  apiHash: '0123456789abcdef0123456789abcdef', // Your api_hash
})

api_id and api_hash can be obtained at https://my.telegram.org/.

The path to libtdjson can be specified in the TDLib constructor's argument. It is directly passed to dlopen / LoadLibrary. Check your OS documentation to see where it searches for the library.

client.connect() => Promise<undefined>

Initialize the client and pass the options to TDLib. Returns a promise.

await client.connect()

client.login(fn?: () => LoginDetails) => Promise<undefined>

Log in to your Telegram account.

await client.login()

By default, tdl asks the user for the phone number, auth code, and password (if specified) in the console. You can pass your functions:

// Example
await client.login(() => ({
  getPhoneNumber: retry => retry
    ? Promise.reject('Invalid phone number')
    : Promise.resolve('+9996620001'),
  getAuthCode: retry => retry
    ? Promise.reject('Invalid auth code')
    : Promise.resolve('22222'),
  getPassword: (passwordHint, retry) => retry
    ? Promise.reject('Invalid password')
    : Promise.resolve('abcdef'),
  getName: () =>
    Promise.resolve({ firstName: 'John', lastName: 'Doe' })
}))

The getName function is called if the user is not signed up.

Also see the LoginDetails interface in the Options section.

It is possible to not use the client.login helper and implement the authorization process manually. This function supports logging in via phone number only. Some other authorization methods like QR code are also available on Telegram.

client.connectAndLogin(fn?: () => LoginDetails) => Promise<undefined>

Same as client.connect().then(() => client.login(fn)).

client.on(event: string, callback: Function) => Client

Attach an event listener to receive updates.

client.on('update', console.log)
client.on('error', console.error)

Ideally you should always have a listener on client.on('error').

client.addListener is an alias to this function.

client.once(event: string, callback: Function) => Client

Add a one-time listener.

client.off(event: string, listener: Function, once?: boolean) => Client

Remove an event listener.

const listener = v => {
  console.log('New update.', v)
  client.off('update', listener)
}
client.on('update', listener)

You can consider using reactive libraries like RxJS or most for convenient event processing.

client.removeListener is an alias to this function.

client.invoke(query: Object) => Promise<Object>

Asynchronously call a TDLib method. Returns a promise, which resolves with the response or rejects with an error.

The API list can be found at https://core.telegram.org/tdlib/docs/annotated.html or in the td_api.tl file. Note: the bytes type means you should pass a base64-encoded string.
Also, tdl renames @type to _.

const chats = await client.invoke({
  _: 'getChats',
  chat_list: { _: 'chatListMain' },
  limit: 4000
})
await client.invoke({
  _: 'sendMessage',
  chat_id: 123456789,
  input_message_content: {
    _: 'inputMessageText',
    text: {
      _: 'formattedText',
      text: '👻'
    }
  }
})

client.execute(query: Object) => (Object | null)

Synchronously call a TDLib method and receive a response. This function can be called only with methods that are marked as "can be called synchronously" in the TDLib documentation.

const res = client.execute({
  _: 'getTextEntities',
  text: '@telegram /test_command https://telegram.org telegram.me'
})

client.close() => Promise<undefined>

Close the TDLib instance.

This method sends { _: 'close' } and waits until the client gets destroyed.

await client.close()

client.setLogFatalErrorCallback(fn: (null | Function)) => undefined

Set the callback that will be called when a fatal error happens in TDLib.

See the TDLib doc.

client.setLogFatalErrorCallback(errorMessage => {
  console.error('Fatal error:', errorMessage)
})

Low-level TDLib API

See TDLib_API.md.


Examples

const { Client } = require('tdl')
const { TDLib } = require('tdl-tdlib-addon')

const client = new Client(new TDLib(), {
  apiId: 2222, // Your api_id, get it at http://my.telegram.org/
  apiHash: '0123456789abcdef0123456789abcdef' // Your api_hash
})

client.on('error', console.error)
client.on('update', update => {
  console.log('Received update:', update)
})

async function main () {
  await client.connectAndLogin()

  console.log(await client.invoke({ _: 'getMe' }))
}

main()

See the examples/ directory.


Log in as a bot

const client = new Client(new TDLib(), {
  apiId: 2222, // Your api_id
  apiHash: '0123456789abcdef0123456789abcdef' // Your api_hash
})

await client.connectAndLogin(() => ({
  type: 'bot',
  getToken: retry => retry
    ? Promise.reject('Token is not valid')
    : Promise.resolve('YOUR_BOT_TOKEN') // Token from @BotFather
}))

Options

// The interface of the options you can pass to the Client constructor:
type Options = {
  apiId: number, // Can be obtained at https://my.telegram.org
  apiHash: string, // Can be obtained at https://my.telegram.org
  databaseDirectory: string, // Relative path (default is '_td_database')
  filesDirectory: string, // Relative path (default is '_td_files')
  databaseEncryptionKey: string, // Optional key for database encryption
  verbosityLevel: number, // Verbosity level (default is 2)
  useTestDc: boolean, // Use test telegram server (default is false)
  tdlibParameters: Object, // Raw TDLib parameters
  // Advanced options:
  skipOldUpdates: boolean, // Don't emit old updates on launch
  receiveTimeout: number,
  useMutableRename: boolean,
  useDefaultVerbosityLevel: boolean,
  disableAuth: boolean
}

// The `login` function accepts one of these two objects:
type LoginDetails = {
  type: 'user',
  getPhoneNumber: (retry?: boolean) => Promise<string>,
  getAuthCode: (retry?: boolean) => Promise<string>,
  getPassword: (passwordHint: string, retry?: boolean) => Promise<string>,
  getName: () => Promise<{ firstName: string, lastName?: string }>
} | {
  type: 'bot',
  getToken: (retry?: boolean) => Promise<string>
}

Only apiId and apiHash are required fields. Any other field can be omitted.

See https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1tdlib_parameters.html for parameters that can be specified in the tdlibParameters option.

Default tdlibParameters:

tdlibParameters: {
  use_message_database: true,
  use_secret_chats: false,
  system_language_code: 'en',
  application_version: '1.0',
  device_model: 'Unknown device',
  system_version: 'Unknown',
  enable_storage_optimizer: true,
  api_id: options.apiId,
  api_hash: options.apiHash,
  database_directory: options.databaseDirectory,
  files_directory: options.filesDirectory,
  use_test_dc: options.useTestDc
}

Typings

tdl fully supports TypeScript and Flow. tdlib-types should be installed to use the typings.

The TDLib types can be imported using:

import type { updateMessageViews, messageInvoice /* ... */ } from 'tdlib-types'

The latest available typings are for TDLib v1.8.0.

You can install typings for other TDLib versions using npm install -D tdlib-types@td-<TDLIB_VERSION>. Example for TDLib v1.5.0: npm install -D [email protected].

See also packages/tdlib-types/README.md.


WebAssembly

tdl also has an experimental wrapper for tdlib in wasm, see packages/tdl-tdlib-wasm/.


Architecture notes

The library is designed to work with different "backends", their interface is described in TDLib_API.md file. So the same main wrapper can be used with node ffi, with node addons, and in the browser with webassembly.

Available "backends" in the tdl repository:

  • tdl-tdlib-addon (recommended)
  • tdl-tdlib-ffi
  • tdl-tdlib-wasm (experimental)

You can easily substitute one with another, since they follow the same interface.


Can I create multiple clients?

You can use multiple clients with tdl-tdlib-addon if the number of clients < UV_THREADPOOL_SIZE.

With tdl-tdlib-ffi it's not possible to use multiple clients simultaneously in one process, see #18. If you try, it will result in use after free. You can create multiple processes using child_process.fork.


Contributing

See CONTRIBUTING.md.


Windows

tdl-tdlib-ffi and tdl-tdlib-addon depend on node-gyp, which may be difficult to install on Windows. You should install Visual Studio (or just Build Tools) and Python first. E.g. see https://gist.github.com/jtrefry/fd0ea70a89e2c3b7779c, https://github.com/Microsoft/nodejs-guidelines/blob/dd5074c/windows-environment.md#compiling-native-addon-modules. npm also has a windows-build-tools package.


UPDATE_APP_TO_LOGIN

If you get this error, update TDLib to v1.7.9 or newer. It is no longer possible to log in using a phone number in older versions of TDLib.