alik0211 / mtproto-core

Telegram API JS (MTProto) client library for Node.js and browser
https://mtproto-core.js.org
GNU General Public License v3.0
637 stars 120 forks source link

QR code login flow #153

Open pentolbakso opened 3 years ago

pentolbakso commented 3 years ago

Hi,

Has anyone succeed implementing the QR code login flow ? My problem is that i always get the "Invalid QR Code" after scanning the QR code on my phone. I've checked the token etc .. looks normal to me.

const base64url = require("base64url");

// init stuff

const resp = await mtproto.call("auth.exportLoginToken", {
  api_id,
  api_hash,
  except_ids: [],
});
const b64encoded = base64url(resp.token);
const urlForQrCode = "tg://login?token=" + b64encoded;
// example => tg://login?token=AQXBIBhgSjk_SSfRXiJW2ER08Koz9lM1Og2fH01qNpmujg

Could it be that we need special permission from telegram to use this feature?

Thanks

TemaSM commented 3 years ago

Maybe it will help somehow: https://core.telegram.org/api/qr-login

alorian commented 3 years ago

Same for me. Tried 5 different base64 libraries. I thought it was an encoding issue. There is a difference between base64 and base64url. I was happy when I read the doc more carefully. But even with the base64url token doesn't work.

Tried to decode QR code image with an online converter but QR code is correct.

I don't have any ideas to check for now.

@pentolbakso did you solve the issue eventually?

If somebody has any ideas about the problem I will be happy to check.

alorian commented 3 years ago

I've login successfully :) The problem was in the "test: true" MTProto param. I removed the "test: true" and telegram app successfully login me with QR code

And it's ok to use even base64 encoded tokens. Not base64url

mrsum commented 2 years ago

@alorian can u paste some example of your code here?

alorian commented 2 years ago

@mrsum I used Nestjs framework for my telegram client implementation. Not sure it will be relevant to anybody because of the application specific code. But I can share some parts if you want. I will try to clean everything unrelevent and simplify my examples

The full workflow

Layers

1. Test controller which shows the QR code itself:

Just regular nestjs controller

import { Controller, Get } from '@nestjs/common';
import { TelegramAuthService } from './auth.service';

@Controller('telegram')
export class TelegramController {
    constructor(private readonly authService: TelegramAuthService) {}

    @Get('/auth')
    async auth() {
        const src = await this.authService.getQRCodeBase64();
        let result = '';
        result += '<img src="' + src + '" alt="qr code for login" />';
        return result;
    }
}

2. The auth service

Simple service

import { Injectable } from '@nestjs/common';
import { TelegramApiService } from './api.service';
import * as buf from 'base64-arraybuffer';
import * as QRCode from 'qrcode';

@Injectable()
export class TelegramAuthService {
    constructor(private api: TelegramApiService) {}

    async getQRCodeBase64() {
        const LoginToken = await this.api.getLoginToken();
        const encodedToken = buf.encode(LoginToken.token);
        const url = 'tg://login?token=' + encodedToken;
        return QRCode.toDataURL(url);
    }
}

3. The telegram API which is called from all external services

A service built on top of the mtproto-core

import { Injectable } from '@nestjs/common';
import * as path from 'path';
import * as MTProto from '@mtproto/core/envs/node';
import { sleep } from '@mtproto/core/src/utils/common';

@Injectable()
export class TelegramApiService {
    app_id = 0000000;
    api_hash = 'hash';
    mtproto: MTProto;

    constructor() {
        this.mtproto = new MTProto({
            api_id: this.app_id,
            api_hash: this.api_hash,
            //test: true,

            storageOptions: {
                path: path.resolve(__dirname, './data/1.json'),
            },
        });

        this.mtproto.updates.on('updatesTooLong', (updateInfo) => {
            console.log('updatesTooLong:', updateInfo);
        });

        this.mtproto.updates.on('updateShortMessage', (updateInfo) => {
            console.log('updateShortMessage:', updateInfo);
        });

        this.mtproto.updates.on('updateShortChatMessage', (updateInfo) => {
            console.log('updateShortChatMessage:', updateInfo);
        });

        this.mtproto.updates.on('updateShort', (updateInfo) => {
            console.log('updateShort:', updateInfo);
        });

        this.mtproto.updates.on('updatesCombined', (updateInfo) => {
            console.log('updatesCombined:', updateInfo);
        });

        this.mtproto.updates.on('updates', (updateInfo) => {
            console.log('updates:', updateInfo);
        });

        this.mtproto.updates.on('updateShortSentMessage', (updateInfo) => {
            console.log('updateShortSentMessage:', updateInfo);
        });
    }

    async getLoginToken() {
        return await this.call('auth.exportLoginToken', {
            app_id: this.app_id,
            api_hash: this.api_hash,
            except_ids: [],
        });
    }

    async call(method: string, params, options = {}) {
        try {
            return await this.mtproto.call(method, params, options);
        } catch (error) {
            console.log(`${method} error:`, error);

            const { error_code, error_message } = error;

            if (error_code === 420) {
                const seconds = Number(error_message.split('FLOOD_WAIT_')[1]);
                const ms = seconds * 1000;

                await sleep(ms);

                return this.call(method, params, options);
            }

            if (error_code === 303) {
                const [type, dcIdAsString] = error_message.split('_MIGRATE_');

                const dcId = Number(dcIdAsString);

                // If auth.sendCode call on incorrect DC need change default DC, because
                // call auth.signIn on incorrect DC return PHONE_CODE_EXPIRED error
                if (type === 'PHONE') {
                    await this.mtproto.setDefaultDc(dcId);
                } else {
                    Object.assign(options, { dcId });
                }

                return this.call(method, params, options);
            }

            return Promise.reject(error);
        }
    }
}

Just tested it and everything works. From the second attempt but works. At the very first time there was an error about wrong QR code but the second attempt logged me in correctly.

Packages in my code: https://www.npmjs.com/package/base64-arraybuffer https://www.npmjs.com/package/qrcode

Hope it will help somebody.

mrsum commented 2 years ago

@alorian thank u my friend, your example is very helpfull

samarth30 commented 2 months ago

Hi @alorian is auth.acceptLoginToken working for you.

I am able to generate QR code perfectly but not able to run auth.acceptLoginToken can you help me with this if possible