17

First of all please note that this is not about creating a bot.

My goal is to create an application that will simply listen to any number of telegram channels that the account I will provide it with is subscribed to and retrieve all messages sent to those channels (as if I was a normal user). My guess is that I will need to

  • Authenticate myself using my account's phone number
  • Be able to setup a callback listener either per channel or a general listener for all incoming messages

I've been looking around the telegram api for a couple of days now and I am extremely confused as to how it works. After giving up on it, I started looking at readymade implementations, mostly for NodeJS but was still not able to find a concrete solution. I'm testing some things with the telegram-js api but running it directly using node didn't work. Does it need to run in a browser? Is there any more streamlined approach to this? Preferably something with good documentation.

PS: I'm fluent in Java and Javascript mostly so I've prioritized libraries based on those languages.

EDIT:

Here is the code that I've written (essentially copied an example)

var { Telegram } = require("../libs/telegram");
var TypeLanguage = require("telegram-tl-node") ;
var MTProto = require("telegram-mt-node");

var schema = require("../libs/api-schema.json");

const APP_ID = "111111";
const APP_HASH = "fb6da8f6abdf876abd6a9d7bf6";
const SERVER = { host: "111.111.111.11", port: "443" };
const config = {
  id: APP_ID,
  hash: APP_HASH,
  version: '0.0.1',
  lang_code: 'en',
  authKey: null
};

let telegram = new Telegram(MTProto, TypeLanguage);
telegram.useSchema(schema);
addPublicKeys(telegram);

let connection = new MTProto.net.HttpConnection(SERVER);

let client = telegram.createClient();
client.setConnection(connection);

connection.connect(function() {
  let ready = client.setup(config);
  ready.then(function(client) {
    // it never resolves this promise
    function callback(response) {
      console.log(response);
    }
    client.callApi("help.getConfig").then(callback, callback);
  });
});

It uses those 2 libs: telegram-mt-node telegram-tl-node

Tibebes. M
  • 6,940
  • 5
  • 15
  • 36
PentaKon
  • 4,139
  • 5
  • 43
  • 80
  • Please refer to apps source code. – Sean Wei Dec 14 '17 at 09:09
  • Did you come up with a solution? It seems like there is no up-to-date node.js library to use the Telegram API (however there are lots for bots). – Jiam30 Jan 11 '18 at 04:46
  • 1
    What I did was download the Telegram Chrome App which is built in Javascript and I simply injected my code at the message listener. What I do then is just run it using `gulp` and open it up in a browser. It receives messages as it normally would but at the same time it executes my custom code as well. Best way to do this in my opinion otherwise you're going to have to jump through a lot of hoops due to telegram's security layer and their weird data models. – PentaKon Jan 11 '18 at 11:15

4 Answers4

13

Late answer but might help others.

You can utilize mtproto-core to authenticate with a regular telegram account and listen to updates (or do anything you can with telegram clients, really)

Here is a sample script I've written that listens to new messages from channels/supergroups the user is subscribed to:

const { MTProto, getSRPParams } = require('@mtproto/core');
const prompts = require('prompts');

const api_id = ...; // insert api_id here
const api_hash = ' ... '; // insert api_hash here

async function getPhone() {
    return (await prompts({
        type: 'text',
        name: 'phone',
        message: 'Enter your phone number:'
    })).phone
}

async function getCode() {
    // you can implement your code fetching strategy here
    return (await prompts({
        type: 'text',
        name: 'code',
        message: 'Enter the code sent:',
    })).code
}

async function getPassword() {
    return (await prompts({
        type: 'text',
        name: 'password',
        message: 'Enter Password:',
    })).password
}


const mtproto = new MTProto({
    api_id,
    api_hash,
});

function startListener() {
    console.log('[+] starting listener')
    mtproto.updates.on('updates', ({ updates }) => {
        const newChannelMessages = updates.filter((update) => update._ === 'updateNewChannelMessage').map(({ message }) => message) // filter `updateNewChannelMessage` types only and extract the 'message' object

        for (const message of newChannelMessages) {
            // printing new channel messages
            console.log(`[${message.to_id.channel_id}] ${message.message}`)
        }
    });
}


