mirror of
https://github.com/Ernous/TorrServerJellyfin.git
synced 2025-12-20 05:56:10 +05:00
update
This commit is contained in:
63
server/tgbot/add.go
Normal file
63
server/tgbot/add.go
Normal file
@@ -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<code>"+link+"</code>")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -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 := "<b>" + filepath.Base(file.Path) + "</b> <i>" + humanize.Bytes(uint64(file.Length)) + "</i> " +
|
||||
"<a href=\"http://" + host + ":" + settings.Port + "/stream/" + filepath.Base(file.Path) + "?link=" + t.Hash().HexString() + "&index=" + strconv.Itoa(file.Id) + "&play\">Download</a>\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: <code>" + id + "</code>, " + 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
|
||||
}
|
||||
|
||||
36
server/tgbot/config/config.go
Normal file
36
server/tgbot/config/config.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
21
server/tgbot/delete.go
Normal file
21
server/tgbot/delete.go
Normal file
@@ -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
|
||||
}
|
||||
83
server/tgbot/files.go
Normal file
83
server/tgbot/files.go
Normal file
@@ -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 := "<b>" + ti.Title + "</b> " +
|
||||
"<i>" + humanize.Bytes(uint64(ti.TorrentSize)) + "</i>\n\n" +
|
||||
"<code>" + ti.Hash + "</code>"
|
||||
|
||||
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 = "<b>" + ti.Title + "</b> " +
|
||||
"<i>" + humanize.Bytes(uint64(ti.TorrentSize)) + "</i>\n\n" +
|
||||
"<code>" + ti.Hash + "</code>\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
|
||||
}
|
||||
28
server/tgbot/list.go
Normal file
28
server/tgbot/list.go
Normal file
@@ -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("<b>"+t.Title+"</b> <i>"+humanize.Bytes(uint64(t.Size))+"</i>", torrKbd)
|
||||
} else {
|
||||
c.Send("<b>"+t.Title+"</b>", torrKbd)
|
||||
}
|
||||
}
|
||||
|
||||
if len(torrents) == 0 {
|
||||
c.Send("Нет торрентов")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -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("Торрент добавлен: <code>" + link + "</code>")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
23
server/tgbot/upload.go
Normal file
23
server/tgbot/upload.go
Normal file
@@ -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)
|
||||
}
|
||||
244
server/tgbot/upload/manager.go
Normal file
244
server/tgbot/upload/manager.go
Normal file
@@ -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(), "<b>Подключение к торренту</b>\n<code>"+hash+"</code>")
|
||||
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
|
||||
}
|
||||
127
server/tgbot/upload/queue.go
Normal file
127
server/tgbot/upload/queue.go
Normal file
@@ -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) + ": <code>" + dlQueue.torrentHash + "</code>\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) + ": <code>" + dlQueue.torrentHash + "</code>\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" +
|
||||
"<b>" + ti.Title + "</b>\n"
|
||||
if name != "" {
|
||||
msg += "<i>" + name + "</i>\n"
|
||||
}
|
||||
msg += "<b>Хэш:</b> <code>" + file.hash + "</code>\n"
|
||||
if file.offset < file.size {
|
||||
msg += "<b>Скорость: </b>" + speed + "\n" +
|
||||
"<b>Осталось: </b>" + wait.String() + "\n" +
|
||||
"<b>Пиры: </b>" + peers + "\n" +
|
||||
"<b>Загружено: </b>" + prc
|
||||
}
|
||||
if fc > 1 {
|
||||
msg += "\n<b>Файлов: </b>" + strconv.Itoa(fi) + "/" + strconv.Itoa(fc)
|
||||
}
|
||||
if file.offset >= file.size {
|
||||
msg += "\n<b>Завершение загрузки, это займет некоторое время</b>"
|
||||
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)
|
||||
}
|
||||
}
|
||||
89
server/tgbot/upload/torrfile.go
Normal file
89
server/tgbot/upload/torrfile.go
Normal file
@@ -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 к <a href='https://github.com/tdlib/telegram-bot-api'>telegram bot api</a>")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user