Last active
March 29, 2025 15:42
-
-
Save sadiqsalau/b24a5f598139f361ae2ed4de1a928392 to your computer and use it in GitHub Desktop.
GramJS with Multiple Sessions and Promises
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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