diff --git a/.gitignore b/.gitignore index 0cffcb3..d7b65e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -config.json \ No newline at end of file +config.json +housekeeper \ No newline at end of file diff --git a/README.md b/README.md index e0ff8fe..021f2c1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,21 @@ # Housekeeper +Housekeeper is a cleanup tool for Plex environements. + +It requires two things - Plex and PlexPy (now known as Tautulli). It has a option for communicating with Telegram for cleanup purposes. + +Please use the example configuration file for configuration. + +The command line args that can be passed: + +```Usage of ./housekeeper: + -c string + Configuration to load + -check + Perform only a check. Do not delete. (default true) + -days int + days to poll + -delete + Perform the delete task. + -sectionid int + pick a section ID``` \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index c05568a..a492f70 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,7 +4,9 @@ import ( "flag" "log" + "git.linuxrocker.com/mattburchett/Housekeeper/pkg/communicator" "git.linuxrocker.com/mattburchett/Housekeeper/pkg/config" + "git.linuxrocker.com/mattburchett/Housekeeper/pkg/eraser" "git.linuxrocker.com/mattburchett/Housekeeper/pkg/locator" ) @@ -39,10 +41,14 @@ func main() { var c string var days int var sectionID int + var check bool + var delete bool flag.StringVar(&c, "c", "", "Configuration to load") flag.IntVar(&days, "days", 0, "days to poll") flag.IntVar(§ionID, "sectionid", 0, "pick a section ID") + flag.BoolVar(&check, "check", true, "Perform only a check. Do not delete.") + flag.BoolVar(&delete, "delete", false, "Perform the delete task.") flag.Parse() if c == "" { log.Fatal("You need to specify a configuration file.") @@ -55,5 +61,22 @@ func main() { if err != nil { log.Fatal(err) } - locator.GetTitles(cfg, sectionID, days) + + ids, titles := locator.GetTitles(cfg, sectionID, days) + + if check { + err = communicator.TelegramPost(cfg, titles) + if err != nil { + log.Fatal(err) + } + } + + if delete { + files := eraser.LookupFileLocation(cfg, ids) + err = eraser.DeleteMedia(delete, files) + if err != nil { + log.Println(err) + } + } + } diff --git a/config_example.json b/config_example.json new file mode 100644 index 0000000..a22a40c --- /dev/null +++ b/config_example.json @@ -0,0 +1,11 @@ +{ + "baseURL": "http://dvr.example.com", + "plexPyContext": "/plexpy", + "plexPyAPIKey": "abc1234", + "plexToken": "ABC1234ABC1234", + "plexHost": "http://192.168.1.1", + "plexPort": 32400, + "telegramToken": "123456789:ABCDEFG", + "telegramChatID": "12345678", + "serverName": "Plex" +} \ No newline at end of file diff --git a/pkg/communicator/communicator.go b/pkg/communicator/communicator.go index 211f475..7a9405c 100644 --- a/pkg/communicator/communicator.go +++ b/pkg/communicator/communicator.go @@ -1 +1,38 @@ package communicator + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "git.linuxrocker.com/mattburchett/Housekeeper/pkg/config" +) + +// TelegramPost will send a message to a specific ChatID in Telegram containing the list of items to be cleaned with this cleaner. +func TelegramPost(config config.Config, titles []string) error { + url := "https://api.telegram.org/bot" + config.TelegramToken + "/sendMessage" + + values := map[string]string{"chat_id": config.TelegramChatID, "text": "The following items are to be removed from " + config.ServerName + " in 24 hours. Please go to Plex and start the title to keep it on" + config.ServerName + ". You do not need to keep watching, just hit play and load a few seconds.\n\n" + fmt.Sprintf("%v", strings.Join(titles, "\n")), "disable_notifications": "true"} + + jsonValue, _ := json.Marshal(values) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + req.Header.Set("X-Custom-Header", "Housekeeper") + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + fmt.Println("response Status:", resp.Status) + fmt.Println("response Headers:", resp.Header) + body, _ := ioutil.ReadAll(resp.Body) + fmt.Println("response Body:", string(body)) + + return err +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 9cc6711..938c246 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -9,13 +9,15 @@ import ( // Config - This struct will hold configuration components. type Config struct { - BaseURL string `json:"baseURL"` - PlexPyContext string `json:"plexPyContext"` - PlexPyAPIKey string `json:"plexPyAPIKey"` - PlexToken string `json:"plexToken"` - PlexHost string `json:"plexHost"` - PlexPort int `json:"plexPort"` - TelegramToken string `json:"telegramToken"` + BaseURL string `json:"baseURL"` + PlexPyContext string `json:"plexPyContext"` + PlexPyAPIKey string `json:"plexPyAPIKey"` + PlexToken string `json:"plexToken"` + PlexHost string `json:"plexHost"` + PlexPort int `json:"plexPort"` + TelegramToken string `json:"telegramToken"` + TelegramChatID string `json:"telegramChatID"` + ServerName string `json:"serverName"` } //GetConfig gets the configuration values for the api using the file in the supplied configPath. diff --git a/pkg/eraser/eraser.go b/pkg/eraser/eraser.go index b58c300..c9a8f15 100644 --- a/pkg/eraser/eraser.go +++ b/pkg/eraser/eraser.go @@ -1 +1,64 @@ package eraser + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "path/filepath" + + "git.linuxrocker.com/mattburchett/Housekeeper/pkg/config" + "git.linuxrocker.com/mattburchett/Housekeeper/pkg/model" +) + +// LookupFileLocation will gather a list of Information based on IDs returned by locator.GetTitles +func LookupFileLocation(config config.Config, ids []int) []string { + fileList := make([]string, 0) + + for _, i := range ids { + plexURL := fmt.Sprintf("%s:%d%s%d%s%s", config.PlexHost, config.PlexPort, "/library/metadata/", i, "/?X-Plex-Token=", config.PlexToken) + + req, err := http.NewRequest(http.MethodGet, plexURL, nil) + if err != nil { + log.Fatal(err) + } + + httpClient := http.Client{} + req.Header.Set("User-Agent", "Housekeeper") + + res, getErr := httpClient.Do(req) + if getErr != nil { + log.Fatal(getErr) + } + + body, readErr := ioutil.ReadAll(res.Body) + if readErr != nil { + log.Fatal(readErr) + } + + if err != nil { + log.Fatal(err) + } + plexModel := model.XMLPlexAPI{} + xml.Unmarshal(body, &plexModel) + fileList = append(fileList, filepath.Dir(plexModel.Video.Media.Part.File)) + } + return fileList +} + +// DeleteMedia will actually perform the deletion. +func DeleteMedia(delete bool, files []string) error { + var err error + if delete { + for _, i := range files { + fmt.Printf("Removing %v\n", i) + err = os.RemoveAll(i) + if err != nil { + log.Fatal(err) + } + } + } + return err +} diff --git a/pkg/locator/locator.go b/pkg/locator/locator.go index b81ea6c..5c87d98 100644 --- a/pkg/locator/locator.go +++ b/pkg/locator/locator.go @@ -2,20 +2,19 @@ package locator import ( "encoding/json" - "encoding/xml" "fmt" "io/ioutil" "log" "net/http" "sort" "strconv" - "strings" "git.linuxrocker.com/mattburchett/Housekeeper/pkg/config" "git.linuxrocker.com/mattburchett/Housekeeper/pkg/model" "git.linuxrocker.com/mattburchett/Housekeeper/pkg/util" ) +// GetCount will gather a count of media in a specific library, required for GetTitles. func GetCount(config config.Config, sectionID int) int { countURL := fmt.Sprintf("%s%s%s%s%s%d", config.BaseURL, config.PlexPyContext, "/api/v2?apikey=", config.PlexPyAPIKey, "&cmd=get_library§ion_id=", sectionID) req, err := http.NewRequest(http.MethodGet, countURL, nil) @@ -48,7 +47,7 @@ func GetCount(config config.Config, sectionID int) int { } // GetTitles will gather a list of information from the media in the library, based on the previous count. -func GetTitles(config config.Config, sectionID int, days int) { +func GetTitles(config config.Config, sectionID int, days int) ([]int, []string) { count := GetCount(config, sectionID) titlesURL := fmt.Sprintf("%s%s%s%s%s%d%s%d", config.BaseURL, config.PlexPyContext, "/api/v2?apikey=", config.PlexPyAPIKey, "&cmd=get_library_media_info§ion_id=", sectionID, "&order_column=last_played&refresh=true&order_dir=asc&length=", count) @@ -108,47 +107,5 @@ func GetTitles(config config.Config, sectionID int, days int) { } } sort.Strings(titles) - - LookupFileLocation(config, ids) - - return -} - -func LookupFileLocation(config config.Config, ids []int) { - // fileLocations := make([]string, 0) - - for _, i := range ids { - plexURL := fmt.Sprintf("%s:%d%s%d%s%s", config.PlexHost, config.PlexPort, "/library/metadata/", i, "/?X-Plex-Token=", config.PlexToken) - - req, err := http.NewRequest(http.MethodGet, plexURL, nil) - if err != nil { - log.Fatal(err) - } - - httpClient := http.Client{} - req.Header.Set("User-Agent", "Housekeeper") - - res, getErr := httpClient.Do(req) - if getErr != nil { - log.Fatal(getErr) - } - - body, readErr := ioutil.ReadAll(res.Body) - if readErr != nil { - log.Fatal(readErr) - } - - if err != nil { - log.Fatal(err) - } - plexModel := model.XMLPlexAPI{} - xml.Unmarshal(body, &plexModel) - - fileList := strings.Split(plexModel.Video.Media.Part.File, "/") - - fmt.Printf("/%v/%v/%v/%v/%v\n", fileList[1], fileList[2], fileList[3], fileList[4], fileList[5]) - } - - // http://172.19.0.105:32400/library/metadata/9/?X-Plex-Token=K1WCALqRK5HVzSQ1J3bM - + return ids, titles } diff --git a/pkg/model/locator_model.go b/pkg/model/locator_model.go index 807a355..adbe1f0 100644 --- a/pkg/model/locator_model.go +++ b/pkg/model/locator_model.go @@ -79,7 +79,7 @@ type XMLPlexAPI struct { Text string `xml:",chardata"` RatingKey string `xml:"ratingKey,attr"` Key string `xml:"key,attr"` - Guid string `xml:"guid,attr"` + GUID string `xml:"guid,attr"` LibrarySectionTitle string `xml:"librarySectionTitle,attr"` LibrarySectionID string `xml:"librarySectionID,attr"` LibrarySectionKey string `xml:"librarySectionKey,attr"` diff --git a/pkg/rotator/rotator.go b/pkg/rotator/rotator.go deleted file mode 100644 index 249e252..0000000 --- a/pkg/rotator/rotator.go +++ /dev/null @@ -1 +0,0 @@ -package rotator diff --git a/pkg/util/epoch.go b/pkg/util/epoch.go index db2fe98..b11de82 100644 --- a/pkg/util/epoch.go +++ b/pkg/util/epoch.go @@ -2,6 +2,7 @@ package util import "time" +// SubtractedEpoch will take the current time in epoch and subtract (days) worth of seconds from the current epoch time. func SubtractedEpoch(days int) int64 { now := time.Now() unix := now.Unix()