33package telegram
44
55import (
6- "bufio"
7- "context"
86 "crypto/rand"
97 "encoding/base64"
108 "encoding/json"
@@ -13,7 +11,6 @@ import (
1311 "math"
1412 "net/http"
1513 "net/url"
16- "os"
1714 "regexp"
1815 "strconv"
1916 "strings"
@@ -31,8 +28,6 @@ func (c *Client) ConnectBot(botToken string) error {
3128 return c .LoginBot (botToken )
3229}
3330
34- const maxRetries = 3
35-
3631var (
3732 botTokenRegex = regexp .MustCompile (`^\d+:[\w\d_-]+$` )
3833 phoneRegex = regexp .MustCompile (`^\+?\d+$` )
@@ -43,30 +38,34 @@ func (c *Client) AuthPrompt() error {
4338 if au , _ := c .IsAuthorized (); au {
4439 return nil
4540 }
46-
47- reader := bufio .NewReader (os .Stdin )
48- for i := range maxRetries {
49- fmt .Print ("Enter phone number (with country code [+42xxx]) or bot token: " )
50- input , err := reader .ReadString ('\n' )
51- if err != nil {
52- return fmt .Errorf ("failed to read input: %w" , err )
53- }
54- input = strings .TrimSpace (input )
55- if input == "" {
56- fmt .Printf ("Invalid response, try again [%d/%d]\n " , i + 1 , maxRetries )
57- continue
58- }
59-
60- if botTokenRegex .MatchString (input ) {
61- return c .LoginBot (input )
62- }
63- if phoneRegex .MatchString (input ) {
64- _ , err := c .Login (input )
65- return err
41+ var input string
42+ maxRetries := 3
43+ for i := 0 ; i < maxRetries ; i ++ {
44+ fmt .Printf ("Enter phone number (with country code [+42xxx]) or bot token: " )
45+ fmt .Scanln (& input )
46+ if input != "" {
47+ if botTokenRegex .MatchString (input ) {
48+ err := c .LoginBot (input )
49+ if err != nil {
50+ return err
51+ }
52+ } else {
53+ if phoneRegex .MatchString (strings .TrimSpace (input )) {
54+ _ , err := c .Login (input )
55+ if err != nil {
56+ return err
57+ }
58+ } else {
59+ fmt .Println ("The input is not a valid phone number or bot token, try again [" , i + 1 , "/" , maxRetries , "]" )
60+ continue
61+ }
62+ }
63+ break
64+ } else {
65+ fmt .Println ("Invalid response, try again" )
6666 }
67- fmt .Printf ("The input is not a valid phone number or bot token, try again [%d/%d]\n " , i + 1 , maxRetries )
6867 }
69- return fmt . Errorf ( "max retries exceeded for authentication" )
68+ return nil
7069}
7170
7271// Authorize client with bot token
@@ -289,163 +288,139 @@ type ScrapeConfig struct {
289288}
290289
291290func (c * Client ) ScrapeAppConfig (config ... * ScrapeConfig ) (int32 , string , bool , error ) {
292- conf : = getVariadic (config , & ScrapeConfig {
291+ var conf = getVariadic (config , & ScrapeConfig {
293292 CreateIfNotExists : true ,
294293 })
295294
296295 if conf .PhoneNum == "" {
297- reader := bufio .NewReader (os .Stdin )
298- fmt .Print ("Enter phone number (with country code [+1xxx]): " )
299- input , err := reader .ReadString ('\n' )
300- if err != nil {
301- return 0 , "" , false , fmt .Errorf ("failed to read phone number: %w" , err )
302- }
303- conf .PhoneNum = strings .TrimSpace (input )
296+ fmt .Printf ("Enter phone number (with country code [+1xxx]): " )
297+ fmt .Scanln (& conf .PhoneNum )
304298 }
305299
306300 if conf .WebCodeCallback == nil {
307301 conf .WebCodeCallback = func () (string , error ) {
308- reader := bufio .NewReader (os .Stdin )
309- fmt .Print ("Enter received web login code: " )
310-
311- code , err := reader .ReadString ('\n' )
312- if err != nil {
313- return "" , fmt .Errorf ("failed to read web code: %w" , err )
314- }
315- return strings .TrimSpace (code ), nil
302+ fmt .Printf ("Enter received web login code: " )
303+ var password string
304+ fmt .Scanln (& password )
305+ return password , nil
316306 }
317307 }
318308
319309 customClient := & http.Client {
320310 Timeout : time .Second * 10 ,
321311 }
322312
323- // Send code request
324313 reqCode , err := http .NewRequest ("POST" , "https://my.telegram.org/auth/send_password" , strings .NewReader ("phone=" + url .QueryEscape (conf .PhoneNum )))
325314 if err != nil {
326- return 0 , "" , false , fmt . Errorf ( "failed to create code request: %w" , err )
315+ return 0 , "" , false , err
327316 }
317+
328318 reqCode .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
329319
330320 respCode , err := customClient .Do (reqCode )
331- if err != nil {
332- return 0 , "" , false , fmt .Errorf ("failed to send code request: %w" , err )
333- }
334- defer respCode .Body .Close ()
335- if respCode .StatusCode != 200 {
336- return 0 , "" , false , fmt .Errorf ("code request failed with status: %d" , respCode .StatusCode )
321+ if err != nil || respCode .StatusCode != 200 {
322+ return 0 , "" , false , err
337323 }
338324
339325 var result struct {
340326 RandomHash string `json:"random_hash"`
341327 }
328+
342329 if err := json .NewDecoder (respCode .Body ).Decode (& result ); err != nil {
343- return 0 , "" , false , errors .Wrap (err , "failed to decode response, too many requests? " )
330+ return 0 , "" , false , errors .Wrap (err , "Too many requests, try again later " )
344331 }
345332
346333 code , err := conf .WebCodeCallback ()
347334 if err != nil {
348- return 0 , "" , false , fmt . Errorf ( "failed to get web code: %w" , err )
335+ return 0 , "" , false , err
349336 }
350337
351- // Login request
352338 reqLogin , err := http .NewRequest ("POST" , "https://my.telegram.org/auth/login" , strings .NewReader ("phone=" + url .QueryEscape (conf .PhoneNum )+ "&random_hash=" + result .RandomHash + "&password=" + url .QueryEscape (code )))
353339 if err != nil {
354- return 0 , "" , false , fmt . Errorf ( "failed to create login request: %w" , err )
340+ return 0 , "" , false , err
355341 }
342+
356343 reqLogin .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
357344
358345 respLogin , err := customClient .Do (reqLogin )
359- if err != nil {
360- return 0 , "" , false , fmt .Errorf ("failed to send login request: %w" , err )
361- }
362- defer respLogin .Body .Close ()
363- if respLogin .StatusCode != 200 {
364- return 0 , "" , false , fmt .Errorf ("login request failed with status: %d" , respLogin .StatusCode )
365- }
366-
367- cookies := respLogin .Cookies ()
368- appID , appHash , err := c .scrapeAppDetails (customClient , cookies , conf .CreateIfNotExists )
369- if err != nil {
346+ if err != nil || respLogin .StatusCode != 200 {
370347 return 0 , "" , false , err
371348 }
372349
373- return appID , appHash , true , nil
374- }
375-
376- func (c * Client ) scrapeAppDetails (client * http.Client , cookies []* http.Cookie , createIfNotExists bool ) (int32 , string , error ) {
377- appIDRegex := regexp .MustCompile (`<label for="app_id".*?>.*?</label>\s*<div.*?>\s*<span.*?><strong>(\d+)</strong></span>` )
378- appHashRegex := regexp .MustCompile (`<label for="app_hash".*?>.*?</label>\s*<div.*?>\s*<span.*?>([a-fA-F0-9]+)</span>` )
350+ cookies := respLogin .Cookies ()
351+ ALREDY_TRIED_CREATION := false
379352
353+ BackToAppsPage:
380354 reqScrape , err := http .NewRequest ("GET" , "https://my.telegram.org/apps" , nil )
381355 if err != nil {
382- return 0 , "" , fmt . Errorf ( "failed to create scrape request: %w" , err )
356+ return 0 , "" , false , err
383357 }
358+
384359 for _ , cookie := range cookies {
385360 reqScrape .AddCookie (cookie )
386361 }
387362
388- respScrape , err := client .Do (reqScrape )
389- if err != nil {
390- return 0 , "" , fmt .Errorf ("failed to send scrape request: %w" , err )
391- }
392- defer respScrape .Body .Close ()
393- if respScrape .StatusCode != 200 {
394- return 0 , "" , fmt .Errorf ("scrape request failed with status: %d" , respScrape .StatusCode )
363+ respScrape , err := customClient .Do (reqScrape )
364+ if err != nil || respScrape .StatusCode != 200 {
365+ return 0 , "" , false , err
395366 }
396367
397368 body , err := io .ReadAll (respScrape .Body )
398369 if err != nil {
399- return 0 , "" , fmt . Errorf ( "failed to read scrape response: %w" , err )
370+ return 0 , "" , false , err
400371 }
401372
373+ appIDRegex := regexp .MustCompile (`<label for="app_id".*?>.*?</label>\s*<div.*?>\s*<span.*?><strong>(\d+)</strong></span>` )
374+ appHashRegex := regexp .MustCompile (`<label for="app_hash".*?>.*?</label>\s*<div.*?>\s*<span.*?>([a-fA-F0-9]+)</span>` )
375+
402376 appID := appIDRegex .FindStringSubmatch (string (body ))
403377 appHash := appHashRegex .FindStringSubmatch (string (body ))
404378
405- if len (appID ) < 2 || len (appHash ) < 2 {
406- if ! createIfNotExists || strings .Contains (string (body ), "Create new application" ) {
407- hiddenHashRegex := regexp .MustCompile (`<input type="hidden" name="hash" value="([a-fA-F0-9]+)"\/>` )
408- hiddenHash := hiddenHashRegex .FindStringSubmatch (string (body ))
409- if len (hiddenHash ) < 2 {
410- return 0 , "" , errors .New ("creation hash not found, try manual creation" )
411- }
379+ if len (appID ) < 2 || len (appHash ) < 2 || strings .Contains (string (body ), "Create new application" ) && ! ALREDY_TRIED_CREATION {
380+ ALREDY_TRIED_CREATION = true
381+ // assume app is not created, create app
382+ hiddenHashRegex := regexp .MustCompile (`<input type="hidden" name="hash" value="([a-fA-F0-9]+)"\/>` )
383+ hiddenHash := hiddenHashRegex .FindStringSubmatch (string (body ))
384+ if len (hiddenHash ) < 2 {
385+ return 0 , "" , false , errors .New ("creation hash not found, try manual creation" )
386+ }
412387
413- appRandomSuffix := make ([]byte , 8 )
414- if _ , err := rand .Read (appRandomSuffix ); err != nil {
415- return 0 , "" , fmt .Errorf ("failed to generate random suffix: %w" , err )
416- }
388+ appRandomSuffix := make ([]byte , 8 )
389+ _ , err := rand .Read (appRandomSuffix )
417390
418- reqCreateApp , err := http .NewRequest ("POST" , "https://my.telegram.org/apps/create" , strings .NewReader ("hash=" + hiddenHash [1 ]+ "&app_title=AppForGogram" + string (appRandomSuffix )+ "&app_shortname=gogramapp" + string (appRandomSuffix )+ "&app_platform=android&app_url=https%3A%2F%2Fgogram.vercel.app&app_desc=ForGoGram" + string (appRandomSuffix )))
419- if err != nil {
420- return 0 , "" , fmt .Errorf ("failed to create app creation request: %w" , err )
421- }
422- reqCreateApp .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
423- for _ , cookie := range cookies {
424- reqCreateApp .AddCookie (cookie )
425- }
391+ if err != nil {
392+ return 0 , "" , false , err
393+ }
426394
427- respCreateApp , err := client .Do (reqCreateApp )
428- if err != nil {
429- return 0 , "" , fmt .Errorf ("failed to send app creation request: %w" , err )
430- }
431- defer respCreateApp .Body .Close ()
432- if respCreateApp .StatusCode != 200 {
433- return 0 , "" , fmt .Errorf ("app creation request failed with status: %d" , respCreateApp .StatusCode )
434- }
395+ reqCreateApp , err := http .NewRequest ("POST" , "https://my.telegram.org/apps/create" , strings .NewReader ("hash=" + hiddenHash [1 ]+ "&app_title=AppForGogram" + string (appRandomSuffix )+ "&app_shortname=gogramapp" + string (appRandomSuffix )+ "&app_platform=android&app_url=https%3A%2F%2Fgogram.vercel.app&app_desc=ForGoGram" + string (appRandomSuffix )))
396+ if err != nil {
397+ return 0 , "" , false , err
398+ }
435399
436- return c .scrapeAppDetails (client , cookies , false )
400+ reqCreateApp .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
401+
402+ for _ , cookie := range cookies {
403+ reqCreateApp .AddCookie (cookie )
404+ }
405+
406+ respCreateApp , err := customClient .Do (reqCreateApp )
407+ if err != nil || respCreateApp .StatusCode != 200 {
408+ return 0 , "" , false , err
437409 }
438- return 0 , "" , errors .New ("failed to scrape app ID or hash" )
410+
411+ goto BackToAppsPage
439412 }
440413
441414 appIdNum , err := strconv .Atoi (appID [1 ])
442415 if err != nil {
443- return 0 , "" , fmt .Errorf ("failed to parse app ID: %w" , err )
444- } else if appIdNum > math .MaxInt32 || appIdNum < math .MinInt32 {
445- return 0 , "" , errors .New ("app ID is out of range" )
416+ return 0 , "" , false , err
446417 }
447418
448- return int32 (appIdNum ), appHash [1 ], nil
419+ if appIdNum > math .MaxInt32 || appIdNum < math .MinInt32 {
420+ return 0 , "" , false , errors .New ("app id is out of range" )
421+ }
422+
423+ return int32 (appIdNum ), appHash [1 ], true , nil
449424}
450425
451426func (c * Client ) AcceptTOS () (bool , error ) {
@@ -457,13 +432,11 @@ func (c *Client) AcceptTOS() (bool, error) {
457432 case * HelpTermsOfServiceUpdateObj :
458433 fmt .Println (tos .TermsOfService .Text )
459434 fmt .Println ("Do you accept the TOS? (y/n)" )
460-
461435 var input string
462436 fmt .Scanln (& input )
463437 if input != "y" {
464438 return false , nil
465439 }
466-
467440 return c .HelpAcceptTermsOfService (tos .TermsOfService .ID )
468441 default :
469442 return false , nil
@@ -584,19 +557,14 @@ func (q *QrToken) Recreate() (*QrToken, error) {
584557 return q , err
585558}
586559
587- func (q * QrToken ) Wait (ctx context.Context , timeout ... int32 ) error {
588- const defaultTimeout int32 = 600 // 10 minutes
589- q .Timeout = getVariadic (timeout , defaultTimeout )
590-
591- ctx , cancel := context .WithTimeout (ctx , time .Duration (q .Timeout )* time .Second )
592- defer cancel ()
593-
560+ func (q * QrToken ) Wait (timeout ... int32 ) error {
561+ const def int32 = 600 // 10 minutes
562+ q .Timeout = getVariadic (timeout , def )
594563 ch := make (chan int )
595564 ev := q .client .AddRawHandler (& UpdateLoginToken {}, func (update Update , client * Client ) error {
596565 ch <- 1
597566 return nil
598567 })
599-
600568 select {
601569 case <- ch :
602570 go q .client .removeHandle (ev )
@@ -625,19 +593,20 @@ func (q *QrToken) Wait(ctx context.Context, timeout ...int32) error {
625593 }
626594 }
627595 return nil
628- case <- ctx . Done ( ):
596+ case <- time . After ( time . Duration ( q . Timeout ) * time . Second ):
629597 go q .client .removeHandle (ev )
630598 return errors .New ("qr login timed out" )
631599 }
632600}
633601
634602func (c * Client ) QRLogin (IgnoreIDs ... int64 ) (* QrToken , error ) {
635603 // Get QR code
636- qr , err := c .AuthExportLoginToken (c .AppID (), c .AppHash (), IgnoreIDs )
604+ var ignoreIDs []int64
605+ ignoreIDs = append (ignoreIDs , IgnoreIDs ... )
606+ qr , err := c .AuthExportLoginToken (c .AppID (), c .AppHash (), ignoreIDs )
637607 if err != nil {
638- return nil , fmt . Errorf ( "failed to export QR token: %v" , err )
608+ return nil , err
639609 }
640-
641610 var (
642611 qrToken []byte
643612 expiresIn int32 = 60
@@ -650,15 +619,14 @@ func (c *Client) QRLogin(IgnoreIDs ...int64) (*QrToken, error) {
650619 qrToken = qr .Token
651620 expiresIn = qr .Expires
652621 }
653-
654622 // Get QR code URL
655623 qrURL := base64 .RawURLEncoding .EncodeToString (qrToken )
656624 return & QrToken {
657625 Token : qrToken ,
658626 Url : fmt .Sprintf ("tg://login?token=%s" , qrURL ),
659627 ExpiresIn : expiresIn ,
660628 client : c ,
661- IgnoredIDs : IgnoreIDs ,
629+ IgnoredIDs : ignoreIDs ,
662630 }, nil
663631}
664632
0 commit comments