Обновлены зависимости в go.mod, добавлены новые пакеты для улучшения функциональности. В коде Telegram-бота изменены вызовы методов на использование нового стиля именования, улучшена обработка сообщений и логирование. Оптимизированы функции для работы с торрентами, включая обработку ошибок и форматирование сообщений.

This commit is contained in:
Struchkov Mark
2025-12-04 21:43:36 +03:00
parent 21cfe56a0b
commit caeb5f06bd
6 changed files with 117 additions and 104 deletions

View File

@@ -59,17 +59,17 @@ func main() {
cachedClient := transmissionClient.NewCachedClient(transClient, 2*time.Second) cachedClient := transmissionClient.NewCachedClient(transClient, 2*time.Second)
// Initialize Telegram bot // Initialize Telegram bot
bot, err := tgbotapi.NewBotAPI(cfg.BotToken) telegramBotAPI, err := tgbotapi.NewBotAPI(cfg.BotToken)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "[ERROR] Telegram: %s\n", err) fmt.Fprintf(os.Stderr, "[ERROR] Telegram: %s\n", err)
os.Exit(1) os.Exit(1)
} }
log.Printf("[INFO] Authorized: %s", bot.Self.UserName) log.Printf("[INFO] Authorized: %s", telegramBotAPI.Self.UserName)
u := tgbotapi.NewUpdate(0) u := tgbotapi.NewUpdate(0)
u.Timeout = config.TelegramUpdateTimeout u.Timeout = config.TelegramUpdateTimeout
updates, err := bot.GetUpdatesChan(u) updates, err := telegramBotAPI.GetUpdatesChan(u)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "[ERROR] Telegram: %s\n", err) fmt.Fprintf(os.Stderr, "[ERROR] Telegram: %s\n", err)
os.Exit(1) os.Exit(1)
@@ -79,7 +79,7 @@ func main() {
mon := monitor.NewMonitor(cachedClient, log, config.DefaultPollInterval) mon := monitor.NewMonitor(cachedClient, log, config.DefaultPollInterval)
// Create bot instance first // Create bot instance first
telegramBot := bot.NewBot(bot, cachedClient, cfg, log, updates, mon) telegramBot := bot.NewBot(telegramBotAPI, cachedClient, cfg, log, updates, mon)
// Set up completion callback - chatID will be set by bot when user sends first message // Set up completion callback - chatID will be set by bot when user sends first message
mon.SetOnComplete(func(torrent *transmission.Torrent) { mon.SetOnComplete(func(torrent *transmission.Torrent) {

11
go.mod
View File

@@ -4,8 +4,15 @@ go 1.21
require ( require (
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/pyed/tailer v0.0.0-20180809195549-5c8b5b0b5b5b
github.com/pyed/transmission v0.0.0-20210101100000-000000000000
gopkg.in/telegram-bot-api.v4 v4.6.4 gopkg.in/telegram-bot-api.v4 v4.6.4
) )
require (
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/martini-contrib/auth v0.0.0-20150219114609-fa62c19b7ae8 // indirect
github.com/pyed/transmission v0.0.0-20240821061748-4cfdbb917ad4
github.com/smartystreets/goconvey v1.8.1 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
)

View File

@@ -179,8 +179,8 @@ func (b *Bot) getCommandHandler(command string, update tgbotapi.Update, tokens [
"/rm": b.handleDel, "/rm": b.handleDel,
"deldata": b.handleDelData, "deldata": b.handleDelData,
"/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) },
"/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) },
"/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) },
@@ -202,6 +202,9 @@ func (b *Bot) handleUpdate(update tgbotapi.Update) {
} }
// Check if user is master // Check if user is master
if update.Message.From == nil {
return
}
if !b.cfg.IsMaster(update.Message.From.UserName) { if !b.cfg.IsMaster(update.Message.From.UserName) {
b.logger.Printf("[INFO] Ignored a message from: %s", update.Message.From.String()) b.logger.Printf("[INFO] Ignored a message from: %s", update.Message.From.String())
return return
@@ -239,7 +242,7 @@ func (b *Bot) handleUpdate(update tgbotapi.Update) {
go b.safeHandler(handler) go b.safeHandler(handler)
} else { } else {
// 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) })
} }
} }

View File

@@ -15,7 +15,6 @@ import (
tgbotapi "gopkg.in/telegram-bot-api.v4" tgbotapi "gopkg.in/telegram-bot-api.v4"
"transmission-telegram/internal/config" "transmission-telegram/internal/config"
"transmission-telegram/internal/formatter" "transmission-telegram/internal/formatter"
"transmission-telegram/pkg/utils"
) )
const ( const (
@@ -76,7 +75,11 @@ func getCompiledRegex(pattern string) (*regexp.Regexp, error) {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, fmt.Errorf("regex compilation timeout (pattern may be too complex): %s", pattern[:min(len(pattern), 50)]) patternPreview := pattern
if len(patternPreview) > 50 {
patternPreview = patternPreview[:50]
}
return nil, fmt.Errorf("regex compilation timeout (pattern may be too complex): %s", patternPreview)
case res := <-resultCh: case res := <-resultCh:
if res.err != nil { if res.err != nil {
return nil, fmt.Errorf("regex compilation error: %w", res.err) return nil, fmt.Errorf("regex compilation error: %w", res.err)
@@ -86,8 +89,8 @@ func getCompiledRegex(pattern string) (*regexp.Regexp, error) {
} }
} }
// min returns the minimum of two integers // minInt returns the minimum of two integers
func min(a, b int) int { func minInt(a, b int) int {
if a < b { if a < b {
return a return a
} }
@@ -117,7 +120,7 @@ func putBuffer(buf *bytes.Buffer) {
func (b *Bot) handleClientError(chatID int64, prefix string, err error) { func (b *Bot) handleClientError(chatID int64, prefix string, err error) {
if err != nil { if err != nil {
b.logger.Printf("[ERROR] %s: %v", prefix, err) b.logger.Printf("[ERROR] %s: %v", prefix, err)
b.sendMessage(chatID, prefix+": "+err.Error(), false) b.SendMessage(chatID, prefix+": "+err.Error(), false)
} }
} }
@@ -132,16 +135,16 @@ func (b *Bot) handleTorrentList(update tgbotapi.Update, errorPrefix, emptyMessag
buf := getBuffer() buf := getBuffer()
defer putBuffer(buf) defer putBuffer(buf)
for i := range torrents { for i := range torrents {
if filter(&torrents[i]) { if filter(torrents[i]) {
buf.WriteString(format(&torrents[i])) buf.WriteString(format(torrents[i]))
} }
} }
if buf.Len() == 0 { if buf.Len() == 0 {
b.sendMessage(update.Message.Chat.ID, emptyMessage, false) b.SendMessage(update.Message.Chat.ID, emptyMessage, false)
return return
} }
b.sendMessage(update.Message.Chat.ID, buf.String(), false) 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
@@ -176,14 +179,14 @@ func (b *Bot) handleList(update tgbotapi.Update, tokens []string) {
if buf.Len() == 0 { if buf.Len() == 0 {
if len(tokens) != 0 { if len(tokens) != 0 {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*list:* No tracker matches: *%s*", tokens[0]), true) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*list:* No tracker matches: *%s*", tokens[0]), true)
return return
} }
b.sendMessage(update.Message.Chat.ID, "*list:* no torrents", false) b.SendMessage(update.Message.Chat.ID, "*list:* no torrents", false)
return return
} }
b.sendMessage(update.Message.Chat.ID, buf.String(), false) b.SendMessage(update.Message.Chat.ID, buf.String(), false)
} }
func (b *Bot) handleHead(update tgbotapi.Update, tokens []string) { func (b *Bot) handleHead(update tgbotapi.Update, tokens []string) {
@@ -207,15 +210,15 @@ func (b *Bot) handleHead(update tgbotapi.Update, tokens []string) {
buf := getBuffer() buf := getBuffer()
defer putBuffer(buf) 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]))
} }
if buf.Len() == 0 { if buf.Len() == 0 {
b.sendMessage(update.Message.Chat.ID, "*head:* no torrents", false) b.SendMessage(update.Message.Chat.ID, "*head:* no torrents", false)
return return
} }
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 {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second)
defer cancel() defer cancel()
@@ -249,15 +252,15 @@ func (b *Bot) handleTail(update tgbotapi.Update, tokens []string) {
buf := getBuffer() buf := getBuffer()
defer putBuffer(buf) 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))
} }
if buf.Len() == 0 { if buf.Len() == 0 {
b.sendMessage(update.Message.Chat.ID, "*tail:* no torrents", false) b.SendMessage(update.Message.Chat.ID, "*tail:* no torrents", false)
return return
} }
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 {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second)
defer cancel() defer cancel()
@@ -324,16 +327,16 @@ func (b *Bot) handleActive(update tgbotapi.Update) {
defer putBuffer(buf) 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]))
} }
} }
if buf.Len() == 0 { if buf.Len() == 0 {
b.sendMessage(update.Message.Chat.ID, "No active torrents", false) b.SendMessage(update.Message.Chat.ID, "No active torrents", false)
return return
} }
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 {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second)
defer cancel() defer cancel()
@@ -353,7 +356,7 @@ func (b *Bot) handleErrors(update tgbotapi.Update) {
func (b *Bot) handleSort(update tgbotapi.Update, tokens []string) { func (b *Bot) handleSort(update tgbotapi.Update, tokens []string) {
if len(tokens) == 0 { if len(tokens) == 0 {
b.sendMessage(update.Message.Chat.ID, `*sort* takes one of: b.SendMessage(update.Message.Chat.ID, `*sort* takes one of:
(*id, name, age, size, progress, downspeed, upspeed, download, upload, ratio*) (*id, name, age, size, progress, downspeed, upspeed, download, upload, ratio*)
optionally start with (*rev*) for reversed order optionally start with (*rev*) for reversed order
e.g. "*sort rev size*" to get biggest torrents first.`, true) e.g. "*sort rev size*" to get biggest torrents first.`, true)
@@ -367,8 +370,8 @@ func (b *Bot) handleSort(update tgbotapi.Update, tokens []string) {
} }
sortMap := map[string]struct { sortMap := map[string]struct {
normal transmission.SortType normal transmission.Sorting
rev transmission.SortType rev transmission.Sorting
}{ }{
"id": {transmission.SortID, transmission.SortRevID}, "id": {transmission.SortID, transmission.SortRevID},
"name": {transmission.SortName, transmission.SortRevName}, "name": {transmission.SortName, transmission.SortRevName},
@@ -385,13 +388,13 @@ func (b *Bot) handleSort(update tgbotapi.Update, tokens []string) {
if sort, ok := sortMap[strings.ToLower(tokens[0])]; ok { if sort, ok := sortMap[strings.ToLower(tokens[0])]; ok {
if reversed { if reversed {
b.client.SetSort(sort.rev) b.client.SetSort(sort.rev)
b.sendMessage(update.Message.Chat.ID, "*sort:* reversed "+tokens[0], false) b.SendMessage(update.Message.Chat.ID, "*sort:* reversed "+tokens[0], false)
} else { } else {
b.client.SetSort(sort.normal) b.client.SetSort(sort.normal)
b.sendMessage(update.Message.Chat.ID, "*sort:* "+tokens[0], false) b.SendMessage(update.Message.Chat.ID, "*sort:* "+tokens[0], false)
} }
} else { } else {
b.sendMessage(update.Message.Chat.ID, "unknown sorting method", false) b.SendMessage(update.Message.Chat.ID, "unknown sorting method", false)
} }
} }
@@ -420,15 +423,15 @@ func (b *Bot) handleTrackers(update tgbotapi.Update) {
} }
if buf.Len() == 0 { if buf.Len() == 0 {
b.sendMessage(update.Message.Chat.ID, "No trackers!", false) b.SendMessage(update.Message.Chat.ID, "No trackers!", false)
return return
} }
b.sendMessage(update.Message.Chat.ID, buf.String(), false) b.SendMessage(update.Message.Chat.ID, buf.String(), false)
} }
func (b *Bot) handleDownloadDir(update tgbotapi.Update, tokens []string) { func (b *Bot) handleDownloadDir(update tgbotapi.Update, tokens []string) {
if len(tokens) < 1 { if len(tokens) < 1 {
b.sendMessage(update.Message.Chat.ID, "Please, specify a path for downloaddir", false) b.SendMessage(update.Message.Chat.ID, "Please, specify a path for downloaddir", false)
return return
} }
@@ -441,16 +444,16 @@ func (b *Bot) handleDownloadDir(update tgbotapi.Update, tokens []string) {
return return
} }
if out.Result != "success" { if out.Result != "success" {
b.sendMessage(update.Message.Chat.ID, "*downloaddir:* "+out.Result, false) b.SendMessage(update.Message.Chat.ID, "*downloaddir:* "+out.Result, false)
return return
} }
b.sendMessage(update.Message.Chat.ID, "*downloaddir:* downloaddir has been successfully changed to "+tokens[0], false) b.SendMessage(update.Message.Chat.ID, "*downloaddir:* downloaddir has been successfully changed to "+tokens[0], false)
} }
func (b *Bot) handleAdd(update tgbotapi.Update, tokens []string) { func (b *Bot) handleAdd(update tgbotapi.Update, tokens []string) {
if len(tokens) == 0 { if len(tokens) == 0 {
b.sendMessage(update.Message.Chat.ID, "*add:* needs at least one URL", false) b.SendMessage(update.Message.Chat.ID, "*add:* needs at least one URL", false)
return return
} }
@@ -458,21 +461,21 @@ func (b *Bot) handleAdd(update tgbotapi.Update, tokens []string) {
cmd := transmission.NewAddCmdByURL(url) cmd := transmission.NewAddCmdByURL(url)
torrent, err := b.client.ExecuteAddCommand(cmd) torrent, err := b.client.ExecuteAddCommand(cmd)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, "*add:* "+err.Error(), false) b.SendMessage(update.Message.Chat.ID, "*add:* "+err.Error(), false)
continue continue
} }
if torrent.Name == "" { if torrent.Name == "" {
b.sendMessage(update.Message.Chat.ID, "*add:* error adding "+url, false) b.SendMessage(update.Message.Chat.ID, "*add:* error adding "+url, false)
continue continue
} }
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*Added:* <%d> %s", torrent.ID, torrent.Name), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*Added:* <%d> %s", torrent.ID, torrent.Name), false)
} }
} }
func (b *Bot) handleSearch(update tgbotapi.Update, tokens []string) { func (b *Bot) handleSearch(update tgbotapi.Update, tokens []string) {
if len(tokens) == 0 { if len(tokens) == 0 {
b.sendMessage(update.Message.Chat.ID, "*search:* needs an argument", false) b.SendMessage(update.Message.Chat.ID, "*search:* needs an argument", false)
return return
} }
@@ -493,15 +496,15 @@ func (b *Bot) handleSearch(update tgbotapi.Update, tokens []string) {
defer putBuffer(buf) 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")
} }
} }
if buf.Len() == 0 { if buf.Len() == 0 {
b.sendMessage(update.Message.Chat.ID, "No matches!", false) b.SendMessage(update.Message.Chat.ID, "No matches!", false)
return return
} }
b.sendMessage(update.Message.Chat.ID, buf.String(), false) b.SendMessage(update.Message.Chat.ID, buf.String(), false)
} }
func (b *Bot) handleLatest(update tgbotapi.Update, tokens []string) { func (b *Bot) handleLatest(update tgbotapi.Update, tokens []string) {
@@ -526,38 +529,38 @@ func (b *Bot) handleLatest(update tgbotapi.Update, tokens []string) {
buf := getBuffer() buf := getBuffer()
defer putBuffer(buf) 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")
} }
if buf.Len() == 0 { if buf.Len() == 0 {
b.sendMessage(update.Message.Chat.ID, "*latest:* No torrents", false) b.SendMessage(update.Message.Chat.ID, "*latest:* No torrents", false)
return return
} }
b.sendMessage(update.Message.Chat.ID, buf.String(), false) b.SendMessage(update.Message.Chat.ID, buf.String(), false)
} }
func (b *Bot) handleInfo(update tgbotapi.Update, tokens []string) { func (b *Bot) handleInfo(update tgbotapi.Update, tokens []string) {
if len(tokens) == 0 { if len(tokens) == 0 {
b.sendMessage(update.Message.Chat.ID, "*info:* needs a torrent ID number", false) b.SendMessage(update.Message.Chat.ID, "*info:* needs a torrent ID number", false)
return return
} }
for _, id := range tokens { for _, id := range tokens {
torrentID, err := strconv.Atoi(id) torrentID, err := strconv.Atoi(id)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*info:* %s is not a number", id), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*info:* %s is not a number", id), false)
continue continue
} }
torrent, err := b.client.GetTorrent(torrentID) torrent, err := b.client.GetTorrent(torrentID)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*info:* Can't find a torrent with an ID of %d", torrentID), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*info:* Can't find a torrent with an ID of %d", torrentID), false)
continue continue
} }
trackers := formatter.ExtractTrackers(torrent, trackerRegex) trackers := formatter.ExtractTrackers(torrent, trackerRegex)
info := formatter.FormatTorrentInfo(torrent, trackers) info := formatter.FormatTorrentInfo(torrent, trackers)
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 {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second)
@@ -569,109 +572,109 @@ func (b *Bot) handleInfo(update tgbotapi.Update, tokens []string) {
func (b *Bot) handleStop(update tgbotapi.Update, tokens []string) { func (b *Bot) handleStop(update tgbotapi.Update, tokens []string) {
if len(tokens) == 0 { if len(tokens) == 0 {
b.sendMessage(update.Message.Chat.ID, "*stop:* needs an argument", false) b.SendMessage(update.Message.Chat.ID, "*stop:* needs an argument", false)
return return
} }
if tokens[0] == "all" { if tokens[0] == "all" {
if err := b.client.StopAll(); err != nil { if err := b.client.StopAll(); err != nil {
b.sendMessage(update.Message.Chat.ID, "*stop:* error occurred while stopping some torrents", false) b.SendMessage(update.Message.Chat.ID, "*stop:* error occurred while stopping some torrents", false)
return return
} }
b.sendMessage(update.Message.Chat.ID, "Stopped all torrents", false) b.SendMessage(update.Message.Chat.ID, "Stopped all torrents", false)
return return
} }
for _, id := range tokens { for _, id := range tokens {
num, err := strconv.Atoi(id) num, err := strconv.Atoi(id)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*stop:* %s is not a number", id), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*stop:* %s is not a number", id), false)
continue continue
} }
status, err := b.client.StopTorrent(num) status, err := b.client.StopTorrent(num)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, "*stop:* "+err.Error(), false) b.SendMessage(update.Message.Chat.ID, "*stop:* "+err.Error(), false)
continue continue
} }
torrent, err := b.client.GetTorrent(num) torrent, err := b.client.GetTorrent(num)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("[fail] *stop:* No torrent with an ID of %d", num), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("[fail] *stop:* No torrent with an ID of %d", num), false)
continue continue
} }
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("[%s] *stop:* %s", status, torrent.Name), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("[%s] *stop:* %s", status, torrent.Name), false)
} }
} }
func (b *Bot) handleStart(update tgbotapi.Update, tokens []string) { func (b *Bot) handleStart(update tgbotapi.Update, tokens []string) {
if len(tokens) == 0 { if len(tokens) == 0 {
b.sendMessage(update.Message.Chat.ID, "*start:* needs an argument", false) b.SendMessage(update.Message.Chat.ID, "*start:* needs an argument", false)
return return
} }
if tokens[0] == "all" { if tokens[0] == "all" {
if err := b.client.StartAll(); err != nil { if err := b.client.StartAll(); err != nil {
b.sendMessage(update.Message.Chat.ID, "*start:* error occurred while starting some torrents", false) b.SendMessage(update.Message.Chat.ID, "*start:* error occurred while starting some torrents", false)
return return
} }
b.sendMessage(update.Message.Chat.ID, "Started all torrents", false) b.SendMessage(update.Message.Chat.ID, "Started all torrents", false)
return return
} }
for _, id := range tokens { for _, id := range tokens {
num, err := strconv.Atoi(id) num, err := strconv.Atoi(id)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*start:* %s is not a number", id), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*start:* %s is not a number", id), false)
continue continue
} }
status, err := b.client.StartTorrent(num) status, err := b.client.StartTorrent(num)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, "*start:* "+err.Error(), false) b.SendMessage(update.Message.Chat.ID, "*start:* "+err.Error(), false)
continue continue
} }
torrent, err := b.client.GetTorrent(num) torrent, err := b.client.GetTorrent(num)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("[fail] *start:* No torrent with an ID of %d", num), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("[fail] *start:* No torrent with an ID of %d", num), false)
continue continue
} }
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("[%s] *start:* %s", status, torrent.Name), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("[%s] *start:* %s", status, torrent.Name), false)
} }
} }
func (b *Bot) handleCheck(update tgbotapi.Update, tokens []string) { func (b *Bot) handleCheck(update tgbotapi.Update, tokens []string) {
if len(tokens) == 0 { if len(tokens) == 0 {
b.sendMessage(update.Message.Chat.ID, "*check:* needs an argument", false) b.SendMessage(update.Message.Chat.ID, "*check:* needs an argument", false)
return return
} }
if tokens[0] == "all" { if tokens[0] == "all" {
if err := b.client.VerifyAll(); err != nil { if err := b.client.VerifyAll(); err != nil {
b.sendMessage(update.Message.Chat.ID, "*check:* error occurred while verifying some torrents", false) b.SendMessage(update.Message.Chat.ID, "*check:* error occurred while verifying some torrents", false)
return return
} }
b.sendMessage(update.Message.Chat.ID, "Verifying all torrents", false) b.SendMessage(update.Message.Chat.ID, "Verifying all torrents", false)
return return
} }
for _, id := range tokens { for _, id := range tokens {
num, err := strconv.Atoi(id) num, err := strconv.Atoi(id)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*check:* %s is not a number", id), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*check:* %s is not a number", id), false)
continue continue
} }
status, err := b.client.VerifyTorrent(num) status, err := b.client.VerifyTorrent(num)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, "*check:* "+err.Error(), false) b.SendMessage(update.Message.Chat.ID, "*check:* "+err.Error(), false)
continue continue
} }
torrent, err := b.client.GetTorrent(num) torrent, err := b.client.GetTorrent(num)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("[fail] *check:* No torrent with an ID of %d", num), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("[fail] *check:* No torrent with an ID of %d", num), false)
continue continue
} }
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("[%s] *check:* %s", status, torrent.Name), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("[%s] *check:* %s", status, torrent.Name), false)
} }
} }
@@ -710,7 +713,7 @@ func (b *Bot) handleStats(update tgbotapi.Update) {
stats.CumulativeActiveTime(), stats.CumulativeActiveTime(),
) )
b.sendMessage(update.Message.Chat.ID, msg, true) b.SendMessage(update.Message.Chat.ID, msg, true)
} }
func (b *Bot) handleDownLimit(update tgbotapi.Update, tokens []string) { func (b *Bot) handleDownLimit(update tgbotapi.Update, tokens []string) {
@@ -723,19 +726,19 @@ func (b *Bot) handleUpLimit(update tgbotapi.Update, tokens []string) {
func (b *Bot) handleSpeedLimit(update tgbotapi.Update, tokens []string, limitType transmission.SpeedLimitType) { func (b *Bot) handleSpeedLimit(update tgbotapi.Update, tokens []string, limitType transmission.SpeedLimitType) {
if len(tokens) < 1 { if len(tokens) < 1 {
b.sendMessage(update.Message.Chat.ID, "Please, specify the limit", false) b.SendMessage(update.Message.Chat.ID, "Please, specify the limit", false)
return return
} }
limit, err := strconv.ParseUint(tokens[0], 10, 32) limit, err := strconv.ParseUint(tokens[0], 10, 32)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, "Please, specify the limit as number of kilobytes", false) b.SendMessage(update.Message.Chat.ID, "Please, specify the limit as number of kilobytes", false)
return return
} }
speedLimitCmd := transmission.NewSpeedLimitCommand(limitType, uint(limit)) speedLimitCmd := transmission.NewSpeedLimitCommand(limitType, uint(limit))
if speedLimitCmd == nil { if speedLimitCmd == nil {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*%s:* internal error", limitType), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*%s:* internal error", limitType), false)
return return
} }
@@ -745,11 +748,11 @@ func (b *Bot) handleSpeedLimit(update tgbotapi.Update, tokens []string, limitTyp
return return
} }
if out.Result != "success" { if out.Result != "success" {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*%s:* %v", limitType, out.Result), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*%s:* %v", limitType, out.Result), false)
return return
} }
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*%s:* limit has been successfully changed to %d KB/s", limitType, limit), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*%s:* limit has been successfully changed to %d KB/s", limitType, limit), false)
} }
func (b *Bot) handleSpeed(update tgbotapi.Update) { func (b *Bot) handleSpeed(update tgbotapi.Update) {
@@ -760,7 +763,7 @@ func (b *Bot) handleSpeed(update tgbotapi.Update) {
} }
msg := fmt.Sprintf("↓ %s ↑ %s", humanize.Bytes(stats.DownloadSpeed), humanize.Bytes(stats.UploadSpeed)) msg := fmt.Sprintf("↓ %s ↑ %s", humanize.Bytes(stats.DownloadSpeed), humanize.Bytes(stats.UploadSpeed))
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 {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(b.cfg.Duration)*b.cfg.Interval+time.Second)
@@ -799,57 +802,57 @@ func (b *Bot) handleCount(update tgbotapi.Update) {
msg := fmt.Sprintf("Downloading: %d\nSeeding: %d\nPaused: %d\nVerifying: %d\n\n- Waiting to -\nDownload: %d\nSeed: %d\nVerify: %d\n\nTotal: %d", msg := fmt.Sprintf("Downloading: %d\nSeeding: %d\nPaused: %d\nVerifying: %d\n\n- Waiting to -\nDownload: %d\nSeed: %d\nVerify: %d\n\nTotal: %d",
downloading, seeding, stopped, checking, downloadingQ, seedingQ, checkingQ, len(torrents)) downloading, seeding, stopped, checking, downloadingQ, seedingQ, checkingQ, len(torrents))
b.sendMessage(update.Message.Chat.ID, msg, false) b.SendMessage(update.Message.Chat.ID, msg, false)
} }
func (b *Bot) handleDel(update tgbotapi.Update, tokens []string) { func (b *Bot) handleDel(update tgbotapi.Update, tokens []string) {
if len(tokens) == 0 { if len(tokens) == 0 {
b.sendMessage(update.Message.Chat.ID, "*del:* needs an ID", false) b.SendMessage(update.Message.Chat.ID, "*del:* needs an ID", false)
return return
} }
for _, id := range tokens { for _, id := range tokens {
num, err := strconv.Atoi(id) num, err := strconv.Atoi(id)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*del:* %s is not an ID", id), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*del:* %s is not an ID", id), false)
continue continue
} }
name, err := b.client.DeleteTorrent(num, false) name, err := b.client.DeleteTorrent(num, false)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, "*del:* "+err.Error(), false) b.SendMessage(update.Message.Chat.ID, "*del:* "+err.Error(), false)
continue continue
} }
b.sendMessage(update.Message.Chat.ID, "*Deleted:* "+name, false) b.SendMessage(update.Message.Chat.ID, "*Deleted:* "+name, false)
} }
} }
func (b *Bot) handleDelData(update tgbotapi.Update, tokens []string) { func (b *Bot) handleDelData(update tgbotapi.Update, tokens []string) {
if len(tokens) == 0 { if len(tokens) == 0 {
b.sendMessage(update.Message.Chat.ID, "*deldata:* needs an ID", false) b.SendMessage(update.Message.Chat.ID, "*deldata:* needs an ID", false)
return return
} }
for _, id := range tokens { for _, id := range tokens {
num, err := strconv.Atoi(id) num, err := strconv.Atoi(id)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("*deldata:* %s is not an ID", id), false) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("*deldata:* %s is not an ID", id), false)
continue continue
} }
name, err := b.client.DeleteTorrent(num, true) name, err := b.client.DeleteTorrent(num, true)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, "*deldata:* "+err.Error(), false) b.SendMessage(update.Message.Chat.ID, "*deldata:* "+err.Error(), false)
continue continue
} }
b.sendMessage(update.Message.Chat.ID, "Deleted with data: "+name, false) b.SendMessage(update.Message.Chat.ID, "Deleted with data: "+name, false)
} }
} }
func (b *Bot) handleVersion(update tgbotapi.Update) { func (b *Bot) handleVersion(update tgbotapi.Update) {
b.sendMessage(update.Message.Chat.ID, fmt.Sprintf("Transmission *%s*\nTransmission-telegram *%s*", b.client.Version(), config.Version), true) b.SendMessage(update.Message.Chat.ID, fmt.Sprintf("Transmission *%s*\nTransmission-telegram *%s*", b.client.Version(), config.Version), true)
} }
func (b *Bot) handleReceiveTorrent(update tgbotapi.Update) { func (b *Bot) handleReceiveTorrent(update tgbotapi.Update) {
@@ -862,7 +865,7 @@ func (b *Bot) handleReceiveTorrent(update tgbotapi.Update) {
} }
file, err := b.api.GetFile(fconfig) file, err := b.api.GetFile(fconfig)
if err != nil { if err != nil {
b.sendMessage(update.Message.Chat.ID, "*receiver:* "+err.Error(), false) b.SendMessage(update.Message.Chat.ID, "*receiver:* "+err.Error(), false)
return return
} }
@@ -893,7 +896,7 @@ func (b *Bot) liveUpdateTorrents(ctx context.Context, chatID int64, msgID int, f
defer putBuffer(buf) defer putBuffer(buf)
for i := range filtered { for i := range filtered {
buf.WriteString(formatter(&filtered[i])) buf.WriteString(formatter(filtered[i]))
} }
// Rate limit before sending // Rate limit before sending
@@ -924,7 +927,7 @@ func (b *Bot) liveUpdateActive(ctx context.Context, chatID int64, msgID int) {
buf := getBuffer() buf := getBuffer()
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]))
} }
} }
@@ -956,7 +959,7 @@ func (b *Bot) liveUpdateActive(ctx context.Context, chatID int64, msgID int) {
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.FormatTorrentActiveStopped(&torrents[i])) buf.WriteString(formatter.FormatTorrentActiveStopped(torrents[i]))
} }
} }

