Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
229572be10 | |||
9af1dda45d | |||
5d4f13827c | |||
69de0397a3 | |||
4e846885dd | |||
d6ed947bb9 | |||
dc7febd2ad | |||
7c6b9c4a74 | |||
|
aeac9e4e26 | ||
|
c9706bd48a | ||
|
c4a1519d77 | ||
|
8306698f9a |
19
cmd/main.go
Normal file
19
cmd/main.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/mattburchett/go_telegram/pkg/core/config"
|
||||||
|
"github.com/mattburchett/go_telegram/pkg/service/telegram"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
conf, err := config.GetConfig("config.json")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to read JSON.")
|
||||||
|
}
|
||||||
|
|
||||||
|
tgBot := telegram.Bot{}
|
||||||
|
tgBot.Config = conf
|
||||||
|
tgBot.New(conf.Telegram.Token)
|
||||||
|
}
|
17
go.mod
Normal file
17
go.mod
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
module github.com/mattburchett/go_telegram
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
replace gopkg.in/tucnak/telebot.v2 => github.com/tucnak/telebot v0.0.0-20171121031619-29bd3707020c
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/yanzay/tbot v1.0.0
|
||||||
|
github.com/yanzay/tbot/v2 v2.1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
|
||||||
|
github.com/smartystreets/goconvey v1.7.2 // indirect
|
||||||
|
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||||
|
github.com/yanzay/log v0.0.0-20160419144809-87352bb23506 // indirect
|
||||||
|
)
|
23
go.sum
Normal file
23
go.sum
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||||
|
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||||
|
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||||
|
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
|
||||||
|
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||||
|
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||||
|
github.com/yanzay/log v0.0.0-20160419144809-87352bb23506 h1:RPrQRi67MUoUPg/3wwL9K9vKVWAB180e2WJgxC02CJE=
|
||||||
|
github.com/yanzay/log v0.0.0-20160419144809-87352bb23506/go.mod h1:x2hAcqPbZzQz79Fzr9xEe7BM73+UsZ3nBucx2u30oSk=
|
||||||
|
github.com/yanzay/tbot v1.0.0 h1:pKLHIvdiHRgU5iTfpWlgMTCWyD4vIQtAsOy7LCYMsAo=
|
||||||
|
github.com/yanzay/tbot v1.0.0/go.mod h1:rS36e4e2P56jkI0bUuZtzjBBIwMIdBBTUHfI4tgG5BM=
|
||||||
|
github.com/yanzay/tbot/v2 v2.1.0 h1:mppieSOIbzaCjp2en66Fz4unIJ57+aalQfdGbtYmaKg=
|
||||||
|
github.com/yanzay/tbot/v2 v2.1.0/go.mod h1:q0+8JblBq9tLAnKHdBIZsHwDvMS9TfO6mNfaAk1VrHg=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
56
pkg/core/config/config.go
Normal file
56
pkg/core/config/config.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config - This struct will hold configuration components.
|
||||||
|
type Config struct {
|
||||||
|
Telegram struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
ChatID string `json:"chatId"`
|
||||||
|
Admins []int `json:"admins"`
|
||||||
|
AuthorizedChats []string `json:"authorizedChats"`
|
||||||
|
} `json:"telegram"`
|
||||||
|
|
||||||
|
Sonarr struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
APIKey string `json:"apiKey"`
|
||||||
|
SeasonLimit int `json:"seasonLimit"`
|
||||||
|
ProfileID int `json:"profileId"`
|
||||||
|
} `json:"sonarr"`
|
||||||
|
CouchPotato struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
APIKey string `json:"apiKey"`
|
||||||
|
ProfileID string `json:"profileId`
|
||||||
|
} `json:"couchpotato"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetConfig gets the configuration values for the api using the file in the supplied configPath.
|
||||||
|
func GetConfig(configPath string) (Config, error) {
|
||||||
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||||
|
return Config{}, fmt.Errorf("could not find the config file at path %s", configPath)
|
||||||
|
}
|
||||||
|
log.Println("Loading Configuration File: " + configPath)
|
||||||
|
return loadConfigFromFile(configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
//if the config loaded from the file errors, no defaults will be loaded and the app will exit.
|
||||||
|
func loadConfigFromFile(configPath string) (conf Config, err error) {
|
||||||
|
file, err := os.Open(configPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error opening config file: %v", err)
|
||||||
|
} else {
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
err = json.NewDecoder(file).Decode(&conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error decoding config file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf, err
|
||||||
|
}
|
1
pkg/service/couchpotato/admin.go
Normal file
1
pkg/service/couchpotato/admin.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package couchpotato
|
87
pkg/service/couchpotato/couchpotato.go
Normal file
87
pkg/service/couchpotato/couchpotato.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package couchpotato
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattburchett/go_telegram/pkg/core/config"
|
||||||
|
"github.com/yanzay/tbot/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type couchpotatoSearch struct {
|
||||||
|
Movies []struct {
|
||||||
|
Title string `json:"original_title"`
|
||||||
|
Imdb string `json:"imdb"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
InLibrary bool `json:"in_library"`
|
||||||
|
InWanted bool `json:"in_wanted"`
|
||||||
|
} `json:"movies"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
ImdbID string `json:"imdbid"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
Requested bool `json:"requested"`
|
||||||
|
Downloaded bool `json:"downloaded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (search couchpotatoSearch) Convert() []request {
|
||||||
|
requests := []request{}
|
||||||
|
for _, result := range search.Movies {
|
||||||
|
requests = append(requests, request{
|
||||||
|
ImdbID: result.Imdb,
|
||||||
|
Title: result.Title,
|
||||||
|
Year: result.Year,
|
||||||
|
Requested: result.InWanted,
|
||||||
|
Downloaded: result.InLibrary,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return requests
|
||||||
|
}
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
Button string `json:"button"`
|
||||||
|
Callback string `json:"callback"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search performs the lookup actions within CouchPotato
|
||||||
|
func Search(m *tbot.Message, config config.Config) ([]response, error) {
|
||||||
|
searchLookup, err := http.Get(config.CouchPotato.URL + config.CouchPotato.APIKey + "/movie.search?q=" + url.QueryEscape(strings.TrimPrefix(strings.TrimPrefix(m.Text, "/m"), " ")))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
search := couchpotatoSearch{}
|
||||||
|
|
||||||
|
searchData, err := ioutil.ReadAll(searchLookup.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
requestJSON := json.Unmarshal(searchData, &search)
|
||||||
|
if requestJSON != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
requests := search.Convert()
|
||||||
|
|
||||||
|
responseData := []response{}
|
||||||
|
for _, r := range requests {
|
||||||
|
responseData = append(responseData,
|
||||||
|
response{
|
||||||
|
fmt.Sprintf("%v (%v)", r.Title, r.Year),
|
||||||
|
fmt.Sprintf("%v", r.ImdbID),
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseData, err
|
||||||
|
}
|
24
pkg/service/sonarr/admin.go
Normal file
24
pkg/service/sonarr/admin.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package sonarr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mattburchett/go_telegram/pkg/core/config"
|
||||||
|
"github.com/yanzay/tbot/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Status contains the Sonarr request for system status.
|
||||||
|
func Status(m *tbot.Message, config config.Config) (string, error) {
|
||||||
|
r, err := http.Get(config.Sonarr.URL + "system/status?apikey=" + config.Sonarr.APIKey)
|
||||||
|
if err != nil {
|
||||||
|
return "Failed to contact Sonarr for data", err
|
||||||
|
}
|
||||||
|
|
||||||
|
rd, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "Failed to read Sonarr status data.", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(rd), err
|
||||||
|
}
|
245
pkg/service/sonarr/sonarr.go
Normal file
245
pkg/service/sonarr/sonarr.go
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
package sonarr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattburchett/go_telegram/pkg/core/config"
|
||||||
|
"github.com/yanzay/tbot/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Response holds the information needed for Telegram callback.
|
||||||
|
type response struct {
|
||||||
|
Button string `json:"button"`
|
||||||
|
Callback string `json:"callback"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type sonarrSearch []struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
SeasonCount int `json:"seasonCount"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
TvdbID int `json:"tvdbId"`
|
||||||
|
Downloaded bool `json:"downloaded"`
|
||||||
|
QualityProfileID int `json:"qualityProfileId"`
|
||||||
|
TitleSlug string `json:"titleSlug"`
|
||||||
|
Images []struct {
|
||||||
|
CoverType string `json:"coverType"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
} `json:"images"`
|
||||||
|
Seasons []struct {
|
||||||
|
SeasonNumber int `json:"seasonNumber"`
|
||||||
|
Monitored bool `json:"monitored"`
|
||||||
|
} `json:"seasons"`
|
||||||
|
ProfileID int `json:"profileId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type sonarrAdd struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
TvdbID int `json:"tvdbId"`
|
||||||
|
QualityProfileID int `json:"qualityProfileId"`
|
||||||
|
TitleSlug string `json:"titleSlug"`
|
||||||
|
Images []struct {
|
||||||
|
CoverType string `json:"coverType"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
} `json:"images"`
|
||||||
|
Seasons []struct {
|
||||||
|
SeasonNumber int `json:"seasonNumber"`
|
||||||
|
Monitored bool `json:"monitored"`
|
||||||
|
} `json:"seasons"`
|
||||||
|
ProfileID int `json:"profileId"`
|
||||||
|
RootFolderPath string `json:"rootFolderPath"`
|
||||||
|
AddOptions struct {
|
||||||
|
IgnoreEpisodesWithFiles bool `json:"ignoreEpisodesWithFiles"`
|
||||||
|
IgnoreEpisodesWithoutFiles bool `json:"ignoreEpisodesWithoutFiles"`
|
||||||
|
SearchForMIssingEpisodes bool `json:"searchForMissingEpisodes"`
|
||||||
|
} `json:"addOptions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type rootFolderLookup []struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
FreeSpace int64 `json:"freeSpace"`
|
||||||
|
TotalSpace int64 `json:"totalSpace"`
|
||||||
|
UnmappedFolders []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
} `json:"unmappedFolders"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search performs the lookup actions within Sonarr.
|
||||||
|
func Search(m *tbot.Message, config config.Config) ([]response, error) {
|
||||||
|
|
||||||
|
var remote sonarrSearch
|
||||||
|
var local sonarrSearch
|
||||||
|
|
||||||
|
// Perform series lookup
|
||||||
|
remoteLookup, err := http.Get(config.Sonarr.URL +
|
||||||
|
"series/lookup?apikey=" +
|
||||||
|
config.Sonarr.APIKey +
|
||||||
|
"&term=" +
|
||||||
|
url.QueryEscape(strings.TrimPrefix(strings.TrimPrefix(m.Text, "/s"), " ")))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform series local database lookup.
|
||||||
|
localLookup, err := http.Get(config.Sonarr.URL + "series?apikey=" + config.Sonarr.APIKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read remote and local Data
|
||||||
|
remoteData, err := ioutil.ReadAll(remoteLookup.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
localData, err := ioutil.ReadAll(localLookup.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal remote and local JSON
|
||||||
|
remoteJSON := json.Unmarshal(remoteData, &remote)
|
||||||
|
if remoteJSON != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
localJSON := json.Unmarshal(localData, &local)
|
||||||
|
if localJSON != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for downloaded items.
|
||||||
|
for r := range remote {
|
||||||
|
for l := range local {
|
||||||
|
if remote[r].TvdbID == local[l].TvdbID {
|
||||||
|
remote[r].Downloaded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form URLs and return to Telegram
|
||||||
|
responseData := []response{}
|
||||||
|
for k := range remote {
|
||||||
|
responseData = append(responseData,
|
||||||
|
response{
|
||||||
|
fmt.Sprintf("%v%v%v (%v) - %v Seasons",
|
||||||
|
seasonHandler(remote[k].SeasonCount, config.Sonarr.SeasonLimit),
|
||||||
|
downloadedHandler(remote[k].Downloaded),
|
||||||
|
remote[k].Title,
|
||||||
|
remote[k].Year,
|
||||||
|
remote[k].SeasonCount),
|
||||||
|
fmt.Sprintf("%d%s%s", remote[k].TvdbID,
|
||||||
|
strings.TrimSuffix(downloadedHandler(remote[k].Downloaded), " "),
|
||||||
|
strings.TrimSuffix(seasonHandler(remote[k].SeasonCount, config.Sonarr.SeasonLimit), " ")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseData, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add will take the callback data and add the show to Sonarr.
|
||||||
|
func Add(callback string, config config.Config) string {
|
||||||
|
// Separate the TVDB ID
|
||||||
|
tvdbid := strings.TrimPrefix(strings.TrimSuffix(strings.TrimSuffix(callback, "+"), "*"), "tv_")
|
||||||
|
|
||||||
|
// Look it up, to gather the information needed and the title.
|
||||||
|
seriesLookup, err := http.Get(config.Sonarr.URL + "/series/lookup?apikey=" + config.Sonarr.APIKey + "&term=tvdb:" + tvdbid)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
seriesData, err := ioutil.ReadAll(seriesLookup.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
series := sonarrSearch{}
|
||||||
|
seriesJSON := json.Unmarshal(seriesData, &series)
|
||||||
|
if seriesJSON != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the "downloaded" asterisk is already in place, go ahead and return.
|
||||||
|
if strings.Contains(callback, "*") {
|
||||||
|
return fmt.Sprintf("%v has already been requested for download.", series[0].Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather the root folder location.
|
||||||
|
rootFolderLookupQuery, err := http.Get(config.Sonarr.URL + "/rootfolder?apikey=" + config.Sonarr.APIKey)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
rootFolderData, err := ioutil.ReadAll(rootFolderLookupQuery.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
rootFolder := rootFolderLookup{}
|
||||||
|
rootFolderJSON := json.Unmarshal(rootFolderData, &rootFolder)
|
||||||
|
if rootFolderJSON != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form the JSON needed for adding to Sonarr.
|
||||||
|
seriesAdd := sonarrAdd{
|
||||||
|
TvdbID: series[0].TvdbID,
|
||||||
|
Title: series[0].Title,
|
||||||
|
// QualityProfileID: series[0].QualityProfileID,
|
||||||
|
TitleSlug: series[0].TitleSlug,
|
||||||
|
Images: series[0].Images,
|
||||||
|
Seasons: series[0].Seasons,
|
||||||
|
RootFolderPath: rootFolder[0].Path,
|
||||||
|
ProfileID: config.Sonarr.ProfileID,
|
||||||
|
}
|
||||||
|
seriesAdd.AddOptions.IgnoreEpisodesWithFiles = false
|
||||||
|
seriesAdd.AddOptions.IgnoreEpisodesWithoutFiles = false
|
||||||
|
seriesAdd.AddOptions.SearchForMIssingEpisodes = true
|
||||||
|
|
||||||
|
// Post it to Sonarr to be added.
|
||||||
|
seriesAddJSON, err := json.Marshal(seriesAdd)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
seriesAddReq, err := http.Post(config.Sonarr.URL+"/series?apikey="+config.Sonarr.APIKey, "application/json", bytes.NewBuffer(seriesAddJSON))
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if seriesAddReq.StatusCode != 201 {
|
||||||
|
return "There was an error processing this request."
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s has been queued for download.", series[0].Title)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadHandler returns the proper string that should be shown in the Telegram response.
|
||||||
|
func downloadedHandler(downloaded bool) string {
|
||||||
|
if downloaded {
|
||||||
|
return "* "
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// seasonHandler returns the proper string that should be shown in the Telegram response.
|
||||||
|
// it is also used to prevent non-admins from downloading shows with a high number of seasons.
|
||||||
|
func seasonHandler(seasonCount int, seasonLimit int) string {
|
||||||
|
if seasonLimit == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if seasonCount >= seasonLimit {
|
||||||
|
return "+ "
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
36
pkg/service/telegram/admin.go
Normal file
36
pkg/service/telegram/admin.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package telegram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/yanzay/tbot/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tb *Bot) myID(m *tbot.Message) {
|
||||||
|
if tb.adminCheck(m.From.ID, false) {
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, strconv.Itoa(m.From.ID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, "You are not an authorized admin.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *Bot) chatID(m *tbot.Message) {
|
||||||
|
if tb.adminCheck(m.From.ID, false) {
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, m.Chat.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, "You are not an authorized admin.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// adminCheck checks for valid bot admins.
|
||||||
|
func (tb *Bot) adminCheck(id int, callback bool) bool {
|
||||||
|
for _, admin := range tb.Config.Telegram.Admins {
|
||||||
|
if id == admin {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
73
pkg/service/telegram/handler.go
Normal file
73
pkg/service/telegram/handler.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package telegram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yanzay/tbot/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler creates the active Telegram handlers.
|
||||||
|
func (tb *Bot) Handler() {
|
||||||
|
|
||||||
|
// Bot Healthcheck
|
||||||
|
tb.Bot.HandleMessage("/ping", func(m *tbot.Message) {
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, "pong")
|
||||||
|
})
|
||||||
|
|
||||||
|
// telegram/couchpotato.go
|
||||||
|
tb.Bot.HandleMessage("/m", tb.couchpotatoSearch)
|
||||||
|
|
||||||
|
// telegram/sonar.go
|
||||||
|
tb.Bot.HandleMessage("/s", tb.sonarrSearch)
|
||||||
|
tb.Bot.HandleMessage("/admin sonarrStatus", tb.sonarrStatus)
|
||||||
|
|
||||||
|
// telegram/testhandler.go
|
||||||
|
tb.Bot.HandleMessage("/test", tb.testHandler)
|
||||||
|
|
||||||
|
// telegram/admin.go
|
||||||
|
tb.Bot.HandleMessage("/admin myID", tb.myID)
|
||||||
|
tb.Bot.HandleMessage("/admin chatID", tb.chatID)
|
||||||
|
|
||||||
|
// Help
|
||||||
|
tb.Bot.HandleMessage("/help$", tb.helpHandler)
|
||||||
|
tb.Bot.HandleMessage("/h$", tb.helpHandler)
|
||||||
|
|
||||||
|
// Callback Handler
|
||||||
|
tb.Bot.HandleCallback(tb.callbackHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// callbackHandler handles callbacks.
|
||||||
|
func (tb *Bot) callbackHandler(cq *tbot.CallbackQuery) {
|
||||||
|
go func() {
|
||||||
|
tb.Client.AnswerCallbackQuery(cq.ID, tbot.OptText("Request received."))
|
||||||
|
tb.Client.DeleteMessage(tb.CallbackChatID, tb.CallbackMessageID)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if strings.Contains(cq.Data, "tv_") {
|
||||||
|
tb.sonarrAdd(cq)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tb.Client.SendMessage(tb.CallbackChatID, cq.Data)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *Bot) helpHandler(m *tbot.Message) {
|
||||||
|
if !tb.whitelistHandler(m) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, "USAGE:\n\n/movie <Movie Name> or /m <Movie Name>\n/show <TV Show Name> or /s <TV Show Name>\n\nEXAMPLES:\n\n/s The Walking Dead\n/m Avatar")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *Bot) whitelistHandler(m *tbot.Message) bool {
|
||||||
|
for _, id := range tb.Config.Telegram.AuthorizedChats {
|
||||||
|
if id == m.Chat.ID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, "This bot is not authorized for use in this chat.")
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
85
pkg/service/telegram/sonarr.go
Normal file
85
pkg/service/telegram/sonarr.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package telegram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattburchett/go_telegram/pkg/service/sonarr"
|
||||||
|
|
||||||
|
"github.com/yanzay/tbot/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sonarr Search
|
||||||
|
func (tb *Bot) sonarrSearch(m *tbot.Message) {
|
||||||
|
if !tb.whitelistHandler(m) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
text := strings.TrimPrefix(strings.TrimPrefix(m.Text, "/s"), " ")
|
||||||
|
if len(text) == 0 {
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, "You must specify a show. Type /help for help.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := sonarr.Search(m, tb.Config)
|
||||||
|
if err != nil {
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inlineResponse := make([][]tbot.InlineKeyboardButton, 0)
|
||||||
|
for _, i := range request {
|
||||||
|
inlineResponse = append(inlineResponse, []tbot.InlineKeyboardButton{{
|
||||||
|
Text: i.Button,
|
||||||
|
CallbackData: "tv_" + i.Callback,
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(request) == 0 {
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, "No results found, try harder.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, _ := tb.Client.SendMessage(m.Chat.ID, "Please select the show you would like to download.", tbot.OptInlineKeyboardMarkup(&tbot.InlineKeyboardMarkup{InlineKeyboard: inlineResponse}))
|
||||||
|
tb.CallbackMessageID = response.MessageID
|
||||||
|
tb.CallbackChatID = m.Chat.ID
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// sonarrAdd will perform the add requests to Sonarr.
|
||||||
|
func (tb *Bot) sonarrAdd(cq *tbot.CallbackQuery) {
|
||||||
|
if strings.Contains(cq.Data, "+") {
|
||||||
|
if tb.adminCheck(cq.From.ID, true) {
|
||||||
|
tb.Client.SendMessage(tb.CallbackChatID, sonarr.Add(cq.Data, tb.Config))
|
||||||
|
} else {
|
||||||
|
tb.Client.AnswerCallbackQuery(cq.ID, tbot.OptText("This request is over the season limit."))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tb.Client.SendMessage(tb.CallbackChatID, sonarr.Add(cq.Data, tb.Config))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin Functions
|
||||||
|
|
||||||
|
// sonarrStatus queries Sonarr for it's system status information.
|
||||||
|
func (tb *Bot) sonarrStatus(m *tbot.Message) {
|
||||||
|
if !tb.whitelistHandler(m) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tb.adminCheck(m.From.ID, false) {
|
||||||
|
request, err := sonarr.Status(m, tb.Config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, fmt.Sprintf("%v: \n %v", request, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, "Sonarr Status:")
|
||||||
|
tb.Client.SendMessage(m.Chat.ID, request)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
pkg/service/telegram/telegram.go
Normal file
37
pkg/service/telegram/telegram.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package telegram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattburchett/go_telegram/pkg/core/config"
|
||||||
|
"github.com/yanzay/tbot/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bot contains all the necessary bot and callback information.
|
||||||
|
type Bot struct {
|
||||||
|
Client *tbot.Client
|
||||||
|
Config config.Config
|
||||||
|
Bot *tbot.Server
|
||||||
|
CallbackChatID string
|
||||||
|
CallbackMessageID int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat middleware.
|
||||||
|
func stat(h tbot.UpdateHandler) tbot.UpdateHandler {
|
||||||
|
return func(u *tbot.Update) {
|
||||||
|
start := time.Now()
|
||||||
|
h(u)
|
||||||
|
log.Printf("Handle time: %v", time.Now().Sub(start))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates an active telegram bot and loads the handlers.
|
||||||
|
func (tb *Bot) New(token string) {
|
||||||
|
tb.Bot = tbot.New(token)
|
||||||
|
tb.Bot.Use(stat)
|
||||||
|
tb.Client = tb.Bot.Client()
|
||||||
|
tb.Handler()
|
||||||
|
tb.Bot.Start()
|
||||||
|
|
||||||
|
}
|
25
pkg/service/telegram/testhandler.go
Normal file
25
pkg/service/telegram/testhandler.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package telegram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yanzay/tbot/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tb *Bot) testHandler(m *tbot.Message) {
|
||||||
|
buttons := make([]string, 0)
|
||||||
|
buttons = append(buttons, "ping", "is", "stupid")
|
||||||
|
|
||||||
|
inline2 := make([][]tbot.InlineKeyboardButton, 0)
|
||||||
|
|
||||||
|
for _, i := range buttons {
|
||||||
|
inline2 = append(inline2, []tbot.InlineKeyboardButton{{
|
||||||
|
Text: i,
|
||||||
|
CallbackData: i,
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, _ := tb.Client.SendMessage(m.Chat.ID, "Inline test. "+strings.TrimPrefix(m.Text, "/test "), tbot.OptInlineKeyboardMarkup(&tbot.InlineKeyboardMarkup{InlineKeyboard: inline2}))
|
||||||
|
tb.CallbackMessageID = msg.MessageID
|
||||||
|
tb.CallbackChatID = m.Chat.ID
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user