Initial commit: docker compose config
Release Docker multi arch / docker (push) Has been cancelled
Test Install Script / Test Script Syntax (push) Has been cancelled
Test Install Script / Test on almalinux-10 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-10 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (root) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (default) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (root) (push) Has been cancelled
Test Install Script / Test on debian-11 (default) (push) Has been cancelled
Test Install Script / Test on debian-11 (root) (push) Has been cancelled
Test Install Script / Test on debian-12 (default) (push) Has been cancelled
Test Install Script / Test on debian-12 (root) (push) Has been cancelled
Test Install Script / Test on debian-13 (default) (push) Has been cancelled
Test Install Script / Test on debian-13 (root) (push) Has been cancelled
Test Install Script / Test on fedora-latest (default) (push) Has been cancelled
Test Install Script / Test on fedora-latest (root) (push) Has been cancelled
Test Install Script / Test on rocky-10 (default) (push) Has been cancelled
Test Install Script / Test on rocky-10 (root) (push) Has been cancelled
Test Install Script / Test on rocky-8 (default) (push) Has been cancelled
Test Install Script / Test on rocky-8 (root) (push) Has been cancelled
Test Install Script / Test on rocky-9 (default) (push) Has been cancelled
Test Install Script / Test on rocky-9 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (root) (push) Has been cancelled

