diff --git a/.env.example b/.env.example index 7110d0f..68f8f1d 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,5 @@ DATABASE_URL=postgres://postgres:dev@localhost/tgbucket TELEGRAM_API_ID= TELEGRAM_API_HASH= -TELEGRAM_SESSION= +TELEGRAM_BOT_TOKEN= +TELEGRAM_CHAT_ID diff --git a/src/configs/mtproto.config.ts b/src/configs/mtproto.config.ts index b8aba3f..0f1051f 100644 --- a/src/configs/mtproto.config.ts +++ b/src/configs/mtproto.config.ts @@ -2,7 +2,7 @@ import { registerAs } from '@nestjs/config'; export const mtprotoConfig = registerAs('mtproto', () => { - const store = JSON.parse(process.env.TELEGRAM_SESSION!); + const store: Record = {}; return { api_id: parseInt(process.env.TELEGRAM_API_ID!, 10), api_hash: process.env.TELEGRAM_API_HASH!, diff --git a/src/telegram/telegram.service.ts b/src/telegram/telegram.service.ts index 8cbb69c..effa94f 100644 --- a/src/telegram/telegram.service.ts +++ b/src/telegram/telegram.service.ts @@ -1,5 +1,6 @@ import { MTProto, MTProtoError } from '@mtproto/core'; -import { Inject, Injectable, Logger } from '@nestjs/common'; +import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import * as BlockStream from 'block-stream2'; import { Readable, Transform, TransformCallback } from 'stream'; import { pipeline } from 'stream/promises'; @@ -19,10 +20,26 @@ import { } from './telegram.types'; @Injectable() -export class TelegramService { +export class TelegramService implements OnModuleInit { private readonly logger = new Logger(this.constructor.name); - constructor(@Inject(MTPROTO) private readonly mtproto: MTProto) {} + constructor( + @Inject(MTPROTO) private readonly mtproto: MTProto, + private readonly config: ConfigService, + ) {} + + async onModuleInit(): Promise { + const token = this.config.get('TELEGRAM_BOT_TOKEN'); + await this.loginBot(token); + } + + async loginBot(token: string): Promise { + const res = await this.callApi('auth.importBotAuthorization', { + bot_auth_token: token, + }); + const username = res?.user?.username; + this.logger.log(`Successfully logged in to Telegram as "${username}"`); + } async uploadFile(fileStream: Readable, size: number): Promise { const partSize = 512 * 1024; // 512kb @@ -133,7 +150,8 @@ export class TelegramService { }); } - async uploadAndSendDocumentToSelf( + async uploadAndSendDocumentToChat( + chatId: string, inputFile: InputFileBig, filename?: string, ): Promise { @@ -147,7 +165,8 @@ export class TelegramService { const updates = await this.callApi('messages.sendMedia', { peer: { - _: 'inputPeerSelf', + _: 'inputPeerChat', + chat_id: chatId, }, media: { _: 'inputMediaUploadedDocument', diff --git a/src/upload/upload.service.ts b/src/upload/upload.service.ts index 352a4e0..69a5809 100644 --- a/src/upload/upload.service.ts +++ b/src/upload/upload.service.ts @@ -1,4 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { FileEntity } from '../files/file.entity'; import { FilesService } from '../files/files.service'; @@ -12,6 +13,7 @@ export class UploadService { constructor( private readonly telegramService: TelegramService, private readonly filesService: FilesService, + private readonly configService: ConfigService, ) {} async processFile(file: UploadFile): Promise { @@ -22,8 +24,10 @@ export class UploadService { ); this.logger.debug('Send media message to Telegram'); + const chatId = this.configService.get('TELEGRAM_CHAT_ID'); const newMessageUpdate = - await this.telegramService.uploadAndSendDocumentToSelf( + await this.telegramService.uploadAndSendDocumentToChat( + chatId, inputFile, file.filename, ); diff --git a/utils/generate-mtproto-session.util.ts b/utils/generate-mtproto-session.util.ts deleted file mode 100644 index 50ba7b0..0000000 --- a/utils/generate-mtproto-session.util.ts +++ /dev/null @@ -1,131 +0,0 @@ -import * as MTProto from '@mtproto/core'; -import * as fs from 'fs'; -import { stdin as input, stdout as output } from 'process'; -import * as readline from 'readline'; - -const rl = readline.createInterface({ input, output }); -const API_ID = ''; -const API_HASH = ''; - -const store: Record = {}; -const api = new MTProto({ - api_id: Number(API_ID), - api_hash: API_HASH, - storageOptions: { - instance: { - get: async (key: string) => store[key], - set: async (key: string, value: string) => { - store[key] = value; - }, - }, - }, -}); - -api.updateInitConnectionParams({ - platform: 'Web', - system_version: '1.1.1', - device_model: 'tgbucket', - app_name: 'TGBucket', - app_version: '1.1.1', -}); - -(async () => { - try { - await createSession(); - process.exit(0); - } catch (e) { - console.error('Failed to create session', e); - process.exit(1); - } -})(); - -/* Core */ -async function createSession(): Promise { - try { - await logIn(); - } catch (e: any) { - if (e.error_message === 'SESSION_PASSWORD_NEEDED') { - console.log('Account have enabled 2FA'); - await logIn2FA(); - return; - } - - throw e; - } -} - -async function logIn(): Promise { - const phone = await prompt('Phone (without +):'); - console.log('phone', phone, phone.length); - const { phone_code_hash } = await sendCode(phone); - - const code = await prompt('Code:'); - const signInResult = await signIn(code, phone, phone_code_hash); - - console.debug('signInResult', signInResult); - saveSessions(); -} - -async function logIn2FA(): Promise { - const password = await prompt('Password:'); - const { srp_id, current_algo, srp_B } = await getPassword(); - const { g, p, salt1, salt2 } = current_algo; - const { A, M1 } = await (api as any).crypto.getSRPParams({ - g, - p, - salt1, - salt2, - gB: srp_B, - password, - }); - - const checkPasswordResult = await checkPassword(srp_id, A, M1); - console.debug('checkPasswordResult', checkPasswordResult); - saveSessions(); -} - -function saveSessions(): void { - const session = JSON.stringify(store); - console.log('Session created:', session); - fs.writeFileSync('./session.json', session); -} - -/* MTProto */ -function sendCode(phone: string) { - return api.call('auth.sendCode', { - phone_number: phone, - settings: { - _: 'codeSettings', - }, - }) as Promise<{ phone_code_hash: string }>; -} - -function signIn(code: string, phone: string, phoneCodeHash: string) { - return api.call('auth.signIn', { - phone_code: code, - phone_number: phone, - phone_code_hash: phoneCodeHash, - }); -} - -function getPassword() { - return api.call('account.getPassword'); -} - -function checkPassword(srpId: string, A: string, M1: string) { - return api.call('auth.checkPassword', { - password: { - _: 'inputCheckPasswordSRP', - srp_id: srpId, - A, - M1, - }, - }); -} - -/* Utils */ -function prompt(text: string): Promise { - return new Promise((resolve) => { - rl.question(text + ' ', (input) => resolve(input)); - }); -}