Files
ridebus-admin/backend/src/services/telegramSource.js
Zakhar Timoshenko f7f0b039dd Add Telegram cancellation import functionality with tests and documentation
- Implemented Telegram cancellation parsing logic in `telegramCancellationParser.js`.
- Created unit tests for cancellation parsing in `telegramCancellationParser.test.js`.
- Developed integration tests for Telegram import service in `telegramImport.integration.test.js`.
- Added API tests for Telegram import endpoints in `telegramImportApi.test.js`.
- Introduced service tests for Telegram import logic in `telegramImportService.test.js`.
- Documented the Telegram cancellation import process in `telegram-cancellation-import.md`.
- Built a React component for managing Telegram imports in `TelegramImportPanel.jsx`.
2026-10-03 00:45:41 +03:00

58 lines
2.6 KiB
JavaScript

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 };