wrapperr/routes/statistics.go
aunefyren 1a7c66c4b4 Major refactor
Basic Auth on front end
Switched to GIN for http routing
Reworked middleware
2023-10-25 15:14:02 +02:00

1462 lines
53 KiB
Go

package routes
import (
"aunefyren/wrapperr/files"
"aunefyren/wrapperr/middlewares"
"aunefyren/wrapperr/models"
"aunefyren/wrapperr/modules"
"aunefyren/wrapperr/utilities"
"errors"
"log"
"net/http"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/goombaio/namegenerator"
"github.com/patrickmn/sortutil"
)
func ApiWrapperGetStatistics(context *gin.Context) {
ipString := utilities.GetOriginIPString(context)
log.Println("New /get/statistics request." + ipString)
configBool, err := files.GetConfigState()
if err != nil {
log.Println("Failed to retrieve configuration state. Error: " + err.Error())
context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve configuration state."})
context.Abort()
return
} else if !configBool {
context.JSON(http.StatusBadRequest, gin.H{"error": "Wrapperr is not configured."})
context.Abort()
return
}
config, err := files.GetConfig()
if err != nil {
log.Println("Failed to load Wrapperr configuration. Error: " + err.Error())
context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load Wrapperr configuration."})
context.Abort()
return
}
adminConfig, err := files.GetAdminConfig()
if err != nil {
log.Println("Failed to load Wrapperr admin configuration. Error: " + err.Error())
context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load Wrapperr admin configuration."})
context.Abort()
return
}
log.Println("1. Configuration check passed." + ipString)
// Check every Tautulli server
for i := 0; i < len(config.TautulliConfig); i++ {
log.Println("Checking Tautulli server '" + config.TautulliConfig[i].TautulliName + "'." + ipString)
tautulli_state, err := modules.TautulliTestConnection(config.TautulliConfig[i].TautulliPort, config.TautulliConfig[i].TautulliIP, config.TautulliConfig[i].TautulliHttps, config.TautulliConfig[i].TautulliRoot, config.TautulliConfig[i].TautulliApiKey)
if err != nil {
log.Println("Failed to reach Tautulli server '" + config.TautulliConfig[i].TautulliName + "'. Error: " + err.Error())
context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reach Tautulli server '" + config.TautulliConfig[i].TautulliName + "'."})
context.Abort()
return
} else if !tautulli_state {
log.Println("Failed to ping Tautulli server '" + config.TautulliConfig[i].TautulliName + "' before retrieving statistics.")
context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reach Tautulli server '" + config.TautulliConfig[i].TautulliName + "'."})
context.Abort()
return
}
}
log.Println("2. Tautulli check passed." + ipString)
var auth_passed bool = false
var user_name string = ""
var user_id int = 0
var cache_limit int = 0
var admin bool = false
// Try to authorize bearer token from header
authorizationHeader := context.GetHeader("Authorization")
payload, _, err := middlewares.AuthGetPayloadFromAuthorization(authorizationHeader, config, adminConfig)
// If it failed and PlexAuth is enabled, respond with and error
// If it didn't fail, and PlexAuth is enabled, declare auth as passed
if err != nil && config.PlexAuth {
log.Println("Failed to authorize authorization header. Error: " + err.Error())
context.JSON(http.StatusUnauthorized, gin.H{"error": "Failed to authorize request."})
context.Abort()
return
} else if config.PlexAuth {
auth_passed = true
}
// If PlexAuth is enabled and the user is admin in payload, declare admin bool as true
if err == nil && payload.Admin {
admin = true
auth_passed = true
}
// If the user is not an admin, and PlexAuth is enabled, validate and retrieve details from Plex Token in payload
if !admin && config.PlexAuth {
plex_object, err := modules.PlexAuthValidateToken(payload.AuthToken, config.ClientKey, config.WrapperrVersion)
if err != nil {
log.Println("Could not validate Plex Auth login. Error: " + err.Error())
context.JSON(http.StatusUnauthorized, gin.H{"error": "Could not validate Plex Auth login."})
context.Abort()
return
}
// Set user details from Plex login
user_name = plex_object.Username
user_id = plex_object.ID
// Check for friendly name using Tautulli
for i := 0; i < len(config.TautulliConfig); i++ {
_, new_username, err := modules.TautulliGetUserId(config.TautulliConfig[i].TautulliPort, config.TautulliConfig[i].TautulliIP, config.TautulliConfig[i].TautulliHttps, config.TautulliConfig[i].TautulliRoot, config.TautulliConfig[i].TautulliApiKey, user_name)
if err == nil {
user_name = new_username
}
break
}
}
log.Println("3. Auth check passed." + ipString)
// Read payload from Post input
var wrapperr_request models.SearchWrapperrRequest
if err := context.ShouldBindJSON(&wrapperr_request); err != nil {
log.Println("Failed to parse request. Error: " + err.Error())
context.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse request."})
context.Abort()
return
}
// If auth is not passed, caching mode is false, and no PlexIdentity was received, mark it as a bad request
if wrapperr_request.PlexIdentity == "" && !auth_passed && !wrapperr_request.CachingMode {
log.Println("Cannot retrieve statistics because search parameter is invalid.")
context.JSON(http.StatusBadRequest, gin.H{"error": "Invalid search parameter."})
context.Abort()
return
}
// If no auth has been passed, caching mode is false, and user is not admin, search for the Plex details using Tautulli and PlexIdentity
if !auth_passed && !wrapperr_request.CachingMode && !admin {
UserNameFound := false
for i := 0; i < len(config.TautulliConfig); i++ {
new_id, new_username, err := modules.TautulliGetUserId(config.TautulliConfig[i].TautulliPort, config.TautulliConfig[i].TautulliIP, config.TautulliConfig[i].TautulliHttps, config.TautulliConfig[i].TautulliRoot, config.TautulliConfig[i].TautulliApiKey, wrapperr_request.PlexIdentity)
if err == nil {
UserNameFound = true
user_name = new_username
user_id = new_id
}
}
if !UserNameFound {
log.Println("Failed to find user in Tautulli. Error: " + err.Error())
context.JSON(http.StatusInternalServerError, gin.H{"error": "Could not find a matching user."})
context.Abort()
return
}
}
// If caching mode is false and user is admin, return bad request error
if !wrapperr_request.CachingMode && admin {
log.Println("Caching mode deactivated, but admin login session retrieved.")
context.JSON(http.StatusUnauthorized, gin.H{"error": "You can not retrieve stats as admin."})
context.Abort()
return
}
// If caching mode is true and user is not admin, return bad request error
if wrapperr_request.CachingMode && !admin {
log.Println("Caching mode received, but user was not verified as admin.")
context.JSON(http.StatusUnauthorized, gin.H{"error": "Only the admin can perform caching."})
context.Abort()
return
}
// If admin and caching mode, set username and user_id to correct values
if wrapperr_request.CachingMode && admin {
user_name = "Caching-Mode"
user_id = 0
cache_limit = wrapperr_request.CachingLimit
if !config.UseCache {
log.Println("Admin attempted to use cache mode, but the cache feature is disabled in the config.")
context.JSON(http.StatusBadRequest, gin.H{"error": "Caching mode enabled, but the cache feature is disabled in the settings."})
context.Abort()
return
}
}
// If no username and no user_id has been declared at this point, something is wrong. Return error.
if user_name == "" && user_id == 0 {
log.Println("At this point the user should have been verified, but username and ID is empty.")
context.JSON(http.StatusInternalServerError, gin.H{"error": "User validation error."})
context.Abort()
return
}
log.Println("4. User details confirmed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ipString)
// Create empty array object for each day in Wrapped period. If cache is enabled, call GetCache() and replace the empty object.
wrapperr_data := []models.WrapperrDay{}
if config.UseCache {
wrapperr_data, err = files.GetCache()
if err != nil {
log.Println("Failed to load cache file. Error: " + err.Error())
context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load cache file."})
context.Abort()
return
}
}
log.Println("5. Cache stage completed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ipString)
// Download/refresh data-set from Tautulli
wrapperr_data, wrapperr_data_complete, err := WrapperrDownloadDays(user_id, wrapperr_data, cache_limit, config)
if err != nil {
log.Println("Failed to download data for wrapping. Error: " + err.Error())
context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to download data for wrapping."})
context.Abort()
return
}
log.Println("6. Tautulli refresh/download stage completed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ipString)
// If cache is enabled, send the object to SaveCache() for later use.
if config.UseCache {
err = files.SaveCache(&wrapperr_data)
if err != nil {
log.Println("Failed to save new cache file for " + user_name + " (" + strconv.Itoa(user_id) + "). Error: " + err.Error())
context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save new cache file for " + user_name + " (" + strconv.Itoa(user_id) + ")."})
context.Abort()
return
}
}
log.Println("7. Cache saving stage completed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ipString)
// If caching mode is in use, stop the process here and return the result to the user
if wrapperr_request.CachingMode {
booleanReply := models.BooleanReply{
Message: "Completed caching request.",
Error: false,
Data: wrapperr_data_complete,
}
ipString := utilities.GetOriginIPString(context)
log.Println("Caching request completed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ipString)
context.JSON(http.StatusOK, booleanReply)
return
}
// Create reply object
var wrapperr_reply models.WrapperrStatisticsReply
wrapperr_reply.User.ID = user_id
wrapperr_reply.User.Name = user_name
wrapperr_reply.Date = time.Now().Format("2006-01-02")
wrapperr_reply.Message = "Statistics retrieved."
// Loop through Wrapperr data and format reply
wrapperrReply, err := WrapperrLoopData(user_id, config, wrapperr_data, wrapperr_reply)
if err != nil {
log.Println("Failed to wrap data. Error: " + err.Error())
context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to wrap data."})
context.Abort()
return
}
ipString = utilities.GetOriginIPString(context)
log.Println("8. Wrapperr request completed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ipString)
context.JSON(http.StatusOK, wrapperrReply)
return
}
func WrapperrDownloadDays(ID int, wrapperr_data []models.WrapperrDay, loop_interval int, config models.WrapperrConfig) ([]models.WrapperrDay, bool, error) {
// Define variables
var complete_date_loop bool = true
var use_loop_interval bool = true
var found_date bool = false
var found_date_index int = 0
// If loop_interval is less than one, do not utilize the loop interval and set use_loop_interval to false
if loop_interval < 1 {
use_loop_interval = false
}
// Go through each Tautulli server
for q := 0; q < len(config.TautulliConfig); q++ {
// Log Tautulli server
log.Println("Checking Tautulli server '" + config.TautulliConfig[q].TautulliName + "'.")
// Define object with end date from wrapped period
end_loop_date := time.Unix(int64(config.WrappedEnd), 0)
// Split the string containing libraries. If the result is zero, set the first object in the array to an empty string
libraries := strings.Split(config.TautulliConfig[q].TautulliLibraries, ",")
if len(libraries) < 1 {
libraries[0] = ""
}
// Create a date time object containing the beginning of the wrapped period. If that date time object is before the end_loop_date variable, keep adding one day, each iteration
for loop_time := time.Unix(int64(config.WrappedStart), 0); !loop_time.After(end_loop_date); loop_time = loop_time.AddDate(0, 0, 1) {
// Define string variable with current iteration date and variable with date time containing the current local time
current_loop_date := loop_time.Format("2006-01-02")
now := time.Now()
// Stop if time has reached current time or end loop date
if loop_time.After(now) || loop_time.After(end_loop_date) {
break
}
// Clean array to populate with results
wrapperr_day := models.WrapperrDay{
Date: current_loop_date,
Data: nil,
DataComplete: true,
TautulliServers: []string{},
}
// Declare variables to populate
found_date = false
found_date_index = 0
tautulli_server_processed := false
// Go through all dates in current dataset
for j := 0; j < len(wrapperr_data); j++ {
// Parse current date string
time_temp, err := time.Parse("2006-01-02", wrapperr_data[j].Date)
if err != nil {
log.Println(err)
}
// If current date in the dataset is current processing date
if time_temp.Format("2006-01-02") == loop_time.Format("2006-01-02") {
// Keep index, save bool as true,
found_date_index = j
found_date = true
// Look at processed servers for current server
for y := 0; y < len(wrapperr_data[j].TautulliServers); y++ {
if wrapperr_data[j].TautulliServers[y] == config.TautulliConfig[q].TautulliName {
tautulli_server_processed = true
}
}
// Keep current dataset
wrapperr_day = wrapperr_data[j]
// Stop looking
break
}
}
if found_date && wrapperr_data[found_date_index].DataComplete && tautulli_server_processed {
// Found the date, the data is complete and the server is processed for the day. Skip.
continue
} else if found_date && !wrapperr_data[found_date_index].DataComplete {
// The date is found, the data is not complete
log.Println("Date " + current_loop_date + " from server '" + config.TautulliConfig[q].TautulliName + "' marked as incomplete in cache. Refreshing.")
// Remove processed server to ensure re-processing by all servers
wrapperr_day.TautulliServers = []string{}
} else if !found_date || !tautulli_server_processed {
// No data found, download with empty dataset
log.Println("Downloading day: " + current_loop_date + " from server '" + config.TautulliConfig[q].TautulliName + "'.")
} else {
// Something is wrong. Quit.
log.Println("Unknown date error from server '" + config.TautulliConfig[q].TautulliName + "'. Skipping.")
continue
}
// Loop through selected libraries for Tautullli API calls
for library_loop := 0; library_loop < len(libraries); library_loop++ {
var library_str string = ""
var grouping string = ""
// If no libraries are selected do not specify one in API call to Tautulli
if libraries[library_loop] == "" {
library_str = ""
} else {
library_str = "&section_id=" + strings.TrimSpace(libraries[library_loop])
}
// Create string for selecting grouping feature in API call
if config.TautulliConfig[q].TautulliGrouping {
grouping = "1"
} else {
grouping = "0"
}
// Get data from Tautulli for server, day, and library
tautulli_data, err := modules.TautulliDownloadStatistics(config.TautulliConfig[q].TautulliPort, config.TautulliConfig[q].TautulliIP, config.TautulliConfig[q].TautulliHttps, config.TautulliConfig[q].TautulliRoot, config.TautulliConfig[q].TautulliApiKey, config.TautulliConfig[q].TautulliLength, library_str, grouping, current_loop_date)
if err != nil {
log.Println(err)
}
// 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,
OriginallyAvailableAt: tautulli_data[j].OriginallyAvailableAt,
}
// Append to day data
wrapperr_day.Data = append(wrapperr_day.Data, tautulli_entry)
}
}
}
// If the date is the current day, mark as incomplete so it can be refreshed the next time
if loop_time.Format("2006-01-02") == now.Format("2006-01-02") {
wrapperr_day.DataComplete = false
}
// Add current Tautulli server to processed servers for this day
if wrapperr_day.TautulliServers == nil || len(wrapperr_day.TautulliServers) == 0 {
var servers []string
servers = append(servers, config.TautulliConfig[q].TautulliName)
wrapperr_day.TautulliServers = servers
} else {
wrapperr_day.TautulliServers = append(wrapperr_day.TautulliServers, config.TautulliConfig[q].TautulliName)
}
// If found in dataset, update the values. if not found, append to dataset array.
if found_date {
wrapperr_data[found_date_index] = wrapperr_day
} else {
wrapperr_data = append(wrapperr_data, wrapperr_day)
}
// Change the loop interval to change date
if loop_interval > 0 && use_loop_interval {
loop_interval -= 1
}
// If loop interval is enabled, and reached 0, break the for loop and mark the data download as incomplete
if loop_interval == 0 && use_loop_interval {
complete_date_loop = false
break
}
}
// If loop interval is enabled, and reached 0, break the for loop and mark the data download as incomplete
if loop_interval == 0 && use_loop_interval {
complete_date_loop = false
break
}
}
return wrapperr_data, complete_date_loop, nil
}
func WrapperrLoopData(user_id int, config models.WrapperrConfig, wrapperr_data []models.WrapperrDay, wrapperr_reply models.WrapperrStatisticsReply) (models.WrapperrStatisticsReply, error) {
end_loop_date := time.Unix(int64(config.WrappedEnd), 0)
start_loop_date := time.Unix(int64(config.WrappedStart), 0)
top_list_limit := config.WrapperrCustomize.StatsTopListLength
var wrapperr_user_movie []models.TautulliEntry
var wrapperr_user_episode []models.TautulliEntry
var wrapperr_user_show []models.TautulliEntry
var wrapperr_user_track []models.TautulliEntry
var wrapperr_user_album []models.TautulliEntry
var wrapperr_user_artist []models.TautulliEntry
var wrapperr_year_user []models.WrapperrYearUserEntry
var wrapperr_year_movie []models.TautulliEntry
var wrapperr_year_show []models.TautulliEntry
var wrapperr_year_artist []models.TautulliEntry
for i := 0; i < len(wrapperr_data); i++ {
for j := 0; j < len(wrapperr_data[i].Data); j++ {
// Create object using datetime from history entry
current_loop_date := time.Unix(int64(wrapperr_data[i].Data[j].Date), 0)
// Stop if time has reached current time or end loop date
if current_loop_date.After(end_loop_date) || current_loop_date.Before(start_loop_date) {
break
}
// If the entry is a movie watched by the current user
if config.WrapperrCustomize.GetUserMovieStats && wrapperr_data[i].Data[j].MediaType == "movie" && wrapperr_data[i].Data[j].UserID == user_id {
// Skip entry if year is invalid (0 or some value where no movies could have been made) or duration is less than 5 min
if wrapperr_data[i].Data[j].Year < 1750 || wrapperr_data[i].Data[j].Duration < 300 {
continue
}
movie_found := false
// 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_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
movie_found = true
break
}
}
if !movie_found {
wrapperr_data[i].Data[j].Plays = 1
wrapperr_user_movie = append(wrapperr_user_movie, wrapperr_data[i].Data[j])
}
}
// If the entry is an episode watched by the current user
if config.WrapperrCustomize.GetUserShowStats && wrapperr_data[i].Data[j].MediaType == "episode" && wrapperr_data[i].Data[j].UserID == user_id {
episode_found := false
show_found := false
// 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_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
episode_found = true
break
}
}
if !episode_found {
wrapperr_data[i].Data[j].Plays = 1
wrapperr_user_episode = append(wrapperr_user_episode, wrapperr_data[i].Data[j])
}
// Look for show within pre-defined array
for d := 0; d < len(wrapperr_user_show); d++ {
if wrapperr_user_show[d].GrandparentTitle == wrapperr_data[i].Data[j].GrandparentTitle {
wrapperr_user_show[d].Plays += 1
wrapperr_user_show[d].Duration += wrapperr_data[i].Data[j].Duration
wrapperr_user_show[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter
show_found = true
break
}
}
if !show_found {
wrapperr_data[i].Data[j].Plays = 1
wrapperr_user_show = append(wrapperr_user_show, wrapperr_data[i].Data[j])
}
}
// If the entry is a track listened to by the current user
if config.WrapperrCustomize.GetUserMusicStats && wrapperr_data[i].Data[j].MediaType == "track" && wrapperr_data[i].Data[j].UserID == user_id {
track_found := false
album_found := false
artist_found := false
// 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_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
track_found = true
break
}
}
if !track_found {
wrapperr_data[i].Data[j].Plays = 1
wrapperr_user_track = append(wrapperr_user_track, wrapperr_data[i].Data[j])
}
// If track has unknown album or artist, skip
if wrapperr_data[i].Data[j].ParentTitle == "[Unknown Album]" || wrapperr_data[i].Data[j].GrandparentTitle == "[Unknown Artist]" {
continue
}
// 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_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
album_found = true
break
}
}
if !album_found {
wrapperr_data[i].Data[j].Plays = 1
wrapperr_user_album = append(wrapperr_user_album, wrapperr_data[i].Data[j])
}
// Look for artist within pre-defined array
for d := 0; d < len(wrapperr_user_artist); d++ {
if wrapperr_user_artist[d].GrandparentTitle == wrapperr_data[i].Data[j].GrandparentTitle {
wrapperr_user_artist[d].Plays += 1
wrapperr_user_artist[d].Duration += wrapperr_data[i].Data[j].Duration
wrapperr_user_artist[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter
artist_found = true
break
}
}
if !artist_found {
wrapperr_data[i].Data[j].Plays = 1
wrapperr_user_artist = append(wrapperr_user_artist, wrapperr_data[i].Data[j])
}
}
// If the entry is a movie watched by any user and the stat setting is configured
if config.WrapperrCustomize.GetYearStatsMovies && wrapperr_data[i].Data[j].MediaType == "movie" {
// Skip entry if year is invalid (0 or some value where no movies could have been made) or duration is less than 5 min
if wrapperr_data[i].Data[j].Year < 1750 || wrapperr_data[i].Data[j].Duration < 300 {
continue
}
movie_found := false
user_found := false
// 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_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
movie_found = true
break
}
}
// If movie was not found, add it to array
if !movie_found {
wrapperr_data[i].Data[j].Plays = 1
wrapperr_year_movie = append(wrapperr_year_movie, wrapperr_data[i].Data[j])
}
// Look for user within pre-defined array
for d := 0; d < len(wrapperr_year_user); d++ {
if wrapperr_year_user[d].UserID == wrapperr_data[i].Data[j].UserID {
wrapperr_year_user[d].Plays += 1
wrapperr_year_user[d].DurationMovies += wrapperr_data[i].Data[j].Duration
wrapperr_year_user[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter
user_found = true
break
}
}
// If user was not found, add it to array
if !user_found {
var user_entry = models.WrapperrYearUserEntry{
Plays: 1,
DurationMovies: wrapperr_data[i].Data[j].Duration,
PausedCounter: wrapperr_data[i].Data[j].PausedCounter,
User: wrapperr_data[i].Data[j].FriendlyName,
UserID: wrapperr_data[i].Data[j].UserID,
}
wrapperr_year_user = append(wrapperr_year_user, user_entry)
}
}
// If the entry is a show watched by any user and the stat setting is configured
if config.WrapperrCustomize.GetYearStatsShows && wrapperr_data[i].Data[j].MediaType == "episode" {
show_found := false
user_found := false
// Look for show within pre-defined array
for d := 0; d < len(wrapperr_year_show); d++ {
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
show_found = true
break
}
}
// If show was not found, add it to array
if !show_found {
wrapperr_data[i].Data[j].Plays = 1
wrapperr_year_show = append(wrapperr_year_show, wrapperr_data[i].Data[j])
}
// Look for user within pre-defined array
for d := 0; d < len(wrapperr_year_user); d++ {
if wrapperr_year_user[d].UserID == wrapperr_data[i].Data[j].UserID {
wrapperr_year_user[d].Plays += 1
wrapperr_year_user[d].DurationShows += wrapperr_data[i].Data[j].Duration
wrapperr_year_user[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter
user_found = true
break
}
}
// If user was not found, add it to array
if !user_found {
var user_entry = models.WrapperrYearUserEntry{
Plays: 1,
DurationShows: wrapperr_data[i].Data[j].Duration,
PausedCounter: wrapperr_data[i].Data[j].PausedCounter,
User: wrapperr_data[i].Data[j].FriendlyName,
UserID: wrapperr_data[i].Data[j].UserID,
}
wrapperr_year_user = append(wrapperr_year_user, user_entry)
}
}
// If the entry is a track listened to by any user and the stat setting is configured
if config.WrapperrCustomize.GetYearStatsMusic && wrapperr_data[i].Data[j].MediaType == "track" {
artist_found := false
user_found := false
// Look for artist within pre-defined array
for d := 0; d < len(wrapperr_year_artist); d++ {
if wrapperr_year_artist[d].GrandparentTitle == wrapperr_data[i].Data[j].GrandparentTitle {
wrapperr_year_artist[d].Plays += 1
wrapperr_year_artist[d].Duration += wrapperr_data[i].Data[j].Duration
wrapperr_year_artist[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter
artist_found = true
break
}
}
// If artist was not found, add it to array
if !artist_found {
wrapperr_data[i].Data[j].Plays = 1
wrapperr_year_artist = append(wrapperr_year_artist, wrapperr_data[i].Data[j])
}
// Look for user within pre-defined array
for d := 0; d < len(wrapperr_year_user); d++ {
if wrapperr_year_user[d].UserID == wrapperr_data[i].Data[j].UserID {
wrapperr_year_user[d].Plays += 1
wrapperr_year_user[d].DurationArtists += wrapperr_data[i].Data[j].Duration
wrapperr_year_user[d].PausedCounter += wrapperr_data[i].Data[j].PausedCounter
user_found = true
break
}
}
// If user was not found, add it to array
if !user_found {
var user_entry = models.WrapperrYearUserEntry{
Plays: 1,
DurationArtists: wrapperr_data[i].Data[j].Duration,
PausedCounter: wrapperr_data[i].Data[j].PausedCounter,
User: wrapperr_data[i].Data[j].FriendlyName,
UserID: wrapperr_data[i].Data[j].UserID,
}
wrapperr_year_user = append(wrapperr_year_user, user_entry)
}
}
}
}
// Format reply for personal movie details
if config.WrapperrCustomize.GetUserMovieStats && len(wrapperr_user_movie) > 0 {
// Sort user movie array by duration
sortutil.DescByField(wrapperr_user_movie, "Duration")
count := 0
for _, entry := range wrapperr_user_movie {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.User.UserMovies.Data.MoviesDuration = append(wrapperr_reply.User.UserMovies.Data.MoviesDuration, entry)
count += 1
}
// Sort user movie array by plays
sortutil.DescByField(wrapperr_user_movie, "Plays")
count = 0
for _, entry := range wrapperr_user_movie {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.User.UserMovies.Data.MoviesPlays = append(wrapperr_reply.User.UserMovies.Data.MoviesPlays, entry)
count += 1
}
// Find longest pause
sortutil.DescByField(wrapperr_user_movie, "PausedCounter")
wrapperr_reply.User.UserMovies.Data.UserMovieMostPaused.Duration = wrapperr_user_movie[0].Duration
wrapperr_reply.User.UserMovies.Data.UserMovieMostPaused.PausedCounter = wrapperr_user_movie[0].PausedCounter
wrapperr_reply.User.UserMovies.Data.UserMovieMostPaused.Plays = wrapperr_user_movie[0].Plays
wrapperr_reply.User.UserMovies.Data.UserMovieMostPaused.Title = wrapperr_user_movie[0].Title
wrapperr_reply.User.UserMovies.Data.UserMovieMostPaused.Year = wrapperr_user_movie[0].Year
// Find average movie completion, duration sum and play sum
movie_completion_sum := 0
movie_duration_sum := 0
for _, entry := range wrapperr_user_movie {
movie_completion_sum += entry.PercentComplete
movie_duration_sum += entry.Duration
}
wrapperr_reply.User.UserMovies.Data.UserMovieFinishingPercent = float64(movie_completion_sum) / float64(len(wrapperr_user_movie))
wrapperr_reply.User.UserMovies.Data.MovieDuration = movie_duration_sum
wrapperr_reply.User.UserMovies.Data.MoviePlays = len(wrapperr_user_movie)
// Find oldest movie
sortutil.AscByField(wrapperr_user_movie, "Year")
wrapperr_reply.User.UserMovies.Data.UserMovieOldest.Duration = wrapperr_user_movie[0].Duration
wrapperr_reply.User.UserMovies.Data.UserMovieOldest.PausedCounter = wrapperr_user_movie[0].PausedCounter
wrapperr_reply.User.UserMovies.Data.UserMovieOldest.Plays = wrapperr_user_movie[0].Plays
wrapperr_reply.User.UserMovies.Data.UserMovieOldest.Title = wrapperr_user_movie[0].Title
wrapperr_reply.User.UserMovies.Data.UserMovieOldest.Year = wrapperr_user_movie[0].Year
wrapperr_reply.User.UserMovies.Message = "All movies processed."
} else {
wrapperr_reply.User.UserMovies.Data.MoviesDuration = []models.TautulliEntry{}
wrapperr_reply.User.UserMovies.Data.MoviesPlays = []models.TautulliEntry{}
wrapperr_reply.User.UserMovies.Message = "No movies processed."
}
// Format reply for personal show details
if config.WrapperrCustomize.GetUserShowStats && len(wrapperr_user_show) > 0 {
// Sort user show array by duration
sortutil.DescByField(wrapperr_user_show, "Duration")
count := 0
for _, entry := range wrapperr_user_show {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.User.UserShows.Data.ShowsDuration = append(wrapperr_reply.User.UserShows.Data.ShowsDuration, entry)
count += 1
}
// Sort user show array by plays
sortutil.DescByField(wrapperr_user_show, "Plays")
count = 0
for _, entry := range wrapperr_user_show {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.User.UserShows.Data.ShowsPlays = append(wrapperr_reply.User.UserShows.Data.ShowsPlays, entry)
count += 1
}
// 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
wrapperr_reply.User.UserShows.Data.EpisodeDurationLongest.ParentTitle = wrapperr_user_episode[0].ParentTitle
wrapperr_reply.User.UserShows.Data.EpisodeDurationLongest.Plays = wrapperr_user_episode[0].Plays
wrapperr_reply.User.UserShows.Data.EpisodeDurationLongest.Title = wrapperr_user_episode[0].Title
// Find duration sum and play sum
episode_duration_sum := 0
for _, entry := range wrapperr_user_episode {
episode_duration_sum += entry.Duration
}
wrapperr_reply.User.UserShows.Data.ShowDuration = episode_duration_sum
wrapperr_reply.User.UserShows.Data.ShowPlays = len(wrapperr_user_show)
// Find show buddy...
wrapperr_reply.User.UserShows.Message = "All shows processed."
} else {
wrapperr_reply.User.UserShows.Data.ShowsDuration = []models.TautulliEntry{}
wrapperr_reply.User.UserShows.Data.ShowsPlays = []models.TautulliEntry{}
wrapperr_reply.User.UserShows.Message = "No shows processed."
}
// Format reply for personal music details
if config.WrapperrCustomize.GetUserMusicStats && len(wrapperr_user_track) > 0 {
// Sort user track array by duration
sortutil.DescByField(wrapperr_user_track, "Duration")
count := 0
for _, entry := range wrapperr_user_track {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.User.UserMusic.Data.TracksDuration = append(wrapperr_reply.User.UserMusic.Data.TracksDuration, entry)
count += 1
}
// Sort user track array by plays
sortutil.DescByField(wrapperr_user_track, "Plays")
count = 0
for _, entry := range wrapperr_user_track {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.User.UserMusic.Data.TracksPlays = append(wrapperr_reply.User.UserMusic.Data.TracksPlays, entry)
count += 1
}
// Sort user album array by duration
sortutil.DescByField(wrapperr_user_album, "Duration")
count = 0
for _, entry := range wrapperr_user_album {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.User.UserMusic.Data.AlbumsDuration = append(wrapperr_reply.User.UserMusic.Data.AlbumsDuration, entry)
count += 1
}
// Sort user album array by plays
sortutil.DescByField(wrapperr_user_album, "Plays")
count = 0
for _, entry := range wrapperr_user_album {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.User.UserMusic.Data.AlbumsPlays = append(wrapperr_reply.User.UserMusic.Data.AlbumsPlays, entry)
count += 1
}
// Sort user artist array by duration
sortutil.DescByField(wrapperr_user_artist, "Duration")
count = 0
for _, entry := range wrapperr_user_artist {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.User.UserMusic.Data.ArtistsDuration = append(wrapperr_reply.User.UserMusic.Data.ArtistsDuration, entry)
count += 1
}
// Sort user artist array by plays
sortutil.DescByField(wrapperr_user_artist, "Plays")
count = 0
for _, entry := range wrapperr_user_artist {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.User.UserMusic.Data.ArtistsPlays = append(wrapperr_reply.User.UserMusic.Data.ArtistsPlays, entry)
count += 1
}
// Find duration sum and play sum
track_duration_sum := 0
for _, entry := range wrapperr_user_track {
track_duration_sum += entry.Duration
}
wrapperr_reply.User.UserMusic.Data.TrackDuration = track_duration_sum
wrapperr_reply.User.UserMusic.Data.TrackPlays = len(wrapperr_user_track)
// Find oldest album
sortutil.AscByField(wrapperr_user_album, "Year")
var oldest_album_index = 0
for d := 0; d < len(wrapperr_user_album); d++ {
if wrapperr_user_album[d].Year > 1000 {
oldest_album_index = d
break
}
}
wrapperr_reply.User.UserMusic.Data.UserAlbumOldest.Duration = wrapperr_user_album[oldest_album_index].Duration
wrapperr_reply.User.UserMusic.Data.UserAlbumOldest.GrandparentTitle = wrapperr_user_album[oldest_album_index].GrandparentTitle
wrapperr_reply.User.UserMusic.Data.UserAlbumOldest.ParentTitle = wrapperr_user_album[oldest_album_index].ParentTitle
wrapperr_reply.User.UserMusic.Data.UserAlbumOldest.Plays = wrapperr_user_album[oldest_album_index].Plays
wrapperr_reply.User.UserMusic.Data.UserAlbumOldest.Year = wrapperr_user_album[oldest_album_index].Year
wrapperr_reply.User.UserMusic.Message = "All tracks processed."
} else {
wrapperr_reply.User.UserMusic.Data.TracksDuration = []models.TautulliEntry{}
wrapperr_reply.User.UserMusic.Data.TracksPlays = []models.TautulliEntry{}
wrapperr_reply.User.UserMusic.Message = "No tracks processed."
}
// Format reply for universal movie details
if config.WrapperrCustomize.GetYearStatsMovies && len(wrapperr_year_movie) > 0 {
// Sort year movie array by duration
sortutil.DescByField(wrapperr_year_movie, "Duration")
count := 0
for _, entry := range wrapperr_year_movie {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.YearStats.YearMovies.Data.MoviesDuration = append(wrapperr_reply.YearStats.YearMovies.Data.MoviesDuration, entry)
count += 1
}
// Sort year movie array by plays
sortutil.DescByField(wrapperr_year_movie, "Plays")
count = 0
for _, entry := range wrapperr_year_movie {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.YearStats.YearMovies.Data.MoviesPlays = append(wrapperr_reply.YearStats.YearMovies.Data.MoviesPlays, entry)
count += 1
}
// Find duration sum and play sum
movie_duration_sum := 0
movie_play_sum := 0
for _, entry := range wrapperr_year_movie {
movie_play_sum += entry.Plays
movie_duration_sum += entry.Duration
}
wrapperr_reply.YearStats.YearMovies.Data.MovieDuration = movie_duration_sum
wrapperr_reply.YearStats.YearMovies.Data.MoviePlays = movie_play_sum
wrapperr_reply.YearStats.YearMovies.Message = "All movies processed."
} else {
wrapperr_reply.YearStats.YearMovies.Data.MoviesDuration = []models.TautulliEntry{}
wrapperr_reply.YearStats.YearMovies.Data.MoviesPlays = []models.TautulliEntry{}
wrapperr_reply.YearStats.YearMovies.Message = "No movies processed."
}
// Format reply for universal show details
if config.WrapperrCustomize.GetYearStatsShows && len(wrapperr_year_show) > 0 {
// Sort year show array by duration
sortutil.DescByField(wrapperr_year_show, "Duration")
count := 0
for _, entry := range wrapperr_year_show {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.YearStats.YearShows.Data.ShowsDuration = append(wrapperr_reply.YearStats.YearShows.Data.ShowsDuration, entry)
count += 1
}
// Sort year show array by plays
sortutil.DescByField(wrapperr_year_show, "Plays")
count = 0
for _, entry := range wrapperr_year_show {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.YearStats.YearShows.Data.ShowsPlays = append(wrapperr_reply.YearStats.YearShows.Data.ShowsPlays, entry)
count += 1
}
// Find duration sum and play sum
show_duration_sum := 0
show_play_sum := 0
for _, entry := range wrapperr_year_show {
show_play_sum += entry.Plays
show_duration_sum += entry.Duration
}
wrapperr_reply.YearStats.YearShows.Data.ShowDuration = show_duration_sum
wrapperr_reply.YearStats.YearShows.Data.ShowPlays = show_play_sum
wrapperr_reply.YearStats.YearShows.Message = "All shows processed."
} else {
wrapperr_reply.YearStats.YearShows.Data.ShowsDuration = []models.TautulliEntry{}
wrapperr_reply.YearStats.YearShows.Data.ShowsPlays = []models.TautulliEntry{}
wrapperr_reply.YearStats.YearShows.Message = "No shows processed."
}
// Format reply for universal artist details
if config.WrapperrCustomize.GetYearStatsMusic && len(wrapperr_year_artist) > 0 {
// Sort year artist array by duration
sortutil.DescByField(wrapperr_year_artist, "Duration")
count := 0
for _, entry := range wrapperr_year_artist {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.YearStats.YearMusic.Data.ArtistsDuration = append(wrapperr_reply.YearStats.YearMusic.Data.ArtistsDuration, entry)
count += 1
}
// Sort year artist array by plays
sortutil.DescByField(wrapperr_year_artist, "Plays")
count = 0
for _, entry := range wrapperr_year_artist {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.YearStats.YearMusic.Data.ArtistsPlays = append(wrapperr_reply.YearStats.YearMusic.Data.ArtistsPlays, entry)
count += 1
}
// Find duration sum and play sum
music_duration_sum := 0
music_play_sum := 0
for _, entry := range wrapperr_year_artist {
music_play_sum += entry.Plays
music_duration_sum += entry.Duration
}
wrapperr_reply.YearStats.YearMusic.Data.MusicDuration = music_duration_sum
wrapperr_reply.YearStats.YearMusic.Data.MusicPlays = music_play_sum
wrapperr_reply.YearStats.YearMusic.Message = "All tracks processed."
} else {
wrapperr_reply.YearStats.YearMusic.Data.ArtistsDuration = []models.TautulliEntry{}
wrapperr_reply.YearStats.YearMusic.Data.ArtistsPlays = []models.TautulliEntry{}
wrapperr_reply.YearStats.YearMusic.Message = "No tracks processed."
}
// Create userID to obfuscated name struct
type obfuscatedUser struct {
userID int
newName string
}
// Create array to store userIDs and their obfucasted name
obfuscateCatalog := []obfuscatedUser{}
// Create name generator engine
seed := time.Now().UTC().UnixNano()
nameGenerator := namegenerator.NewNameGenerator(seed)
// Format reply for universal user details
if (config.WrapperrCustomize.GetYearStatsMusic || config.WrapperrCustomize.GetYearStatsMovies || config.WrapperrCustomize.GetYearStatsShows) && config.WrapperrCustomize.GetYearStatsLeaderboard && len(wrapperr_year_user) > 0 {
// Create new array with duration sum, then sort year users array by duration
var wrapperr_year_user_summed []models.WrapperrYearUserEntry
for d := 0; d < len(wrapperr_year_user); d++ {
wrapperr_year_user[d].Duration = wrapperr_year_user[d].DurationMovies + wrapperr_year_user[d].DurationShows + wrapperr_year_user[d].DurationArtists
if config.WrapperrCustomize.ObfuscateOtherUsers {
// Declare variables
var newName string
currentUserID := wrapperr_year_user[d].UserID
// Give new name
if currentUserID == 0 {
newName = "Managed user"
} else {
// Generate random name
newNameGen := nameGenerator.Generate()
// Try to improve random name
newNameGenArray := strings.Split(newNameGen, "-")
newNamePartOne := newNameGenArray[0]
newNamePartTwo := newNameGenArray[1]
newName = strings.Title(newNamePartOne) + " " + strings.Title(newNamePartTwo)
}
// Create obfucasted struct type
obfuscatedUser := obfuscatedUser{
userID: wrapperr_year_user[d].UserID,
newName: newName,
}
// Verify user is not already in catalog
userIDFound := false
for o := 0; o < len(obfuscateCatalog); o++ {
if obfuscateCatalog[o].userID == currentUserID {
userIDFound = true
break
}
}
// If not found, push to catalog
if !userIDFound {
obfuscateCatalog = append(obfuscateCatalog, obfuscatedUser)
}
// Obfuscate user in dataset if it is not themself
if currentUserID != user_id {
wrapperr_year_user[d].FriendlyName = newName
wrapperr_year_user[d].User = newName
wrapperr_year_user[d].UserID = 0
}
}
wrapperr_year_user_summed = append(wrapperr_year_user_summed, wrapperr_year_user[d])
}
sortutil.DescByField(wrapperr_year_user, "Duration")
count := 0
for _, entry := range wrapperr_year_user {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.YearStats.YearUsers.Data.UsersDuration = append(wrapperr_reply.YearStats.YearUsers.Data.UsersDuration, entry)
count += 1
}
// Sort year show array by plays
sortutil.DescByField(wrapperr_year_user, "Plays")
count = 0
for _, entry := range wrapperr_year_user {
if count >= top_list_limit && top_list_limit != 0 {
break
}
wrapperr_reply.YearStats.YearUsers.Data.UsersPlays = append(wrapperr_reply.YearStats.YearUsers.Data.UsersPlays, entry)
count += 1
}
wrapperr_reply.YearStats.YearMovies.Message = "All users processed."
// Scrub the data after ordering array
if !config.WrapperrCustomize.GetYearStatsLeaderboardNumbers {
for index, _ := range wrapperr_reply.YearStats.YearUsers.Data.UsersPlays {
wrapperr_reply.YearStats.YearUsers.Data.UsersPlays[index].Duration = 0
wrapperr_reply.YearStats.YearUsers.Data.UsersPlays[index].DurationArtists = 0
wrapperr_reply.YearStats.YearUsers.Data.UsersPlays[index].DurationMovies = 0
wrapperr_reply.YearStats.YearUsers.Data.UsersPlays[index].DurationShows = 0
wrapperr_reply.YearStats.YearUsers.Data.UsersPlays[index].Plays = 0
wrapperr_reply.YearStats.YearUsers.Data.UsersPlays[index].PausedCounter = 0
}
for index, _ := range wrapperr_reply.YearStats.YearUsers.Data.UsersDuration {
wrapperr_reply.YearStats.YearUsers.Data.UsersDuration[index].Duration = 0
wrapperr_reply.YearStats.YearUsers.Data.UsersDuration[index].DurationArtists = 0
wrapperr_reply.YearStats.YearUsers.Data.UsersDuration[index].DurationMovies = 0
wrapperr_reply.YearStats.YearUsers.Data.UsersDuration[index].DurationShows = 0
wrapperr_reply.YearStats.YearUsers.Data.UsersDuration[index].Plays = 0
wrapperr_reply.YearStats.YearUsers.Data.UsersDuration[index].PausedCounter = 0
}
}
} else {
wrapperr_reply.YearStats.YearUsers.Data.UsersDuration = []models.WrapperrYearUserEntry{}
wrapperr_reply.YearStats.YearUsers.Data.UsersPlays = []models.WrapperrYearUserEntry{}
wrapperr_reply.YearStats.YearMovies.Message = "No users processed."
}
// Get show buddy
if config.WrapperrCustomize.GetUserShowBuddy {
if len(wrapperr_reply.User.UserShows.Data.ShowsDuration) < 1 {
wrapperr_reply.User.UserShows.Data.ShowBuddy.Message = "Show buddy is enabled but no top show was not found."
wrapperr_reply.User.UserShows.Data.ShowBuddy.Error = true
} else {
err, buddy_name, buddy_id, buddy_found, buddy_duration := GetUserShowBuddy(config, wrapperr_reply.User.UserShows.Data.ShowsDuration[0], user_id, wrapperr_data)
var show_buddy models.WrapperrShowBuddy
if err != nil {
log.Println("Show buddy threw error: ")
log.Println(err)
show_buddy.Message = "Failed to retrieve show buddy."
show_buddy.Error = true
} else {
// Obfuscate username of buddy if enabled
if config.WrapperrCustomize.ObfuscateOtherUsers {
// Verify user is not already in catalog of users to maintain name consistancy
userIDFound := false
userNameFound := ""
for o := 0; o < len(obfuscateCatalog); o++ {
if obfuscateCatalog[o].userID == buddy_id {
userIDFound = true
userNameFound = obfuscateCatalog[o].newName
break
}
}
// If not found, give random name
// If found, give buddy that username
if !userIDFound {
buddy_name = nameGenerator.Generate()
} else {
buddy_name = userNameFound
}
// Remove buddy id
buddy_id = 0
}
show_buddy.Message = "Show buddy retrieved."
show_buddy.Error = false
show_buddy.BuddyName = buddy_name
show_buddy.BuddyID = buddy_id
show_buddy.BuddyDuration = buddy_duration
show_buddy.BuddyFound = buddy_found
log.Println("Show buddy retrieved.")
}
wrapperr_reply.User.UserShows.Data.ShowBuddy = show_buddy
}
} else {
wrapperr_reply.User.UserShows.Data.ShowBuddy.Message = "Show buddy is disabled in the settings."
wrapperr_reply.User.UserShows.Data.ShowBuddy.Error = true
}
return wrapperr_reply, nil
}
func GetUserShowBuddy(config models.WrapperrConfig, top_show models.TautulliEntry, user_id int, wrapperr_data []models.WrapperrDay) (error, string, int, bool, int) {
var top_show_users []models.WrapperrYearUserEntry
var top_show_buddy_name = "Something went wrong."
var top_show_buddy_id = 0
var top_show_buddy_duration = 0
var top_show_buddy_found = false
var error_bool = errors.New("Something went wrong.")
end_loop_date := time.Unix(int64(config.WrappedEnd), 0)
start_loop_date := time.Unix(int64(config.WrappedStart), 0)
for i := 0; i < len(wrapperr_data); i++ {
for j := 0; j < len(wrapperr_data[i].Data); j++ {
// Create object using datetime from history entry
current_loop_date := time.Unix(int64(wrapperr_data[i].Data[j].Date), 0)
// Stop if time has reached current time or end loop date
if current_loop_date.After(end_loop_date) || current_loop_date.Before(start_loop_date) {
continue
}
if wrapperr_data[i].Data[j].GrandparentTitle == top_show.GrandparentTitle {
user_found := false
// Look for user within pre-defined array
for d := 0; d < len(top_show_users); d++ {
if top_show_users[d].UserID == wrapperr_data[i].Data[j].UserID {
top_show_users[d].Plays += 1
top_show_users[d].Duration += wrapperr_data[i].Data[j].Duration
user_found = true
break
}
}
// If user was not found, add it to array
if !user_found {
var user_entry = models.WrapperrYearUserEntry{
Plays: 1,
Duration: wrapperr_data[i].Data[j].Duration,
FriendlyName: wrapperr_data[i].Data[j].FriendlyName,
UserID: wrapperr_data[i].Data[j].UserID,
}
top_show_users = append(top_show_users, user_entry)
}
}
}
}
if len(top_show_users) < 2 {
return nil, "None", 0, false, 0
}
sortutil.DescByField(top_show_users, "Duration")
// Find requesters index
user_index := 0
for index, user := range top_show_users {
if user_id == user.UserID {
user_index = index
break
}
}
// Find relative users
for index, user := range top_show_users {
if user.UserID != user_id && len(top_show_users) == 2 {
top_show_buddy_name = user.FriendlyName
top_show_buddy_id = user.UserID
top_show_buddy_duration = user.Duration
top_show_buddy_found = true
error_bool = nil
break
}
if user.UserID != user_id && index == user_index-1 {
top_show_buddy_name = user.FriendlyName
top_show_buddy_id = user.UserID
top_show_buddy_duration = user.Duration
top_show_buddy_found = true
error_bool = nil
break
}
if user.UserID != user_id && index == user_index+1 {
top_show_buddy_name = user.FriendlyName
top_show_buddy_id = user.UserID
top_show_buddy_duration = user.Duration
top_show_buddy_found = true
error_bool = nil
break
}
}
return error_bool, top_show_buddy_name, top_show_buddy_id, top_show_buddy_found, top_show_buddy_duration
}