transmission-telegram/main.go

1485 lines
40 KiB
Go
Raw Normal View History

2016-06-19 08:07:00 +03:00
package main
import (
2016-06-20 05:09:41 +03:00
"bytes"
2016-06-19 08:07:00 +03:00
"flag"
"fmt"
2016-07-12 14:10:02 +03:00
"log"
2016-06-19 08:07:00 +03:00
"os"
2016-06-21 18:42:30 +03:00
"regexp"
"strconv"
2016-06-19 08:07:00 +03:00
"strings"
2016-06-25 07:44:34 +03:00
"time"
2016-06-20 05:09:41 +03:00
"unicode/utf8"
2016-06-19 08:07:00 +03:00
2016-06-25 07:44:34 +03:00
"github.com/dustin/go-humanize"
"github.com/pyed/tailer"
2016-06-19 08:07:00 +03:00
"github.com/pyed/transmission"
"gopkg.in/telegram-bot-api.v4"
2016-06-19 08:07:00 +03:00
)
2016-07-20 02:56:08 +03:00
const (
2017-03-01 14:19:32 +03:00
VERSION = "v1.4"
2016-07-20 02:56:08 +03:00
HELP = `
*list* or *li*
Lists all the torrents, takes an optional argument which is a query to list only torrents that has a tracker matches the query, or some of it.
*head* or *he*
Lists the first n number of torrents, n defaults to 5 if no argument is provided.
*tail* or *ta*
Lists the last n number of torrents, n defaults to 5 if no argument is provided.
*down* or *dl*
Lists torrents with the status of Downloading or in the queue to download.
*seeding* or *sd*
Lists torrents with the status of Seeding or in the queue to seed.
*paused* or *pa*
Lists Paused torrents.
*checking* or *ch*
Lists torrents with the status of Verifying or in the queue to verify.
*active* or *ac*
Lists torrents that are actively uploading or downloading.
*errors* or *er*
Lists torrents with with errors along with the error message.
*sort* or *so*
Manipulate the sorting of the aforementioned commands, Call it without arguments for more.
*trackers* or *tr*
Lists all the trackers along with the number of torrents.
*add* or *ad*
Takes one or many URLs or magnets to add them, You can send a .torrent file via Telegram to add it.
*search* or *se*
Takes a query and lists torrents with matching names.
*latest* or *la*
Lists the newest n torrents, n defaults to 5 if no argument is provided.
*info* or *in*
Takes one or more torrent's IDs to list more info about them.
*stop* or *sp*
2016-07-29 20:33:31 +03:00
Takes one or more torrent's IDs to stop them, or _all_ to stop all torrents.
2016-07-20 02:56:08 +03:00
*start* or *st*
2016-07-29 20:33:31 +03:00
Takes one or more torrent's IDs to start them, or _all_ to start all torrents.
2016-07-20 02:56:08 +03:00
*check* or *ck*
2016-07-29 20:33:31 +03:00
Takes one or more torrent's IDs to verify them, or _all_ to verify all torrents.
2016-07-20 02:56:08 +03:00
*del*
Takes one or more torrent's IDs to delete them.
*deldata*
Takes one or more torrent's IDs to delete them and their data.
*stats* or *sa*
Shows Transmission's stats.
*speed* or *ss*
Shows the upload and download speeds.
*count* or *co*
Shows the torrents counts per status.
*help*
Shows this help message.
*version*
Shows version numbers.
- Prefix commands with '/' if you want to talk to your bot in a group.
2016-07-29 20:33:31 +03:00
- report any issues [here](https://github.com/pyed/transmission-telegram)
2016-07-20 02:56:08 +03:00
`
)
2016-06-19 22:33:57 +03:00
2016-06-19 08:07:00 +03:00
var (
2016-07-20 02:56:08 +03:00
2016-06-19 22:02:15 +03:00
// flags
BotToken string
2017-02-24 23:56:42 +03:00
Masters masterSlice
RPCURL string
Username string
Password string
LogFile string
TransLogFile string // Transmission log file
NoLive bool
2016-06-19 08:07:00 +03:00
2016-06-19 22:02:15 +03:00
// transmission
2016-07-01 06:14:38 +03:00
Client *transmission.TransmissionClient
2016-06-19 22:02:15 +03:00
// telegram
Bot *tgbotapi.BotAPI
Updates <-chan tgbotapi.Update
2016-07-04 23:09:24 +03:00
2017-02-24 23:38:39 +03:00
// chatID will be used to keep track of which chat to send completion notifictions.
chatID int64
2017-02-24 23:38:39 +03:00
// logging
logger = log.New(os.Stdout, "", log.LstdFlags)
2016-07-12 14:14:00 +03:00
// interval in seconds for live updates, affects: "active", "info", "speed", "head", "tail"
2017-03-13 18:56:31 +03:00
interval time.Duration = 5
2016-07-05 07:42:00 +03:00
// duration controls how many intervals will happen
2017-03-13 18:56:31 +03:00
duration = 10
// since telegram's markdown can't be escaped, we have to replace some chars
// affects only markdown users: info, active, head, tail
mdReplacer = strings.NewReplacer("*", "•",
"[", "(",
"]", ")",
"_", "-",
"`", "'")
2016-06-19 08:07:00 +03:00
)
2017-02-24 23:56:42 +03:00
// we need a type for masters for the flag package to parse them as a slice
type masterSlice []string
// String is mandatory functions for the flag package
func (masters *masterSlice) String() string {
return fmt.Sprintf("%s", *masters)
2016-10-07 01:02:26 +03:00
}
2017-02-24 23:56:42 +03:00
// Set is mandatory functions for the flag package
func (masters *masterSlice) Set(master string) error {
*masters = append(*masters, strings.ToLower(master))
return nil
2016-10-07 01:02:26 +03:00
}
2017-02-24 23:56:42 +03:00
// Contains takes a string and return true of masterSlice has it
func (masters masterSlice) Contains(master string) bool {
master = strings.ToLower(master)
for i := range masters {
if masters[i] == master {
return true
}
}
return false
}
2016-06-19 22:02:15 +03:00
// init flags
2016-06-19 08:07:00 +03:00
func init() {
// define arguments and parse them.
flag.StringVar(&BotToken, "token", "", "Telegram bot token, Can be passed via environment variable 'TT_BOTT'")
2016-10-07 01:02:26 +03:00
flag.Var(&Masters, "master", "Your telegram handler, So the bot will only respond to you. Can specify more than one")
2016-07-29 20:33:31 +03:00
flag.StringVar(&RPCURL, "url", "http://localhost:9091/transmission/rpc", "Transmission RPC URL")
2016-06-19 22:33:57 +03:00
flag.StringVar(&Username, "username", "", "Transmission username")
flag.StringVar(&Password, "password", "", "Transmission password")
2016-07-12 14:10:02 +03:00
flag.StringVar(&LogFile, "logfile", "", "Send logs to a file")
flag.StringVar(&TransLogFile, "transmission-logfile", "", "Open transmission logfile to monitor torrents completion")
flag.BoolVar(&NoLive, "no-live", false, "Don't edit and update info after sending")
2016-06-19 08:07:00 +03:00
// set the usage message
flag.Usage = func() {
2017-04-09 00:09:06 +03:00
fmt.Fprint(os.Stderr, "Usage: transmission-telegram <-token=TOKEN> <-master=@tuser> [-master=@yuser2] [-url=http://] [-username=user] [-password=pass]\n\n")
2016-06-19 08:07:00 +03:00
flag.PrintDefaults()
}
flag.Parse()
// if we don't have BotToken passed, check the environment variable "TT_BOTT"
if BotToken == "" {
if token := os.Getenv("TT_BOTT"); len(token) > 1 {
BotToken = token
}
}
2016-06-19 08:07:00 +03:00
// make sure that we have the two madatory arguments: telegram token & master's handler.
2016-06-19 22:33:57 +03:00
if BotToken == "" ||
2016-10-07 01:02:26 +03:00
len(Masters) < 1 {
2016-07-05 07:42:00 +03:00
fmt.Fprintf(os.Stderr, "Error: Mandatory argument missing! (-token or -master)\n\n")
2016-06-19 08:07:00 +03:00
flag.Usage()
os.Exit(1)
}
2016-06-20 05:09:41 +03:00
// make sure that the handler doesn't contain @
for i := range Masters {
Masters[i] = strings.Replace(Masters[i], "@", "", -1)
}
2016-07-12 14:10:02 +03:00
// if we got a log file, log to it
if LogFile != "" {
logf, err := os.OpenFile(LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
2017-02-24 23:38:39 +03:00
logger.SetOutput(logf)
2016-06-20 05:09:41 +03:00
}
2016-07-29 20:33:31 +03:00
// if we got a transmission log file, monitor it for torrents completion to notify upon them.
if TransLogFile != "" {
go func() {
ft := tailer.RunFileTailer(TransLogFile, false, nil)
// [2017-02-22 21:00:00.898] File-Name State changed from "Incomplete" to "Complete" (torrent.c:2218)
2017-03-01 14:19:32 +03:00
const (
substring = `"Incomplete" to "Complete"`
start = len(`[2017-02-22 21:00:00.898] `)
end = len(` State changed from "Incomplete" to "Complete" (torrent.c:2218)`)
)
for {
select {
case line := <-ft.Lines():
if strings.Contains(line, substring) {
// if we don't have a chatID continue
if chatID == 0 {
continue
}
msg := fmt.Sprintf("Completed: %s", line[start:len(line)-end])
send(msg, chatID, false)
2017-02-24 23:38:39 +03:00
}
case err := <-ft.Errors():
logger.Printf("[ERROR] tailing transmission log: %s", err)
return
}
}
}()
}
2016-07-29 20:33:31 +03:00
// if the `-username` flag isn't set, look into the environment variable 'TR_AUTH'
if Username == "" {
if values := strings.Split(os.Getenv("TR_AUTH"), ":"); len(values) > 1 {
Username, Password = values[0], values[1]
}
}
// log the flags
2017-02-24 23:56:42 +03:00
logger.Printf("[INFO] Token=%s\n\t\tMasters=%s\n\t\tURL=%s\n\t\tUSER=%s\n\t\tPASS=%s",
2016-10-07 01:02:26 +03:00
BotToken, Masters, RPCURL, Username, Password)
2016-06-19 22:02:15 +03:00
}
2016-06-19 08:07:00 +03:00
2016-06-19 22:02:15 +03:00
// init transmission
func init() {
2016-07-05 07:42:00 +03:00
var err error
2016-07-29 20:33:31 +03:00
Client, err = transmission.New(RPCURL, Username, Password)
2016-07-05 07:42:00 +03:00
if err != nil {
2016-07-20 14:48:10 +03:00
fmt.Fprintf(os.Stderr, "[ERROR] Transmission: Make sure you have the right URL, Username and Password")
2016-07-05 07:42:00 +03:00
os.Exit(1)
}
2016-06-19 22:02:15 +03:00
}
// init telegram
func init() {
// authorize using the token
var err error
2016-06-19 22:33:57 +03:00
Bot, err = tgbotapi.NewBotAPI(BotToken)
2016-06-19 22:02:15 +03:00
if err != nil {
2016-07-20 14:48:10 +03:00
fmt.Fprintf(os.Stderr, "[ERROR] Telegram: %s", err)
2016-06-19 22:02:15 +03:00
os.Exit(1)
}
2017-02-24 23:38:39 +03:00
logger.Printf("[INFO] Authorized: %s", Bot.Self.UserName)
2016-06-19 22:02:15 +03:00
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
Updates, err = Bot.GetUpdatesChan(u)
if err != nil {
2016-07-20 14:48:10 +03:00
fmt.Fprintf(os.Stderr, "[ERROR] Telegram: %s", err)
2016-06-19 08:07:00 +03:00
os.Exit(1)
}
}
2016-06-19 22:33:57 +03:00
func main() {
for update := range Updates {
// ignore edited messages
if update.Message == nil {
continue
}
2017-02-24 23:56:42 +03:00
// ignore non masters
if !Masters.Contains(update.Message.From.UserName) {
2017-02-24 23:38:39 +03:00
logger.Printf("[INFO] Ignored a message from: %s", update.Message.From.String())
2016-07-03 08:12:11 +03:00
continue
}
// update chatID for complete notification
if TransLogFile != "" && chatID != update.Message.Chat.ID {
chatID = update.Message.Chat.ID
}
2016-06-19 22:33:57 +03:00
// tokenize the update
tokens := strings.Split(update.Message.Text, " ")
command := strings.ToLower(tokens[0])
switch command {
2016-07-04 01:52:51 +03:00
case "list", "/list", "li", "/li":
go list(update, tokens[1:])
2016-06-20 05:09:41 +03:00
2016-07-04 01:52:51 +03:00
case "head", "/head", "he", "/he":
go head(update, tokens[1:])
2016-07-01 14:07:56 +03:00
2016-07-04 01:52:51 +03:00
case "tail", "/tail", "ta", "/ta":
go tail(update, tokens[1:])
2016-07-01 14:07:56 +03:00
2016-07-20 02:56:08 +03:00
case "downs", "/downs", "dl", "/dl":
go downs(update)
2016-06-21 18:42:30 +03:00
2016-07-04 01:52:51 +03:00
case "seeding", "/seeding", "sd", "/sd":
go seeding(update)
2016-06-30 02:03:59 +03:00
2016-07-04 01:52:51 +03:00
case "paused", "/paused", "pa", "/pa":
go paused(update)
2016-06-30 02:03:59 +03:00
2016-07-04 01:52:51 +03:00
case "checking", "/checking", "ch", "/ch":
go checking(update)
2016-06-30 02:03:59 +03:00
2016-07-04 01:52:51 +03:00
case "active", "/active", "ac", "/ac":
go active(update)
2016-06-21 18:42:30 +03:00
2016-07-04 01:52:51 +03:00
case "errors", "/errors", "er", "/er":
go errors(update)
2016-06-21 18:42:30 +03:00
2016-07-04 01:52:51 +03:00
case "sort", "/sort", "so", "/so":
go sort(update, tokens[1:])
2016-07-01 06:14:38 +03:00
2016-07-04 01:52:51 +03:00
case "trackers", "/trackers", "tr", "/tr":
go trackers(update)
2016-06-21 18:42:30 +03:00
2016-07-04 01:52:51 +03:00
case "add", "/add", "ad", "/ad":
go add(update, tokens[1:])
2016-07-04 01:52:51 +03:00
case "search", "/search", "se", "/se":
go search(update, tokens[1:])
2016-06-21 18:42:30 +03:00
2016-07-04 01:52:51 +03:00
case "latest", "/latest", "la", "/la":
go latest(update, tokens[1:])
2016-06-21 18:42:30 +03:00
2016-07-04 01:52:51 +03:00
case "info", "/info", "in", "/in":
go info(update, tokens[1:])
2016-06-25 07:44:34 +03:00
2016-07-04 01:52:51 +03:00
case "stop", "/stop", "sp", "/sp":
go stop(update, tokens[1:])
2016-06-21 18:42:30 +03:00
2016-07-04 01:52:51 +03:00
case "start", "/start", "st", "/st":
go start(update, tokens[1:])
2016-06-21 18:42:30 +03:00
2016-07-04 01:52:51 +03:00
case "check", "/check", "ck", "/ck":
go check(update, tokens[1:])
2016-06-21 18:42:30 +03:00
2016-07-04 01:52:51 +03:00
case "stats", "/stats", "sa", "/sa":
go stats(update)
2016-06-25 07:44:34 +03:00
2016-07-04 01:52:51 +03:00
case "speed", "/speed", "ss", "/ss":
2016-07-03 08:12:11 +03:00
go speed(update)
2016-06-25 07:44:34 +03:00
2016-07-04 01:52:51 +03:00
case "count", "/count", "co", "/co":
go count(update)
2016-06-30 02:03:59 +03:00
2016-06-19 22:33:57 +03:00
case "del", "/del":
go del(update, tokens[1:])
2016-06-25 07:44:34 +03:00
2016-06-19 22:33:57 +03:00
case "deldata", "/deldata":
go deldata(update, tokens[1:])
2016-06-25 07:44:34 +03:00
2016-06-19 22:33:57 +03:00
case "help", "/help":
2016-07-20 02:56:08 +03:00
go send(HELP, update.Message.Chat.ID, true)
2016-06-19 22:33:57 +03:00
case "version", "/version":
2017-01-23 23:49:14 +03:00
go getVersion(update)
2016-06-27 20:23:26 +03:00
case "":
// might be a file received
go receiveTorrent(update)
2016-06-19 22:33:57 +03:00
default:
// no such command, try help
go send("no such command, try /help", update.Message.Chat.ID, false)
2016-06-19 22:33:57 +03:00
}
}
}
2016-06-20 05:09:41 +03:00
// list will form and send a list of all the torrents
2016-07-20 14:48:10 +03:00
// takes an optional argument which is a query to match against trackers
// to list only torrents that has a tracker that matchs.
func list(ud tgbotapi.Update, tokens []string) {
2016-06-20 05:09:41 +03:00
torrents, err := Client.GetTorrents()
if err != nil {
send("list: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-20 05:09:41 +03:00
return
}
buf := new(bytes.Buffer)
2016-07-01 18:41:03 +03:00
// if it gets a query, it will list torrents that has trackers that match the query
if len(tokens) != 0 {
// (?i) for case insensitivity
regx, err := regexp.Compile("(?i)" + tokens[0])
if err != nil {
send("list: "+err.Error(), ud.Message.Chat.ID, false)
2016-07-01 18:41:03 +03:00
return
}
for i := range torrents {
if regx.MatchString(torrents[i].GetTrackers()) {
buf.WriteString(fmt.Sprintf("<%d> %s\n", torrents[i].ID, torrents[i].Name))
}
}
} else { // if we did not get a query, list all torrents
for i := range torrents {
buf.WriteString(fmt.Sprintf("<%d> %s\n", torrents[i].ID, torrents[i].Name))
}
2016-06-20 05:09:41 +03:00
}
if buf.Len() == 0 {
2016-07-09 12:31:27 +03:00
// if we got a tracker query show different message
if len(tokens) != 0 {
send(fmt.Sprintf("list: No tracker matches: *%s*", tokens[0]), ud.Message.Chat.ID, true)
return
}
send("list: No torrents", ud.Message.Chat.ID, false)
2016-06-20 05:09:41 +03:00
return
}
send(buf.String(), ud.Message.Chat.ID, false)
2016-06-20 05:09:41 +03:00
}
2016-07-01 14:07:56 +03:00
// head will list the first 5 or n torrents
func head(ud tgbotapi.Update, tokens []string) {
2016-07-01 14:07:56 +03:00
var (
n = 5 // default to 5
err error
)
if len(tokens) > 0 {
n, err = strconv.Atoi(tokens[0])
if err != nil {
send("head: argument must be a number", ud.Message.Chat.ID, false)
2016-07-01 14:07:56 +03:00
return
}
}
torrents, err := Client.GetTorrents()
if err != nil {
send("head: "+err.Error(), ud.Message.Chat.ID, false)
2016-07-01 14:07:56 +03:00
return
}
// make sure that we stay in the boundaries
if n <= 0 || n > len(torrents) {
n = len(torrents)
}
buf := new(bytes.Buffer)
for i := range torrents[:n] {
torrentName := mdReplacer.Replace(torrents[i].Name) // escape markdown
2016-07-09 12:31:27 +03:00
buf.WriteString(fmt.Sprintf("`<%d>` *%s*\n%s *%s* of *%s* (*%.1f%%*) ↓ *%s* ↑ *%s* R: *%s*\n\n",
torrents[i].ID, torrentName, torrents[i].TorrentStatus(), humanize.Bytes(torrents[i].Have()),
humanize.Bytes(torrents[i].SizeWhenDone), torrents[i].PercentDone*100, humanize.Bytes(torrents[i].RateDownload),
humanize.Bytes(torrents[i].RateUpload), torrents[i].Ratio()))
2016-07-01 14:07:56 +03:00
}
if buf.Len() == 0 {
send("head: No torrents", ud.Message.Chat.ID, false)
2016-07-01 14:07:56 +03:00
return
}
2016-07-10 16:28:19 +03:00
msgID := send(buf.String(), ud.Message.Chat.ID, true)
if NoLive {
return
}
2016-07-10 16:28:19 +03:00
// keep the info live
for i := 0; i < duration; i++ {
time.Sleep(time.Second * interval)
buf.Reset()
torrents, err = Client.GetTorrents()
2016-07-10 16:28:19 +03:00
if err != nil {
continue // try again if some error heppened
}
if len(torrents) < 1 {
continue
}
// make sure that we stay in the boundaries
if n <= 0 || n > len(torrents) {
n = len(torrents)
}
for _, torrent := range torrents[:n] {
torrentName := mdReplacer.Replace(torrent.Name) // escape markdown
2016-07-10 16:28:19 +03:00
buf.WriteString(fmt.Sprintf("`<%d>` *%s*\n%s *%s* of *%s* (*%.1f%%*) ↓ *%s* ↑ *%s* R: *%s*\n\n",
torrent.ID, torrentName, torrent.TorrentStatus(), humanize.Bytes(torrent.Have()),
humanize.Bytes(torrent.SizeWhenDone), torrent.PercentDone*100, humanize.Bytes(torrent.RateDownload),
humanize.Bytes(torrent.RateUpload), torrent.Ratio()))
2016-07-10 16:28:19 +03:00
}
// no need to check if it is empty, as if the buffer is empty telegram won't change the message
editConf := tgbotapi.NewEditMessageText(ud.Message.Chat.ID, msgID, buf.String())
editConf.ParseMode = tgbotapi.ModeMarkdown
Bot.Send(editConf)
}
2016-07-01 14:07:56 +03:00
}
// tail lists the last 5 or n torrents
func tail(ud tgbotapi.Update, tokens []string) {
2016-07-01 14:07:56 +03:00
var (
n = 5 // default to 5
err error
)
if len(tokens) > 0 {
n, err = strconv.Atoi(tokens[0])
if err != nil {
send("tail: argument must be a number", ud.Message.Chat.ID, false)
2016-07-01 14:07:56 +03:00
return
}
}
torrents, err := Client.GetTorrents()
if err != nil {
send("tail: "+err.Error(), ud.Message.Chat.ID, false)
2016-07-01 14:07:56 +03:00
return
}
// make sure that we stay in the boundaries
if n <= 0 || n > len(torrents) {
n = len(torrents)
}
buf := new(bytes.Buffer)
for _, torrent := range torrents[len(torrents)-n:] {
torrentName := mdReplacer.Replace(torrent.Name) // escape markdown
2016-07-09 12:31:27 +03:00
buf.WriteString(fmt.Sprintf("`<%d>` *%s*\n%s *%s* of *%s* (*%.1f%%*) ↓ *%s* ↑ *%s* R: *%s*\n\n",
torrent.ID, torrentName, torrent.TorrentStatus(), humanize.Bytes(torrent.Have()),
humanize.Bytes(torrent.SizeWhenDone), torrent.PercentDone*100, humanize.Bytes(torrent.RateDownload),
humanize.Bytes(torrent.RateUpload), torrent.Ratio()))
2016-07-01 14:07:56 +03:00
}
if buf.Len() == 0 {
send("tail: No torrents", ud.Message.Chat.ID, false)
2016-07-01 14:07:56 +03:00
return
}
2016-07-10 16:28:19 +03:00
msgID := send(buf.String(), ud.Message.Chat.ID, true)
if NoLive {
return
}
2016-07-10 16:28:19 +03:00
// keep the info live
for i := 0; i < duration; i++ {
time.Sleep(time.Second * interval)
buf.Reset()
torrents, err = Client.GetTorrents()
2016-07-10 16:28:19 +03:00
if err != nil {
continue // try again if some error heppened
}
if len(torrents) < 1 {
continue
}
// make sure that we stay in the boundaries
if n <= 0 || n > len(torrents) {
n = len(torrents)
}
2016-07-10 16:28:19 +03:00
for _, torrent := range torrents[len(torrents)-n:] {
torrentName := mdReplacer.Replace(torrent.Name) // escape markdown
buf.WriteString(fmt.Sprintf("`<%d>` *%s*\n%s *%s* of *%s* (*%.1f%%*) ↓ *%s* ↑ *%s* R: *%s*\n\n",
torrent.ID, torrentName, torrent.TorrentStatus(), humanize.Bytes(torrent.Have()),
humanize.Bytes(torrent.SizeWhenDone), torrent.PercentDone*100, humanize.Bytes(torrent.RateDownload),
humanize.Bytes(torrent.RateUpload), torrent.Ratio()))
}
// no need to check if it is empty, as if the buffer is empty telegram won't change the message
editConf := tgbotapi.NewEditMessageText(ud.Message.Chat.ID, msgID, buf.String())
editConf.ParseMode = tgbotapi.ModeMarkdown
Bot.Send(editConf)
}
2016-07-01 14:07:56 +03:00
}
2016-06-30 02:03:59 +03:00
// downs will send the names of torrents with status 'Downloading' or in queue to
func downs(ud tgbotapi.Update) {
2016-06-21 18:42:30 +03:00
torrents, err := Client.GetTorrents()
if err != nil {
send("downs: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
buf := new(bytes.Buffer)
for i := range torrents {
// Downloading or in queue to download
2016-06-30 02:03:59 +03:00
if torrents[i].Status == transmission.StatusDownloading ||
torrents[i].Status == transmission.StatusDownloadPending {
2016-07-03 06:38:00 +03:00
buf.WriteString(fmt.Sprintf("<%d> %s\n", torrents[i].ID, torrents[i].Name))
2016-06-21 18:42:30 +03:00
}
}
if buf.Len() == 0 {
send("No downloads", ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
send(buf.String(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
}
2016-06-30 02:03:59 +03:00
// seeding will send the names of the torrents with the status 'Seeding' or in the queue to
func seeding(ud tgbotapi.Update) {
2016-06-30 02:03:59 +03:00
torrents, err := Client.GetTorrents()
if err != nil {
send("seeding: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-30 02:03:59 +03:00
return
}
buf := new(bytes.Buffer)
for i := range torrents {
if torrents[i].Status == transmission.StatusSeeding ||
torrents[i].Status == transmission.StatusSeedPending {
2016-07-03 06:38:00 +03:00
buf.WriteString(fmt.Sprintf("<%d> %s\n", torrents[i].ID, torrents[i].Name))
2016-06-30 02:03:59 +03:00
}
}
if buf.Len() == 0 {
send("No torrents seeding", ud.Message.Chat.ID, false)
2016-06-30 02:03:59 +03:00
return
}
send(buf.String(), ud.Message.Chat.ID, false)
2016-06-30 02:03:59 +03:00
}
// paused will send the names of the torrents with status 'Paused'
func paused(ud tgbotapi.Update) {
2016-06-30 02:03:59 +03:00
torrents, err := Client.GetTorrents()
if err != nil {
send("paused: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-30 02:03:59 +03:00
return
}
buf := new(bytes.Buffer)
for i := range torrents {
if torrents[i].Status == transmission.StatusStopped {
2016-07-04 05:49:36 +03:00
buf.WriteString(fmt.Sprintf("<%d> %s\n%s (%.1f%%) DL: %s UL: %s R: %s\n\n",
2016-06-30 02:03:59 +03:00
torrents[i].ID, torrents[i].Name, torrents[i].TorrentStatus(),
torrents[i].PercentDone*100, humanize.Bytes(torrents[i].DownloadedEver),
humanize.Bytes(torrents[i].UploadedEver), torrents[i].Ratio()))
}
}
if buf.Len() == 0 {
send("No paused torrents", ud.Message.Chat.ID, false)
2016-06-30 02:03:59 +03:00
return
}
send(buf.String(), ud.Message.Chat.ID, false)
2016-06-30 02:03:59 +03:00
}
// checking will send the names of torrents with the status 'verifying' or in the queue to
func checking(ud tgbotapi.Update) {
2016-06-30 02:03:59 +03:00
torrents, err := Client.GetTorrents()
if err != nil {
send("checking: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-30 02:03:59 +03:00
return
}
buf := new(bytes.Buffer)
for i := range torrents {
if torrents[i].Status == transmission.StatusChecking ||
torrents[i].Status == transmission.StatusCheckPending {
buf.WriteString(fmt.Sprintf("<%d> %s\n%s (%.1f%%)\n\n",
torrents[i].ID, torrents[i].Name, torrents[i].TorrentStatus(),
torrents[i].PercentDone*100))
}
}
if buf.Len() == 0 {
send("No torrents verifying", ud.Message.Chat.ID, false)
2016-06-30 02:03:59 +03:00
return
}
send(buf.String(), ud.Message.Chat.ID, false)
2016-06-30 02:03:59 +03:00
}
2016-07-03 06:38:00 +03:00
// active will send torrents that are actively downloading or uploading
func active(ud tgbotapi.Update) {
2016-06-21 18:42:30 +03:00
torrents, err := Client.GetTorrents()
if err != nil {
send("active: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
buf := new(bytes.Buffer)
for i := range torrents {
2016-07-03 06:38:00 +03:00
if torrents[i].RateDownload > 0 ||
torrents[i].RateUpload > 0 {
// escape markdown
torrentName := mdReplacer.Replace(torrents[i].Name)
2016-07-09 12:31:27 +03:00
buf.WriteString(fmt.Sprintf("`<%d>` *%s*\n%s *%s* of *%s* (*%.1f%%*) ↓ *%s* ↑ *%s* R: *%s*\n\n",
torrents[i].ID, torrentName, torrents[i].TorrentStatus(), humanize.Bytes(torrents[i].Have()),
2016-07-04 23:09:24 +03:00
humanize.Bytes(torrents[i].SizeWhenDone), torrents[i].PercentDone*100, humanize.Bytes(torrents[i].RateDownload),
2016-06-30 02:03:59 +03:00
humanize.Bytes(torrents[i].RateUpload), torrents[i].Ratio()))
2016-06-21 18:42:30 +03:00
}
}
if buf.Len() == 0 {
send("No active torrents", ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
2016-07-04 23:09:24 +03:00
msgID := send(buf.String(), ud.Message.Chat.ID, true)
2016-07-04 23:09:24 +03:00
if NoLive {
return
}
2016-07-05 07:42:00 +03:00
// keep the active list live for 'duration * interval'
for i := 0; i < duration; i++ {
2016-07-10 16:28:19 +03:00
time.Sleep(time.Second * interval)
2016-07-04 23:09:24 +03:00
// reset the buffer to reuse it
buf.Reset()
// update torrents
torrents, err = Client.GetTorrents()
if err != nil {
continue // if there was error getting torrents, skip to the next iteration
}
// do the same loop again
for i := range torrents {
if torrents[i].RateDownload > 0 ||
torrents[i].RateUpload > 0 {
2016-07-10 16:28:19 +03:00
torrentName := mdReplacer.Replace(torrents[i].Name) // replace markdown chars
2016-07-09 12:31:27 +03:00
buf.WriteString(fmt.Sprintf("`<%d>` *%s*\n%s *%s* of *%s* (*%.1f%%*) ↓ *%s* ↑ *%s* R: *%s*\n\n",
torrents[i].ID, torrentName, torrents[i].TorrentStatus(), humanize.Bytes(torrents[i].Have()),
2016-07-04 23:09:24 +03:00
humanize.Bytes(torrents[i].SizeWhenDone), torrents[i].PercentDone*100, humanize.Bytes(torrents[i].RateDownload),
humanize.Bytes(torrents[i].RateUpload), torrents[i].Ratio()))
}
}
// no need to check if it is empty, as if the buffer is empty telegram won't change the message
editConf := tgbotapi.NewEditMessageText(ud.Message.Chat.ID, msgID, buf.String())
editConf.ParseMode = tgbotapi.ModeMarkdown
2016-07-04 23:09:24 +03:00
Bot.Send(editConf)
}
// sleep one more time before putting the dashes
time.Sleep(time.Second * interval)
2016-07-04 23:09:24 +03:00
// replace the speed with dashes to indicate that we are done being live
buf.Reset()
for i := range torrents {
if torrents[i].RateDownload > 0 ||
torrents[i].RateUpload > 0 {
// escape markdown
torrentName := mdReplacer.Replace(torrents[i].Name)
2016-07-09 12:31:27 +03:00
buf.WriteString(fmt.Sprintf("`<%d>` *%s*\n%s *%s* of *%s* (*%.1f%%*) ↓ *-* ↑ *-* R: *%s*\n\n",
torrents[i].ID, torrentName, torrents[i].TorrentStatus(), humanize.Bytes(torrents[i].Have()),
2016-07-04 23:09:24 +03:00
humanize.Bytes(torrents[i].SizeWhenDone), torrents[i].PercentDone*100, torrents[i].Ratio()))
}
}
editConf := tgbotapi.NewEditMessageText(ud.Message.Chat.ID, msgID, buf.String())
editConf.ParseMode = tgbotapi.ModeMarkdown
2016-07-04 23:09:24 +03:00
Bot.Send(editConf)
2016-06-21 18:42:30 +03:00
}
// errors will send torrents with errors
func errors(ud tgbotapi.Update) {
2016-06-21 18:42:30 +03:00
torrents, err := Client.GetTorrents()
if err != nil {
send("errors: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
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 {
send("No errors", ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
send(buf.String(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
}
2016-07-01 06:14:38 +03:00
// sort changes torrents sorting
func sort(ud tgbotapi.Update, tokens []string) {
2016-07-01 06:14:38 +03:00
if len(tokens) == 0 {
send(`sort takes one of:
(*id, name, age, size, progress, downspeed, upspeed, download, upload, ratio*)
optionally start with (*rev*) for reversed order
e.g. "*sort rev size*" to get biggest torrents first.`, ud.Message.Chat.ID, true)
2016-07-01 06:14:38 +03:00
return
}
var reversed bool
if strings.ToLower(tokens[0]) == "rev" {
reversed = true
tokens = tokens[1:]
}
switch strings.ToLower(tokens[0]) {
case "id":
if reversed {
Client.SetSort(transmission.SortRevID)
break
}
Client.SetSort(transmission.SortID)
case "name":
if reversed {
Client.SetSort(transmission.SortRevName)
break
}
Client.SetSort(transmission.SortName)
case "age":
if reversed {
Client.SetSort(transmission.SortRevAge)
break
}
Client.SetSort(transmission.SortAge)
case "size":
if reversed {
Client.SetSort(transmission.SortRevSize)
break
}
Client.SetSort(transmission.SortSize)
case "progress":
if reversed {
Client.SetSort(transmission.SortRevProgress)
break
}
Client.SetSort(transmission.SortProgress)
2016-07-02 08:20:40 +03:00
case "downspeed":
if reversed {
Client.SetSort(transmission.SortRevDownSpeed)
break
}
Client.SetSort(transmission.SortDownSpeed)
case "upspeed":
if reversed {
Client.SetSort(transmission.SortRevUpSpeed)
break
}
Client.SetSort(transmission.SortUpSpeed)
2016-07-01 06:14:38 +03:00
case "download":
if reversed {
Client.SetSort(transmission.SortRevDownloaded)
break
}
Client.SetSort(transmission.SortDownloaded)
case "upload":
if reversed {
Client.SetSort(transmission.SortRevUploaded)
break
}
Client.SetSort(transmission.SortUploaded)
case "ratio":
if reversed {
Client.SetSort(transmission.SortRevRatio)
break
}
Client.SetSort(transmission.SortRatio)
default:
send("unkown sorting method", ud.Message.Chat.ID, false)
2016-07-01 06:14:38 +03:00
return
}
if reversed {
send("sort: reversed "+tokens[0], ud.Message.Chat.ID, false)
2016-07-01 06:14:38 +03:00
return
}
send("sort: "+tokens[0], ud.Message.Chat.ID, false)
2016-07-01 06:14:38 +03:00
}
2016-07-01 18:41:03 +03:00
var trackerRegex = regexp.MustCompile(`[https?|udp]://([^:/]*)`)
2016-06-21 18:42:30 +03:00
// trackers will send a list of trackers and how many torrents each one has
func trackers(ud tgbotapi.Update) {
2016-06-21 18:42:30 +03:00
torrents, err := Client.GetTorrents()
if err != nil {
send("trackers: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
trackers := make(map[string]int)
for i := range torrents {
for _, tracker := range torrents[i].Trackers {
sm := trackerRegex.FindSubmatch([]byte(tracker.Announce))
if len(sm) > 1 {
currentTracker := string(sm[1])
n, ok := trackers[currentTracker]
if !ok {
trackers[currentTracker] = 1
continue
}
trackers[currentTracker] = n + 1
}
}
}
buf := new(bytes.Buffer)
for k, v := range trackers {
buf.WriteString(fmt.Sprintf("%d - %s\n", v, k))
}
if buf.Len() == 0 {
send("No trackers!", ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
send(buf.String(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
}
// add takes an URL to a .torrent file to add it to transmission
func add(ud tgbotapi.Update, tokens []string) {
if len(tokens) == 0 {
send("add: needs atleast one URL", ud.Message.Chat.ID, false)
return
}
// loop over the URL/s and add them
for _, url := range tokens {
cmd := transmission.NewAddCmdByURL(url)
torrent, err := Client.ExecuteAddCommand(cmd)
if err != nil {
send("add: "+err.Error(), ud.Message.Chat.ID, false)
continue
}
// check if torrent.Name is empty, then an error happened
if torrent.Name == "" {
send("add: error adding "+url, ud.Message.Chat.ID, false)
continue
}
send(fmt.Sprintf("Added: <%d> %s", torrent.ID, torrent.Name), ud.Message.Chat.ID, false)
}
}
// receiveTorrent gets an update that potentially has a .torrent file to add
func receiveTorrent(ud tgbotapi.Update) {
2016-12-18 08:36:08 +03:00
if ud.Message.Document == nil {
return // has no document
}
// get the file ID and make the config
2016-07-20 14:48:10 +03:00
fconfig := tgbotapi.FileConfig{
FileID: ud.Message.Document.FileID,
}
file, err := Bot.GetFile(fconfig)
if err != nil {
send("receiver: "+err.Error(), ud.Message.Chat.ID, false)
return
}
// add by file URL
add(ud, []string{file.Link(BotToken)})
}
2016-06-21 18:42:30 +03:00
// search takes a query and returns torrents with match
func search(ud tgbotapi.Update, tokens []string) {
2016-06-21 18:42:30 +03:00
// make sure that we got a query
if len(tokens) == 0 {
send("search: needs an argument", ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
query := strings.Join(tokens, " ")
// "(?i)" for case insensitivity
regx, err := regexp.Compile("(?i)" + query)
if err != nil {
send("search: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
torrents, err := Client.GetTorrents()
if err != nil {
send("search: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
buf := new(bytes.Buffer)
for i := range torrents {
if regx.MatchString(torrents[i].Name) {
buf.WriteString(fmt.Sprintf("<%d> %s\n", torrents[i].ID, torrents[i].Name))
}
}
if buf.Len() == 0 {
send("No matches!", ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
send(buf.String(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
}
// latest takes n and returns the latest n torrents
func latest(ud tgbotapi.Update, tokens []string) {
2016-06-21 18:42:30 +03:00
var (
n = 5 // default to 5
err error
)
if len(tokens) > 0 {
n, err = strconv.Atoi(tokens[0])
if err != nil {
send("latest: argument must be a number", ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
}
torrents, err := Client.GetTorrents()
if err != nil {
send("latest: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
// make sure that we stay in the boundaries
2016-07-01 14:07:56 +03:00
if n <= 0 || n > len(torrents) {
n = len(torrents)
2016-06-21 18:42:30 +03:00
}
2016-07-01 06:14:38 +03:00
// sort by age, and set reverse to true to get the latest first
torrents.SortAge(true)
2016-06-21 18:42:30 +03:00
buf := new(bytes.Buffer)
for i := range torrents[:n] {
buf.WriteString(fmt.Sprintf("<%d> %s\n", torrents[i].ID, torrents[i].Name))
}
if buf.Len() == 0 {
send("latest: No torrents", ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
send(buf.String(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
}
2016-06-25 07:44:34 +03:00
// info takes an id of a torrent and returns some info about it
func info(ud tgbotapi.Update, tokens []string) {
2016-06-25 07:44:34 +03:00
if len(tokens) == 0 {
send("info: needs a torrent ID number", ud.Message.Chat.ID, false)
2016-06-25 07:44:34 +03:00
return
}
2016-07-04 05:49:36 +03:00
for _, id := range tokens {
torrentID, err := strconv.Atoi(id)
if err != nil {
send(fmt.Sprintf("info: %s is not a number", id), ud.Message.Chat.ID, false)
2016-07-04 05:49:36 +03:00
continue
}
2016-06-25 07:44:34 +03:00
2016-07-04 05:49:36 +03:00
// get the torrent
torrent, err := Client.GetTorrent(torrentID)
if err != nil {
send(fmt.Sprintf("info: Can't find a torrent with an ID of %d", torrentID), ud.Message.Chat.ID, false)
2016-07-04 05:49:36 +03:00
continue
}
2016-06-25 07:44:34 +03:00
2016-07-04 05:49:36 +03:00
// get the trackers using 'trackerRegex'
var trackers string
for _, tracker := range torrent.Trackers {
sm := trackerRegex.FindSubmatch([]byte(tracker.Announce))
if len(sm) > 1 {
trackers += string(sm[1]) + " "
}
}
// format the info
torrentName := mdReplacer.Replace(torrent.Name) // escape markdown
2016-07-09 12:31:27 +03:00
info := fmt.Sprintf("`<%d>` *%s*\n%s *%s* of *%s* (*%.1f%%*) ↓ *%s* ↑ *%s* R: *%s*\nDL: *%s* UP: *%s*\nAdded: *%s*, ETA: *%s*\nTrackers: `%s`",
torrent.ID, torrentName, torrent.TorrentStatus(), humanize.Bytes(torrent.Have()), humanize.Bytes(torrent.SizeWhenDone),
2016-07-04 05:49:36 +03:00
torrent.PercentDone*100, humanize.Bytes(torrent.RateDownload), humanize.Bytes(torrent.RateUpload), torrent.Ratio(),
humanize.Bytes(torrent.DownloadedEver), humanize.Bytes(torrent.UploadedEver), time.Unix(torrent.AddedDate, 0).Format(time.Stamp),
torrent.ETA(), trackers)
// send it
msgID := send(info, ud.Message.Chat.ID, true)
2016-07-04 05:49:36 +03:00
if NoLive {
return
}
2016-07-05 07:42:00 +03:00
// this go-routine will make the info live for 'duration * interval'
go func(torrentID, msgID int) {
2016-07-05 07:42:00 +03:00
for i := 0; i < duration; i++ {
2016-07-10 16:28:19 +03:00
time.Sleep(time.Second * interval)
torrent, err = Client.GetTorrent(torrentID)
2016-07-04 05:49:36 +03:00
if err != nil {
continue // skip this iteration if there's an error retrieving the torrent's info
}
2016-06-25 07:44:34 +03:00
torrentName := mdReplacer.Replace(torrent.Name)
2016-07-09 12:31:27 +03:00
info := fmt.Sprintf("`<%d>` *%s*\n%s *%s* of *%s* (*%.1f%%*) ↓ *%s* ↑ *%s* R: *%s*\nDL: *%s* UP: *%s*\nAdded: *%s*, ETA: *%s*\nTrackers: `%s`",
torrent.ID, torrentName, torrent.TorrentStatus(), humanize.Bytes(torrent.Have()), humanize.Bytes(torrent.SizeWhenDone),
2016-07-04 05:49:36 +03:00
torrent.PercentDone*100, humanize.Bytes(torrent.RateDownload), humanize.Bytes(torrent.RateUpload), torrent.Ratio(),
humanize.Bytes(torrent.DownloadedEver), humanize.Bytes(torrent.UploadedEver), time.Unix(torrent.AddedDate, 0).Format(time.Stamp),
torrent.ETA(), trackers)
// update the message
editConf := tgbotapi.NewEditMessageText(ud.Message.Chat.ID, msgID, info)
editConf.ParseMode = tgbotapi.ModeMarkdown
2016-07-04 05:49:36 +03:00
Bot.Send(editConf)
}
// sleep one more time before the dashes
time.Sleep(time.Second * interval)
2016-07-04 05:49:36 +03:00
// at the end write dashes to indicate that we are done being live.
torrentName := mdReplacer.Replace(torrent.Name)
2016-07-09 12:31:27 +03:00
info := fmt.Sprintf("`<%d>` *%s*\n%s *%s* of *%s* (*%.1f%%*) ↓ *- B* ↑ *- B* R: *%s*\nDL: *%s* UP: *%s*\nAdded: *%s*, ETA: *-*\nTrackers: `%s`",
torrent.ID, torrentName, torrent.TorrentStatus(), humanize.Bytes(torrent.Have()), humanize.Bytes(torrent.SizeWhenDone),
2016-07-04 05:49:36 +03:00
torrent.PercentDone*100, torrent.Ratio(), humanize.Bytes(torrent.DownloadedEver), humanize.Bytes(torrent.UploadedEver),
time.Unix(torrent.AddedDate, 0).Format(time.Stamp), trackers)
editConf := tgbotapi.NewEditMessageText(ud.Message.Chat.ID, msgID, info)
editConf.ParseMode = tgbotapi.ModeMarkdown
2016-07-04 05:49:36 +03:00
Bot.Send(editConf)
}(torrentID, msgID)
2016-07-04 05:49:36 +03:00
}
2016-06-25 07:44:34 +03:00
}
2016-06-27 18:40:09 +03:00
// stop takes id[s] of torrent[s] or 'all' to stop them
func stop(ud tgbotapi.Update, tokens []string) {
2016-06-21 18:42:30 +03:00
// make sure that we got at least one argument
if len(tokens) == 0 {
send("stop: needs an argument", ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
2016-06-27 18:40:09 +03:00
// if the first argument is 'all' then stop all torrents
if tokens[0] == "all" {
if err := Client.StopAll(); err != nil {
send("stop: error occurred while stopping some torrents", ud.Message.Chat.ID, false)
2016-06-27 18:40:09 +03:00
return
}
send("stopped all torrents", ud.Message.Chat.ID, false)
2016-06-27 18:40:09 +03:00
return
}
2016-06-21 18:42:30 +03:00
for _, id := range tokens {
num, err := strconv.Atoi(id)
if err != nil {
send(fmt.Sprintf("stop: %s is not a number", id), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
continue
}
status, err := Client.StopTorrent(num)
if err != nil {
send("stop: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
continue
}
torrent, err := Client.GetTorrent(num)
if err != nil {
send(fmt.Sprintf("[fail] stop: No torrent with an ID of %d", num), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
send(fmt.Sprintf("[%s] stop: %s", status, torrent.Name), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
}
}
2016-06-27 18:40:09 +03:00
// start takes id[s] of torrent[s] or 'all' to start them
func start(ud tgbotapi.Update, tokens []string) {
2016-06-21 18:42:30 +03:00
// make sure that we got at least one argument
if len(tokens) == 0 {
send("start: needs an argument", ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
2016-06-27 18:40:09 +03:00
// if the first argument is 'all' then start all torrents
if tokens[0] == "all" {
if err := Client.StartAll(); err != nil {
send("start: error occurred while starting some torrents", ud.Message.Chat.ID, false)
2016-06-27 18:40:09 +03:00
return
}
send("started all torrents", ud.Message.Chat.ID, false)
2016-06-27 18:40:09 +03:00
return
}
2016-06-21 18:42:30 +03:00
for _, id := range tokens {
num, err := strconv.Atoi(id)
if err != nil {
send(fmt.Sprintf("start: %s is not a number", id), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
continue
}
status, err := Client.StartTorrent(num)
if err != nil {
send("stop: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
continue
}
torrent, err := Client.GetTorrent(num)
if err != nil {
send(fmt.Sprintf("[fail] start: No torrent with an ID of %d", num), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
send(fmt.Sprintf("[%s] start: %s", status, torrent.Name), ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
}
}
2016-06-27 18:40:09 +03:00
// check takes id[s] of torrent[s] or 'all' to verify them
func check(ud tgbotapi.Update, tokens []string) {
2016-06-27 18:40:09 +03:00
// make sure that we got at least one argument
if len(tokens) == 0 {
send("check: needs an argument", ud.Message.Chat.ID, false)
2016-06-21 18:42:30 +03:00
return
}
2016-06-27 18:40:09 +03:00
// if the first argument is 'all' then start all torrents
if tokens[0] == "all" {
if err := Client.VerifyAll(); err != nil {
send("check: error occurred while verifying some torrents", ud.Message.Chat.ID, false)
2016-06-27 18:40:09 +03:00
return
2016-06-21 18:42:30 +03:00
}
send("verifying all torrents", ud.Message.Chat.ID, false)
2016-06-27 18:40:09 +03:00
return
2016-06-21 18:42:30 +03:00
}
2016-06-27 18:40:09 +03:00
for _, id := range tokens {
num, err := strconv.Atoi(id)
if err != nil {
send(fmt.Sprintf("check: %s is not a number", id), ud.Message.Chat.ID, false)
2016-06-27 18:40:09 +03:00
continue
}
status, err := Client.VerifyTorrent(num)
if err != nil {
send("stop: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-27 18:40:09 +03:00
continue
}
torrent, err := Client.GetTorrent(num)
if err != nil {
send(fmt.Sprintf("[fail] check: No torrent with an ID of %d", num), ud.Message.Chat.ID, false)
2016-06-27 18:40:09 +03:00
return
}
send(fmt.Sprintf("[%s] check: %s", status, torrent.Name), ud.Message.Chat.ID, false)
2016-06-27 18:40:09 +03:00
}
2016-06-21 18:42:30 +03:00
}
2016-06-25 07:44:34 +03:00
// stats echo back transmission stats
func stats(ud tgbotapi.Update) {
2016-06-25 07:44:34 +03:00
stats, err := Client.GetStats()
if err != nil {
send("stats: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-25 07:44:34 +03:00
return
}
msg := fmt.Sprintf(
`
Total: *%d*
Active: *%d*
Paused: *%d*
_Current Stats_
Downloaded: *%s*
Uploaded: *%s*
2016-07-10 16:55:48 +03:00
Running time: *%s*
_Accumulative Stats_
Sessions: *%d*
Downloaded: *%s*
Uploaded: *%s*
2016-07-10 16:55:48 +03:00
Total Running time: *%s*
2016-06-25 07:44:34 +03:00
`,
stats.TorrentCount,
stats.ActiveTorrentCount,
stats.PausedTorrentCount,
humanize.Bytes(stats.CurrentStats.DownloadedBytes),
humanize.Bytes(stats.CurrentStats.UploadedBytes),
2016-07-10 16:55:48 +03:00
stats.CurrentActiveTime(),
2016-06-25 07:44:34 +03:00
stats.CumulativeStats.SessionCount,
humanize.Bytes(stats.CumulativeStats.DownloadedBytes),
humanize.Bytes(stats.CumulativeStats.UploadedBytes),
2016-07-10 16:55:48 +03:00
stats.CumulativeActiveTime(),
2016-06-25 07:44:34 +03:00
)
send(msg, ud.Message.Chat.ID, true)
2016-06-25 07:44:34 +03:00
}
// speed will echo back the current download and upload speeds
2016-07-03 08:12:11 +03:00
func speed(ud tgbotapi.Update) {
stats, err := Client.GetStats()
if err != nil {
send("speed: "+err.Error(), ud.Message.Chat.ID, false)
return
}
2016-07-03 08:12:11 +03:00
msg := fmt.Sprintf("↓ %s ↑ %s", humanize.Bytes(stats.DownloadSpeed), humanize.Bytes(stats.UploadSpeed))
2016-07-03 08:12:11 +03:00
msgID := send(msg, ud.Message.Chat.ID, false)
if NoLive {
return
}
for i := 0; i < duration; i++ {
time.Sleep(time.Second * interval)
stats, err = Client.GetStats()
if err != nil {
2016-07-03 08:12:11 +03:00
continue
}
msg = fmt.Sprintf("↓ %s ↑ %s", humanize.Bytes(stats.DownloadSpeed), humanize.Bytes(stats.UploadSpeed))
2016-07-03 08:12:11 +03:00
editConf := tgbotapi.NewEditMessageText(ud.Message.Chat.ID, msgID, msg)
Bot.Send(editConf)
2016-07-04 23:09:24 +03:00
time.Sleep(time.Second * interval)
2016-06-25 07:44:34 +03:00
}
// sleep one more time before switching to dashes
time.Sleep(time.Second * interval)
2016-06-25 07:44:34 +03:00
// show dashes to indicate that we are done updating.
2016-07-03 08:12:11 +03:00
editConf := tgbotapi.NewEditMessageText(ud.Message.Chat.ID, msgID, "↓ - B ↑ - B")
Bot.Send(editConf)
2016-06-25 07:44:34 +03:00
}
2016-06-30 02:03:59 +03:00
// count returns current torrents count per status
func count(ud tgbotapi.Update) {
2016-06-30 02:03:59 +03:00
torrents, err := Client.GetTorrents()
if err != nil {
send("count: "+err.Error(), ud.Message.Chat.ID, false)
2016-06-30 02:03:59 +03:00
return
}
var downloading, seeding, stopped, checking, downloadingQ, seedingQ, checkingQ int
for i := range torrents {
switch torrents[i].Status {
case transmission.StatusDownloading:
downloading++
case transmission.StatusSeeding:
seeding++
case transmission.StatusStopped:
stopped++
case transmission.StatusChecking:
checking++
case transmission.StatusDownloadPending:
downloadingQ++
case transmission.StatusSeedPending:
seedingQ++
case transmission.StatusCheckPending:
checkingQ++
}
}
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))
send(msg, ud.Message.Chat.ID, false)
2016-06-30 02:03:59 +03:00
}
2016-06-25 07:44:34 +03:00
// del takes an id or more, and delete the corresponding torrent/s
func del(ud tgbotapi.Update, tokens []string) {
2016-06-25 07:44:34 +03:00
// make sure that we got an argument
if len(tokens) == 0 {
send("del: needs an ID", ud.Message.Chat.ID, false)
2016-06-25 07:44:34 +03:00
return
}
// loop over tokens to read each potential id
for _, id := range tokens {
num, err := strconv.Atoi(id)
if err != nil {
send(fmt.Sprintf("del: %s is not an ID", id), ud.Message.Chat.ID, false)
return
}
2016-06-25 07:44:34 +03:00
name, err := Client.DeleteTorrent(num, false)
if err != nil {
send("del: "+err.Error(), ud.Message.Chat.ID, false)
return
}
send("Deleted: "+name, ud.Message.Chat.ID, false)
}
2016-06-25 07:44:34 +03:00
}
// deldata takes an id or more, and delete the corresponding torrent/s with their data
func deldata(ud tgbotapi.Update, tokens []string) {
2016-06-25 07:44:34 +03:00
// make sure that we got an argument
if len(tokens) == 0 {
send("deldata: needs an ID", ud.Message.Chat.ID, false)
2016-06-25 07:44:34 +03:00
return
}
// loop over tokens to read each potential id
for _, id := range tokens {
num, err := strconv.Atoi(id)
if err != nil {
send(fmt.Sprintf("deldata: %s is not an ID", id), ud.Message.Chat.ID, false)
return
}
2016-06-25 07:44:34 +03:00
name, err := Client.DeleteTorrent(num, true)
if err != nil {
send("deldata: "+err.Error(), ud.Message.Chat.ID, false)
return
}
2016-06-25 07:44:34 +03:00
send("Deleted with data: "+name, ud.Message.Chat.ID, false)
}
2016-06-25 07:44:34 +03:00
}
2017-01-23 23:49:14 +03:00
// getVersion sends transmission version + transmission-telegram version
func getVersion(ud tgbotapi.Update) {
2016-07-08 08:33:03 +03:00
send(fmt.Sprintf("Transmission *%s*\nTransmission-telegram *%s*", Client.Version(), VERSION), ud.Message.Chat.ID, true)
2016-06-27 20:23:26 +03:00
}
2016-07-03 08:12:11 +03:00
// send takes a chat id and a message to send, returns the message id of the send message
func send(text string, chatID int64, markdown bool) int {
2016-06-20 05:09:41 +03:00
// set typing action
action := tgbotapi.NewChatAction(chatID, tgbotapi.ChatTyping)
Bot.Send(action)
// check the rune count, telegram is limited to 4096 chars per message;
// so if our message is > 4096, split it in chunks the send them.
msgRuneCount := utf8.RuneCountInString(text)
LenCheck:
2016-07-08 08:33:03 +03:00
stop := 4095
2016-06-20 05:09:41 +03:00
if msgRuneCount > 4096 {
2016-07-08 08:33:03 +03:00
for text[stop] != 10 { // '\n'
stop--
}
msg := tgbotapi.NewMessage(chatID, text[:stop])
2016-06-21 18:42:30 +03:00
msg.DisableWebPagePreview = true
if markdown {
msg.ParseMode = tgbotapi.ModeMarkdown
}
2016-06-20 05:09:41 +03:00
// send current chunk
if _, err := Bot.Send(msg); err != nil {
2017-02-24 23:38:39 +03:00
logger.Printf("[ERROR] Send: %s", err)
2016-06-20 05:09:41 +03:00
}
// move to the next chunk
2016-07-08 08:33:03 +03:00
text = text[stop:]
2016-06-20 05:09:41 +03:00
msgRuneCount = utf8.RuneCountInString(text)
goto LenCheck
}
// if msgRuneCount < 4096, send it normally
msg := tgbotapi.NewMessage(chatID, text)
2016-06-21 18:42:30 +03:00
msg.DisableWebPagePreview = true
if markdown {
msg.ParseMode = tgbotapi.ModeMarkdown
}
2016-07-03 08:12:11 +03:00
resp, err := Bot.Send(msg)
if err != nil {
2017-02-24 23:38:39 +03:00
logger.Printf("[ERROR] Send: %s", err)
2016-06-20 05:09:41 +03:00
}
2016-07-03 08:12:11 +03:00
return resp.MessageID
2016-06-20 05:09:41 +03:00
}