// checking authentication status
mtproto
    .call('users.getFullUser', {
        id: {
            _: 'inputUserSelf',
        },
    })
    .then(startListener) // means the user is logged in -> so start the listener
    .catch(async error => {

        // The user is not logged in
        console.log('[+] You must log in')
        const phone_number = await getPhone()

        mtproto.call('auth.sendCode', {
            phone_number: phone_number,
            settings: {
                _: 'codeSettings',
            },
        })
            .catch(error => {
                if (error.error_message.includes('_MIGRATE_')) {
                    const [type, nextDcId] = error.error_message.split('_MIGRATE_');

                    mtproto.setDefaultDc(+nextDcId);

                    return sendCode(phone_number);
                }
            })
            .then(async result => {
                return mtproto.call('auth.signIn', {
                    phone_code: await getCode(),
                    phone_number: phone_number,
                    phone_code_hash: result.phone_code_hash,
                });
            })
            .catch(error => {
                if (error.error_message === 'SESSION_PASSWORD_NEEDED') {
                    return mtproto.call('account.getPassword').then(async result => {
                        const { srp_id, current_algo, srp_B } = result;
                        const { salt1, salt2, g, p } = current_algo;

                        const { A, M1 } = await getSRPParams({
                            g,
                            p,
                            salt1,
                            salt2,
                            gB: srp_B,
                            password: await getPassword(),
                        });

                        return mtproto.call('auth.checkPassword', {
                            password: {
                                _: 'inputCheckPasswordSRP',
                                srp_id,
                                A,
                                M1,
                            },
                        });
                    });
                }
            })
            .then(result => {
                console.log('[+] successfully authenticated');
                // start listener since the user has logged in now
                startListener()
            });
    })

You can find the values for api_id and api_hash from https://my.telegram.org.

On the first run the script prompts the user for phone_number, code, and password.

[+] You must log in
√ Enter your phone number: ... <phone_number>
√ Enter the code sent: ... <code>
√ Enter Password: ... <2FA password>

and after the authentication is over a sample run outputs:

[+] starting listener
[13820XXXXX] Ja
[13820XXXXX] Bis bald guys��
[13820XXXXX] Ja. �
[13820XXXXX] Bis später
[13820XXXXX] Jaaa�

The way I've checked the authentication status was taken from here.

Alternative libraries worth checking out that are active the time of writing (and can be used to create the same behavior with): Airgram (tdlib) and GramJs

Tibebes. M
  • 6,940
  • 5
  • 15
  • 36
  • For some reason the messages are coming in blocks, i.e. not real-time, is this normal? Also, the method it is listening on is `updates.on("updates")`, how does it differ from `updates.on("updateNewChannelMessage")`, do you have a link to it? And, would you suggest Airgram as a better and more well-maintained option? Is the code roughly similar if I were to translate this into Airgram API? I am currently using Telegraf but since it does not have multi-accounts support and no Typescript, I am thinking of switching over while I am just getting started. – CyberMew Sep 13 '20 at 19:07
  • Hi, I tries it Sometimes I get the updates, after that it stops and does not trigger while new message arrives in my telegram account. Is there any solution for this – Mani Kant Tiwari May 06 '21 at 07:50
4

here is my working code by using gramjs and its purely on nodejs. Getting all the messages from all the channels without any delay's.

import {
  TelegramClient
} from "telegram";
import {
  NewMessage,
  NewMessageEvent
} from "telegram/events";
import {
  StringSession
} from "telegram/sessions";
const input = require("input");

const apiId = 1233456677;
const apiHash = "xxxxxxxxxxxxxxxxx";
let stringSession = new StringSession("xxxxxxxxxxxxxxxxx");

(async() => {
  console.log("Loading interactive example...");
  const client = new TelegramClient(stringSession, apiId, apiHash, {
    connectionRetries: 5,
  });
  await client.start({
    phoneNumber: async() => await input.text("Please enter your number: "),
    password: async() => await input.text("Please enter your password: "),
    phoneCode: async() =>
      await input.text("Please enter the code you received: "),
    onError: (err) => console.log(err),
  });
  console.log("You should now be connected.");
  const session: any = client.session.save();
  stringSession = new StringSession(session); // Save this string to avoid logging in again - specially in nodemon
  console.log(client.session.save()); // --> you can also copy this session from your console once you get it and paste it in line number 8 - new StringSession("XXXXXXXXXXXXXX")
  // once you saved add the JWT Token on line no. 8 as mention above next time you will getting directly connected.
  await client.sendMessage("me", {
    message: "Hello!"
  });
  async function handler(event: NewMessageEvent) {
    console.log("[newmessage]", event);
  }
  client.addEventHandler(handler, new NewMessage({}));
})();

