diff --git a/bot/handle_add_task.go b/bot/handle_add_task.go index b07ea32..d34dfb0 100644 --- a/bot/handle_add_task.go +++ b/bot/handle_add_task.go @@ -18,6 +18,7 @@ import ( "github.com/krau/SaveAny-Bot/dao" "github.com/krau/SaveAny-Bot/queue" "github.com/krau/SaveAny-Bot/types" + "github.com/krau/SaveAny-Bot/userclient" "gorm.io/gorm" ) @@ -153,7 +154,14 @@ func AddToQueue(ctx *ext.Context, update *ext.Update) error { task.StoragePath = path.Join(dir.Path, record.FileName) } } else { - file, err := FileFromMessage(ctx, record.ChatID, record.MessageID, record.FileName) + var file *types.File + var err error + if record.UseUserClient && userclient.UC != nil { + uctx := userclient.UC.CreateContext() + file, err = FileFromMessage(uctx, record.ChatID, record.MessageID, record.FileName) + } else { + file, err = FileFromMessage(ctx, record.ChatID, record.MessageID, record.FileName) + } if err != nil { common.Log.Errorf("获取消息中的文件失败: %s", err) ctx.AnswerCallback(&tg.MessagesSetBotCallbackAnswerRequest{ @@ -168,6 +176,7 @@ func AddToQueue(ctx *ext.Context, update *ext.Update) error { task = types.Task{ Ctx: ctx, Status: types.Pending, + UseUserClient: record.UseUserClient, FileDBID: record.ID, File: file, StorageName: storageName, diff --git a/bot/handle_link.go b/bot/handle_link.go index 5a3198a..b87a4dd 100644 --- a/bot/handle_link.go +++ b/bot/handle_link.go @@ -12,6 +12,7 @@ import ( "github.com/krau/SaveAny-Bot/common" "github.com/krau/SaveAny-Bot/dao" "github.com/krau/SaveAny-Bot/types" + "github.com/krau/SaveAny-Bot/userclient" ) var ( @@ -50,6 +51,31 @@ func parseLink(ctx *ext.Context, link string) (chatID int64, messageID int, err return chatID, messageID, nil } +// use passed ctx client to fetch file from message, +// +// if failed try using userclient +func tryFetchFileFromMessage(ctx *ext.Context, chatID int64, messageID int, fileName string) (*types.File, bool, error) { + file, err := FileFromMessage(ctx, chatID, messageID, fileName) + if err == nil { + return file, false, nil + } + if (strings.Contains(err.Error(), "peer not found") || strings.Contains(err.Error(), "unexpected message type")) && userclient.UC != nil { + common.Log.Warnf("无法获取文件 %d:%d, 尝试使用 userbot: %s", chatID, messageID, err) + uctx := userclient.GetCtx() + // TODO: 群组支持 + file, err = FileFromMessage(uctx, chatID, messageID, fileName) + if err == nil { + return file, true, nil + } + return nil, true, err + } + return nil, false, err +} + +func tryFetchMessage(ctx *ext.Context, chatID int64, messageID int) (*tg.Message, error) { + return GetTGMessage(ctx, chatID, messageID) +} + func handleLinkMessage(ctx *ext.Context, update *ext.Update) error { common.Log.Trace("Got link message") link := linkRegex.FindString(update.EffectiveMessage.Text) @@ -70,26 +96,25 @@ func handleLinkMessage(ctx *ext.Context, update *ext.Update) error { return dispatcher.EndGroups } - // storages := storage.GetUserStorages(user.ChatID) - // if len(storages) == 0 { - // ctx.Reply(update, ext.ReplyTextString("无可用的存储"), nil) - // return dispatcher.EndGroups - // } - replied, err := ctx.Reply(update, ext.ReplyTextString("正在获取文件..."), nil) if err != nil { common.Log.Errorf("回复失败: %s", err) return dispatcher.EndGroups } - file, err := FileFromMessage(ctx, linkChatID, messageID, "") + file, useUserClient, err := tryFetchFileFromMessage(ctx, linkChatID, messageID, "") if err != nil { common.Log.Errorf("获取文件失败: %s", err) ctx.Reply(update, ext.ReplyTextString("获取文件失败: "+err.Error()), nil) return dispatcher.EndGroups } if file.FileName == "" { - file.FileName = GenFileNameFromMessage(*update.EffectiveMessage.Message, file) + msg, err := tryFetchMessage(ctx, linkChatID, messageID) + if err != nil { + file.FileName = fmt.Sprintf("%d_%d", linkChatID, messageID) + } else { + file.FileName = GenFileNameFromMessage(*msg, file) + } } receivedFile := &dao.ReceivedFile{ @@ -99,6 +124,7 @@ func handleLinkMessage(ctx *ext.Context, update *ext.Update) error { MessageID: messageID, ReplyMessageID: replied.ID, ReplyChatID: update.GetUserChat().GetID(), + UseUserClient: useUserClient, } record, err := dao.SaveReceivedFile(receivedFile) if err != nil { @@ -116,6 +142,7 @@ func handleLinkMessage(ctx *ext.Context, update *ext.Update) error { Ctx: ctx, Status: types.Pending, FileDBID: record.ID, + UseUserClient: useUserClient, File: file, StorageName: user.DefaultStorage, UserID: user.ChatID, diff --git a/bot/handlers.go b/bot/handlers.go index 4876914..d4eef3c 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -4,31 +4,38 @@ import ( "github.com/celestix/gotgproto/dispatcher" "github.com/celestix/gotgproto/dispatcher/handlers" "github.com/celestix/gotgproto/dispatcher/handlers/filters" + "github.com/celestix/gotgproto/ext" "github.com/krau/SaveAny-Bot/common" ) -func RegisterHandlers(dispatcher dispatcher.Dispatcher) { - dispatcher.AddHandler(handlers.NewMessage(filters.Message.All, checkPermission)) - dispatcher.AddHandler(handlers.NewCommand("start", start)) - dispatcher.AddHandler(handlers.NewCommand("help", help)) - dispatcher.AddHandler(handlers.NewCommand("silent", silent)) - dispatcher.AddHandler(handlers.NewCommand("storage", storageCmd)) - dispatcher.AddHandler(handlers.NewCommand("save", saveCmd)) - dispatcher.AddHandler(handlers.NewCommand("dir", dirCmd)) - dispatcher.AddHandler(handlers.NewCommand("rule", ruleCmd)) +func RegisterHandlers(disp dispatcher.Dispatcher) { + disp.AddHandler(handlers.NewMessage(filters.Message.ChatType(filters.ChatTypeChannel), func(ctx *ext.Context, u *ext.Update) error { + return dispatcher.EndGroups + })) + disp.AddHandler(handlers.NewMessage(filters.Message.ChatType(filters.ChatTypeChat), func(ctx *ext.Context, u *ext.Update) error { + return dispatcher.EndGroups + })) + disp.AddHandler(handlers.NewMessage(filters.Message.All, checkPermission)) + disp.AddHandler(handlers.NewCommand("start", start)) + disp.AddHandler(handlers.NewCommand("help", help)) + disp.AddHandler(handlers.NewCommand("silent", silent)) + disp.AddHandler(handlers.NewCommand("storage", storageCmd)) + disp.AddHandler(handlers.NewCommand("save", saveCmd)) + disp.AddHandler(handlers.NewCommand("dir", dirCmd)) + disp.AddHandler(handlers.NewCommand("rule", ruleCmd)) linkRegexFilter, err := filters.Message.Regex(linkRegexString) if err != nil { common.Log.Panicf("创建正则表达式过滤器失败: %s", err) } - dispatcher.AddHandler(handlers.NewMessage(linkRegexFilter, handleLinkMessage)) + disp.AddHandler(handlers.NewMessage(linkRegexFilter, handleLinkMessage)) telegraphUrlRegexFilter, err := filters.Message.Regex(TelegraphUrlRegexString) if err != nil { common.Log.Panicf("创建 Telegraph URL 正则表达式过滤器失败: %s", err) } - dispatcher.AddHandler(handlers.NewMessage(telegraphUrlRegexFilter, handleTelegraph)) - dispatcher.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix("add"), AddToQueue)) - dispatcher.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix("set_default"), setDefaultStorage)) - dispatcher.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix("cancel"), cancelTask)) - dispatcher.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix("send_here"), sendFileToTelegram)) - dispatcher.AddHandler(handlers.NewMessage(filters.Message.Media, handleFileMessage)) + disp.AddHandler(handlers.NewMessage(telegraphUrlRegexFilter, handleTelegraph)) + disp.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix("add"), AddToQueue)) + disp.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix("set_default"), setDefaultStorage)) + disp.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix("cancel"), cancelTask)) + disp.AddHandler(handlers.NewCallbackQuery(filters.CallbackQuery.Prefix("send_here"), sendFileToTelegram)) + disp.AddHandler(handlers.NewMessage(filters.Message.Media, handleFileMessage)) } diff --git a/cmd/run.go b/cmd/run.go index 2a31c1a..a2d76e2 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "os" "os/signal" @@ -17,6 +18,7 @@ import ( "github.com/krau/SaveAny-Bot/i18n" "github.com/krau/SaveAny-Bot/i18n/i18nk" "github.com/krau/SaveAny-Bot/storage" + "github.com/krau/SaveAny-Bot/userclient" "github.com/spf13/cobra" ) @@ -76,5 +78,15 @@ func InitAll() { dao.Init() storage.LoadStorages() common.Init() + if config.Cfg.Telegram.Userbot.Enable { + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + uc, err := userclient.Login(ctx) + if err != nil { + common.Log.Errorf("User client login failed: %s", err) + os.Exit(1) + } + common.Log.Infof("User client logged in as %s", uc.Self.FirstName) + } bot.Init() } diff --git a/config/viper.go b/config/viper.go index 6492cb7..d015b5c 100644 --- a/config/viper.go +++ b/config/viper.go @@ -51,13 +51,19 @@ type dbConfig struct { } type telegramConfig struct { - Token string `toml:"token" mapstructure:"token"` - AppID int `toml:"app_id" mapstructure:"app_id" json:"app_id"` - AppHash string `toml:"app_hash" mapstructure:"app_hash" json:"app_hash"` - Timeout int `toml:"timeout" mapstructure:"timeout" json:"timeout"` - Proxy proxyConfig `toml:"proxy" mapstructure:"proxy"` - FloodRetry int `toml:"flood_retry" mapstructure:"flood_retry" json:"flood_retry"` - RpcRetry int `toml:"rpc_retry" mapstructure:"rpc_retry" json:"rpc_retry"` + Token string `toml:"token" mapstructure:"token"` + AppID int `toml:"app_id" mapstructure:"app_id" json:"app_id"` + AppHash string `toml:"app_hash" mapstructure:"app_hash" json:"app_hash"` + Timeout int `toml:"timeout" mapstructure:"timeout" json:"timeout"` + Proxy proxyConfig `toml:"proxy" mapstructure:"proxy"` + FloodRetry int `toml:"flood_retry" mapstructure:"flood_retry" json:"flood_retry"` + RpcRetry int `toml:"rpc_retry" mapstructure:"rpc_retry" json:"rpc_retry"` + Userbot userbotConfig `toml:"userbot" mapstructure:"userbot" json:"userbot"` +} + +type userbotConfig struct { + Enable bool `toml:"enable" mapstructure:"enable"` + Session string `toml:"session" mapstructure:"session"` } type proxyConfig struct { @@ -97,6 +103,8 @@ func Init() error { viper.SetDefault("telegram.timeout", 60) viper.SetDefault("telegram.flood_retry", 5) viper.SetDefault("telegram.rpc_retry", 5) + viper.SetDefault("telegram.userbot.enable", false) + viper.SetDefault("telegram.userbot.session", "data/usersession.db") viper.SetDefault("temp.base_path", "cache/") viper.SetDefault("temp.cache_ttl", 30) diff --git a/core/download.go b/core/download.go index b37edc0..39df7e2 100644 --- a/core/download.go +++ b/core/download.go @@ -22,6 +22,7 @@ import ( "github.com/krau/SaveAny-Bot/config" "github.com/krau/SaveAny-Bot/storage" "github.com/krau/SaveAny-Bot/types" + "github.com/krau/SaveAny-Bot/userclient" "golang.org/x/sync/errgroup" ) @@ -62,8 +63,11 @@ func processPendingTask(task *types.Task) error { if task.File.FileSize == 0 { return processPhoto(task, taskStorage) } - - downloadBuilder := Downloader.Download(bot.Client.API(), task.File.Location).WithThreads(getTaskThreads(task.File.FileSize)) + api := bot.Client.API() + if task.UseUserClient && userclient.UC != nil { + api = userclient.UC.API() + } + downloadBuilder := Downloader.Download(api, task.File.Location).WithThreads(getTaskThreads(task.File.FileSize)) notsupportStreamStorage, notsupportStream := taskStorage.(storage.StorageNotSupportStream) cancelMarkUp := getCancelTaskMarkup(task) diff --git a/dao/model.go b/dao/model.go index d6d3eff..7c14402 100644 --- a/dao/model.go +++ b/dao/model.go @@ -16,6 +16,7 @@ type ReceivedFile struct { FileName string IsTelegraph bool TelegraphURL string + UseUserClient bool // Whether to use userbot client to fetch the file } type User struct { diff --git a/go.mod b/go.mod index 8cce7cb..dc3d0e5 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,14 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/celestix/gotgproto v1.0.0-beta21 github.com/celestix/telegraph-go/v2 v2.0.4 + github.com/cenkalti/backoff/v4 v4.3.0 + github.com/charmbracelet/huh v0.7.0 + github.com/charmbracelet/log v0.4.2 github.com/eko/gocache/lib/v4 v4.2.0 github.com/eko/gocache/store/go_cache/v4 v4.2.2 + github.com/fatih/color v1.18.0 github.com/gabriel-vasile/mimetype v1.4.9 + github.com/go-faster/errors v0.7.1 github.com/gookit/slog v0.5.8 github.com/gotd/contrib v0.21.0 github.com/gotd/td v0.125.0 @@ -22,20 +27,30 @@ require ( require ( github.com/AnimeKaizoku/cacher v1.0.2 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/catppuccin/go v0.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charmbracelet/bubbles v0.21.0 // indirect + github.com/charmbracelet/bubbletea v1.3.4 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13 // indirect + github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/coder/websocket v1.8.13 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/fatih/color v1.18.0 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect - github.com/go-faster/errors v0.7.1 // indirect github.com/go-faster/jx v1.1.0 // indirect github.com/go-faster/xor v1.0.0 // indirect github.com/go-faster/yaml v0.4.6 // indirect github.com/go-ini/ini v1.67.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang/mock v1.6.0 // indirect @@ -48,10 +63,17 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/minio/crc64nvme v1.0.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ogen-go/ogen v1.14.0 // indirect @@ -63,6 +85,7 @@ require ( github.com/prometheus/common v0.64.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/xid v1.6.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/tcnksm/go-gitconfig v0.1.2 // indirect diff --git a/go.sum b/go.sum index 2cdae72..88cc556 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,20 @@ github.com/AnimeKaizoku/cacher v1.0.2 h1:7Bf5qRylWb7q2Evib0OXlhG37/t7BP2HK/7IyPv github.com/AnimeKaizoku/cacher v1.0.2/go.mod h1:jw0de/b0K6W7Y3T9rHCMGVKUf6oG7hENNcssxYcZTCc= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= +github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/celestix/gotgproto v1.0.0-beta21 h1:VUuAC/Kj5Sdu/WZan3ZUb0GFNAavFxMYxmHAhCBX0J8= github.com/celestix/gotgproto v1.0.0-beta21/go.mod h1:viDkHe9rBegJoEE/jNuFfbBM0XZ3pSx/ugjaNaVnbvU= github.com/celestix/telegraph-go/v2 v2.0.4 h1:w8HWymJFhMSMPjdGoyTh3/NqE3eXAT1njTvelh0338k= @@ -14,9 +24,41 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= +github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= +github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI= +github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/huh v0.7.0 h1:W8S1uyGETgj9Tuda3/JdVkc3x7DBLZYPZc4c+/rnRdc= +github.com/charmbracelet/huh v0.7.0/go.mod h1:UGC3DZHlgOKHvHC07a5vHag41zzhpPFj34U92sOmyuk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= +github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= +github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U= +github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4= +github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI= +github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE= github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= @@ -29,6 +71,8 @@ github.com/eko/gocache/lib/v4 v4.2.0 h1:MNykyi5Xw+5Wu3+PUrvtOCaKSZM1nUSVftbzmeC7 github.com/eko/gocache/lib/v4 v4.2.0/go.mod h1:7ViVmbU+CzDHzRpmB4SXKyyzyuJ8A3UW3/cszpcqB4M= github.com/eko/gocache/store/go_cache/v4 v4.2.2 h1:tAI9nl6TLoJyKG1ujF0CS0n/IgTEMl+NivxtR5R3/hw= github.com/eko/gocache/store/go_cache/v4 v4.2.2/go.mod h1:T9zkHokzr8K9EiC7RfMbDg6HSwaV6rv3UdcNu13SGcA= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -55,6 +99,8 @@ github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I= github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -118,18 +164,32 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg= github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.92 h1:jpBFWyRS3p8P/9tsRc+NuvqoFi7qAmTCFPoRFmobbVw= github.com/minio/minio-go/v7 v7.0.92/go.mod h1:vTIc8DNcnAZIhyFsk8EB90AbPjj3j68aWIEQCiPj7d0= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -164,6 +224,9 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rhysd/go-github-selfupdate v1.2.3 h1:iaa+J202f+Nc+A8zi75uccC8Wg3omaM7HDeimXA22Ag= github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -254,6 +317,7 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= diff --git a/types/task.go b/types/task.go index 155f15a..25ed4b6 100644 --- a/types/task.go +++ b/types/task.go @@ -13,15 +13,16 @@ import ( ) type Task struct { - Ctx context.Context - Cancel context.CancelFunc - Error error - Status TaskStatus - StorageName string - StoragePath string - StartTime time.Time - FileDBID uint + Ctx context.Context + Cancel context.CancelFunc + Error error + UseUserClient bool + Status TaskStatus + StorageName string + StoragePath string + StartTime time.Time + FileDBID uint File *File FileMessageID int FileChatID int64 diff --git a/userclient/auth.go b/userclient/auth.go new file mode 100644 index 0000000..d20eb5b --- /dev/null +++ b/userclient/auth.go @@ -0,0 +1,80 @@ +package userclient + +import ( + "strings" + + "github.com/celestix/gotgproto" + "github.com/charmbracelet/huh" + "github.com/charmbracelet/log" + "github.com/fatih/color" +) + +type termialAuthConversator struct{} + +func (t *termialAuthConversator) AskPhoneNumber() (string, error) { + phone := "" + err := huh.NewInput().Title("Your Phone Number"). + Placeholder("+44 123456"). + Prompt("> "). + Value(&phone). + WithTheme(huh.ThemeCatppuccin()). + Run() + + if err != nil { + return "", err + } + + log.Info("Sending code to your phone number...") + + return strings.TrimSpace(phone), nil +} + +func (t *termialAuthConversator) AskCode() (string, error) { + code := "" + err := huh.NewInput().Title("Your Code"). + Placeholder("123456"). + Value(&code). + Prompt("> "). + WithTheme(huh.ThemeCatppuccin()). + Run() + + if err != nil { + return "", err + } + + return strings.TrimSpace(code), nil +} + +func (t *termialAuthConversator) AskPassword() (string, error) { + pwd := "" + + err := huh.NewInput().Title("Your 2FA Password"). + EchoMode(huh.EchoModePassword). + Value(&pwd). + Prompt("> "). + WithTheme(huh.ThemeCatppuccin()). + Run() + if err != nil { + return "", err + } + + return strings.TrimSpace(pwd), nil +} + +func (t *termialAuthConversator) AuthStatus(authStatus gotgproto.AuthStatus) { + switch authStatus.Event { + case gotgproto.AuthStatusPhoneRetrial: + color.Red("The phone number you just entered seems to be incorrect,") + color.Red("Attempts Left: %d", authStatus.AttemptsLeft) + color.Red("Please try again....") + case gotgproto.AuthStatusPasswordRetrial: + color.Red("The 2FA password you just entered seems to be incorrect,") + color.Red("Attempts Left: %d", authStatus.AttemptsLeft) + color.Red("Please try again....") + case gotgproto.AuthStatusPhoneCodeRetrial: + color.Red("The OTP you just entered seems to be incorrect,") + color.Red("Attempts Left: %d", authStatus.AttemptsLeft) + color.Red("Please try again....") + default: + } +} diff --git a/userclient/middlewares/middlewares.go b/userclient/middlewares/middlewares.go new file mode 100644 index 0000000..3b36bef --- /dev/null +++ b/userclient/middlewares/middlewares.go @@ -0,0 +1,29 @@ +package middlewares + +import ( + "context" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/gotd/contrib/middleware/floodwait" + "github.com/gotd/td/telegram" + "github.com/krau/SaveAny-Bot/userclient/middlewares/recovery" + "github.com/krau/SaveAny-Bot/userclient/middlewares/retry" +) + +func NewDefaultMiddlewares(ctx context.Context, timeout time.Duration) []telegram.Middleware { + return []telegram.Middleware{ + recovery.New(ctx, newBackoff(timeout)), + retry.New(5), + floodwait.NewSimpleWaiter(), + } +} + +func newBackoff(timeout time.Duration) backoff.BackOff { + b := backoff.NewExponentialBackOff() + + b.Multiplier = 1.1 + b.MaxElapsedTime = timeout + b.MaxInterval = 10 * time.Second + return b +} diff --git a/userclient/middlewares/recovery/recovery.go b/userclient/middlewares/recovery/recovery.go new file mode 100644 index 0000000..fbbd562 --- /dev/null +++ b/userclient/middlewares/recovery/recovery.go @@ -0,0 +1,61 @@ +package recovery + +import ( + "context" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/go-faster/errors" + "github.com/gotd/td/bin" + "github.com/gotd/td/telegram" + "github.com/gotd/td/tg" + "github.com/gotd/td/tgerr" + "github.com/krau/SaveAny-Bot/common" +) + +type recovery struct { + ctx context.Context + backoff backoff.BackOff +} + +func New(ctx context.Context, backoff backoff.BackOff) telegram.Middleware { + return &recovery{ + ctx: ctx, + backoff: backoff, + } +} + +func (r *recovery) Handle(next tg.Invoker) telegram.InvokeFunc { + return func(ctx context.Context, input bin.Encoder, output bin.Decoder) error { + + return backoff.RetryNotify(func() error { + if err := next.Invoke(ctx, input, output); err != nil { + if r.shouldRecover(ctx, err) { + return errors.Wrap(err, "recover") + } + + return backoff.Permanent(err) + } + + return nil + }, r.backoff, func(err error, duration time.Duration) { + common.Log.Debug("Wait for connection recovery", "error", err, "duration", duration) + }) + } +} + +func (r *recovery) shouldRecover(ctx context.Context, err error) bool { + // context in recovery is used to stop recovery process by external os signal, otherwise we will wait till max retries when user press ctrl+c + select { + case <-r.ctx.Done(): + return false + case <-ctx.Done(): + return false + default: + } + + // we try recover when encountered any error that is not telegram business error + _, ok := tgerr.As(err) + + return !ok +} diff --git a/userclient/middlewares/retry/retry.go b/userclient/middlewares/retry/retry.go new file mode 100644 index 0000000..325531d --- /dev/null +++ b/userclient/middlewares/retry/retry.go @@ -0,0 +1,56 @@ +package retry + +import ( + "context" + "fmt" + + "github.com/go-faster/errors" + "github.com/gotd/td/bin" + "github.com/gotd/td/telegram" + "github.com/gotd/td/tg" + "github.com/gotd/td/tgerr" + "github.com/krau/SaveAny-Bot/common" +) + +var internalErrors = []string{ + "Timedout", // #373 + "No workers running", + "RPC_CALL_FAIL", + "RPC_MCGET_FAIL", + "WORKER_BUSY_TOO_LONG_RETRY", // #462 + "memory limit exit", // #504 +} + +type retry struct { + max int + errors []string +} + +func (r retry) Handle(next tg.Invoker) telegram.InvokeFunc { + return func(ctx context.Context, input bin.Encoder, output bin.Decoder) error { + retries := 0 + + for retries < r.max { + if err := next.Invoke(ctx, input, output); err != nil { + if tgerr.Is(err, r.errors...) { + common.Log.Debug("retry middleware", "retries", retries, "error", err) + retries++ + continue + } + return errors.Wrap(err, "retry middleware skip") + } + + return nil + } + + return fmt.Errorf("retry limit reached after %d attempts", r.max) + } +} + +// New returns middleware that retries request if it fails with one of provided errors. +func New(max int, errors ...string) telegram.Middleware { + return retry{ + max: max, + errors: append(errors, internalErrors...), // #373 + } +} diff --git a/userclient/userclient.go b/userclient/userclient.go new file mode 100644 index 0000000..259af4a --- /dev/null +++ b/userclient/userclient.go @@ -0,0 +1,74 @@ +package userclient + +import ( + "context" + "time" + + "github.com/celestix/gotgproto" + "github.com/celestix/gotgproto/ext" + "github.com/celestix/gotgproto/sessionMaker" + "github.com/glebarez/sqlite" + "github.com/krau/SaveAny-Bot/common" + "github.com/krau/SaveAny-Bot/config" + "github.com/krau/SaveAny-Bot/userclient/middlewares" +) + +var UC *gotgproto.Client +var ectx *ext.Context + +func GetCtx() *ext.Context { + if ectx != nil { + return ectx + } + ectx = UC.CreateContext() + return ectx +} + +func Login(ctx context.Context) (*gotgproto.Client, error) { + common.Log.Debug("Logging in as user client") + if UC != nil { + return UC, nil + } + res := make(chan struct { + client *gotgproto.Client + err error + }) + go func() { + tclient, err := gotgproto.NewClient( + config.Cfg.Telegram.AppID, + config.Cfg.Telegram.AppHash, + gotgproto.ClientTypePhone(""), + &gotgproto.ClientOpts{ + Session: sessionMaker.SqlSession(sqlite.Open(config.Cfg.Telegram.Userbot.Session)), + AuthConversator: &termialAuthConversator{}, + // Context: ctx, + DisableCopyright: true, + Middlewares: middlewares.NewDefaultMiddlewares(ctx, 5*time.Minute), + }, + ) + if err != nil { + res <- struct { + client *gotgproto.Client + err error + }{nil, err} + } + res <- struct { + client *gotgproto.Client + err error + }(struct { + client *gotgproto.Client + err error + }{tclient, nil}) + }() + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case r := <-res: + if r.err != nil { + return nil, r.err + } + UC = r.client + return UC, nil + } +}