diff --git a/Dockerfile b/Dockerfile index 40664a6..d2c989f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,9 +17,16 @@ COPY plex/*.go plex/ COPY spotify/*.go spotify/ COPY types/*.go types/ COPY utils/*.go utils/ -COPY web/*.go web/ -COPY web/*.html web/ -COPY web/static/* web/static/ + +COPY web/movies/*.go web/movies/ +COPY web/movies/*.html web/movies/ +COPY web/music/*.go web/music/ +COPY web/music/*.html web/music/ +COPY web/tv/*.go web/tv/ +COPY web/tv/*.html web/tv/ +COPY web/settings/*.go web/settings/ +COPY web/settings/*.html web/settings/ +COPY web/server.go web/index.html web/static/ /web/ # Build RUN CGO_ENABLED=0 GOOS=linux go build -o /plex-lookup diff --git a/TODO b/TODO index 3636cde..09cc378 100644 --- a/TODO +++ b/TODO @@ -8,7 +8,6 @@ - allow amazon tv search for newer series - update movies to use tv like search -- write a function to calculate plex dates - allow amazon tv search for indivdual series ## done @@ -28,3 +27,4 @@ - bastille is showing "give me the future" as an album. wrong !! - remove dead fields from the tv data types - remove links to tv series we already have in plex. eg dont show adventure time series 1 and 2 ? +- write a function to calculate plex dates diff --git a/plex/plex.go b/plex/plex.go index dbff20b..54a46bd 100644 --- a/plex/plex.go +++ b/plex/plex.go @@ -518,6 +518,24 @@ func GetPlexMovies(ipAddress, libraryID, plexToken, resolution string, filters [ return movieList } +func extractMovies(xmlString string) (movieList []types.PlexMovie) { + var container MovieContainer + err := xml.Unmarshal([]byte(xmlString), &container) + if err != nil { + fmt.Println("Error parsing XML:", err) + return + } + + for i := range container.Video { + movieList = append(movieList, types.PlexMovie{ + Title: container.Video[i].Title, + Year: container.Video[i].Year, + DateAdded: parsePlexDate(container.Video[i].AddedAt)}) + } + return movieList +} + +// ================================================================================================= func GetPlexTV(ipAddress, libraryID, plexToken string) (tvShowList []types.PlexTVShow) { url := fmt.Sprintf("http://%s:32400/library/sections/%s/all", ipAddress, libraryID) @@ -559,13 +577,13 @@ func GetPlexTV(ipAddress, libraryID, plexToken string) (tvShowList []types.PlexT return filteredTVShows } -func GetPlexMusicArtists(ipAddress, libraryID, plexToken string) (artists []types.PlexMusicArtist) { - url := fmt.Sprintf("http://%s:32400/library/sections/%s/all", ipAddress, libraryID) +func GetPlexTVSeasons(ipAddress, plexToken, ratingKey string) (seasonList []types.PlexTVSeason) { + url := fmt.Sprintf("http://%s:32400/library/metadata/%s/children?", ipAddress, ratingKey) req, err := http.NewRequestWithContext(context.Background(), "GET", url, http.NoBody) if err != nil { fmt.Println("Error creating request:", err) - return artists + return seasonList } req.Header.Set("X-Plex-Token", plexToken) @@ -574,7 +592,7 @@ func GetPlexMusicArtists(ipAddress, libraryID, plexToken string) (artists []type resp, err := client.Do(req) if err != nil { fmt.Println("Error sending request:", err) - return artists + return seasonList } defer resp.Body.Close() @@ -582,30 +600,45 @@ func GetPlexMusicArtists(ipAddress, libraryID, plexToken string) (artists []type body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println("Error reading response body:", err) - return artists + return seasonList } - artists, err = extractMusicArtists(string(body)) - - if err != nil { - fmt.Println("Error extracting plex artists:", err) - return artists + seasonList = extractTVSeasons(string(body)) + // os.WriteFile("seasons.xml", body, 0644) + // now we need to get the episodes for each TV show + for i := range seasonList { + episodes := GetPlexTVEpisodes(ipAddress, plexToken, seasonList[i].RatingKey) + if len(episodes) > 0 { + seasonList[i].Episodes = episodes + } } - // now we need to get the albums for each artist - for i := range artists { - artists[i].Albums = GetPlexMusicAlbums(ipAddress, plexToken, libraryID, artists[i].RatingKey) + // remove seasons with no episodes + var filteredSeasons []types.PlexTVSeason + for i := range seasonList { + if len(seasonList[i].Episodes) < 1 { + continue + } + // lets add all of the resolutions for the episodes + var listOfResolutions []string + for j := range seasonList[i].Episodes { + listOfResolutions = append(listOfResolutions, seasonList[i].Episodes[j].Resolution) + } + // now we have all of the resolutions for the episodes + seasonList[i].LowestResolution = findLowestResolution(listOfResolutions) + // get the last episode added date + seasonList[i].LastEpisodeAdded = seasonList[i].Episodes[len(seasonList[i].Episodes)-1].DateAdded + filteredSeasons = append(filteredSeasons, seasonList[i]) } - - return artists + return filteredSeasons } -func GetPlexMusicAlbums(ipAddress, plexToken, libraryID, ratingKey string) (albums []types.PlexMusicAlbum) { - url := fmt.Sprintf("http://%s:32400/library/sections/%s/all?artist.id=%s&type=9", ipAddress, libraryID, ratingKey) +func GetPlexTVEpisodes(ipAddress, plexToken, ratingKey string) (episodeList []types.PlexTVEpisode) { + url := fmt.Sprintf("http://%s:32400/library/metadata/%s/children?", ipAddress, ratingKey) req, err := http.NewRequestWithContext(context.Background(), "GET", url, http.NoBody) if err != nil { fmt.Println("Error creating request:", err) - return albums + return episodeList } req.Header.Set("X-Plex-Token", plexToken) @@ -614,7 +647,7 @@ func GetPlexMusicAlbums(ipAddress, plexToken, libraryID, ratingKey string) (albu resp, err := client.Do(req) if err != nil { fmt.Println("Error sending request:", err) - return albums + return episodeList } defer resp.Body.Close() @@ -622,68 +655,78 @@ func GetPlexMusicAlbums(ipAddress, plexToken, libraryID, ratingKey string) (albu body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println("Error reading response body:", err) - return albums + return episodeList } - albums, _ = extractMusicAlbums(string(body)) + episodeList = extractTVEpisodes(string(body)) + return episodeList +} - return albums +func extractTVShows(xmlString string) (showList []types.PlexTVShow) { + var container TVContainer + err := xml.Unmarshal([]byte(xmlString), &container) + if err != nil { + fmt.Println("Error parsing XML:", err) + return + } + + for i := range container.Directory { + showList = append(showList, types.PlexTVShow{ + Title: container.Directory[i].Title, Year: container.Directory[i].Year, + DateAdded: parsePlexDate(container.Directory[i].AddedAt), RatingKey: container.Directory[i].RatingKey}) + } + return showList } -func extractMusicAlbums(xmlString string) (albums []types.PlexMusicAlbum, err error) { - var container AlbumContainer - err = xml.Unmarshal([]byte(xmlString), &container) +func extractTVSeasons(xmlString string) (seasonList []types.PlexTVSeason) { + var container SeasonContainer + err := xml.Unmarshal([]byte(xmlString), &container) if err != nil { fmt.Println("Error parsing XML:", err) - return albums, err + return } for i := range container.Directory { - intTime, err := strconv.ParseInt(container.Directory[i].AddedAt, 10, 64) - var parsedDate time.Time - if err != nil { - parsedDate = time.Time{} - } else { - parsedDate = time.Unix(intTime, 0) + if strings.HasPrefix(container.Directory[i].Title, "Season") { + seasonNumber, _ := strconv.Atoi(container.Directory[i].Index) + seasonList = append(seasonList, types.PlexTVSeason{ + Title: container.Directory[i].Title, RatingKey: container.Directory[i].RatingKey, Number: seasonNumber}) } - albums = append(albums, types.PlexMusicAlbum{ - Title: container.Directory[i].Title, - Year: container.Directory[i].Year, - DateAdded: parsedDate, - RatingKey: container.Directory[i].RatingKey}) } - return albums, nil + return seasonList } -func extractMusicArtists(xmlString string) (artists []types.PlexMusicArtist, err error) { - var container ArtistContainer - err = xml.Unmarshal([]byte(xmlString), &container) +func extractTVEpisodes(xmlString string) (episodeList []types.PlexTVEpisode) { + var container EpisodeContainer + err := xml.Unmarshal([]byte(xmlString), &container) if err != nil { fmt.Println("Error parsing XML:", err) - return artists, err + return } - for i := range container.Directory { - intTime, err := strconv.ParseInt(container.Directory[i].AddedAt, 10, 64) + for i := range container.Video { + intTime, err := strconv.ParseInt(container.Video[i].AddedAt, 10, 64) var parsedDate time.Time if err != nil { parsedDate = time.Time{} } else { parsedDate = time.Unix(intTime, 0) } - artists = append(artists, types.PlexMusicArtist{ - Name: container.Directory[i].Title, RatingKey: container.Directory[i].RatingKey, DateAdded: parsedDate}) + episodeList = append(episodeList, types.PlexTVEpisode{ + Title: container.Video[i].Title, Resolution: container.Video[i].Media.VideoResolution, + Index: container.Video[i].Index, DateAdded: parsedDate}) } - return artists, nil + return episodeList } -func GetPlexTVSeasons(ipAddress, plexToken, ratingKey string) (seasonList []types.PlexTVSeason) { - url := fmt.Sprintf("http://%s:32400/library/metadata/%s/children?", ipAddress, ratingKey) +// ================================================================================================= +func GetPlexMusicArtists(ipAddress, libraryID, plexToken string) (artists []types.PlexMusicArtist) { + url := fmt.Sprintf("http://%s:32400/library/sections/%s/all", ipAddress, libraryID) req, err := http.NewRequestWithContext(context.Background(), "GET", url, http.NoBody) if err != nil { fmt.Println("Error creating request:", err) - return seasonList + return artists } req.Header.Set("X-Plex-Token", plexToken) @@ -692,7 +735,7 @@ func GetPlexTVSeasons(ipAddress, plexToken, ratingKey string) (seasonList []type resp, err := client.Do(req) if err != nil { fmt.Println("Error sending request:", err) - return seasonList + return artists } defer resp.Body.Close() @@ -700,70 +743,30 @@ func GetPlexTVSeasons(ipAddress, plexToken, ratingKey string) (seasonList []type body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println("Error reading response body:", err) - return seasonList + return artists } - seasonList = extractTVSeasons(string(body)) - // os.WriteFile("seasons.xml", body, 0644) - // now we need to get the episodes for each TV show - for i := range seasonList { - episodes := GetPlexTVEpisodes(ipAddress, plexToken, seasonList[i].RatingKey) - if len(episodes) > 0 { - seasonList[i].Episodes = episodes - } - } - // remove seasons with no episodes - var filteredSeasons []types.PlexTVSeason - for i := range seasonList { - if len(seasonList[i].Episodes) < 1 { - continue - } - // lets add all of the resolutions for the episodes - var listOfResolutions []string - for j := range seasonList[i].Episodes { - listOfResolutions = append(listOfResolutions, seasonList[i].Episodes[j].Resolution) - } - // now we have all of the resolutions for the episodes - seasonList[i].LowestResolution = findLowestResolution(listOfResolutions) - // get the last episode added date - seasonList[i].LastEpisodeAdded = seasonList[i].Episodes[len(seasonList[i].Episodes)-1].DateAdded - filteredSeasons = append(filteredSeasons, seasonList[i]) - } - return filteredSeasons -} + artists, err = extractMusicArtists(string(body)) -func findLowestResolution(resolutions []string) (lowestResolution string) { - if slices.Contains(resolutions, types.PlexResolutionSD) { - return types.PlexResolutionSD - } - if slices.Contains(resolutions, types.PlexResolution240) { - return types.PlexResolution240 - } - if slices.Contains(resolutions, types.PlexResolution480) { - return types.PlexResolution480 - } - if slices.Contains(resolutions, types.PlexResolution576) { - return types.PlexResolution576 - } - if slices.Contains(resolutions, types.PlexResolution720) { - return types.PlexResolution720 - } - if slices.Contains(resolutions, types.PlexResolution1080) { - return types.PlexResolution1080 + if err != nil { + fmt.Println("Error extracting plex artists:", err) + return artists } - if slices.Contains(resolutions, types.PlexResolution4K) { - return types.PlexResolution4K + // now we need to get the albums for each artist + for i := range artists { + artists[i].Albums = GetPlexMusicAlbums(ipAddress, plexToken, libraryID, artists[i].RatingKey) } - return "" + + return artists } -func GetPlexTVEpisodes(ipAddress, plexToken, ratingKey string) (episodeList []types.PlexTVEpisode) { - url := fmt.Sprintf("http://%s:32400/library/metadata/%s/children?", ipAddress, ratingKey) +func GetPlexMusicAlbums(ipAddress, plexToken, libraryID, ratingKey string) (albums []types.PlexMusicAlbum) { + url := fmt.Sprintf("http://%s:32400/library/sections/%s/all?artist.id=%s&type=9", ipAddress, libraryID, ratingKey) req, err := http.NewRequestWithContext(context.Background(), "GET", url, http.NoBody) if err != nil { fmt.Println("Error creating request:", err) - return episodeList + return albums } req.Header.Set("X-Plex-Token", plexToken) @@ -772,7 +775,7 @@ func GetPlexTVEpisodes(ipAddress, plexToken, ratingKey string) (episodeList []ty resp, err := client.Do(req) if err != nil { fmt.Println("Error sending request:", err) - return episodeList + return albums } defer resp.Body.Close() @@ -780,11 +783,47 @@ func GetPlexTVEpisodes(ipAddress, plexToken, ratingKey string) (episodeList []ty body, err := io.ReadAll(resp.Body) if err != nil { fmt.Println("Error reading response body:", err) - return episodeList + return albums } - episodeList = extractTVEpisodes(string(body)) - return episodeList + albums, _ = extractMusicAlbums(string(body)) + + return albums +} + +func extractMusicArtists(xmlString string) (artists []types.PlexMusicArtist, err error) { + var container ArtistContainer + err = xml.Unmarshal([]byte(xmlString), &container) + if err != nil { + fmt.Println("Error parsing XML:", err) + return artists, err + } + + for i := range container.Directory { + artists = append(artists, types.PlexMusicArtist{ + Name: container.Directory[i].Title, + RatingKey: container.Directory[i].RatingKey, + DateAdded: parsePlexDate(container.Directory[i].AddedAt)}) + } + return artists, nil +} + +func extractMusicAlbums(xmlString string) (albums []types.PlexMusicAlbum, err error) { + var container AlbumContainer + err = xml.Unmarshal([]byte(xmlString), &container) + if err != nil { + fmt.Println("Error parsing XML:", err) + return albums, err + } + + for i := range container.Directory { + albums = append(albums, types.PlexMusicAlbum{ + Title: container.Directory[i].Title, + Year: container.Directory[i].Year, + DateAdded: parsePlexDate(container.Directory[i].AddedAt), + RatingKey: container.Directory[i].RatingKey}) + } + return albums, nil } func GetPlexLibraries(ipAddress, plexToken string) (libraryList []types.PlexLibrary, err error) { @@ -832,90 +871,38 @@ func extractLibraries(xmlString string) (libraryList []types.PlexLibrary, err er return libraryList, nil } -func extractMovies(xmlString string) (movieList []types.PlexMovie) { - var container MovieContainer - err := xml.Unmarshal([]byte(xmlString), &container) - if err != nil { - fmt.Println("Error parsing XML:", err) - return +// ================================================================================================= +func findLowestResolution(resolutions []string) (lowestResolution string) { + if slices.Contains(resolutions, types.PlexResolutionSD) { + return types.PlexResolutionSD } - - for i := range container.Video { - intTime, err := strconv.ParseInt(container.Video[i].AddedAt, 10, 64) - var parsedDate time.Time - if err != nil { - parsedDate = time.Time{} - } else { - parsedDate = time.Unix(intTime, 0) - } - - movieList = append(movieList, types.PlexMovie{ - Title: container.Video[i].Title, Year: container.Video[i].Year, DateAdded: parsedDate}) + if slices.Contains(resolutions, types.PlexResolution240) { + return types.PlexResolution240 } - return movieList -} - -func extractTVShows(xmlString string) (showList []types.PlexTVShow) { - var container TVContainer - err := xml.Unmarshal([]byte(xmlString), &container) - if err != nil { - fmt.Println("Error parsing XML:", err) - return + if slices.Contains(resolutions, types.PlexResolution480) { + return types.PlexResolution480 } - - for i := range container.Directory { - intTime, err := strconv.ParseInt(container.Directory[i].AddedAt, 10, 64) - var parsedDate time.Time - if err != nil { - parsedDate = time.Time{} - } else { - parsedDate = time.Unix(intTime, 0) - } - - showList = append(showList, types.PlexTVShow{ - Title: container.Directory[i].Title, Year: container.Directory[i].Year, - DateAdded: parsedDate, RatingKey: container.Directory[i].RatingKey}) + if slices.Contains(resolutions, types.PlexResolution576) { + return types.PlexResolution576 } - return showList -} - -func extractTVSeasons(xmlString string) (seasonList []types.PlexTVSeason) { - var container SeasonContainer - err := xml.Unmarshal([]byte(xmlString), &container) - if err != nil { - fmt.Println("Error parsing XML:", err) - return + if slices.Contains(resolutions, types.PlexResolution720) { + return types.PlexResolution720 } - - for i := range container.Directory { - if strings.HasPrefix(container.Directory[i].Title, "Season") { - seasonNumber, _ := strconv.Atoi(container.Directory[i].Index) - seasonList = append(seasonList, types.PlexTVSeason{ - Title: container.Directory[i].Title, RatingKey: container.Directory[i].RatingKey, Number: seasonNumber}) - } + if slices.Contains(resolutions, types.PlexResolution1080) { + return types.PlexResolution1080 } - return seasonList + if slices.Contains(resolutions, types.PlexResolution4K) { + return types.PlexResolution4K + } + return "" } -func extractTVEpisodes(xmlString string) (episodeList []types.PlexTVEpisode) { - var container EpisodeContainer - err := xml.Unmarshal([]byte(xmlString), &container) +func parsePlexDate(plexDate string) (parsedDate time.Time) { + intTime, err := strconv.ParseInt(plexDate, 10, 64) if err != nil { - fmt.Println("Error parsing XML:", err) - return - } - - for i := range container.Video { - intTime, err := strconv.ParseInt(container.Video[i].AddedAt, 10, 64) - var parsedDate time.Time - if err != nil { - parsedDate = time.Time{} - } else { - parsedDate = time.Unix(intTime, 0) - } - episodeList = append(episodeList, types.PlexTVEpisode{ - Title: container.Video[i].Title, Resolution: container.Video[i].Media.VideoResolution, - Index: container.Video[i].Index, DateAdded: parsedDate}) + parsedDate = time.Time{} + } else { + parsedDate = time.Unix(intTime, 0) } - return episodeList + return parsedDate } diff --git a/plex/plex_test.go b/plex/plex_test.go index c4eff6b..823523f 100644 --- a/plex/plex_test.go +++ b/plex/plex_test.go @@ -142,3 +142,29 @@ func Test_findLowestResolution(t *testing.T) { }) } } + +func Test_parsePlexDate(t *testing.T) { + tests := []struct { + name string + plexDate string + wantParsedDate time.Time + }{ + { + name: "validate int date", + plexDate: "1676229015", + wantParsedDate: time.Date(2021, time.February, 1, 0, 0, 0, 0, time.UTC), + }, + { + name: "validate int date", + plexDate: "0", + wantParsedDate: time.Date(1970, time.January, 1, 0, 0, 16, 0, time.UTC), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotParsedDate := parsePlexDate(tt.plexDate); gotParsedDate.Compare(tt.wantParsedDate) == 0 { + t.Errorf("parsePlexDate()\n%v\nwant\n%v", gotParsedDate, tt.wantParsedDate) + } + }) + } +} diff --git a/types/types.go b/types/types.go index 6e59e53..69fa540 100644 --- a/types/types.go +++ b/types/types.go @@ -15,6 +15,7 @@ const ( PlexResolution1080 = "1080" PlexResolution4K = "4k" ConcurrencyLimit = 10 + StringTrue = "true" ) type SearchResults struct { @@ -39,6 +40,11 @@ type Configuration struct { SpotifyClientSecret string } +type FilteringOptions struct { + AudioLanguage string + NewerVersion bool +} + // ============================================================================================================== type PlexMovie struct { Title string diff --git a/web/index.html b/web/index.html index e17f89b..cd10fe6 100644 --- a/web/index.html +++ b/web/index.html @@ -14,7 +14,7 @@