diff --git a/server/tgbot/add.go b/server/tgbot/add.go new file mode 100644 index 0000000..d7d7b3c --- /dev/null +++ b/server/tgbot/add.go @@ -0,0 +1,63 @@ +package tgbot + +import ( + "errors" + tele "gopkg.in/telebot.v4" + "server/log" + set "server/settings" + "server/torr" + "server/web/api/utils" + "strings" +) + +func addTorrent(c tele.Context, link string) error { + msg, err := c.Bot().Send(c.Sender(), "Подключение к торренту...") + if err != nil { + return err + } + log.TLogln("tg add torrent", link) + link = strings.ReplaceAll(link, "&", "&") + torrSpec, err := utils.ParseLink(link) + + if err != nil { + log.TLogln("tg error parse link:", err) + return err + } + + tor, err := torr.AddTorrent(torrSpec, "", "", "", "") + + if tor.Data != "" && set.BTsets.EnableDebug { + log.TLogln("torrent data:", tor.Data) + } + if tor.Category != "" && set.BTsets.EnableDebug { + log.TLogln("torrent category:", tor.Category) + } + + if err != nil { + log.TLogln("tg error add torrent:", err) + c.Bot().Edit(msg, "Ошибка при подключении: "+err.Error()) + return err + } + + if !tor.GotInfo() { + log.TLogln("tg error add torrent: timeout connection get torrent info") + c.Bot().Edit(msg, "Ошибка при добаваления торрента: timeout connection get torrent info") + return errors.New("timeout connection get torrent info") + } + + if tor.Title == "" { + tor.Title = torrSpec.DisplayName // prefer dn over name + tor.Title = strings.ReplaceAll(tor.Title, "rutor.info", "") + tor.Title = strings.ReplaceAll(tor.Title, "_", " ") + tor.Title = strings.Trim(tor.Title, " ") + if tor.Title == "" { + tor.Title = tor.Name() + } + } + + torr.SaveTorrentToDB(tor) + + c.Bot().Edit(msg, "Торрент добавлен:\n"+link+"") + + return nil +} diff --git a/server/tgbot/bot.go b/server/tgbot/bot.go index ae1249b..1f7fbb5 100644 --- a/server/tgbot/bot.go +++ b/server/tgbot/bot.go @@ -1,44 +1,23 @@ package tgbot import ( - "encoding/json" "errors" - "github.com/dustin/go-humanize" tele "gopkg.in/telebot.v4" "gopkg.in/telebot.v4/middleware" "net/http" - "os" - "path/filepath" "server/log" - "server/settings" - "server/torr" - "server/web" + "server/tgbot/config" + up "server/tgbot/upload" "strconv" "strings" "time" ) -type Config struct { - WhiteIds []int64 -} - -var cfg *Config - -func init() { - cfg = &Config{} - fn := filepath.Join(settings.Path, "tg.cfg") - buf, err := os.ReadFile(fn) - if err != nil { - return - } - err = json.Unmarshal(buf, &cfg) - if err != nil { - log.TLogln("Error read tg config:", err) - } -} - func Start(token string) { + config.LoadConfig() + pref := tele.Settings{ + URL: config.Cfg.HostTG, Token: token, Poller: &tele.LongPoller{Timeout: 5 * time.Minute}, ParseMode: tele.ModeHTML, @@ -53,8 +32,11 @@ func Start(token string) { return } - if len(cfg.WhiteIds) > 0 { - b.Use(middleware.Whitelist(cfg.WhiteIds...)) + if len(config.Cfg.WhiteIds) > 0 { + b.Use(middleware.Whitelist(config.Cfg.WhiteIds...)) + } + if len(config.Cfg.BlackIds) > 0 { + b.Use(middleware.Blacklist(config.Cfg.BlackIds...)) } //Commands @@ -63,14 +45,32 @@ func Start(token string) { b.Handle("/help", help) b.Handle("/Help", help) b.Handle("/start", help) + b.Handle("/id", help) b.Handle("/list", list) + b.Handle("/clear", clear) //Text b.Handle(tele.OnText, func(c tele.Context) error { txt := c.Text() if strings.HasPrefix(strings.ToLower(txt), "magnet:") || isHash(txt) { - return addTorrent(c, txt) + err := addTorrent(c, txt) + if err != nil { + return err + } + return list(c) + } else if c.Message().ReplyTo != nil && c.Message().ReplyTo.ReplyMarkup != nil && len(c.Message().ReplyTo.ReplyMarkup.InlineKeyboard) > 0 { + data := c.Message().ReplyTo.ReplyMarkup.InlineKeyboard[0][0].Data + if strings.HasPrefix(strings.ToLower(data), "\fall|") { + hash := strings.TrimPrefix(data, "\fall|") + from, to, err := ParseRange(c.Message().Text) + if err != nil { + c.Send("Ошибка, нужно указывать числа, пример: 2-12") + return err + } + up.AddRange(c, hash, from, to) + } + return nil } else { return c.Send("Вставьте магнет/хэш торрента чтоб добавить его на сервер") } @@ -80,75 +80,43 @@ func Start(token string) { args := c.Args() if len(args) > 0 { if args[0] == "\ffiles" { - msg, err := c.Bot().Send(c.Sender(), "Подключение к торренту...") - t := torr.GetTorrent(args[1]) - if t == nil { - c.Edit(msg, "Torrent not connected: "+args[1]) - return nil - } - if err == nil { - go func() { - for !t.WaitInfo() { - time.Sleep(time.Second) - t = torr.GetTorrent(args[1]) - } - c.Bot().Delete(msg) - host := settings.PubIPv4 - if host == "" { - ips := web.GetLocalIps() - if len(ips) == 0 { - host = "127.0.0.1" - } else { - host = ips[0] - } - } - - t = torr.GetTorrent(args[1]) - st := t.Status() - txt := "Файлы:\n" - for _, file := range st.FileStats { - ff := "" + filepath.Base(file.Path) + " " + humanize.Bytes(uint64(file.Length)) + " " + - "Download\n\n" - if len(txt+ff) > 4096 { - c.Send(txt) - txt = "" - } - txt += ff - } - if len(txt) > 0 { - c.Send(txt) - } - }() - } - return err + return files(c) } if args[0] == "\fdelete" { + deleteTorrent(c) + return list(c) + } + if args[0] == "\fupload" { + return upload(c) + } + if args[0] == "\fuploadall" { + uploadall(c) return nil } + if args[0] == "\fcancel" { + if num, err := strconv.Atoi(args[1]); err == nil { + up.Cancel(num) + c.Bot().Delete(c.Callback().Message) + return nil + } + } } return errors.New("Ошибка кнопка не распознана") }) + up.Start() + go b.Start() } func help(c tele.Context) error { + id := strconv.FormatInt(c.Sender().ID, 10) return c.Send("Бот для управления TorrServer\n\n" + "Список комманд:\n" + - " /help - эта справка\n" + - " /list - показать список торрентов на сервере") -} - -func list(c tele.Context) error { - list := torr.ListTorrent() - - for _, t := range list { - btnFiles := tele.InlineButton{Text: "Файлы", Unique: "files", Data: t.Hash().String()} - btnDelete := tele.InlineButton{Text: "Удалить", Unique: "delete", Data: t.Hash().String()} - torrKbd := &tele.ReplyMarkup{InlineKeyboard: [][]tele.InlineButton{{btnFiles, btnDelete}}} - c.Send(t.Title+" "+humanize.Bytes(uint64(t.Size)), torrKbd) - } - return nil + " /help - Эта справка\n" + + " /list - Показать список торрентов на сервере\n" + + " /clear - Удалить все торренты\n\n" + + "Ваш id: " + id + ", " + strings.Join([]string{c.Sender().Username, c.Sender().FirstName, c.Sender().LastName}, ", ")) } func isHash(txt string) bool { @@ -164,3 +132,22 @@ func isHash(txt string) bool { } return false } + +func ParseRange(rng string) (int, int, error) { + parts := strings.Split(rng, "-") + + if len(parts) != 2 { + return -1, -1, errors.New("Неверный формат строки") + } + + num1, err1 := strconv.Atoi(strings.TrimSpace(parts[0])) + if err1 != nil { + return -1, -1, err1 + } + + num2, err2 := strconv.Atoi(strings.TrimSpace(parts[1])) + if err2 != nil { + return -1, -1, err2 + } + return num1, num2, nil +} diff --git a/server/tgbot/config/config.go b/server/tgbot/config/config.go new file mode 100644 index 0000000..57da102 --- /dev/null +++ b/server/tgbot/config/config.go @@ -0,0 +1,36 @@ +package config + +import ( + "encoding/json" + "os" + "path/filepath" + "server/log" + "server/settings" +) + +type Config struct { + HostTG string + WhiteIds []int64 + BlackIds []int64 +} + +var Cfg *Config + +func LoadConfig() { + Cfg = &Config{} + fn := filepath.Join(settings.Path, "tg.cfg") + buf, err := os.ReadFile(fn) + if err != nil { + Cfg.WhiteIds = []int64{} + Cfg.BlackIds = []int64{} + buf, _ = json.MarshalIndent(Cfg, "", " ") + if buf != nil { + os.WriteFile(fn, buf, 0666) + } + return + } + err = json.Unmarshal(buf, &Cfg) + if err != nil { + log.TLogln("Error read tg config:", err) + } +} diff --git a/server/tgbot/delete.go b/server/tgbot/delete.go new file mode 100644 index 0000000..d065a3d --- /dev/null +++ b/server/tgbot/delete.go @@ -0,0 +1,21 @@ +package tgbot + +import ( + tele "gopkg.in/telebot.v4" + "server/torr" +) + +func deleteTorrent(c tele.Context) { + args := c.Args() + hash := args[1] + torr.RemTorrent(hash) + return +} + +func clear(c tele.Context) error { + torrents := torr.ListTorrent() + for _, t := range torrents { + torr.RemTorrent(t.TorrentSpec.InfoHash.HexString()) + } + return nil +} diff --git a/server/tgbot/files.go b/server/tgbot/files.go new file mode 100644 index 0000000..23935b0 --- /dev/null +++ b/server/tgbot/files.go @@ -0,0 +1,83 @@ +package tgbot + +import ( + "github.com/dustin/go-humanize" + tele "gopkg.in/telebot.v4" + "path/filepath" + "server/settings" + "server/torr" + "server/web" + "strconv" + "time" +) + +func files(c tele.Context) error { + args := c.Args() + msg, err := c.Bot().Send(c.Sender(), "Подключение к торренту...") + t := torr.GetTorrent(args[1]) + if t == nil { + c.Edit(msg, "Torrent not connected: "+args[1]) + return nil + } + if err == nil { + go func() { + for !t.WaitInfo() { + time.Sleep(time.Second) + t = torr.GetTorrent(args[1]) + } + c.Bot().Delete(msg) + host := settings.PubIPv4 + if host == "" { + ips := web.GetLocalIps() + if len(ips) == 0 { + host = "127.0.0.1" + } else { + host = ips[0] + } + } + + t = torr.GetTorrent(args[1]) + ti := t.Status() + + txt := "" + ti.Title + " " + + "" + humanize.Bytes(uint64(ti.TorrentSize)) + "\n\n" + + "" + ti.Hash + "" + + filesKbd := &tele.ReplyMarkup{} + var files []tele.Row + + i := len(txt) + for _, f := range ti.FileStats { + btn := filesKbd.Data("#"+strconv.Itoa(f.Id)+": "+humanize.Bytes(uint64(f.Length))+"\n"+filepath.Base(f.Path), "upload", ti.Hash, strconv.Itoa(f.Id)) + link := filesKbd.URL("Ссылка", "http://"+host+":"+settings.Port+"/stream/"+filepath.Base(f.Path)+"?link="+t.Hash().HexString()+"&index="+strconv.Itoa(f.Id)+"&play") + files = append(files, filesKbd.Row(btn, link)) + if i+len(txt) > 1024 || len(files) > 99 { + filesKbd := &tele.ReplyMarkup{} + filesKbd.Inline(files...) + c.Send(txt, filesKbd) + files = files[:0] + i = len(txt) + } + i += len(btn.Text + link.Text) + } + + if len(files) > 0 { + filesKbd.Inline(files...) + c.Send(txt, filesKbd) + } + + if len(files) > 1 { + txt = "" + ti.Title + " " + + "" + humanize.Bytes(uint64(ti.TorrentSize)) + "\n\n" + + "" + ti.Hash + "\n\n" + + "Чтобы скачать несколько файлов, ответьте на это сообщение, с какого файла скачать по какой, пример: 2-12\n\n" + + "Скачать все файлы? Всего:" + strconv.Itoa(len(ti.FileStats)) + files = files[:0] + files = append(files, filesKbd.Row(filesKbd.Data("Скачать все файлы", "uploadall", ti.Hash))) + filesKbd.Inline(files...) + c.Send(txt, filesKbd) + } + }() + } + return err +} diff --git a/server/tgbot/list.go b/server/tgbot/list.go new file mode 100644 index 0000000..48b5595 --- /dev/null +++ b/server/tgbot/list.go @@ -0,0 +1,28 @@ +package tgbot + +import ( + "github.com/dustin/go-humanize" + tele "gopkg.in/telebot.v4" + "server/torr" +) + +func list(c tele.Context) error { + torrents := torr.ListTorrent() + + for _, t := range torrents { + btnFiles := tele.InlineButton{Text: "Файлы", Unique: "files", Data: t.Hash().String()} + btnDelete := tele.InlineButton{Text: "Удалить", Unique: "delete", Data: t.Hash().String()} + torrKbd := &tele.ReplyMarkup{InlineKeyboard: [][]tele.InlineButton{{btnFiles, btnDelete}}} + if t.Size > 0 { + c.Send(""+t.Title+" "+humanize.Bytes(uint64(t.Size))+"", torrKbd) + } else { + c.Send(""+t.Title+"", torrKbd) + } + } + + if len(torrents) == 0 { + c.Send("Нет торрентов") + } + + return nil +} diff --git a/server/tgbot/tshelper.go b/server/tgbot/tshelper.go deleted file mode 100644 index 2734498..0000000 --- a/server/tgbot/tshelper.go +++ /dev/null @@ -1,58 +0,0 @@ -package tgbot - -import ( - tele "gopkg.in/telebot.v4" - "server/log" - set "server/settings" - "server/torr" - "server/web/api/utils" - "strings" -) - -func addTorrent(c tele.Context, link string) error { - log.TLogln("tg add torrent", link) - link = strings.ReplaceAll(link, "&", "&") - torrSpec, err := utils.ParseLink(link) - - if err != nil { - log.TLogln("tg error parse link:", err) - return err - } - - tor, err := torr.AddTorrent(torrSpec, "", "", "", "") - - if tor.Data != "" && set.BTsets.EnableDebug { - log.TLogln("torrent data:", tor.Data) - } - if tor.Category != "" && set.BTsets.EnableDebug { - log.TLogln("torrent category:", tor.Category) - } - - if err != nil { - log.TLogln("tg error add torrent:", err) - return err - } - - go func() { - if !tor.GotInfo() { - log.TLogln("tg error add torrent: timeout connection get torrent info") - c.Send("Ошибка при добаваления торрента: timeout connection get torrent info") - return - } - - if tor.Title == "" { - tor.Title = torrSpec.DisplayName // prefer dn over name - tor.Title = strings.ReplaceAll(tor.Title, "rutor.info", "") - tor.Title = strings.ReplaceAll(tor.Title, "_", " ") - tor.Title = strings.Trim(tor.Title, " ") - if tor.Title == "" { - tor.Title = tor.Name() - } - } - - torr.SaveTorrentToDB(tor) - c.Send("Торрент добавлен: " + link + "") - }() - - return nil -} diff --git a/server/tgbot/upload.go b/server/tgbot/upload.go new file mode 100644 index 0000000..f060da3 --- /dev/null +++ b/server/tgbot/upload.go @@ -0,0 +1,23 @@ +package tgbot + +import ( + tele "gopkg.in/telebot.v4" + up "server/tgbot/upload" + "strconv" +) + +func upload(c tele.Context) error { + args := c.Args() + idstr := args[2] + id, err := strconv.Atoi(idstr) + if err != nil { + return err + } + up.AddRange(c, args[1], id, id) + return nil +} + +func uploadall(c tele.Context) { + args := c.Args() + up.AddRange(c, args[1], 1, -1) +} diff --git a/server/tgbot/upload/manager.go b/server/tgbot/upload/manager.go new file mode 100644 index 0000000..5840a88 --- /dev/null +++ b/server/tgbot/upload/manager.go @@ -0,0 +1,244 @@ +package upload + +import ( + "errors" + "fmt" + tele "gopkg.in/telebot.v4" + "log" + "math" + "path/filepath" + "server/torr" + "server/torr/state" + "strconv" + "sync" + "time" +) + +type Worker struct { + id int + c tele.Context + msg *tele.Message + torrentHash string + isCancelled bool + from int + to int + ti *state.TorrentStatus +} + +type Manager struct { + queue []*Worker + working map[int]*Worker + ids int + wrkSync sync.Mutex + queueLock sync.Mutex +} + +func (m *Manager) Start() { + m.working = make(map[int]*Worker) + go m.work() +} + +func (m *Manager) AddRange(c tele.Context, hash string, from, to int) { + m.queueLock.Lock() + defer m.queueLock.Unlock() + + if len(m.queue) > 50 { + c.Bot().Send(c.Recipient(), "Очередь переполнена, попробуйте попозже\n\nЭлементов в очереди:"+strconv.Itoa(len(m.queue))) + return + } + + m.ids++ + if m.ids > math.MaxInt { + m.ids = 0 + } + + var msg *tele.Message + var err error + + for i := 0; i < 20; i++ { + msg, err = c.Bot().Send(c.Recipient(), "Подключение к торренту\n"+hash+"") + if err == nil { + break + } else { + log.Println("Error send msg, try again:", i+1, "/", 20) + } + } + + if err != nil { + log.Println("Error send msg:", err) + return + } + + t := torr.GetTorrent(hash) + t.WaitInfo() + for t.Status().Stat != state.TorrentWorking { + time.Sleep(time.Second) + t = torr.GetTorrent(hash) + } + ti := t.Status() + + if from == 1 && to == -1 { + to = len(ti.FileStats) + } + if to > len(ti.FileStats) { + to = len(ti.FileStats) + } + if from < 1 { + from = 1 + } + if to >= 0 && to < from { + from, to = to, from + } + if to > len(ti.FileStats) { + to = len(ti.FileStats) + } + + w := &Worker{ + id: m.ids, + c: c, + torrentHash: hash, + msg: msg, + ti: ti, + from: from, + to: to, + } + + m.queue = append(m.queue, w) +} + +func (m *Manager) Cancel(id int) { + m.queueLock.Lock() + defer m.queueLock.Unlock() + var rem []int + for i, w := range m.queue { + if w.id == id { + w.isCancelled = true + w.c.Bot().Delete(w.msg) + rem = append(rem, i) + return + } + } + for _, i := range rem { + m.queue = append(m.queue[:i], m.queue[i+1:]...) + } + if wrk, ok := m.working[id]; ok { + wrk.isCancelled = true + return + } +} + +func (m *Manager) work() { + for { + m.queueLock.Lock() + if len(m.working) > 0 { + m.queueLock.Unlock() + m.sendQueueStatus() + time.Sleep(time.Second) + continue + } + if len(m.queue) == 0 { + m.queueLock.Unlock() + time.Sleep(time.Second) + continue + } + wrk := m.queue[0] + m.queue = m.queue[1:] + m.working[wrk.id] = wrk + m.queueLock.Unlock() + + m.sendQueueStatus() + + loading(wrk) + + m.queueLock.Lock() + delete(m.working, wrk.id) + m.queueLock.Unlock() + } +} + +func (m *Manager) sendQueueStatus() { + m.queueLock.Lock() + defer m.queueLock.Unlock() + for i, wrk := range m.queue { + if wrk.msg == nil { + continue + } + torrKbd := &tele.ReplyMarkup{} + torrKbd.Inline([]tele.Row{torrKbd.Row(torrKbd.Data("Отмена", "cancel", strconv.Itoa(wrk.id)))}...) + + msg := "Номер в очереди " + strconv.Itoa(i+1) + + wrk.c.Bot().Edit(wrk.msg, msg, torrKbd) + } +} + +func loading(wrk *Worker) { + iserr := false + + t := torr.GetTorrent(wrk.torrentHash) + t.WaitInfo() + for t.Status().Stat != state.TorrentWorking { + time.Sleep(time.Second) + t = torr.GetTorrent(wrk.torrentHash) + } + wrk.ti = t.Status() + + for i := wrk.from - 1; i <= wrk.to-1; i++ { + file := wrk.ti.FileStats[i] + if wrk.isCancelled { + return + } + + err := uploadFile(wrk, file, i+1, len(wrk.ti.FileStats)) + if err != nil { + errstr := fmt.Sprintf("Ошибка загрузки в телеграм: %v", err.Error()) + wrk.c.Bot().Edit(wrk.msg, errstr) + iserr = true + break + } + } + if !iserr { + wrk.c.Bot().Delete(wrk.msg) + } +} + +func uploadFile(wrk *Worker, file *state.TorrentFileStat, fi, fc int) error { + caption := filepath.Base(file.Path) + torrFile, err := NewTorrFile(wrk, file) + if err != nil { + return err + } + + var wa sync.WaitGroup + wa.Add(1) + complete := false + go func() { + for !complete { + updateLoadStatus(wrk, torrFile, fi, fc) + time.Sleep(1 * time.Second) + } + wa.Done() + }() + + d := &tele.Document{} + d.FileName = file.Path + d.Caption = caption + d.File.FileReader = torrFile + + for i := 0; i < 20; i++ { + err = wrk.c.Send(d) + if err == nil || errors.Is(err, ERR_STOPPED) { + break + } else { + log.Println("Error send msg, try again:", i+1, "/", 20) + } + } + + complete = true + wa.Wait() + torrFile.Close() + if errors.Is(err, ERR_STOPPED) { + err = nil + } + return err +} diff --git a/server/tgbot/upload/queue.go b/server/tgbot/upload/queue.go new file mode 100644 index 0000000..bf1a453 --- /dev/null +++ b/server/tgbot/upload/queue.go @@ -0,0 +1,127 @@ +package upload + +import ( + "fmt" + "github.com/dustin/go-humanize" + tele "gopkg.in/telebot.v4" + "server/torr" + "strconv" + "time" +) + +type DLQueue struct { + id int + c tele.Context + hash string + fileID string + fileName string + updateMsg *tele.Message +} + +var ( + manager = &Manager{} +) + +func Start() { + manager.Start() +} + +func ShowQueue(c tele.Context) error { + msg := "" + manager.queueLock.Lock() + defer manager.queueLock.Unlock() + if len(manager.queue) == 0 && len(manager.working) == 0 { + return c.Send("Очередь пуста") + } + if len(manager.working) > 0 { + msg += "Закачиваются:\n" + i := 0 + for _, dlQueue := range manager.working { + s := "#" + strconv.Itoa(i+1) + ": " + dlQueue.torrentHash + "\n" + if len(msg+s) > 1024 { + c.Send(msg) + msg = "" + } + msg += s + i++ + } + if len(msg) > 0 { + c.Send(msg) + msg = "" + } + } + if len(manager.queue) > 0 { + msg = "В очереди:\n" + for i, dlQueue := range manager.queue { + s := "#" + strconv.Itoa(i+1) + ": " + dlQueue.torrentHash + "\n" + if len(msg+s) > 1024 { + c.Send(msg) + msg = "" + } + msg += s + } + if len(msg) > 0 { + c.Send(msg) + msg = "" + } + } + return nil +} + +func AddRange(c tele.Context, hash string, from, to int) { + manager.AddRange(c, hash, from, to) +} + +func Cancel(id int) { + manager.Cancel(id) +} + +func updateLoadStatus(wrk *Worker, file *TorrFile, fi, fc int) { + if wrk.msg == nil { + return + } + t := torr.GetTorrent(wrk.torrentHash) + ti := t.Status() + if wrk.isCancelled { + wrk.c.Bot().Edit(wrk.msg, "Остановка...") + } else { + wrk.c.Send(tele.UploadingVideo) + if ti.DownloadSpeed == 0 { + ti.DownloadSpeed = 1.0 + } + wait := time.Duration(float64(file.Loaded())/ti.DownloadSpeed) * time.Second + speed := humanize.Bytes(uint64(ti.DownloadSpeed)) + "/sec" + peers := fmt.Sprintf("%v · %v/%v", ti.ConnectedSeeders, ti.ActivePeers, ti.TotalPeers) + prc := fmt.Sprintf("%.2f%% %v / %v", float64(file.offset)*100.0/float64(file.size), humanize.Bytes(uint64(file.offset)), humanize.Bytes(uint64(file.size))) + + name := file.name + if name == ti.Title { + name = "" + } + + msg := "Загрузка торрента:\n" + + "" + ti.Title + "\n" + if name != "" { + msg += "" + name + "\n" + } + msg += "Хэш: " + file.hash + "\n" + if file.offset < file.size { + msg += "Скорость: " + speed + "\n" + + "Осталось: " + wait.String() + "\n" + + "Пиры: " + peers + "\n" + + "Загружено: " + prc + } + if fc > 1 { + msg += "\nФайлов: " + strconv.Itoa(fi) + "/" + strconv.Itoa(fc) + } + if file.offset >= file.size { + msg += "\nЗавершение загрузки, это займет некоторое время" + wrk.c.Bot().Edit(wrk.msg, msg) + return + } + + torrKbd := &tele.ReplyMarkup{} + torrKbd.Inline([]tele.Row{torrKbd.Row(torrKbd.Data("Отмена", "cancel", strconv.Itoa(wrk.id)))}...) + wrk.c.Bot().Edit(wrk.msg, msg, torrKbd) + } +} diff --git a/server/tgbot/upload/torrfile.go b/server/tgbot/upload/torrfile.go new file mode 100644 index 0000000..7935fb5 --- /dev/null +++ b/server/tgbot/upload/torrfile.go @@ -0,0 +1,89 @@ +package upload + +import ( + "errors" + "fmt" + "github.com/anacrolix/torrent" + "log" + "path/filepath" + sets "server/settings" + "server/tgbot/config" + "server/torr" + "server/torr/state" + "server/torr/storage/torrstor" +) + +var ERR_STOPPED = errors.New("stopped") + +type TorrFile struct { + hash string + name string + wrk *Worker + offset int64 + size int64 + id int + + reader *torrstor.Reader +} + +func NewTorrFile(wrk *Worker, stFile *state.TorrentFileStat) (*TorrFile, error) { + if config.Cfg.HostTG != "" && stFile.Length > 2*1024*1024*1024 { + return nil, errors.New("Размер файла должен быть больше 2GB") + } + if config.Cfg.HostTG == "" && stFile.Length > 50*1024*1024 { + return nil, errors.New("Размер файла должен быть больше 50MB\nЧтобы закачивать файлы до 2GB нужно в tg.cfg указать host к telegram bot api") + } + + tf := new(TorrFile) + tf.hash = wrk.torrentHash + tf.name = filepath.Base(stFile.Path) + tf.wrk = wrk + tf.size = stFile.Length + + t := torr.GetTorrent(wrk.torrentHash) + t.WaitInfo() + + files := t.Files() + var file *torrent.File + for _, tfile := range files { + if tfile.Path() == stFile.Path { + file = tfile + break + } + } + if file == nil { + return nil, fmt.Errorf("file with id %v not found", stFile.Id) + } + if int64(sets.MaxSize) > 0 && file.Length() > int64(sets.MaxSize) { + log.Println("file", file.DisplayPath(), "size exceeded max allowed", sets.MaxSize, "bytes") + return nil, fmt.Errorf("file size exceeded max allowed %d bytes", sets.MaxSize) + } + + reader := t.NewReader(file) + if sets.BTsets.ResponsiveMode { + reader.SetResponsive() + } + tf.reader = reader + + return tf, nil +} + +func (t *TorrFile) Read(p []byte) (n int, err error) { + if t.wrk.isCancelled { + return 0, ERR_STOPPED + } + n, err = t.reader.Read(p) + t.offset += int64(n) + return +} + +func (t *TorrFile) Loaded() int64 { + return t.size - t.offset +} + +func (t *TorrFile) Close() { + if t.reader != nil { + t.reader.Close() + t.reader = nil + } +}