View File

@@ -165,7 +165,7 @@ func (c *CachedClient) DeleteTorrent(id int, deleteData bool) (string, error) {
} }
// ExecuteCommand executes a command and invalidates cache // ExecuteCommand executes a command and invalidates cache
func (c *CachedClient) ExecuteCommand(cmd transmission.Command) (*transmission.CommandResult, error) { func (c *CachedClient) ExecuteCommand(cmd *transmission.Command) (*transmission.Command, error) {
result, err := c.client.ExecuteCommand(cmd) result, err := c.client.ExecuteCommand(cmd)
if err == nil { if err == nil {
c.InvalidateCache() c.InvalidateCache()
@@ -174,7 +174,7 @@ func (c *CachedClient) ExecuteCommand(cmd transmission.Command) (*transmission.C
} }
// ExecuteAddCommand executes an add command and invalidates cache // ExecuteAddCommand executes an add command and invalidates cache
func (c *CachedClient) ExecuteAddCommand(cmd transmission.AddCommand) (*transmission.Torrent, error) { func (c *CachedClient) ExecuteAddCommand(cmd *transmission.Command) (transmission.TorrentAdded, error) {
torrent, err := c.client.ExecuteAddCommand(cmd) torrent, err := c.client.ExecuteAddCommand(cmd)
if err == nil { if err == nil {
c.InvalidateCache() c.InvalidateCache()
@@ -183,7 +183,7 @@ func (c *CachedClient) ExecuteAddCommand(cmd transmission.AddCommand) (*transmis
} }
// SetSort sets sort type and invalidates cache since it changes the order // SetSort sets sort type and invalidates cache since it changes the order
func (c *CachedClient) SetSort(sort transmission.SortType) { func (c *CachedClient) SetSort(sort transmission.Sorting) {
c.client.SetSort(sort) c.client.SetSort(sort)
c.InvalidateCache() c.InvalidateCache()
} }

View File

@@ -16,9 +16,9 @@ type Client interface {
StartTorrent(id int) (string, error) StartTorrent(id int) (string, error)
VerifyTorrent(id int) (string, error) VerifyTorrent(id int) (string, error)
DeleteTorrent(id int, deleteData bool) (string, error) DeleteTorrent(id int, deleteData bool) (string, error)
ExecuteCommand(cmd transmission.Command) (*transmission.CommandResult, error) ExecuteCommand(cmd *transmission.Command) (*transmission.Command, error)
ExecuteAddCommand(cmd transmission.AddCommand) (*transmission.Torrent, error) ExecuteAddCommand(cmd *transmission.Command) (transmission.TorrentAdded, error)
SetSort(sort transmission.SortType) SetSort(sort transmission.Sorting)
Version() string Version() string
} }