How to not to be a Telegram Bot with GramJS

Alexey Sutyagin
Geek Culture
Published in
5 min readDec 22, 2022
Photo by Yuyeung Lau on Unsplash

There are quite a few articles on how to make a bot for Telegram. Telegram also has an excellent API for creating a client for Telegram. Let’s implement a console application in Node.js that will receive pictures from chat and become familiar with the library GramJS and Telegram API for clients.

Preparation

We will use the GramJS library to handle the Telegram API.

Let’s start by creating a basic wrapper for our application:

npm init -y && npm install telegram

That way, we will create a new module where we will do our development and, at the same time, install the required library to work with the Telegram API.

Authorization

Authorize the application in Telegram. We want to log in as a user and not as a bot because there is another API for bots, and we don’t need it.

Since our application isn’t huge — we won’t use a library to handle the configuration but save our secret apiID and apiHash data inside the config.js file with the following content:

const config = {
telegram_credentials: {
apiId: <your apiId>,
apiHash: <your apiHash>,
},
chatName: <your desired chat name>,
};
module.exports = config;

Now we use the slightly modified code from the example to sign in to Telegram.

const config = require('./config');
const {
telegram_credentials: { apiId, apiHash },
chatName,
} = config;
const stringSession = new StringSession("");
async function authorize() {
const client = new TelegramClient(stringSession, apiId, apiHash, {
connectionRetries: 5,
});
await client.start({
phoneNumber: async () => await input.text('number ?'),
password: async () => await input.text('password?'),
phoneCode: async () => await input.text('Code ?'),
onError: (err) => console.log(err),
});
console.log('You should now be connected.');
}

Persistent authorization

To avoid logging in every time — we need to save the session. We will keep the session in the same folder and check and overwrite it if necessary. We already have support for this in the library. Yes, it would help if you implemented a different mechanism for long-term running, but it’s fine as a playground for our purposes.

Let’s rewrite the authorization code to use the file storage.

const storeSession = new StoreSession('my_session'); // This will save the session in a folder named my_session

async function authorize() {
const client = new TelegramClient(storeSession, apiId, apiHash, {
connectionRetries: 5,
});
// same function as above
}

To check authorization, let’s refine our authorization function. Let it first try to join the telegram servers, and if it doesn’t work, walk us through the authorization flow again.

To do that, instead of starting the authorization right away, let’s add the following code

await client.connect();
if (await client.checkAuthorization()) {
// We are logged here
}

The complete code for the authorization function will be provided in the listing at the end of the article.

Image download

For demonstration purposes, we want to get all the pictures of the picture from the chat specified in the configuration. To download the images, we will use the API method. In order not to download the same photos several times — let’s check if they already exist in the folder on our computer.

const fs = require('fs');
const path = require('path');

async function checkAndDownload(client, photo) {
const filename = path.join('images', photo.id + '.jpg'); // Most of images in telegram in JPG
try {
await fs.promises.access(filename); // Check existance of file on the disk
console.log(`File has already exist ${filename}`);
} catch (error) {
const buffer = await client.downloadMedia(photo, {
progressCallback: console.log,
});
if (buffer !== undefined) {
await fs.promises.writeFile(filename, buffer);
}
}
}

Receive images all in parallel

If there are few images and we are ready to get them at once — let’s do that. To do this, call the getMessages method with the necessary filter and the name of the chat.

const images = await client.getMessages(chatName, {
filter: new Api.InputMessagesFilterPhotos(),
});

This method is suitable because it gets all the messages we want in one request, but if we have more than 1000 messages — it will not work.

Receive images sequentially

There is another method that allows iterating through all the messages. We use it to get all the pictures.

async function receiveMessages() {
console.log('Start receive messages');

const client = await authorize();
for await (const photo of client.iterMessages(chatName, {
filter: new Api.InputMessagesFilterPhotos(),
})) {
try {
await checkAndDownload(client, photo);
} catch (error) {
console.log(error);
}
}
console.log('Finish download');
return;
}

Conclusion

We created a console application that can receive parameters from the console, connect as a client to Telegram, and save and retrieve a session so that you don’t have to log in every time. At the same time, we figured out how not to overload the Telegram servers with unnecessary requests and implemented a solution to work with many images. We gained experience with the Telegram API and GramJS library, allowing us to create more complicated applications in the future.

Resources

https://gram.js.org/

https://core.telegram.org/api

Listing

const telegram = require('telegram');
const input = require('input');
const path = require('path');
const fs = require('fs');
const config = require('./config');

const {
telegram_credentials: { apiId, apiHash },
chatName,
} = config;
const storeSession = new telegram.sessions.StoreSession('my_session');
async function authorize() {
const client = new telegram.TelegramClient(storeSession, apiId, apiHash, {
connectionRetries: 5,
});
await client.connect();
if (await client.checkAuthorization()) {
console.log('I am logged in!');
} else {
console.log(
"I am connected to telegram servers but not logged in with any account. Let's autorize"
);
await client.start({
phoneNumber: async () => await input.text('number ?'),
password: async () => await input.text('password?'),
phoneCode: async () => await input.text('Code ?'),
onError: (err) => console.log(err),
});
client.session.save();
console.log('You should now be connected');
}
return client;
}
async function checkAndDownload(client, photo) {
const filename = path.join('images', photo.id + '.jpg');
try {
await fs.promises.access(filename);
console.log(`File has already exist ${filename}`);
} catch (error) {
const buffer = await client.downloadMedia(photo, {
progressCallback: console.log,
});
if (buffer !== undefined) {
await fs.promises.writeFile(filename, buffer);
}
}
}
async function receiveMessages() {
console.log('Start receive messages');
const client = await authorize();
for await (const photo of client.iterMessages(chatName, {
filter: new telegram.Api.InputMessagesFilterPhotos(),
})) {
try {
await checkAndDownload(client, photo);
} catch (error) {
console.log(error);
}
}
console.log('Finish download');
return;
}
(async () => {
return await receiveMessages();
})();

--

--