This commit is contained in:
2026-05-30 12:07:11 +00:00
commit 616c6b1c62
381 changed files with 55145 additions and 0 deletions
+275
View File
@@ -0,0 +1,275 @@
package tgbot
import (
"strings"
"sync"
"time"
tele "gopkg.in/telebot.v4"
"server/dlna"
"server/rutor"
"server/settings"
"server/torr"
)
type pendingPreset struct {
Sets *settings.BTSets
Preset string // name for display
UserID int64
IsDef bool
CreatedAt time.Time
}
var (
pendingPresetMu sync.Mutex
pendingPresets = make(map[string]pendingPreset)
)
func init() {
go func() {
ticker := time.NewTicker(5 * time.Minute)
for range ticker.C {
pendingPresetMu.Lock()
now := time.Now()
for key, p := range pendingPresets {
if now.Sub(p.CreatedAt) > 30*time.Minute {
delete(pendingPresets, key)
}
}
pendingPresetMu.Unlock()
}
}()
}
func cmdPreset(c tele.Context) error {
uid := c.Sender().ID
if !isAdmin(uid) {
return c.Send(tr(uid, "admin_only"))
}
if settings.BTsets == nil {
return c.Send(tr(uid, "settings_not_loaded"))
}
if settings.ReadOnly {
return c.Send(tr(uid, "settings_readonly"))
}
args := strings.Fields(c.Text())
if len(args) < 2 {
return c.Send(tr(uid, "preset_usage"))
}
sets := new(settings.BTSets)
*sets = *settings.BTsets
first := strings.ToLower(args[1])
presetName := first
if len(args) == 2 {
if ok, _ := applyNamedPreset(sets, first, uid); ok {
return sendPresetConfirm(c, uid, sets, presetName, false)
}
if first == "default" || first == "def" || first == "сброс" {
return sendPresetConfirm(c, uid, nil, "default", true)
}
}
// Parse key-value pairs: cache 256 preload 50 conn 100
applied, errMsg := applyPresetKV(sets, args[1:], uid)
if !applied {
return c.Send(errMsg)
}
presetName = strings.Join(args[1:], " ")
return sendPresetConfirm(c, uid, sets, presetName, false)
}
func sendPresetConfirm(c tele.Context, uid int64, sets *settings.BTSets, presetName string, isDef bool) error {
btnYes := tele.InlineButton{Text: tr(uid, "btn_yes"), Unique: "fpreset", Data: "1"}
btnNo := tele.InlineButton{Text: tr(uid, "btn_no"), Unique: "fpreset", Data: "0"}
kbd := &tele.ReplyMarkup{InlineKeyboard: [][]tele.InlineButton{{btnYes, btnNo}}}
msg := tr(uid, "preset_confirm") + "\n\n<code>" + presetName + "</code>"
sent, err := c.Bot().Send(c.Chat(), msg, kbd, tele.ModeHTML)
if err != nil {
return err
}
pendingPresetMu.Lock()
pendingPresets[chatMsgKey(sent.Chat.ID, sent.ID)] = pendingPreset{
Sets: sets, Preset: presetName, UserID: uid, IsDef: isDef, CreatedAt: time.Now(),
}
pendingPresetMu.Unlock()
return nil
}
func presetConfirm(c tele.Context, confirm string) error {
uid := c.Sender().ID
if !isAdmin(uid) {
return c.Respond(&tele.CallbackResponse{Text: tr(uid, "admin_only")})
}
key := chatMsgKey(c.Callback().Message.Chat.ID, c.Callback().Message.ID)
pendingPresetMu.Lock()
p, ok := pendingPresets[key]
delete(pendingPresets, key)
pendingPresetMu.Unlock()
if !ok || p.UserID != uid {
_ = c.Bot().Delete(c.Callback().Message)
return c.Respond(&tele.CallbackResponse{Text: tr(uid, "canceled")})
}
if confirm != "1" {
_ = c.Bot().Delete(c.Callback().Message)
return c.Respond(&tele.CallbackResponse{Text: tr(uid, "canceled")})
}
_ = c.Bot().Delete(c.Callback().Message)
if settings.ReadOnly {
return c.Respond(&tele.CallbackResponse{Text: tr(uid, "settings_readonly")})
}
if p.IsDef {
torr.SetDefSettings()
dlna.Stop()
rutor.Stop()
rutor.Start()
return c.Send(tr(uid, "settings_reset_done"))
}
if p.Sets == nil {
return c.Respond(&tele.CallbackResponse{Text: tr(uid, "callback_unknown")})
}
torr.SetSettings(p.Sets)
dlna.Stop()
if p.Sets.EnableDLNA {
dlna.Start()
}
rutor.Stop()
rutor.Start()
return c.Send(tr(uid, "preset_applied") + p.Preset)
}
func applyNamedPreset(s *settings.BTSets, name string, uid int64) (bool, string) {
switch name {
case "performance", "perf", "производительность":
s.CacheSize = 512 * 1024 * 1024
s.PreloadCache = 95
s.ReaderReadAHead = 100
s.ConnectionsLimit = 100
s.TorrentDisconnectTimeout = 60
s.PeersListenPort = 0
s.DownloadRateLimit = 0
s.UploadRateLimit = 0
s.RetrackersMode = 1
s.ResponsiveMode = true
return true, tr(uid, "preset_applied") + " performance"
case "storage", "store", "хранение":
s.CacheSize = 64 * 1024 * 1024
s.PreloadCache = 25
s.ReaderReadAHead = 50
s.RemoveCacheOnDrop = true
return true, tr(uid, "preset_applied") + " storage"
case "streaming", "stream", "стриминг":
s.CacheSize = 256 * 1024 * 1024
s.PreloadCache = 75
s.ReaderReadAHead = 95
s.ConnectionsLimit = 50
s.ResponsiveMode = true
return true, tr(uid, "preset_applied") + " streaming"
case "low", "minimal", "минимум":
s.CacheSize = 64 * 1024 * 1024
s.PreloadCache = 25
s.ReaderReadAHead = 50
s.ConnectionsLimit = 25
s.TorrentDisconnectTimeout = 30
return true, tr(uid, "preset_applied") + " low"
case "default", "def", "сброс":
return false, "" // handled in cmdPreset
}
return false, ""
}
func applyPresetKV(s *settings.BTSets, args []string, uid int64) (bool, string) {
if len(args) < 2 {
return false, tr(uid, "preset_usage")
}
applied := false
for i := 0; i < len(args)-1; i += 2 {
key := strings.ToLower(args[i])
val := strings.ToLower(strings.TrimSpace(args[i+1]))
ok := false
switch key {
case "cache":
if v := parseInt(val); v > 0 {
s.CacheSize = int64(v) * 1024 * 1024
ok = true
}
case "preload":
if v := parseInt(val); v >= 0 && v <= 100 {
s.PreloadCache = v
ok = true
}
case "readahead":
if v := parseInt(val); v >= 5 && v <= 100 {
s.ReaderReadAHead = v
ok = true
}
case "conn", "connections":
if v := parseInt(val); v > 0 {
s.ConnectionsLimit = v
ok = true
}
case "timeout":
if v := parseInt(val); v > 0 {
s.TorrentDisconnectTimeout = v
ok = true
}
case "port":
v := parseInt(val)
if val == "auto" || val == "0" {
v = 0
}
if v >= 0 && (v == 0 || (v >= 1024 && v <= 65535)) {
s.PeersListenPort = v
ok = true
}
case "down", "download":
v := 0
if val != "inf" && val != "∞" && val != "0" {
v = parseInt(val)
}
s.DownloadRateLimit = v
ok = true
case "up", "upload":
v := 0
if val != "inf" && val != "∞" && val != "0" {
v = parseInt(val)
}
s.UploadRateLimit = v
ok = true
case "retr", "retrackers":
var v int
switch val {
case "off":
v = 0
case "add":
v = 1
case "rem", "remove":
v = 2
case "repl", "replace":
v = 3
default:
v = parseInt(val)
}
if v >= 0 && v <= 3 {
s.RetrackersMode = v
ok = true
}
case "responsive":
s.ResponsiveMode = val == "1" || val == "on" || val == "true" || val == "да" || val == "yes"
ok = true
case "cachedrop":
s.RemoveCacheOnDrop = val == "1" || val == "on" || val == "true" || val == "да" || val == "yes"
ok = true
}
if ok {
applied = true
}
}
if !applied {
return false, tr(uid, "preset_usage")
}
return true, ""
}