const fs = require('node:fs/promises'); const { assertExternalSessionFile } = require('../config/telegram'); class TelegramSource { constructor(config) { this.config = config; this.client = null; this.channel = null; } async connect() { if (this.client?.connected && this.channel) return; if (this.client) await this.close(); const file = await assertExternalSessionFile(this.config.sessionFile); const session = (await fs.readFile(file, 'utf8')).trim(); if (!session) throw new Error('SESSION_MISSING'); const { TelegramClient } = require('teleproto'); const { StringSession } = require('teleproto/sessions'); const { Logger } = require('teleproto/extensions/Logger'); const client = new TelegramClient(new StringSession(session), this.config.apiId, this.config.apiHash, { connectionRetries: 2, requestRetries: 2, floodSleepThreshold: 0, baseLogger: new Logger('none') }); this.client = client; await client.connect(); if (!await client.checkAuthorization()) throw new Error('AUTH_REQUIRED'); if ((await client.getMe()).bot) throw new Error('USER_ACCOUNT_REQUIRED'); const channel = await client.getEntity(this.config.channel); if (channel.className !== 'Channel' || !channel.broadcast || channel.username?.toLowerCase() !== this.config.channel.toLowerCase()) { throw new Error('CHANNEL_INVALID'); } this.channel = channel; } async fetchMessages(since) { await this.connect(); const messages = []; // Pagination revisits recent messages so edits are detected after restarts. // The bound prevents an accidental full-history import of a busy channel. for await (const message of this.client.iterMessages(this.channel, { limit: 1000, waitTime: 1 })) { if (message.date && message.date * 1000 < since.getTime()) return messages.reverse(); if (message.className !== 'Message' || !message.date) continue; messages.push({ channelId: this.channel.id.toString(), channelUsername: this.channel.username, messageId: message.id, text: message.message || '', hasMedia: Boolean(message.media && message.media.className !== 'MessageMediaWebPage'), publishedAt: new Date(message.date * 1000).toISOString(), editedAt: message.editDate ? new Date(message.editDate * 1000).toISOString() : null }); } if (messages.length >= 1000) throw new Error('HISTORY_LIMIT'); return messages.reverse(); } async close() { const client = this.client; this.client = null; this.channel = null; if (client) await client.destroy(); } } module.exports = { TelegramSource };