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
+ }
+}