Skip to content

Instantly share code, notes, and snippets.

@sadiqsalau
Last active March 29, 2025 15:42
Show Gist options
  • Save sadiqsalau/b24a5f598139f361ae2ed4de1a928392 to your computer and use it in GitHub Desktop.
Save sadiqsalau/b24a5f598139f361ae2ed4de1a928392 to your computer and use it in GitHub Desktop.
GramJS with Multiple Sessions and Promises
const client = await GramClient.create("default");
/** POST: /telegram/login */
await client.startPending(); // Starts Client, response can be called later
await client.startResponse("phoneNumber", "phone");
/** POST: /telegram/code */
await client.startResponse("phoneCode", "code");
/** POST: /telegram/password */
await client.startResponse("password", "password");
/** Fastify */
import { Api } from "telegram";
import GramClient from "./lib/GramClient.js";
fastify
.addHook("preHandler", async function (request, reply) {
const { session } = request.body;
if (!session) {
return reply.code(400).send({ error: "Session is required!" });
}
try {
request.client = await GramClient.create(session);
} catch (error) {
return reply.code(500).send({ error: "Failed to get client!" });
}
})
.post(
"/login",
{
schema: {
body: {
type: "object",
required: ["phone"],
properties: {
phone: { type: "string" },
},
},
},
},
async function (request) {
/** Start Pending */
await request.client.startPending();
/** Send Phone Number */
const status = await request.client.startResponse(
"phoneNumber",
request.body.phone
);
/** Return Response */
return { status };
}
)
.post(
"/code",
{
schema: {
body: {
type: "object",
required: ["code"],
properties: {
code: { type: "string" },
},
},
},
},
async function (request) {
/** Send Phone Code */
const status = await request.client.startResponse(
"phoneCode",
request.body.code
);
return { status };
}
)
.post(
"/password",
{
schema: {
body: {
type: "object",
required: ["password"],
properties: {
password: { type: "string" },
},
},
},
},
async function (request) {
/** Send Password */
const status = await request.client.startResponse(
"password",
request.body.password
);
return { status };
}
)
.post(
"/webview",
{
schema: {
body: {
type: "object",
required: ["bot", "shortName", "startParam"],
properties: {
bot: { type: "string" },
shortName: { type: "string" },
startParam: { type: "string" },
},
},
},
},
async function (request) {
/** Connect */
if (!request.client.connected) {
await request.client.connect();
}
/** Get WebView */
const result = await request.client.invoke(
request.body.shortName
? new Api.messages.RequestAppWebView({
platform: "android",
peer: request.body.bot,
startParam: request.body.startParam,
app: new Api.InputBotAppShortName({
botId: await request.client.getInputEntity(request.body.bot),
shortName: request.body.shortName,
}),
})
: new Api.messages.RequestMainWebView({
platform: "android",
bot: request.body.bot,
peer: request.body.bot,
startParam: request.body.startParam,
})
);
return { result };
}
)
.post("/logout", async function (request) {
/** Logout */
await request.client.logout();
});
import fs from "node:fs/promises";
import { Api, TelegramClient } from "telegram";
import { StringSession } from "telegram/sessions/index.js";
const config = {
apiId: null,
apiHash: null,
appVersion: null,
deviceModel:
null,
systemVersion: null,
systemLangCode: null,
langCode: null,
};
export default class GramClient extends TelegramClient {
/**
* @type {Map<string, GramClient>}
*/
static instances = new Map();
/** Constructor */
constructor(name, session, sessionFilePath, sessionFileExists) {
super(session, config.apiId, config.apiHash, {
connectionRetries: 5,
appVersion: config.appVersion,
deviceModel: config.deviceModel,
systemVersion: config.systemVersion,
systemLangCode: config.systemLangCode,
langCode: config.langCode,
});
/** Store Name */
this.name = name;
/** Store File Path */
this.sessionFilePath = sessionFilePath;
/** Store Session File State */
this.sessionFileExists = sessionFileExists;
}
/** Start Handler */
createStartHandler(stage) {
return () =>
new Promise((resolve) => {
/** Resolve Stage Promise */
this.startStagePromise?.resolve?.(stage);
/** Set Stage */
this.startStage = stage;
/** Set Handler */
this.startHandlers[stage] = (data) => {
resolve(data);
/** Return new promise for the next stage */
return new Promise((_resolve, _reject) => {
this.startStagePromise = { resolve: _resolve, reject: _reject };
});
};
});
}
/** Start Response */
async startResponse(stage, response) {
return await this.startHandlers[stage](response);
}
/** Start Pending */
startPending() {
if (this.startPromise) {
return;
}
return new Promise((_resolve, _reject) => {
/** Reset Start Stage Promise */
this.startStagePromise = { resolve: _resolve, reject: _reject };
/** Reset Start Stage */
this.startStage = null;
/** Reset Start Handlers */
this.startHandlers = {};
/** Store Global Start Promise */
this.startPromise = this.start({
phoneNumber: this.createStartHandler("phoneNumber"),
phoneCode: this.createStartHandler("phoneCode"),
password: this.createStartHandler("password"),
onError: (error) => {
if (this.startStagePromise) {
this.startStagePromise.reject(error);
} else {
console.error(
"Error occurred before handler was initialized:",
error
);
}
},
}).then(async () => {
await this.saveSession();
await this.startStagePromise?.resolve?.("authenticated");
});
});
}
/** Logout */
async logout() {
try {
/** Try to reconnect */
if (this.disconnected) {
await this.connect();
}
/** Logout */
await this.invoke(new Api.auth.LogOut({}));
/** Destroy */
await this.destroy();
} catch (e) {
/** Logout */
console.error(e);
} finally {
/** Delete Session */
await this.deleteSession();
/** Remove Instance */
await GramClient.delete(this.name);
}
}
/** Save Session */
async saveSession() {
/** Write to File */
await fs.writeFile(
this.sessionFilePath,
JSON.stringify(this.session.save())
);
/** Mark as Saved */
this.sessionFileExists = true;
}
/** Delete Session */
async deleteSession() {
/** Delete File */
if (this.sessionFileExists) {
await fs.unlink(this.sessionFilePath);
}
/** Mark as Removed */
this.sessionFileExists = false;
}
/**
* Starts a Client
* @param {string} name
* @returns {GramClient}
*/
static async create(name) {
if (this.instances.has(name)) return this.instances.get(name);
const sessionFilePath = `sessions/session_${name}.json`;
const sessionFileExists = await fs
.access(sessionFilePath)
.then(() => true)
.catch(() => false);
const sessionData = sessionFileExists
? JSON.parse(await fs.readFile(sessionFilePath))
: "";
const stringSession = new StringSession(sessionData);
return this.instances
.set(
name,
new GramClient(name, stringSession, sessionFilePath, sessionFileExists)
)
.get(name);
}
/** Delete Instance */
static delete(name) {
this.instances.delete(name);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment