diff --git a/.github/workflows/docker-image-beta.yml b/.github/workflows/docker-image-beta.yml index e3910c0..de314bd 100644 --- a/.github/workflows/docker-image-beta.yml +++ b/.github/workflows/docker-image-beta.yml @@ -20,7 +20,7 @@ jobs: tags: | type-raw,value=beta flavor: | - latest=true + latest=false - name: Set up QEMU uses: docker/setup-qemu-action@v2 with: diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 2b8578f..912b846 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -33,7 +33,6 @@ jobs: with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - registry: docker.io - name: Login to GHCR uses: docker/login-action@v2 with: diff --git a/main.go b/main.go index 559f7e4..1cf0b1c 100644 --- a/main.go +++ b/main.go @@ -24,22 +24,18 @@ func main() { // Create and define file for logging file, err := os.OpenFile("config/wrapperr.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) if err != nil { - log.Println("Failed to load configuration file. Error: ") - log.Println(err) + log.Println("Failed to load configuration file. Error: " + err.Error()) - fmt.Println("Failed to load configuration file. Error: ") - fmt.Println(err) + fmt.Println("Failed to load configuration file. Error: " + err.Error()) os.Exit(1) } config, err := files.GetConfig() if err != nil { - log.Println("Failed to load configuration file. Error: ") - log.Println(err) + log.Println("Failed to load configuration file. Error: " + err.Error()) - fmt.Println("Failed to load configuration file. Error: ") - fmt.Println(err) + fmt.Println("Failed to load configuration file. Error: " + err.Error()) os.Exit(1) } @@ -48,19 +44,16 @@ func main() { if config.Timezone != "" { loc, err := time.LoadLocation(config.Timezone) if err != nil { - fmt.Println("Failed to set time zone from config. Error: ") - fmt.Println(err) + fmt.Println("Failed to set time zone from config. Error: " + err.Error()) fmt.Println("Removing value...") - log.Println("Failed to set time zone from config. Error: ") - log.Println(err) + log.Println("Failed to set time zone from config. Error: " + err.Error()) log.Println("Removing value...") config.Timezone = "" err = files.SaveConfig(config) if err != nil { - log.Println("Failed to set new time zone in the config. Error: ") - log.Println(err) + log.Println("Failed to set new time zone in the config. Error: " + err.Error()) log.Println("Exiting...") os.Exit(1) } @@ -124,8 +117,31 @@ func main() { // Get stats route router.HandleFunc(root+"/api/get/statistics", routes.ApiWrapperGetStatistics) - // Static routes - router.PathPrefix(root).Handler(http.StripPrefix(root, http.FileServer(http.Dir("./web/")))) + // Assets route + assetsFileServer := http.FileServer(http.Dir("./web/assets/")) + router.PathPrefix(root + "/assets").Handler(http.StripPrefix(root+"/assets", assetsFileServer)) + + // JS route + jsFileServer := http.FileServer(http.Dir("./web/js/")) + router.PathPrefix(root + "/js").Handler(http.StripPrefix(root+"/js", jsFileServer)) + + // HTML frontpage route + router.HandleFunc(root+"/", func(w http.ResponseWriter, r *http.Request) { + // Using the http.ServeFile function to serve the frontpage.html file + http.ServeFile(w, r, "./web/html/frontpage.html") + }) + + // HTML admin route + router.HandleFunc(root+"/admin", func(w http.ResponseWriter, r *http.Request) { + // Using the http.ServeFile function to serve the admin.html file + http.ServeFile(w, r, "./web/html/admin.html") + }) + + // TXT robots route + router.HandleFunc(root+"/robots.txt", func(w http.ResponseWriter, r *http.Request) { + // Using the http.ServeFile function to serve the robots.txt file + http.ServeFile(w, r, "./web/txt/robots.txt") + }) // Start web-server log.Fatal(http.ListenAndServe(":"+strconv.Itoa(port), router)) diff --git a/models/models.go b/models/models.go index cf4ab96..7e048f5 100644 --- a/models/models.go +++ b/models/models.go @@ -422,25 +422,26 @@ type WrapperrDay struct { } type TautulliEntry struct { - Date int `json:"date"` - Duration int `json:"duration"` - RowID int `json:"row_id"` - FriendlyName string `json:"friendly_name"` - FullTitle string `json:"full_title"` - GrandparentRatingKey int `json:"grandparent_rating_key"` - GrandparentTitle string `json:"grandparent_title"` - OriginalTitle string `json:"original_title"` - MediaType string `json:"media_type"` - ParentRatingKey int `json:"parent_rating_key"` - ParentTitle string `json:"parent_title"` - PausedCounter int `json:"paused_counter"` - PercentComplete int `json:"percent_complete"` - RatingKey int `json:"rating_key"` - Title string `json:"title"` - User string `json:"user"` - UserID int `json:"user_id"` - Year int `json:"year"` - Plays int `json:"plays"` + Date int `json:"date"` + Duration int `json:"duration"` + RowID int `json:"row_id"` + FriendlyName string `json:"friendly_name"` + FullTitle string `json:"full_title"` + GrandparentRatingKey int `json:"grandparent_rating_key"` + GrandparentTitle string `json:"grandparent_title"` + OriginalTitle string `json:"original_title"` + MediaType string `json:"media_type"` + ParentRatingKey int `json:"parent_rating_key"` + ParentTitle string `json:"parent_title"` + PausedCounter int `json:"paused_counter"` + PercentComplete int `json:"percent_complete"` + RatingKey int `json:"rating_key"` + Title string `json:"title"` + User string `json:"user"` + UserID int `json:"user_id"` + Year int `json:"year"` + OriginallyAvailableAt string `json:"originally_available_at"` + Plays int `json:"plays"` } type WrapperrYearUserEntry struct { diff --git a/modules/tautulli.go b/modules/tautulli.go index 0ea4449..3ee2bd9 100644 --- a/modules/tautulli.go +++ b/modules/tautulli.go @@ -15,39 +15,56 @@ import ( func TautulliTestConnection(TautulliPort int, TautulliIP string, TautulliHttps bool, TautulliRoot string, TautulliApiKey string) (bool, error) { - url_string, err := utilities.BuildURL(TautulliPort, TautulliIP, TautulliHttps, TautulliRoot) + urlString, err := utilities.BuildURL(TautulliPort, TautulliIP, TautulliHttps, TautulliRoot) if err != nil { - log.Println(err) + errString := strings.Replace(err.Error(), TautulliApiKey, "REDACTED", -1) + log.Println("Failed to build Tautulli connection URL. Error: " + errString) return false, errors.New("Failed to build Tautulli connection URL.") } - url_string = url_string + "api/v2/" + "?apikey=" + TautulliApiKey + "&cmd=status" + urlString = urlString + "api/v2/" + "?apikey=" + TautulliApiKey + "&cmd=status" params := url.Values{} payload := strings.NewReader(params.Encode()) - req, err := http.NewRequest("GET", url_string, payload) + req, err := http.NewRequest("GET", urlString, payload) if err != nil { - log.Println(err) - return false, errors.New("Failed to reach Tautulli server.") + errString := strings.Replace(err.Error(), TautulliApiKey, "REDACTED", -1) + log.Println("Failed to reach Tautulli server. Error: " + errString) + return false, errors.New("Failed to reach Tautulli server. Error: " + errString) } req.Header.Add("Accept", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { - log.Println(err) - return false, errors.New("Failed to reach Tautulli server.") + errString := strings.Replace(err.Error(), TautulliApiKey, "REDACTED", -1) + log.Println("Failed to reach Tautulli server. Error: " + errString) + return false, errors.New("Failed to reach Tautulli server. Error: " + errString) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) + if err != nil { + errString := strings.Replace(err.Error(), TautulliApiKey, "REDACTED", -1) + log.Println("Failed to read Tautulli server response. Error: " + errString) + return false, errors.New("Failed to read Tautulli response. Error: " + errString) + } else if res.StatusCode != 200 { + errString := "Tautulli didn't respond with status code 200, got: " + res.Status + " instead." + reply := string(body) + replyString := strings.Replace(reply, TautulliApiKey, "REDACTED", -1) + log.Println("Failed to connect to Tautulli server. \n\nReply: " + replyString + ". +n\nError: " + errString) + return false, errors.New("Failed to connect to Tautulli server. \n\nReply: " + replyString + ". \n\nError: " + errString) + } var body_reply models.TautulliStatusReply - json.Unmarshal(body, &body_reply) + err = json.Unmarshal(body, &body_reply) if err != nil { - log.Println(err) - return false, errors.New("Failed to parse Tautulli response.") + errString := strings.Replace(err.Error(), TautulliApiKey, "REDACTED", -1) + reply := string(body) + replyString := strings.Replace(reply, TautulliApiKey, "REDACTED", -1) + log.Println("Failed to parse Tautulli server response. \n\nReply: " + replyString + ". +n\nError: " + errString) + return false, errors.New("Failed to parse Tautulli server response. \n\nReply: " + replyString + ". \n\nError: " + errString) } var tautulli_status bool = false @@ -56,6 +73,12 @@ func TautulliTestConnection(TautulliPort int, TautulliIP string, TautulliHttps b tautulli_status = true + } else if body_reply.Response.Result == "error" { + + errString := strings.Replace(body_reply.Response.Message, TautulliApiKey, "REDACTED", -1) + log.Println("Tautulli server responsed with an error. Error: " + errString) + return false, errors.New("Tautulli server responsed with an error. Error: " + errString) + } return tautulli_status, nil diff --git a/routes/no_auth.go b/routes/no_auth.go index bde7ee6..cabcbb7 100644 --- a/routes/no_auth.go +++ b/routes/no_auth.go @@ -304,8 +304,7 @@ func ApiGetTautulliConncection(w http.ResponseWriter, r *http.Request) { tautulli_state, err := modules.TautulliTestConnection(tautulli_connection.TautulliPort, tautulli_connection.TautulliIP, tautulli_connection.TautulliHttps, tautulli_connection.TautulliRoot, tautulli_connection.TautulliApiKey) if err != nil { - log.Println(err) - utilities.RespondDefaultError(w, r, errors.New("Failed to reach Tautulli server."), 500) + utilities.RespondDefaultError(w, r, err, 500) return } diff --git a/routes/statistics.go b/routes/statistics.go index 4cd9085..ed2d4b4 100644 --- a/routes/statistics.go +++ b/routes/statistics.go @@ -376,25 +376,27 @@ func WrapperrDownloadDays(ID int, wrapperr_data []models.WrapperrDay, loop_inter // Loop through retrieved data from Tautulli for j := 0; j < len(tautulli_data); j++ { if tautulli_data[j].MediaType == "movie" || tautulli_data[j].MediaType == "episode" || tautulli_data[j].MediaType == "track" { + tautulli_entry := models.TautulliEntry{ - Date: tautulli_data[j].Date, - RowID: tautulli_data[j].RowID, - Duration: tautulli_data[j].Duration, - FriendlyName: tautulli_data[j].FriendlyName, - FullTitle: tautulli_data[j].FullTitle, - GrandparentRatingKey: tautulli_data[j].GrandparentRatingKey, - GrandparentTitle: tautulli_data[j].GrandparentTitle, - OriginalTitle: tautulli_data[j].OriginalTitle, - MediaType: tautulli_data[j].MediaType, - ParentRatingKey: tautulli_data[j].ParentRatingKey, - ParentTitle: tautulli_data[j].ParentTitle, - PausedCounter: tautulli_data[j].PausedCounter, - PercentComplete: tautulli_data[j].PercentComplete, - RatingKey: tautulli_data[j].RatingKey, - Title: tautulli_data[j].Title, - User: tautulli_data[j].User, - UserID: tautulli_data[j].UserID, - Year: tautulli_data[j].Year, + Date: tautulli_data[j].Date, + RowID: tautulli_data[j].RowID, + Duration: tautulli_data[j].Duration, + FriendlyName: tautulli_data[j].FriendlyName, + FullTitle: tautulli_data[j].FullTitle, + GrandparentRatingKey: tautulli_data[j].GrandparentRatingKey, + GrandparentTitle: tautulli_data[j].GrandparentTitle, + OriginalTitle: tautulli_data[j].OriginalTitle, + MediaType: tautulli_data[j].MediaType, + ParentRatingKey: tautulli_data[j].ParentRatingKey, + ParentTitle: tautulli_data[j].ParentTitle, + PausedCounter: tautulli_data[j].PausedCounter, + PercentComplete: tautulli_data[j].PercentComplete, + RatingKey: tautulli_data[j].RatingKey, + Title: tautulli_data[j].Title, + User: tautulli_data[j].User, + UserID: tautulli_data[j].UserID, + Year: tautulli_data[j].Year, + OriginallyAvailableAt: tautulli_data[j].OriginallyAvailableAt, } // Append to day data @@ -490,7 +492,7 @@ func WrapperrLoopData(user_id int, config *models.WrapperrConfig, wrapperr_data // Look for movie within pre-defined array for d := 0; d < len(wrapperr_user_movie); d++ { - if wrapperr_user_movie[d].Year == wrapperr_data[i].Data[j].Year && wrapperr_user_movie[d].Title == wrapperr_data[i].Data[j].Title { + if ((wrapperr_user_movie[d].Year == wrapperr_data[i].Data[j].Year && wrapperr_data[i].Data[j].OriginallyAvailableAt == "") || wrapperr_user_movie[d].OriginallyAvailableAt == wrapperr_data[i].Data[j].OriginallyAvailableAt) && wrapperr_user_movie[d].Title == wrapperr_data[i].Data[j].Title { wrapperr_user_movie[d].Plays += 1 wrapperr_user_movie[d].Duration += wrapperr_data[i].Data[j].Duration wrapperr_user_movie[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter @@ -515,7 +517,8 @@ func WrapperrLoopData(user_id int, config *models.WrapperrConfig, wrapperr_data // Look for episode within pre-defined array for d := 0; d < len(wrapperr_user_episode); d++ { - if wrapperr_user_episode[d].Year == wrapperr_data[i].Data[j].Year && wrapperr_user_episode[d].Title == wrapperr_data[i].Data[j].Title { + + if ((wrapperr_user_episode[d].Year == wrapperr_data[i].Data[j].Year && wrapperr_data[i].Data[j].OriginallyAvailableAt == "") || wrapperr_user_episode[d].OriginallyAvailableAt == wrapperr_data[i].Data[j].OriginallyAvailableAt) && wrapperr_user_episode[d].Title == wrapperr_data[i].Data[j].Title && wrapperr_user_episode[d].ParentTitle == wrapperr_data[i].Data[j].ParentTitle && wrapperr_user_episode[d].GrandparentTitle == wrapperr_data[i].Data[j].GrandparentTitle { wrapperr_user_episode[d].Plays += 1 wrapperr_user_episode[d].Duration += wrapperr_data[i].Data[j].Duration wrapperr_user_episode[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter @@ -558,7 +561,7 @@ func WrapperrLoopData(user_id int, config *models.WrapperrConfig, wrapperr_data // Look for track within pre-defined array for d := 0; d < len(wrapperr_user_track); d++ { - if wrapperr_user_track[d].Year == wrapperr_data[i].Data[j].Year && wrapperr_user_track[d].Title == wrapperr_data[i].Data[j].Title { + if ((wrapperr_user_track[d].Year == wrapperr_data[i].Data[j].Year && wrapperr_data[i].Data[j].OriginallyAvailableAt == "") || wrapperr_user_track[d].OriginallyAvailableAt == wrapperr_data[i].Data[j].OriginallyAvailableAt) && wrapperr_user_track[d].Title == wrapperr_data[i].Data[j].Title { wrapperr_user_track[d].Plays += 1 wrapperr_user_track[d].Duration += wrapperr_data[i].Data[j].Duration wrapperr_user_track[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter @@ -580,7 +583,7 @@ func WrapperrLoopData(user_id int, config *models.WrapperrConfig, wrapperr_data // Look for album within pre-defined array for d := 0; d < len(wrapperr_user_album); d++ { - if wrapperr_user_album[d].Year == wrapperr_data[i].Data[j].Year && wrapperr_user_album[d].ParentTitle == wrapperr_data[i].Data[j].ParentTitle { + if ((wrapperr_user_album[d].Year == wrapperr_data[i].Data[j].Year && wrapperr_data[i].Data[j].OriginallyAvailableAt == "") || wrapperr_user_album[d].OriginallyAvailableAt == wrapperr_data[i].Data[j].OriginallyAvailableAt) && wrapperr_user_album[d].ParentTitle == wrapperr_data[i].Data[j].ParentTitle { wrapperr_user_album[d].Plays += 1 wrapperr_user_album[d].Duration += wrapperr_data[i].Data[j].Duration wrapperr_user_album[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter @@ -627,7 +630,7 @@ func WrapperrLoopData(user_id int, config *models.WrapperrConfig, wrapperr_data // Look for movie within pre-defined array for d := 0; d < len(wrapperr_year_movie); d++ { - if wrapperr_year_movie[d].Year == wrapperr_data[i].Data[j].Year && wrapperr_year_movie[d].Title == wrapperr_data[i].Data[j].Title { + if ((wrapperr_year_movie[d].Year == wrapperr_data[i].Data[j].Year && wrapperr_data[i].Data[j].OriginallyAvailableAt == "") || wrapperr_year_movie[d].OriginallyAvailableAt == wrapperr_data[i].Data[j].OriginallyAvailableAt) && wrapperr_year_movie[d].Title == wrapperr_data[i].Data[j].Title { wrapperr_year_movie[d].Plays += 1 wrapperr_year_movie[d].Duration += wrapperr_data[i].Data[j].Duration wrapperr_year_movie[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter @@ -677,7 +680,7 @@ func WrapperrLoopData(user_id int, config *models.WrapperrConfig, wrapperr_data // Look for show within pre-defined array for d := 0; d < len(wrapperr_year_show); d++ { - if wrapperr_year_show[d].Year == wrapperr_data[i].Data[j].Year && wrapperr_year_show[d].GrandparentTitle == wrapperr_data[i].Data[j].GrandparentTitle { + if wrapperr_year_show[d].GrandparentTitle == wrapperr_data[i].Data[j].GrandparentTitle { wrapperr_year_show[d].Plays += 1 wrapperr_year_show[d].Duration += wrapperr_data[i].Data[j].Duration wrapperr_year_show[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter @@ -859,7 +862,7 @@ func WrapperrLoopData(user_id int, config *models.WrapperrConfig, wrapperr_data count += 1 } - // Find longest pause + // Find the longest duration spent on a certain episode sortutil.DescByField(wrapperr_user_episode, "Duration") wrapperr_reply.User.UserShows.Data.EpisodeDurationLongest.Duration = wrapperr_user_episode[0].Duration wrapperr_reply.User.UserShows.Data.EpisodeDurationLongest.GrandparentTitle = wrapperr_user_episode[0].GrandparentTitle diff --git a/web/admin/index.html b/web/html/admin.html similarity index 94% rename from web/admin/index.html rename to web/html/admin.html index 73afb63..b375c9c 100644 --- a/web/admin/index.html +++ b/web/html/admin.html @@ -9,12 +9,12 @@