Рефакторинг обработки команд Telegram-бота для использования отображения (map) с целью более чистой маршрутизации и улучшения обработки ошибок. Добавлен новый метод для логирования ошибок клиентов и оптимизировано использование буфера в командах, связанных с торрентами. Улучшено логирование и управление контекстом при работе с live‑обновлениями, чтобы предотвратить блокирующие операции.
This commit is contained in:
@@ -28,12 +28,15 @@ func main() {
|
|||||||
|
|
||||||
// Initialize logger
|
// Initialize logger
|
||||||
var log *logger.Logger
|
var log *logger.Logger
|
||||||
|
var logf *os.File
|
||||||
if cfg.LogFile != "" {
|
if cfg.LogFile != "" {
|
||||||
logf, err := os.OpenFile(cfg.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
var err error
|
||||||
|
logf, err = os.OpenFile(cfg.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "[ERROR] Failed to open log file: %s\n", err)
|
fmt.Fprintf(os.Stderr, "[ERROR] Failed to open log file: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
defer logf.Close()
|
||||||
log = logger.New(logf)
|
log = logger.New(logf)
|
||||||
} else {
|
} else {
|
||||||
log = logger.NewStdout()
|
log = logger.NewStdout()
|
||||||
|
|||||||
@@ -74,6 +74,127 @@ func (b *Bot) safeHandler(handler func()) {
|
|||||||
handler()
|
handler()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// commandHandler represents a handler function type
|
||||||
|
type commandHandler func(update tgbotapi.Update, tokens []string)
|
||||||
|
|
||||||
|
// getCommandHandler returns the appropriate handler for a command
|
||||||
|
func (b *Bot) getCommandHandler(command string, update tgbotapi.Update, tokens []string) func() {
|
||||||
|
// Map of command aliases to handlers
|
||||||
|
commandMap := map[string]func(update tgbotapi.Update, tokens []string){
|
||||||
|
"list": b.handleList,
|
||||||
|
"/list": b.handleList,
|
||||||
|
"li": b.handleList,
|
||||||
|
"/li": b.handleList,
|
||||||
|
"/ls": b.handleList,
|
||||||
|
"ls": b.handleList,
|
||||||
|
"head": b.handleHead,
|
||||||
|
"/head": b.handleHead,
|
||||||
|
"he": b.handleHead,
|
||||||
|
"/he": b.handleHead,
|
||||||
|
"tail": b.handleTail,
|
||||||
|
"/tail": b.handleTail,
|
||||||
|
"ta": b.handleTail,
|
||||||
|
"/ta": b.handleTail,
|
||||||
|
"downs": func(u tgbotapi.Update, _ []string) { b.handleDowns(u) },
|
||||||
|
"/downs": func(u tgbotapi.Update, _ []string) { b.handleDowns(u) },
|
||||||
|
"dg": func(u tgbotapi.Update, _ []string) { b.handleDowns(u) },
|
||||||
|
"/dg": func(u tgbotapi.Update, _ []string) { b.handleDowns(u) },
|
||||||
|
"seeding": func(u tgbotapi.Update, _ []string) { b.handleSeeding(u) },
|
||||||
|
"/seeding": func(u tgbotapi.Update, _ []string) { b.handleSeeding(u) },
|
||||||
|
"sd": func(u tgbotapi.Update, _ []string) { b.handleSeeding(u) },
|
||||||
|
"/sd": func(u tgbotapi.Update, _ []string) { b.handleSeeding(u) },
|
||||||
|
"paused": func(u tgbotapi.Update, _ []string) { b.handlePaused(u) },
|
||||||
|
"/paused": func(u tgbotapi.Update, _ []string) { b.handlePaused(u) },
|
||||||
|
"pa": func(u tgbotapi.Update, _ []string) { b.handlePaused(u) },
|
||||||
|
"/pa": func(u tgbotapi.Update, _ []string) { b.handlePaused(u) },
|
||||||
|
"checking": func(u tgbotapi.Update, _ []string) { b.handleChecking(u) },
|
||||||
|
"/checking": func(u tgbotapi.Update, _ []string) { b.handleChecking(u) },
|
||||||
|
"ch": func(u tgbotapi.Update, _ []string) { b.handleChecking(u) },
|
||||||
|
"/ch": func(u tgbotapi.Update, _ []string) { b.handleChecking(u) },
|
||||||
|
"active": func(u tgbotapi.Update, _ []string) { b.handleActive(u) },
|
||||||
|
"/active": func(u tgbotapi.Update, _ []string) { b.handleActive(u) },
|
||||||
|
"ac": func(u tgbotapi.Update, _ []string) { b.handleActive(u) },
|
||||||
|
"/ac": func(u tgbotapi.Update, _ []string) { b.handleActive(u) },
|
||||||
|
"errors": func(u tgbotapi.Update, _ []string) { b.handleErrors(u) },
|
||||||
|
"/errors": func(u tgbotapi.Update, _ []string) { b.handleErrors(u) },
|
||||||
|
"er": func(u tgbotapi.Update, _ []string) { b.handleErrors(u) },
|
||||||
|
"/er": func(u tgbotapi.Update, _ []string) { b.handleErrors(u) },
|
||||||
|
"sort": b.handleSort,
|
||||||
|
"/sort": b.handleSort,
|
||||||
|
"so": b.handleSort,
|
||||||
|
"/so": b.handleSort,
|
||||||
|
"trackers": func(u tgbotapi.Update, _ []string) { b.handleTrackers(u) },
|
||||||
|
"/trackers": func(u tgbotapi.Update, _ []string) { b.handleTrackers(u) },
|
||||||
|
"tr": func(u tgbotapi.Update, _ []string) { b.handleTrackers(u) },
|
||||||
|
"/tr": func(u tgbotapi.Update, _ []string) { b.handleTrackers(u) },
|
||||||
|
"downloaddir": b.handleDownloadDir,
|
||||||
|
"dd": b.handleDownloadDir,
|
||||||
|
"add": b.handleAdd,
|
||||||
|
"/add": b.handleAdd,
|
||||||
|
"ad": b.handleAdd,
|
||||||
|
"/ad": b.handleAdd,
|
||||||
|
"search": b.handleSearch,
|
||||||
|
"/search": b.handleSearch,
|
||||||
|
"se": b.handleSearch,
|
||||||
|
"/se": b.handleSearch,
|
||||||
|
"latest": b.handleLatest,
|
||||||
|
"/latest": b.handleLatest,
|
||||||
|
"la": b.handleLatest,
|
||||||
|
"/la": b.handleLatest,
|
||||||
|
"info": b.handleInfo,
|
||||||
|
"/info": b.handleInfo,
|
||||||
|
"in": b.handleInfo,
|
||||||
|
"/in": b.handleInfo,
|
||||||
|
"stop": b.handleStop,
|
||||||
|
"/stop": b.handleStop,
|
||||||
|
"sp": b.handleStop,
|
||||||
|
"/sp": b.handleStop,
|
||||||
|
"start": b.handleStart,
|
||||||
|
"/start": b.handleStart,
|
||||||
|
"st": b.handleStart,
|
||||||
|
"/st": b.handleStart,
|
||||||
|
"check": b.handleCheck,
|
||||||
|
"/check": b.handleCheck,
|
||||||
|
"ck": b.handleCheck,
|
||||||
|
"/ck": b.handleCheck,
|
||||||
|
"stats": func(u tgbotapi.Update, _ []string) { b.handleStats(u) },
|
||||||
|
"/stats": func(u tgbotapi.Update, _ []string) { b.handleStats(u) },
|
||||||
|
"sa": func(u tgbotapi.Update, _ []string) { b.handleStats(u) },
|
||||||
|
"/sa": func(u tgbotapi.Update, _ []string) { b.handleStats(u) },
|
||||||
|
"downlimit": b.handleDownLimit,
|
||||||
|
"dl": b.handleDownLimit,
|
||||||
|
"uplimit": b.handleUpLimit,
|
||||||
|
"ul": b.handleUpLimit,
|
||||||
|
"speed": func(u tgbotapi.Update, _ []string) { b.handleSpeed(u) },
|
||||||
|
"/speed": func(u tgbotapi.Update, _ []string) { b.handleSpeed(u) },
|
||||||
|
"ss": func(u tgbotapi.Update, _ []string) { b.handleSpeed(u) },
|
||||||
|
"/ss": func(u tgbotapi.Update, _ []string) { b.handleSpeed(u) },
|
||||||
|
"count": func(u tgbotapi.Update, _ []string) { b.handleCount(u) },
|
||||||
|
"/count": func(u tgbotapi.Update, _ []string) { b.handleCount(u) },
|
||||||
|
"co": func(u tgbotapi.Update, _ []string) { b.handleCount(u) },
|
||||||
|
"/co": func(u tgbotapi.Update, _ []string) { b.handleCount(u) },
|
||||||
|
"del": b.handleDel,
|
||||||
|
"/del": b.handleDel,
|
||||||
|
"rm": b.handleDel,
|
||||||
|
"/rm": b.handleDel,
|
||||||
|
"deldata": b.handleDelData,
|
||||||
|
"/deldata": b.handleDelData,
|
||||||
|
"help": func(u tgbotapi.Update, _ []string) { b.sendMessage(u.Message.Chat.ID, HelpText, true) },
|
||||||
|
"/help": func(u tgbotapi.Update, _ []string) { b.sendMessage(u.Message.Chat.ID, HelpText, true) },
|
||||||
|
"version": func(u tgbotapi.Update, _ []string) { b.handleVersion(u) },
|
||||||
|
"/version": func(u tgbotapi.Update, _ []string) { b.handleVersion(u) },
|
||||||
|
"ver": func(u tgbotapi.Update, _ []string) { b.handleVersion(u) },
|
||||||
|
"/ver": func(u tgbotapi.Update, _ []string) { b.handleVersion(u) },
|
||||||
|
"": func(u tgbotapi.Update, _ []string) { b.handleReceiveTorrent(u) },
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler, ok := commandMap[command]; ok {
|
||||||
|
return func() { handler(update, tokens) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// handleUpdate processes a Telegram update
|
// handleUpdate processes a Telegram update
|
||||||
func (b *Bot) handleUpdate(update tgbotapi.Update) {
|
func (b *Bot) handleUpdate(update tgbotapi.Update) {
|
||||||
if update.Message == nil {
|
if update.Message == nil {
|
||||||
@@ -112,67 +233,11 @@ func (b *Bot) handleUpdate(update tgbotapi.Update) {
|
|||||||
command := strings.ToLower(tokens[0])
|
command := strings.ToLower(tokens[0])
|
||||||
|
|
||||||
// Route to appropriate handler with panic recovery
|
// Route to appropriate handler with panic recovery
|
||||||
switch command {
|
// Use map for cleaner command routing
|
||||||
case "list", "/list", "li", "/li", "/ls", "ls":
|
handler := b.getCommandHandler(command, update, tokens[1:])
|
||||||
go b.safeHandler(func() { b.handleList(update, tokens[1:]) })
|
if handler != nil {
|
||||||
case "head", "/head", "he", "/he":
|
go b.safeHandler(handler)
|
||||||
go b.safeHandler(func() { b.handleHead(update, tokens[1:]) })
|
} else {
|
||||||
case "tail", "/tail", "ta", "/ta":
|
|
||||||
go b.safeHandler(func() { b.handleTail(update, tokens[1:]) })
|
|
||||||
case "downs", "/downs", "dg", "/dg":
|
|
||||||
go b.safeHandler(func() { b.handleDowns(update) })
|
|
||||||
case "seeding", "/seeding", "sd", "/sd":
|
|
||||||
go b.safeHandler(func() { b.handleSeeding(update) })
|
|
||||||
case "paused", "/paused", "pa", "/pa":
|
|
||||||
go b.safeHandler(func() { b.handlePaused(update) })
|
|
||||||
case "checking", "/checking", "ch", "/ch":
|
|
||||||
go b.safeHandler(func() { b.handleChecking(update) })
|
|
||||||
case "active", "/active", "ac", "/ac":
|
|
||||||
go b.safeHandler(func() { b.handleActive(update) })
|
|
||||||
case "errors", "/errors", "er", "/er":
|
|
||||||
go b.safeHandler(func() { b.handleErrors(update) })
|
|
||||||
case "sort", "/sort", "so", "/so":
|
|
||||||
go b.safeHandler(func() { b.handleSort(update, tokens[1:]) })
|
|
||||||
case "trackers", "/trackers", "tr", "/tr":
|
|
||||||
go b.safeHandler(func() { b.handleTrackers(update) })
|
|
||||||
case "downloaddir", "dd":
|
|
||||||
go b.safeHandler(func() { b.handleDownloadDir(update, tokens[1:]) })
|
|
||||||
case "add", "/add", "ad", "/ad":
|
|
||||||
go b.safeHandler(func() { b.handleAdd(update, tokens[1:]) })
|
|
||||||
case "search", "/search", "se", "/se":
|
|
||||||
go b.safeHandler(func() { b.handleSearch(update, tokens[1:]) })
|
|
||||||
case "latest", "/latest", "la", "/la":
|
|
||||||
go b.safeHandler(func() { b.handleLatest(update, tokens[1:]) })
|
|
||||||
case "info", "/info", "in", "/in":
|
|
||||||
go b.safeHandler(func() { b.handleInfo(update, tokens[1:]) })
|
|
||||||
case "stop", "/stop", "sp", "/sp":
|
|
||||||
go b.safeHandler(func() { b.handleStop(update, tokens[1:]) })
|
|
||||||
case "start", "/start", "st", "/st":
|
|
||||||
go b.safeHandler(func() { b.handleStart(update, tokens[1:]) })
|
|
||||||
case "check", "/check", "ck", "/ck":
|
|
||||||
go b.safeHandler(func() { b.handleCheck(update, tokens[1:]) })
|
|
||||||
case "stats", "/stats", "sa", "/sa":
|
|
||||||
go b.safeHandler(func() { b.handleStats(update) })
|
|
||||||
case "downlimit", "dl":
|
|
||||||
go b.safeHandler(func() { b.handleDownLimit(update, tokens[1:]) })
|
|
||||||
case "uplimit", "ul":
|
|
||||||
go b.safeHandler(func() { b.handleUpLimit(update, tokens[1:]) })
|
|
||||||
case "speed", "/speed", "ss", "/ss":
|
|
||||||
go b.safeHandler(func() { b.handleSpeed(update) })
|
|
||||||
case "count", "/count", "co", "/co":
|
|
||||||
go b.safeHandler(func() { b.handleCount(update) })
|
|
||||||
case "del", "/del", "rm", "/rm":
|
|
||||||
go b.safeHandler(func() { b.handleDel(update, tokens[1:]) })
|
|
||||||
case "deldata", "/deldata":
|
|
||||||
go b.safeHandler(func() { b.handleDelData(update, tokens[1:]) })
|
|
||||||
case "help", "/help":
|
|
||||||
go b.safeHandler(func() { b.sendMessage(update.Message.Chat.ID, HelpText, true) })
|
|
||||||
case "version", "/version", "ver", "/ver":
|
|
||||||
go b.safeHandler(func() { b.handleVersion(update) })
|
|
||||||
case "":
|
|
||||||
// might be a file received
|
|
||||||
go b.safeHandler(func() { b.handleReceiveTorrent(update) })
|
|
||||||
default:
|
|
||||||
// no such command
|
// no such command
|
||||||
go b.safeHandler(func() { b.sendMessage(update.Message.Chat.ID, "No such command, try /help", false) })
|
go b.safeHandler(func() { b.sendMessage(update.Message.Chat.ID, "No such command, try /help", false) })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package bot
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -60,21 +61,53 @@ func putBuffer(buf *bytes.Buffer) {
|
|||||||
// rateLimitWait waits for the next available API call slot
|
// rateLimitWait waits for the next available API call slot
|
||||||
// This function is defined in helpers.go to access the rate limiter
|
// This function is defined in helpers.go to access the rate limiter
|
||||||
|
|
||||||
|
// handleClientError sends an error message to the user and logs it
|
||||||
|
func (b *Bot) handleClientError(chatID int64, prefix string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Printf("[ERROR] %s: %v", prefix, err)
|
||||||
|
b.sendMessage(chatID, prefix+": "+err.Error(), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleTorrentList is a generic handler for listing torrents with filtering and formatting
|
||||||
|
func (b *Bot) handleTorrentList(update tgbotapi.Update, errorPrefix, emptyMessage string, filter func(*transmission.Torrent) bool, format func(*transmission.Torrent) string) {
|
||||||
|
torrents, err := b.client.GetTorrents()
|
||||||
|
if err != nil {
|
||||||
|
b.handleClientError(update.Message.Chat.ID, errorPrefix, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := getBuffer()
|
||||||
|
defer putBuffer(buf)
|
||||||
|
for i := range torrents {
|
||||||
|
if filter(&torrents[i]) {
|
||||||
|
buf.WriteString(format(&torrents[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf.Len() == 0 {
|
||||||
|
b.sendMessage(update.Message.Chat.ID, emptyMessage, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.sendMessage(update.Message.Chat.ID, buf.String(), false)
|
||||||
|
}
|
||||||
|
|
||||||
// Handler methods for bot commands - these are placeholders that need full implementation
|
// Handler methods for bot commands - these are placeholders that need full implementation
|
||||||
// For now, they provide basic functionality
|
// For now, they provide basic functionality
|
||||||
|
|
||||||
func (b *Bot) handleList(update tgbotapi.Update, tokens []string) {
|
func (b *Bot) handleList(update tgbotapi.Update, tokens []string) {
|
||||||
torrents, err := b.client.GetTorrents()
|
torrents, err := b.client.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*list:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*list*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := getBuffer()
|
||||||
|
defer putBuffer(buf)
|
||||||
if len(tokens) != 0 {
|
if len(tokens) != 0 {
|
||||||
regx, err := getCompiledRegex(tokens[0])
|
regx, err := getCompiledRegex(tokens[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*list:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*list*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +144,7 @@ func (b *Bot) handleHead(update tgbotapi.Update, tokens []string) {
|
|||||||
|
|
||||||
torrents, err := b.client.GetTorrents()
|
torrents, err := b.client.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*head:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*head*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +152,8 @@ func (b *Bot) handleHead(update tgbotapi.Update, tokens []string) {
|
|||||||
n = len(torrents)
|
n = len(torrents)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := getBuffer()
|
||||||
|
defer putBuffer(buf)
|
||||||
for i := range torrents[:n] {
|
for i := range torrents[:n] {
|
||||||
buf.WriteString(formatter.FormatTorrentDetailed(&torrents[i]))
|
buf.WriteString(formatter.FormatTorrentDetailed(&torrents[i]))
|
||||||
}
|
}
|
||||||
@@ -131,7 +165,9 @@ func (b *Bot) handleHead(update tgbotapi.Update, tokens []string) {
|
|||||||
|
|
||||||
msgID := b.sendMessage(update.Message.Chat.ID, buf.String(), true)
|
msgID := b.sendMessage(update.Message.Chat.ID, buf.String(), true)
|
||||||
if !b.cfg.NoLive {
|
if !b.cfg.NoLive {
|
||||||
b.liveUpdateTorrents(update.Message.Chat.ID, msgID, func(torrents transmission.Torrents) transmission.Torrents {
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second)
|
||||||
|
defer cancel()
|
||||||
|
b.liveUpdateTorrents(ctx, update.Message.Chat.ID, msgID, func(torrents transmission.Torrents) transmission.Torrents {
|
||||||
if n > len(torrents) {
|
if n > len(torrents) {
|
||||||
return torrents[:len(torrents)]
|
return torrents[:len(torrents)]
|
||||||
}
|
}
|
||||||
@@ -150,7 +186,7 @@ func (b *Bot) handleTail(update tgbotapi.Update, tokens []string) {
|
|||||||
|
|
||||||
torrents, err := b.client.GetTorrents()
|
torrents, err := b.client.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*tail:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*tail*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +194,8 @@ func (b *Bot) handleTail(update tgbotapi.Update, tokens []string) {
|
|||||||
n = len(torrents)
|
n = len(torrents)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := getBuffer()
|
||||||
|
defer putBuffer(buf)
|
||||||
for _, torrent := range torrents[len(torrents)-n:] {
|
for _, torrent := range torrents[len(torrents)-n:] {
|
||||||
buf.WriteString(formatter.FormatTorrentDetailed(&torrent))
|
buf.WriteString(formatter.FormatTorrentDetailed(&torrent))
|
||||||
}
|
}
|
||||||
@@ -170,7 +207,9 @@ func (b *Bot) handleTail(update tgbotapi.Update, tokens []string) {
|
|||||||
|
|
||||||
msgID := b.sendMessage(update.Message.Chat.ID, buf.String(), true)
|
msgID := b.sendMessage(update.Message.Chat.ID, buf.String(), true)
|
||||||
if !b.cfg.NoLive {
|
if !b.cfg.NoLive {
|
||||||
b.liveUpdateTorrents(update.Message.Chat.ID, msgID, func(torrents transmission.Torrents) transmission.Torrents {
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second)
|
||||||
|
defer cancel()
|
||||||
|
b.liveUpdateTorrents(ctx, update.Message.Chat.ID, msgID, func(torrents transmission.Torrents) transmission.Torrents {
|
||||||
if n > len(torrents) {
|
if n > len(torrents) {
|
||||||
return torrents
|
return torrents
|
||||||
}
|
}
|
||||||
@@ -180,100 +219,57 @@ func (b *Bot) handleTail(update tgbotapi.Update, tokens []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) handleDowns(update tgbotapi.Update) {
|
func (b *Bot) handleDowns(update tgbotapi.Update) {
|
||||||
torrents, err := b.client.GetTorrents()
|
b.handleTorrentList(update, "*downs*", "No downloads",
|
||||||
if err != nil {
|
func(t *transmission.Torrent) bool {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*downs:* "+err.Error(), false)
|
return t.Status == transmission.StatusDownloading ||
|
||||||
return
|
t.Status == transmission.StatusDownloadPending
|
||||||
}
|
},
|
||||||
|
func(t *transmission.Torrent) string {
|
||||||
buf := new(bytes.Buffer)
|
return formatter.FormatTorrentShort(t) + "\n"
|
||||||
for i := range torrents {
|
})
|
||||||
if torrents[i].Status == transmission.StatusDownloading ||
|
|
||||||
torrents[i].Status == transmission.StatusDownloadPending {
|
|
||||||
buf.WriteString(formatter.FormatTorrentShort(&torrents[i]) + "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf.Len() == 0 {
|
|
||||||
b.sendMessage(update.Message.Chat.ID, "No downloads", false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.sendMessage(update.Message.Chat.ID, buf.String(), false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) handleSeeding(update tgbotapi.Update) {
|
func (b *Bot) handleSeeding(update tgbotapi.Update) {
|
||||||
torrents, err := b.client.GetTorrents()
|
b.handleTorrentList(update, "*seeding*", "No torrents seeding",
|
||||||
if err != nil {
|
func(t *transmission.Torrent) bool {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*seeding:* "+err.Error(), false)
|
return t.Status == transmission.StatusSeeding ||
|
||||||
return
|
t.Status == transmission.StatusSeedPending
|
||||||
}
|
},
|
||||||
|
func(t *transmission.Torrent) string {
|
||||||
buf := new(bytes.Buffer)
|
return formatter.FormatTorrentShort(t) + "\n"
|
||||||
for i := range torrents {
|
})
|
||||||
if torrents[i].Status == transmission.StatusSeeding ||
|
|
||||||
torrents[i].Status == transmission.StatusSeedPending {
|
|
||||||
buf.WriteString(formatter.FormatTorrentShort(&torrents[i]) + "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf.Len() == 0 {
|
|
||||||
b.sendMessage(update.Message.Chat.ID, "No torrents seeding", false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.sendMessage(update.Message.Chat.ID, buf.String(), false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) handlePaused(update tgbotapi.Update) {
|
func (b *Bot) handlePaused(update tgbotapi.Update) {
|
||||||
torrents, err := b.client.GetTorrents()
|
b.handleTorrentList(update, "*paused*", "No paused torrents",
|
||||||
if err != nil {
|
func(t *transmission.Torrent) bool {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*paused:* "+err.Error(), false)
|
return t.Status == transmission.StatusStopped
|
||||||
return
|
},
|
||||||
}
|
func(t *transmission.Torrent) string {
|
||||||
|
return formatter.FormatTorrentPaused(t)
|
||||||
buf := new(bytes.Buffer)
|
})
|
||||||
for i := range torrents {
|
|
||||||
if torrents[i].Status == transmission.StatusStopped {
|
|
||||||
buf.WriteString(formatter.FormatTorrentPaused(&torrents[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf.Len() == 0 {
|
|
||||||
b.sendMessage(update.Message.Chat.ID, "No paused torrents", false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.sendMessage(update.Message.Chat.ID, buf.String(), false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) handleChecking(update tgbotapi.Update) {
|
func (b *Bot) handleChecking(update tgbotapi.Update) {
|
||||||
torrents, err := b.client.GetTorrents()
|
b.handleTorrentList(update, "*checking*", "No torrents verifying",
|
||||||
if err != nil {
|
func(t *transmission.Torrent) bool {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*checking:* "+err.Error(), false)
|
return t.Status == transmission.StatusChecking ||
|
||||||
return
|
t.Status == transmission.StatusCheckPending
|
||||||
}
|
},
|
||||||
|
func(t *transmission.Torrent) string {
|
||||||
buf := new(bytes.Buffer)
|
return formatter.FormatTorrentChecking(t)
|
||||||
for i := range torrents {
|
})
|
||||||
if torrents[i].Status == transmission.StatusChecking ||
|
|
||||||
torrents[i].Status == transmission.StatusCheckPending {
|
|
||||||
buf.WriteString(formatter.FormatTorrentChecking(&torrents[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf.Len() == 0 {
|
|
||||||
b.sendMessage(update.Message.Chat.ID, "No torrents verifying", false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.sendMessage(update.Message.Chat.ID, buf.String(), false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) handleActive(update tgbotapi.Update) {
|
func (b *Bot) handleActive(update tgbotapi.Update) {
|
||||||
torrents, err := b.client.GetTorrents()
|
torrents, err := b.client.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*active:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*active*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := getBuffer()
|
||||||
|
defer putBuffer(buf)
|
||||||
for i := range torrents {
|
for i := range torrents {
|
||||||
if torrents[i].RateDownload > 0 || torrents[i].RateUpload > 0 {
|
if torrents[i].RateDownload > 0 || torrents[i].RateUpload > 0 {
|
||||||
buf.WriteString(formatter.FormatTorrentDetailed(&torrents[i]))
|
buf.WriteString(formatter.FormatTorrentDetailed(&torrents[i]))
|
||||||
@@ -287,29 +283,20 @@ func (b *Bot) handleActive(update tgbotapi.Update) {
|
|||||||
|
|
||||||
msgID := b.sendMessage(update.Message.Chat.ID, buf.String(), true)
|
msgID := b.sendMessage(update.Message.Chat.ID, buf.String(), true)
|
||||||
if !b.cfg.NoLive {
|
if !b.cfg.NoLive {
|
||||||
b.liveUpdateActive(update.Message.Chat.ID, msgID)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second)
|
||||||
|
defer cancel()
|
||||||
|
b.liveUpdateActive(ctx, update.Message.Chat.ID, msgID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) handleErrors(update tgbotapi.Update) {
|
func (b *Bot) handleErrors(update tgbotapi.Update) {
|
||||||
torrents, err := b.client.GetTorrents()
|
b.handleTorrentList(update, "*errors*", "No errors",
|
||||||
if err != nil {
|
func(t *transmission.Torrent) bool {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*errors:* "+err.Error(), false)
|
return t.Error != 0
|
||||||
return
|
},
|
||||||
}
|
func(t *transmission.Torrent) string {
|
||||||
|
return fmt.Sprintf("<%d> %s\n%s\n", t.ID, t.Name, t.ErrorString)
|
||||||
buf := new(bytes.Buffer)
|
})
|
||||||
for i := range torrents {
|
|
||||||
if torrents[i].Error != 0 {
|
|
||||||
buf.WriteString(fmt.Sprintf("<%d> %s\n%s\n", torrents[i].ID, torrents[i].Name, torrents[i].ErrorString))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf.Len() == 0 {
|
|
||||||
b.sendMessage(update.Message.Chat.ID, "No errors", false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.sendMessage(update.Message.Chat.ID, buf.String(), false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) handleSort(update tgbotapi.Update, tokens []string) {
|
func (b *Bot) handleSort(update tgbotapi.Update, tokens []string) {
|
||||||
@@ -359,7 +346,7 @@ func (b *Bot) handleSort(update tgbotapi.Update, tokens []string) {
|
|||||||
func (b *Bot) handleTrackers(update tgbotapi.Update) {
|
func (b *Bot) handleTrackers(update tgbotapi.Update) {
|
||||||
torrents, err := b.client.GetTorrents()
|
torrents, err := b.client.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*trackers:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*trackers*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,7 +361,8 @@ func (b *Bot) handleTrackers(update tgbotapi.Update) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := getBuffer()
|
||||||
|
defer putBuffer(buf)
|
||||||
for k, v := range trackers {
|
for k, v := range trackers {
|
||||||
buf.WriteString(fmt.Sprintf("%d - %s\n", v, k))
|
buf.WriteString(fmt.Sprintf("%d - %s\n", v, k))
|
||||||
}
|
}
|
||||||
@@ -397,7 +385,7 @@ func (b *Bot) handleDownloadDir(update tgbotapi.Update, tokens []string) {
|
|||||||
|
|
||||||
out, err := b.client.ExecuteCommand(cmd)
|
out, err := b.client.ExecuteCommand(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*downloaddir:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*downloaddir*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if out.Result != "success" {
|
if out.Result != "success" {
|
||||||
@@ -439,17 +427,18 @@ func (b *Bot) handleSearch(update tgbotapi.Update, tokens []string) {
|
|||||||
query := strings.Join(tokens, " ")
|
query := strings.Join(tokens, " ")
|
||||||
regx, err := getCompiledRegex(query)
|
regx, err := getCompiledRegex(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*search:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*search*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
torrents, err := b.client.GetTorrents()
|
torrents, err := b.client.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*search:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*search*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := getBuffer()
|
||||||
|
defer putBuffer(buf)
|
||||||
for i := range torrents {
|
for i := range torrents {
|
||||||
if regx.MatchString(torrents[i].Name) {
|
if regx.MatchString(torrents[i].Name) {
|
||||||
buf.WriteString(formatter.FormatTorrentShort(&torrents[i]) + "\n")
|
buf.WriteString(formatter.FormatTorrentShort(&torrents[i]) + "\n")
|
||||||
@@ -473,7 +462,7 @@ func (b *Bot) handleLatest(update tgbotapi.Update, tokens []string) {
|
|||||||
|
|
||||||
torrents, err := b.client.GetTorrents()
|
torrents, err := b.client.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*latest:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*latest*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +471,8 @@ func (b *Bot) handleLatest(update tgbotapi.Update, tokens []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
torrents.SortAge(true)
|
torrents.SortAge(true)
|
||||||
buf := new(bytes.Buffer)
|
buf := getBuffer()
|
||||||
|
defer putBuffer(buf)
|
||||||
for i := range torrents[:n] {
|
for i := range torrents[:n] {
|
||||||
buf.WriteString(formatter.FormatTorrentShort(&torrents[i]) + "\n")
|
buf.WriteString(formatter.FormatTorrentShort(&torrents[i]) + "\n")
|
||||||
}
|
}
|
||||||
@@ -518,7 +508,9 @@ func (b *Bot) handleInfo(update tgbotapi.Update, tokens []string) {
|
|||||||
msgID := b.sendMessage(update.Message.Chat.ID, info, true)
|
msgID := b.sendMessage(update.Message.Chat.ID, info, true)
|
||||||
|
|
||||||
if !b.cfg.NoLive {
|
if !b.cfg.NoLive {
|
||||||
b.liveUpdateInfo(update.Message.Chat.ID, msgID, torrentID, trackers)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second)
|
||||||
|
defer cancel()
|
||||||
|
b.liveUpdateInfo(ctx, update.Message.Chat.ID, msgID, torrentID, trackers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,7 +626,7 @@ func (b *Bot) handleCheck(update tgbotapi.Update, tokens []string) {
|
|||||||
func (b *Bot) handleStats(update tgbotapi.Update) {
|
func (b *Bot) handleStats(update tgbotapi.Update) {
|
||||||
stats, err := b.client.GetStats()
|
stats, err := b.client.GetStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*stats:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*stats*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,7 +689,7 @@ func (b *Bot) handleSpeedLimit(update tgbotapi.Update, tokens []string, limitTyp
|
|||||||
|
|
||||||
out, err := b.client.ExecuteCommand(speedLimitCmd)
|
out, err := b.client.ExecuteCommand(speedLimitCmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*%s:* %v", limitType, err.Error()), false)
|
b.handleClientError(update.Message.Chat.ID, fmt.Sprintf("*%s*", limitType), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if out.Result != "success" {
|
if out.Result != "success" {
|
||||||
@@ -711,7 +703,7 @@ func (b *Bot) handleSpeedLimit(update tgbotapi.Update, tokens []string, limitTyp
|
|||||||
func (b *Bot) handleSpeed(update tgbotapi.Update) {
|
func (b *Bot) handleSpeed(update tgbotapi.Update) {
|
||||||
stats, err := b.client.GetStats()
|
stats, err := b.client.GetStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*speed:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*speed*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -719,14 +711,16 @@ func (b *Bot) handleSpeed(update tgbotapi.Update) {
|
|||||||
msgID := b.sendMessage(update.Message.Chat.ID, msg, false)
|
msgID := b.sendMessage(update.Message.Chat.ID, msg, false)
|
||||||
|
|
||||||
if !b.cfg.NoLive {
|
if !b.cfg.NoLive {
|
||||||
b.liveUpdateSpeed(update.Message.Chat.ID, msgID)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second)
|
||||||
|
defer cancel()
|
||||||
|
b.liveUpdateSpeed(ctx, update.Message.Chat.ID, msgID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) handleCount(update tgbotapi.Update) {
|
func (b *Bot) handleCount(update tgbotapi.Update) {
|
||||||
torrents, err := b.client.GetTorrents()
|
torrents, err := b.client.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.sendMessage(update.Message.Chat.ID, "*count:* "+err.Error(), false)
|
b.handleClientError(update.Message.Chat.ID, "*count*", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,9 +818,14 @@ func (b *Bot) handleReceiveTorrent(update tgbotapi.Update) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Live update helpers
|
// Live update helpers
|
||||||
func (b *Bot) liveUpdateTorrents(chatID int64, msgID int, filter func(transmission.Torrents) transmission.Torrents, formatter func(*transmission.Torrent) string) {
|
func (b *Bot) liveUpdateTorrents(ctx context.Context, chatID int64, msgID int, filter func(transmission.Torrents) transmission.Torrents, formatter func(*transmission.Torrent) string) {
|
||||||
for i := 0; i < b.cfg.Duration; i++ {
|
for i := 0; i < b.cfg.Duration; i++ {
|
||||||
time.Sleep(b.cfg.Interval)
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(b.cfg.Interval):
|
||||||
|
}
|
||||||
|
|
||||||
torrents, err := b.client.GetTorrents()
|
torrents, err := b.client.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@@ -855,9 +854,14 @@ func (b *Bot) liveUpdateTorrents(chatID int64, msgID int, filter func(transmissi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) liveUpdateActive(chatID int64, msgID int) {
|
func (b *Bot) liveUpdateActive(ctx context.Context, chatID int64, msgID int) {
|
||||||
for i := 0; i < b.cfg.Duration; i++ {
|
for i := 0; i < b.cfg.Duration; i++ {
|
||||||
time.Sleep(b.cfg.Interval)
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(b.cfg.Interval):
|
||||||
|
}
|
||||||
|
|
||||||
torrents, err := b.client.GetTorrents()
|
torrents, err := b.client.GetTorrents()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@@ -882,7 +886,12 @@ func (b *Bot) liveUpdateActive(chatID int64, msgID int) {
|
|||||||
putBuffer(buf)
|
putBuffer(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(b.cfg.Interval)
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(b.cfg.Interval):
|
||||||
|
}
|
||||||
|
|
||||||
torrents, _ := b.client.GetTorrents()
|
torrents, _ := b.client.GetTorrents()
|
||||||
buf := getBuffer()
|
buf := getBuffer()
|
||||||
defer putBuffer(buf)
|
defer putBuffer(buf)
|
||||||
@@ -902,9 +911,14 @@ func (b *Bot) liveUpdateActive(chatID int64, msgID int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) liveUpdateInfo(chatID int64, msgID int, torrentID int, trackers string) {
|
func (b *Bot) liveUpdateInfo(ctx context.Context, chatID int64, msgID int, torrentID int, trackers string) {
|
||||||
for i := 0; i < b.cfg.Duration; i++ {
|
for i := 0; i < b.cfg.Duration; i++ {
|
||||||
time.Sleep(b.cfg.Interval)
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(b.cfg.Interval):
|
||||||
|
}
|
||||||
|
|
||||||
torrent, err := b.client.GetTorrent(torrentID)
|
torrent, err := b.client.GetTorrent(torrentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@@ -921,7 +935,12 @@ func (b *Bot) liveUpdateInfo(chatID int64, msgID int, torrentID int, trackers st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(b.cfg.Interval)
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(b.cfg.Interval):
|
||||||
|
}
|
||||||
|
|
||||||
torrent, _ := b.client.GetTorrent(torrentID)
|
torrent, _ := b.client.GetTorrent(torrentID)
|
||||||
info := formatter.FormatTorrentInfoStopped(torrent, trackers)
|
info := formatter.FormatTorrentInfoStopped(torrent, trackers)
|
||||||
// Rate limit before sending
|
// Rate limit before sending
|
||||||
@@ -933,9 +952,14 @@ func (b *Bot) liveUpdateInfo(chatID int64, msgID int, torrentID int, trackers st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) liveUpdateSpeed(chatID int64, msgID int) {
|
func (b *Bot) liveUpdateSpeed(ctx context.Context, chatID int64, msgID int) {
|
||||||
for i := 0; i < b.cfg.Duration; i++ {
|
for i := 0; i < b.cfg.Duration; i++ {
|
||||||
time.Sleep(b.cfg.Interval)
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(b.cfg.Interval):
|
||||||
|
}
|
||||||
|
|
||||||
stats, err := b.client.GetStats()
|
stats, err := b.client.GetStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@@ -949,10 +973,14 @@ func (b *Bot) liveUpdateSpeed(chatID int64, msgID int) {
|
|||||||
b.logger.Printf("[ERROR] Failed to update message: %s", err)
|
b.logger.Printf("[ERROR] Failed to update message: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
time.Sleep(b.cfg.Interval)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(b.cfg.Interval)
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(b.cfg.Interval):
|
||||||
|
}
|
||||||
|
|
||||||
// Rate limit before sending
|
// Rate limit before sending
|
||||||
rateLimitWait()
|
rateLimitWait()
|
||||||
editConf := tgbotapi.NewEditMessageText(chatID, msgID, "↓ - ↑ -")
|
editConf := tgbotapi.NewEditMessageText(chatID, msgID, "↓ - ↑ -")
|
||||||
|
|||||||
@@ -14,23 +14,37 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// rateLimiter limits API calls to Telegram (30 messages per second)
|
// rateLimiter limits API calls to Telegram (30 messages per second)
|
||||||
|
// Uses a channel-based approach to avoid mutex contention
|
||||||
type rateLimiter struct {
|
type rateLimiter struct {
|
||||||
ticker *time.Ticker
|
ch chan struct{}
|
||||||
mu sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// telegramRateLimiter limits API calls to 30 per second
|
// telegramRateLimiter limits API calls to 30 per second
|
||||||
telegramRateLimiter = &rateLimiter{
|
telegramRateLimiter = func() *rateLimiter {
|
||||||
ticker: time.NewTicker(time.Second / 30), // 30 calls per second
|
rl := &rateLimiter{
|
||||||
|
ch: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
|
// Start the ticker goroutine
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(time.Second / 30) // 30 calls per second
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
// Try to send a token, but don't block if channel is full
|
||||||
|
select {
|
||||||
|
case rl.ch <- struct{}{}:
|
||||||
|
default:
|
||||||
|
// Channel is full, skip this tick
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return rl
|
||||||
|
}()
|
||||||
)
|
)
|
||||||
|
|
||||||
// wait waits for the next available slot
|
// wait waits for the next available slot
|
||||||
func (rl *rateLimiter) wait() {
|
func (rl *rateLimiter) wait() {
|
||||||
rl.mu.Lock()
|
<-rl.ch
|
||||||
defer rl.mu.Unlock()
|
|
||||||
<-rl.ticker.C
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rateLimitWait waits for the next available API call slot
|
// rateLimitWait waits for the next available API call slot
|
||||||
|
|||||||
@@ -86,10 +86,11 @@ func (m *Monitor) checkCompletions() {
|
|||||||
m.statesMutex.Lock()
|
m.statesMutex.Lock()
|
||||||
defer m.statesMutex.Unlock()
|
defer m.statesMutex.Unlock()
|
||||||
|
|
||||||
// Create a set of current torrent IDs
|
// Create a set of current torrent IDs for efficient lookup
|
||||||
currentTorrentIDs := make(map[int]bool, len(torrents))
|
// Use map[int]struct{} for better memory efficiency than map[int]bool
|
||||||
|
currentTorrentIDs := make(map[int]struct{}, len(torrents))
|
||||||
for _, torrent := range torrents {
|
for _, torrent := range torrents {
|
||||||
currentTorrentIDs[torrent.ID] = true
|
currentTorrentIDs[torrent.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check each torrent for completion and update states
|
// Check each torrent for completion and update states
|
||||||
@@ -97,23 +98,26 @@ func (m *Monitor) checkCompletions() {
|
|||||||
prevState, exists := m.states[torrent.ID]
|
prevState, exists := m.states[torrent.ID]
|
||||||
|
|
||||||
// Detect completion: transition from Downloading to Seeding with 100% done
|
// Detect completion: transition from Downloading to Seeding with 100% done
|
||||||
|
// Optimized: only check if we were tracking this torrent
|
||||||
completed := false
|
completed := false
|
||||||
if exists && prevState.Status == transmission.StatusDownloading {
|
if exists && prevState.Status == transmission.StatusDownloading {
|
||||||
// Case 1: Status changed from Downloading to Seeding (most common)
|
// Case 1: Status changed from Downloading to Seeding (most common)
|
||||||
if torrent.Status == transmission.StatusSeeding && torrent.PercentDone >= 1.0 {
|
if torrent.Status == transmission.StatusSeeding && torrent.PercentDone >= 1.0 {
|
||||||
completed = true
|
completed = true
|
||||||
}
|
} else if torrent.Status == transmission.StatusDownloading &&
|
||||||
// Case 2: Torrent reached 100% while still in Downloading status
|
|
||||||
if torrent.Status == transmission.StatusDownloading &&
|
|
||||||
prevState.PercentDone < 1.0 &&
|
prevState.PercentDone < 1.0 &&
|
||||||
torrent.PercentDone >= 1.0 {
|
torrent.PercentDone >= 1.0 {
|
||||||
|
// Case 2: Torrent reached 100% while still in Downloading status
|
||||||
completed = true
|
completed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if completed && m.onComplete != nil {
|
if completed && m.onComplete != nil {
|
||||||
|
// Call callback outside of lock to avoid potential deadlocks
|
||||||
|
m.statesMutex.Unlock()
|
||||||
m.onComplete(torrent)
|
m.onComplete(torrent)
|
||||||
m.logger.Printf("[INFO] Torrent completed: %s (ID: %d)", torrent.Name, torrent.ID)
|
m.logger.Printf("[INFO] Torrent completed: %s (ID: %d)", torrent.Name, torrent.ID)
|
||||||
|
m.statesMutex.Lock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state - only track downloading torrents to save memory
|
// Update state - only track downloading torrents to save memory
|
||||||
@@ -130,8 +134,9 @@ func (m *Monitor) checkCompletions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean up states for torrents that no longer exist
|
// Clean up states for torrents that no longer exist
|
||||||
|
// More efficient: iterate over states map instead of currentTorrentIDs
|
||||||
for torrentID := range m.states {
|
for torrentID := range m.states {
|
||||||
if !currentTorrentIDs[torrentID] {
|
if _, exists := currentTorrentIDs[torrentID]; !exists {
|
||||||
delete(m.states, torrentID)
|
delete(m.states, torrentID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user