Note - Ignore the "Run Code Snippet" as found it best way to add whole code instead of formatting.

Aditya toke
  • 461
  • 4
  • 14
  • there is not any name or id of new message owner (channel owner), is there? – Behzad Oct 19 '22 at 22:52
  • what is string session how do i get it ? – qubitz Dec 03 '22 at 20:38
  • @qubitz string session is nothing but a place to store our oauth login, so that when you run code again it doesnt ask for the login. How do I get it ? In the first initial login you will get the oauth copy and paste it in the new StringSession("xxxx") you can read the code in which I have explained in detail – Aditya toke Dec 10 '22 at 18:39
3

I used gram.js library and essentially did this:

import { TelegramClient } from 'telegram'
TelegramClient().addEventHandler(handler, { chats: [1234567890] })

The bot does NOT need to be a member of the channel you want to listen to.

My code runs as a Node.js app.

You need to first create a token by talking to @BotFather with Telegram.

Steven Almeroth
  • 7,758
  • 2
  • 50
  • 57
  • I have read your code and done a similar setup. https://pastebin.com/h90FwZGA . However, the handler doesn't seem to register any event. Could you please shed some light on how it works? – Algo7 Feb 26 '22 at 23:34
  • Telegram client != Telegram bot. Why do you need TOKEN for bot, if you suggest to use CLIENT which authorize by phone code? – TotalAMD Apr 03 '22 at 03:17
0

You can use the gram.js library in the following way:

  1. Install these:

    npm install properties-reader
    npm install telegram
    npm install input
    
  2. Then get your apiId and apiHash from Telegram Auth in the App Configuration section.

  3. Create a file config.properties with a content similar to that:

    [telegram]
    apiId=12345678
    apiHash=12345678901234567890123456789012
    
  4. Inside of your nodejs code you can listen to a specific chat like this (see chatId inside of the code below):

    const PropertiesReader = require('properties-reader');
    const configs = PropertiesReader('config.properties');
    getProp = (bundle, key) => {return configs.get(`${bundle}.${key}`);}
    
    const { TelegramClient } = require("telegram");
    const { StoreSession } = require("telegram/sessions");
    const { NewMessage } = require("telegram/events");
    const { EditedMessage } = require("telegram/events/EditedMessage");
    const input = require("input");
    
    const apiId = getProp("telegram", "apiId")
    const apiHash = getProp("telegram", "apiHash")
    const storeSession = new StoreSession("telegram_session"); // see: https://painor.gitbook.io/gramjs/getting-started/authorization#store-session
    
    (async () => {
        console.log("Loading interactive example...");
        const client = new TelegramClient(storeSession, apiId, apiHash, {
            connectionRetries: 5,
        });
        await client.start({
            phoneNumber: async () => await input.text("Please enter your number: "),
            password: async () => await input.text("Please enter your password: "),
            phoneCode: async () =>
                await input.text("Please enter the code you received: "),
            onError: (err) => console.log(err),
        });
        console.log("You should now be connected.");
        client.session.save(); // Save the session to avoid logging in again
    
        async function eventPrint(event) {
            // see 'node_modules/telegram/tl/custom/message.d.ts'
            const message = event.message
            const isNew = message.editDate === undefined
            const text = message.text
            const date = new Date(message.date*1000)
    
            console.log(`The message is ${isNew ? 'new' : 'an update'}`)
            console.log(`The text is: ${text}`)
            console.log(`The date is: ${date}`)
        }
    
        // to get the chatId:
        // option 1: open telegram on a web browser, go to the chat, and look the url in the address bar
        // option 2: open telegram app, copy link to any message, it should be something like: https://t.me/c/1234567890/12345, the first number after "/c/" is the chatId
        const chatId = 1234567890
        client.addEventHandler(eventPrint, new NewMessage({ chats: [chatId] }));
        client.addEventHandler(eventPrint, new EditedMessage({ chats: [chatId] }));
    })();
    
madx
  • 6,723
  • 4
  • 55
  • 59