// Package qrlogin provides QR login flow implementation. // // See https://core.telegram.org/api/qr-login.
package qrlogin import ( ) // QR implements Telegram QR login flow. type QR struct { api *tg.Client appID int appHash string migrate func(ctx context.Context, dcID int) error clock clock.Clock } // NewQR creates new QR func ( *tg.Client, int, string, Options) QR { .setDefaults() return QR{ api: , appID: , appHash: , clock: .Clock, migrate: .Migrate, } } // Export exports new login token. // // See https://core.telegram.org/api/qr-login#exporting-a-login-token. func ( QR) ( context.Context, ...int64) (Token, error) { , := .api.AuthExportLoginToken(, &tg.AuthExportLoginTokenRequest{ APIID: .appID, APIHash: .appHash, ExceptIDs: , }) if != nil { return Token{}, errors.Wrap(, "export") } , := .(*tg.AuthLoginToken) if ! { return Token{}, errors.Errorf("unexpected type %T", ) } return NewToken(.Token, .Expires), nil } // Accept accepts given token. // // See https://core.telegram.org/api/qr-login#accepting-a-login-token. func ( QR) ( context.Context, Token) (*tg.Authorization, error) { return AcceptQR(, .api, ) } // Import imports accepted token. // // See https://core.telegram.org/api/qr-login#confirming-importing-the-login-token. func ( QR) ( context.Context) (*tg.AuthAuthorization, error) { , := .api.AuthExportLoginToken(, &tg.AuthExportLoginTokenRequest{ APIID: .appID, APIHash: .appHash, }) if != nil { return nil, errors.Wrap(, "import") } switch t := .(type) { case *tg.AuthLoginTokenMigrateTo: if .migrate == nil { return nil, &MigrationNeededError{ MigrateTo: , } } if := .migrate(, .DCID); != nil { return nil, errors.Wrap(, "migrate") } , := .api.AuthImportLoginToken(, .Token) if != nil { return nil, errors.Wrap(, "import") } , := .(*tg.AuthLoginTokenSuccess) if ! { return nil, errors.Errorf("unexpected type %T", ) } , := .Authorization.(*tg.AuthAuthorization) if ! { return nil, errors.Errorf("unexpected type %T", .Authorization) } return , nil case *tg.AuthLoginTokenSuccess: , := .Authorization.(*tg.AuthAuthorization) if ! { return nil, errors.Errorf("unexpected type %T", .Authorization) } return , nil default: return nil, errors.Errorf("unexpected type %T", ) } } // LoggedIn is signal channel to notify about tg.UpdateLoginToken. type LoggedIn <-chan struct{} // OnLoginToken sets handler for given dispatcher and returns signal channel. func ( interface { (tg.LoginTokenHandler) }, ) LoggedIn { := make(chan struct{}) .(func( context.Context, tg.Entities, *tg.UpdateLoginToken) error { select { case <- struct{}{}: return nil default: } return nil }) return } // Auth generates new QR login token, shows it and awaits acceptation. // // NB: Show callback may be called more than once if QR expires. func ( QR) ( context.Context, LoggedIn, func( context.Context, Token) error, ...int64, ) (*tg.AuthAuthorization, error) { := func( Token) time.Duration { return .Expires().Sub(.clock.Now()).Truncate(time.Second) } , := .Export(, ...) if != nil { return nil, } := .clock.Timer(()) defer clock.StopTimer() for { if := (, ); != nil { return nil, errors.Wrap(, "show") } select { case <-.Done(): return nil, .Err() case <-.C(): , := .Export(, ...) if != nil { return nil, } = .Reset(()) continue case <-: } return .Import() } }