wrapperr/route_statistics.go
aunefyren 3b7e3c08d6 Squashed commit of the following:
commit 23a91e9daad1c482435581c69c756257ac703149
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Mon Jul 4 17:47:18 2022 +0200

    Removed tmp text

commit 1c09e583b2fa5e30a36c809e85fc43b328e3ad7f
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sun Jul 3 15:33:36 2022 +0200

    Read log in admin interface

commit 4ae253185a33bb13a94b1a200bf3aa92a62fb17d
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sat Jul 2 00:27:39 2022 +0200

    Added comments

commit 89cabc36e86ae541fa9b001928211ba48e9ba48b
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sat Jul 2 00:08:47 2022 +0200

    Update index.js

commit 12279e7ed47eeb45454ab525aa745eeaac0da180
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sat Jul 2 00:02:36 2022 +0200

    Update index.html

commit a1c76b3e4d06cd25f7795390155521f2c341465f
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 23:56:39 2022 +0200

    Errors and frontend

    Cookies are more stable
    Errors are more concise

commit ee170aeb3acf3f586c93e7b142a08bbb32a17134
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 22:33:27 2022 +0200

    Better error handling

commit 6317a1fa15ccfaf91fb2d8737e346621d9884008
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 22:18:45 2022 +0200

    Added returns on errors

commit 7126ddf2fbb824de0906fd6d7bfc2ca8acac0fdf
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 22:09:07 2022 +0200

    Added ASCII Art

commit 1fd45ede34abe6ae9555d9d6f23f87a2e67dba3b
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 21:39:24 2022 +0200

    Form CSS tweak

commit 9861c662cd5f2b66721b3d2f2030d92d84bdf8bc
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 21:24:03 2022 +0200

    Update route_admin_auth.go

commit 119de6b2b1124266aae6cd5fec90fff2d2ddd24f
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 21:19:36 2022 +0200

    Update route_admin_auth.go

commit 87a43dfc4945fe5ce45b8d9d06c7dcd6a5c9ee0c
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 21:17:21 2022 +0200

    Bugs and CSS

    Time zone validation
    Caching log is prettier
    Return bug on certain API endpoints fixed

commit 3e81df71516572944b8e46bad129da82235b1b85
Author: Aune <31650531+aunefyren@users.noreply.github.com>
Date:   Fri Jul 1 18:36:21 2022 +0200

    Create codeql-analysis.yml

commit 32d05f3fff0231139df0e590b98097d6c6e3cdfb
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 18:23:05 2022 +0200

    Update index.js

commit c3fcdee18406e1a9d96b26c85169e1db365bb36a
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 18:15:38 2022 +0200

    Capital W

commit a243d310597b9f8148621690b9662f93b8467e83
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 18:08:53 2022 +0200

    Update index.js

commit 068739fc76b96c4379bf93aa39d11db1c2dfcada
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 17:58:48 2022 +0200

    Update README.md

commit d61074bde69f5dac1a1a2dd6b2164564131f53d1
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 17:56:55 2022 +0200

    Docker-compose mistake

commit 3036905d92d23cb0664559471fa9e0a81565fa64
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 17:55:30 2022 +0200

    README touchups

commit 6f07775121cec5cddf17f25efe349b300d43b8e6
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jul 1 17:48:10 2022 +0200

    Rewrote README

commit 58a26b11c77e6d21be0f433cd22c4da46f5d0450
Author: Aune <oystein.sverre@gmail.com>
Date:   Fri Jul 1 10:00:58 2022 +0200

    Changed release workflow

commit ffeea7ca689f405b6e629411f5bc9fc8c19e6b34
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Thu Jun 30 20:09:01 2022 +0200

    Updated workflow to 1.18

commit aa321e87729673b4ce047f87b68f7223ea02d159
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Thu Jun 30 19:58:44 2022 +0200

    Update README.md

commit 5c4e59474882689f6dadf61f140a4ec18f744a1e
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Thu Jun 30 18:16:30 2022 +0200

    CSS fixes

commit 97f3fc57a389e5dd637b560e48fee86c4163dcd5
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Wed Jun 29 22:00:32 2022 +0200

    Option to disable winter theme

    And loading screen

commit cf804312c7b9b72b416ef2a53947efab79ab604b
Author: Aune <31650531+aunefyren@users.noreply.github.com>
Date:   Tue Jun 28 22:05:43 2022 +0200

    Update README.md

commit 362a15ee1daa2464ad4f27b7e3107a63d736e591
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Tue Jun 28 18:41:19 2022 +0200

    Fixed buddy bug

commit 58f4d0060230c2349634689438088dcdf48d7715
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Mon Jun 27 19:26:37 2022 +0200

    Finalized show buddy

commit 0f89a1df974af9947d9e297f3c662d4082c0403d
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sun Jun 26 12:47:37 2022 +0200

    Improved logging

commit b57cb24928c2e213c38ce566478af11937e6e4e3
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sat Jun 25 13:42:14 2022 +0200

    Finalized shareable links

    Links can be deleted
    Links are retrieved to the front page
    Links can be copied to clipboard

commit 000022b882877bc306e13203578a3d1e184afc9a
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sun Jun 12 18:47:34 2022 +0200

    Get share links now functional

commit df697f68b2bcedc7c623d9d9ec291a9625b62160
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sun Jun 12 00:29:52 2022 +0200

    Share link formatting bug

commit 3b357ca72767fcbb9742a935f29770ab488ac36f
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sun Jun 12 00:26:15 2022 +0200

    Share link creation

    Started implementation
    Missing get and delete functionality
    Added delete cache option to Tautulli settings

commit 54e079951ff131d9177fd0f4a79238c03043d875
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Jun 10 19:13:01 2022 +0200

    Fixed bugs

    Shows with seasons in different years were split on top lists
    Amount of media plays was not unique to media-release, just amount of plays overall
    Fixed display for music minutes
    Logging can now be disabled
    Tautulli libraries were not included in API calls

commit 26508af56a182b9611affb4401e5db494ae27c8b
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Tue Jun 7 20:38:08 2022 +0200

    Fixed bug where top-lists under 10 crashed the API

commit 0f28e39c8d4fe444c8d30caaaee9ca138d469270
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Tue Jun 7 20:17:41 2022 +0200

    Fixed bug for year users duration-sum

commit 1e3cb11637cfe001936d4b950b28564d2eec2a4b
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sun May 29 19:51:15 2022 +0200

    Statistics retrieval finished

    Revamped config get/set functions
    Redid some UI explanations
    Renamed template JSON

commit 93390d469bab45560574e678a88b38dcef60f100
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Mon May 23 22:23:31 2022 +0200

    Bug problem

commit 3eb49b8da1c77d33923913e817cb41201cab3963
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sun May 22 23:48:11 2022 +0200

    Users

commit aa6b3ec5121bb73cbe5828d9f42e1fd14514ddbd
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sun May 22 13:11:35 2022 +0200

    More stats

    Leaderboard and show buddy missing
    Working on bug that resets config (?!)

commit 29742a2b6305a3835c63fc2c7760018b3e2267f0
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sun May 22 00:41:19 2022 +0200

    Basis for stats retrieval

    personal movies, shows and music is functional

commit a0df0759a76689638d47657abde755cc34dd4a1b
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri May 20 17:06:24 2022 +0200

    Removed Docker folder

commit 3e192997c08a5406a3d98e850218a674559f9ff0
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri May 20 17:06:05 2022 +0200

    Renaming and Pop-up fix

    Pop ups should now not be blocked. Hopefully...

commit 57b2d03fb57762c6c2aec7433a4f2affd6faa090
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Apr 29 15:37:26 2022 +0200

    CSS changes

    New rounded corners
    Transparent background colors
    Yellow borders
    Blurred background image
    Better footer placement
    More depth to snow particles

commit b562e75bee6ff612019fa08d221fbf1ee69edd11
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Apr 29 12:37:18 2022 +0200

    API endpoints changes

    Get wrapperr-version now also retrieves config-state
    Login via Plex Auth disabled if Wrapperr is unconfigured
    Cookies changed to strict samesite value
    Logging changes

commit e01bfc41cf46b3d7bc3b6627c93b67c0f70aa718
Author: Aune <31650531+aunefyren@users.noreply.github.com>
Date:   Fri Apr 29 10:47:12 2022 +0200

    Update README.md

    More shields

commit 2d2893cb9b212016c3455fd4f8dd3a5b229550ba
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Wed Apr 27 22:09:24 2022 +0200

    Comments and time zone fixes

commit 271c16b00e0eb4d58a97ed3b82f0e96993a7d853
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Wed Apr 27 16:26:44 2022 +0200

    Caching and caching loop

commit 44376b4d57765258602ec585e59b0825938d3c77
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Wed Apr 27 10:33:18 2022 +0200

    Pulling Tautulli History

    Moved Tautulli functions to their own file

commit 5355dafc68c5c2ed5a7c7e413388981438314500
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Tue Apr 26 22:00:49 2022 +0200

    Start of Tautulli data collection

    Added time-zone setting in code
    Created cache structs

commit 8bcce997c3b1a0556752ff99b5925cce7e18c4b6
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Tue Apr 26 17:25:06 2022 +0200

    Comments and caching mode

commit 6e951efbcd6acb0b442a691ceb9919e95d3e9e98
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Sat Apr 23 19:03:00 2022 +0200

    User details fetch (From Plex & Tautulli)

commit cb5b9384c0b716ef33770a0f96f6e4c0601cff50
Author: Aune <31650531+aunefyren@users.noreply.github.com>
Date:   Sat Apr 23 12:07:57 2022 +0200

    Shields.io stats

commit 343e0ac727576652ccd81e565a7a385879b1bfb4
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Mon Apr 18 22:14:49 2022 +0200

    Begun stats function

    Moved Taut test to separate function from API endpoint

commit b7e34224d7524358a4b59bd667d75b3b6527361c
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Apr 15 02:26:54 2022 +0200

    Update util.go

commit 4a2598f77cce3c5e9d5fc66e9e870bf3cd4bfb51
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Apr 15 02:22:34 2022 +0200

    Error handling

commit 09b80cf16c054d2767cd1cf50ae17f7f72268a53
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Thu Apr 14 15:01:59 2022 +0200

    Updated readme to explain current standing

commit 273b08f43ca317b6cd4ede01efb5040e63cc9839
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Thu Apr 14 14:46:34 2022 +0200

    Removed test-log commands

commit f2e85beb9d4aa9157b32933c50495c5f029787e4
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Thu Apr 14 14:26:17 2022 +0200

    Removed legacy PHP files

commit aaafea6da38173224d1b15063d4f32468ec12879
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Thu Apr 14 14:24:41 2022 +0200

    URL builder & test Tautulli connection

commit a02a172a7fc85a5a595fdb4003b00c8aa7d5f475
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Thu Apr 14 02:37:48 2022 +0200

    Moved Plex Auth Get Pin to front end

    By moving this API call to the front end (JS), the IP address is identical when logging in. This removes the red warning.

commit 744fae84a088cc77ba86d3c3687aff8d9caa77d7
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Thu Apr 14 01:33:40 2022 +0200

    Plex Auth

    Plex Auth login, validation and JWT token creation

commit 820bbca58a5679ef6a1e98ec586442cd841ac958
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Wed Apr 13 20:45:00 2022 +0200

    Update config

    API now updates config file

commit cb581d69007b32e8e924ac22ba66c923dd9eef23
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Wed Apr 13 15:49:04 2022 +0200

    More API functions

    - Admin login
    - Config/Admin file creation
    - Config API retrieval

commit f832df727de5c5fe583f03e2d8906714ceaf8017
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Tue Apr 12 09:30:58 2022 +0200

    Functional API endpoints

    Some basic functionality
    - Retrieve wrapperr version
    - Retrieve admin state
    - Create admin

commit 76193b7230e90d578bc561c8f479a87885c0b50f
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Mon Apr 11 16:51:35 2022 +0200

    Config loaders

commit c197a1b91b6307432e560756da97887bbfb36de5
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Fri Apr 8 17:20:36 2022 +0200

    Config files

commit 2ef850426a14ecf3dc39ab4b122fb479bb1e8f11
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Thu Apr 7 13:16:01 2022 +0200

    Functional jwt validattion and creation

commit a52de743daeb3dce18db7e0848eea37d4c35b707
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Wed Apr 6 22:25:01 2022 +0200

    Auth token jwt

commit 2f446a484ad6e4330a3a40cc708a543ca47ce148
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Wed Apr 6 14:19:49 2022 +0200

    Middleware

commit 1a1bc6ef09b9bce995d9d4b3f4bf7b0d1ead988a
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Wed Apr 6 09:14:02 2022 +0200

    Added more comments

commit 8c36dc74a98aa66565851ab01aff91e641287f06
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Wed Apr 6 09:08:26 2022 +0200

    Port start argument

    Also included as ENV in Dockerfile

commit 6bfbbdf5c3f955ab680ee4b47863bd7f017ffd5a
Author: Aune <31650531+aunefyren@users.noreply.github.com>
Date:   Tue Apr 5 23:31:36 2022 +0200

    Whopsie

commit 7600f2812e01d266f3488ed32a49d1591740a3e6
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Tue Apr 5 20:05:13 2022 +0200

    Routing

commit 0dbb1c756fcd411e60b73752554d75c8bf04f0b5
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Tue Apr 5 18:20:12 2022 +0200

    Added warnings & Dockerfile

commit a787a0acd41ab182ca0607dc3c1796a28ce947bd
Author: aunefyren <oystein.sverre@gmail.com>
Date:   Tue Apr 5 17:51:48 2022 +0200

    Go branch init

    The assets are served, but no API
2022-07-04 17:47:59 +02:00

1222 lines
42 KiB
Go

package main
import (
"encoding/json"
"errors"
"io/ioutil"
"log"
"net/http"
"strconv"
"strings"
"time"
"github.com/patrickmn/sortutil"
)
func ApiWrapperGetStatistics(w http.ResponseWriter, r *http.Request) {
ip_string := GetOriginIPString(w, r)
log.Println("New /get/statistics request." + ip_string)
bool_state, err := GetConfigState()
if err != nil {
log.Println(err)
respond_default_error(w, r, errors.New("Failed to retrieve confguration state."), 500)
return
} else if !bool_state {
log.Println("Wrapperr get statistics failed. Configuration state function retrieved false response.")
respond_default_error(w, r, errors.New("Can't retrieve statistics because Wrapperr is not configured."), 400)
return
}
config, err := GetConfig()
if err != nil {
log.Println(err)
respond_default_error(w, r, errors.New("Failed to load Wrapperr configuration."), 500)
return
}
log.Println("1. Configuration check passed." + ip_string)
tautulli_state, err := TautulliTestConnection(config.TautulliConfig.TautulliPort, config.TautulliConfig.TautulliIP, config.TautulliConfig.TautulliHttps, config.TautulliConfig.TautulliRoot, config.TautulliConfig.TautulliApiKey)
if err != nil {
log.Println(err)
respond_default_error(w, r, errors.New("Failed to reach Tautulli server."), 500)
return
} else if !tautulli_state {
log.Println("Failed to ping Tautulli server before retrieving statistics.")
respond_default_error(w, r, errors.New("Failed to reach Tautulli server."), 400)
return
}
log.Println("2. Tautulli check passed." + ip_string)
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
payload, err := AuthorizeToken(w, r)
// 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(err)
respond_default_error(w, r, errors.New("Failed to authorize request."), 401)
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 := PlexAuthValidateToken(payload.AuthToken, config.ClientKey, config.WrapperrVersion)
if err != nil {
log.Println(err)
respond_default_error(w, r, errors.New("Could not validate Plex Auth login."), 500)
return
}
user_name = plex_object.Username
user_id = plex_object.ID
}
log.Println("3. Auth check passed." + ip_string)
// Read payload from Post input
reqBody, _ := ioutil.ReadAll(r.Body)
var wrapperr_request SearchWrapperrRequest
json.Unmarshal(reqBody, &wrapperr_request)
// If auth is not passed, caching mode is false, and no PlexIdentity was recieved, 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.")
respond_default_error(w, r, errors.New("Invalid search parameter."), 400)
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 {
new_id, new_username, err := TautulliGetUserId(config.TautulliConfig.TautulliPort, config.TautulliConfig.TautulliIP, config.TautulliConfig.TautulliHttps, config.TautulliConfig.TautulliRoot, config.TautulliConfig.TautulliApiKey, wrapperr_request.PlexIdentity)
if err != nil {
log.Println(err)
respond_default_error(w, r, errors.New("Could not find user."), 500)
return
}
user_name = new_username
user_id = new_id
}
// 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.")
respond_default_error(w, r, errors.New("You can not retrieve stats as admin."), 400)
return
}
// If caching mode is true and user is not admin, return bad request error
if wrapperr_request.CachingMode && !admin {
log.Println("Caching mode recieved, but user was not verified as admin.")
respond_default_error(w, r, errors.New("Only the admin can perform caching."), 401)
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.")
respond_default_error(w, r, errors.New("Caching mode enabled, but the cache feature is disabled in the settings."), 500)
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.")
respond_default_error(w, r, errors.New("User validation error."), 500)
return
}
log.Println("4. User details confirmed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ip_string)
// Create empty array object for each day in Wrapped period. If cache is enabled, call GetCache() and replace the empty object.
wrapperr_data := []WrapperrDay{}
if config.UseCache {
wrapperr_data, err = GetCache()
if err != nil {
log.Println(err)
respond_default_error(w, r, errors.New("Failed to load cache file."), 500)
return
}
}
log.Println("5. Cache stage completed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ip_string)
// 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(err)
}
log.Println("6. Tautulli refresh/download stage completed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ip_string)
// If cache is enabled, send the object to SaveCache() for later use.
if config.UseCache {
err = SaveCache(&wrapperr_data)
if err != nil {
log.Println(err)
respond_default_error(w, r, errors.New("Failed to save new cache file for "+user_name+" ("+strconv.Itoa(user_id)+")."), 500)
return
}
}
log.Println("7. Cache saving stage completed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ip_string)
// If caching mode is in use, stop the proccess here and return the result to the user
if wrapperr_request.CachingMode {
boolean_reply := BooleanReply{
Message: "Completed caching request.",
Error: false,
Data: wrapperr_data_complete,
}
ip_string := GetOriginIPString(w, r)
log.Println("Caching request completed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ip_string)
respondWithJSON(w, http.StatusOK, boolean_reply)
return
}
// Create reply object
var wrapperr_reply 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
wrapperr_reply, err = WrapperrLoopData(user_id, config, wrapperr_data, wrapperr_reply)
ip_string = GetOriginIPString(w, r)
log.Println("8. Wrapperr request completed for " + user_name + " (" + strconv.Itoa(user_id) + ")." + ip_string)
respondWithJSON(w, http.StatusOK, wrapperr_reply)
return
}
func WrapperrDownloadDays(ID int, wrapperr_data []WrapperrDay, loop_interval int, config *WrapperrConfig) ([]WrapperrDay, bool, error) {
// Define object with end date from wrapped period
end_loop_date := time.Unix(int64(config.WrappedEnd), 0)
// Define variables
var complete_date_loop bool = true
var use_loop_interval bool = true
// 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
}
// 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.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
}
var found_date bool = false
var found_date_index int = 0
for j := 0; j < len(wrapperr_data); j++ {
loc, err := time.LoadLocation(config.Timezone)
if err != nil {
log.Println(err)
}
time_temp, err := time.ParseInLocation("2006-01-02", wrapperr_data[j].Date, loc)
if err != nil {
log.Println(err)
}
if time_temp.Equal(loop_time) {
found_date_index = j
found_date = true
break
}
}
if found_date && wrapperr_data[found_date_index].DataComplete {
continue
} else if found_date && !wrapperr_data[found_date_index].DataComplete {
log.Println("Date " + current_loop_date + " marked as incomplete in cache. Refreshing.")
} else if !found_date {
log.Println("Downloading day: " + current_loop_date)
} else {
log.Println("Unknown date error. Skipping.")
continue
}
// Clean array to populate with results
wrapperr_day := WrapperrDay{
Date: current_loop_date,
Data: nil,
DataComplete: true,
}
// Loop through selected libraries
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])
}
if config.TautulliConfig.TautulliGrouping {
grouping = "1"
} else {
grouping = "0"
}
tautulli_data, err := TautulliDownloadStatistics(config.TautulliConfig.TautulliPort, config.TautulliConfig.TautulliIP, config.TautulliConfig.TautulliHttps, config.TautulliConfig.TautulliRoot, config.TautulliConfig.TautulliApiKey, config.TautulliConfig.TautulliLength, library_str, grouping, current_loop_date)
if err != nil {
log.Println(err)
}
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 := 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,
}
wrapperr_day.Data = append(wrapperr_day.Data, tautulli_entry)
}
}
}
if loop_time.Format("2006-01-02") == now.Format("2006-01-02") {
wrapperr_day.DataComplete = false
}
if found_date {
wrapperr_data[found_date_index] = wrapperr_day
} else {
wrapperr_data = append(wrapperr_data, wrapperr_day)
}
if loop_interval > 0 && use_loop_interval {
loop_interval -= 1
}
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 *WrapperrConfig, wrapperr_data []WrapperrDay, wrapperr_reply WrapperrStatisticsReply) (WrapperrStatisticsReply, error) {
end_loop_date := time.Unix(int64(config.WrappedEnd), 0)
start_loop_date := time.Unix(int64(config.WrappedStart), 0)
top_list_limit := 10
var wrapperr_user_movie []TautulliEntry
var wrapperr_user_episode []TautulliEntry
var wrapperr_user_show []TautulliEntry
var wrapperr_user_track []TautulliEntry
var wrapperr_user_album []TautulliEntry
var wrapperr_user_artist []TautulliEntry
var wrapperr_year_user []WrapperrYearUserEntry
var wrapperr_year_movie []TautulliEntry
var wrapperr_year_show []TautulliEntry
var wrapperr_year_artist []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_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_user_episode[d].Title == wrapperr_data[i].Data[j].Title {
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_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_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_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 = 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].Year == wrapperr_data[i].Data[j].Year && 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 = 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 = 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 {
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 {
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 = []TautulliEntry{}
wrapperr_reply.User.UserMovies.Data.MoviesPlays = []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 {
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 {
break
}
wrapperr_reply.User.UserShows.Data.ShowsPlays = append(wrapperr_reply.User.UserShows.Data.ShowsPlays, entry)
count += 1
}
// Find longest pause
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 = []TautulliEntry{}
wrapperr_reply.User.UserShows.Data.ShowsPlays = []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 {
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 {
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 {
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 {
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 {
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 {
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 = []TautulliEntry{}
wrapperr_reply.User.UserMusic.Data.TracksPlays = []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 {
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 {
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 = []TautulliEntry{}
wrapperr_reply.YearStats.YearMovies.Data.MoviesPlays = []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 {
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 {
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 = []TautulliEntry{}
wrapperr_reply.YearStats.YearShows.Data.ShowsPlays = []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 {
break
}
wrapperr_reply.YearStats.YearMusic.Data.ArtistsDuration = append(wrapperr_reply.YearStats.YearMusic.Data.ArtistsDuration, entry)
count += 1
}
// Sort year show array by plays
sortutil.DescByField(wrapperr_year_artist, "Plays")
count = 0
for _, entry := range wrapperr_year_artist {
if count >= top_list_limit {
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 = []TautulliEntry{}
wrapperr_reply.YearStats.YearMusic.Data.ArtistsPlays = []TautulliEntry{}
wrapperr_reply.YearStats.YearMusic.Message = "No tracks processed."
}
// 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 []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
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 {
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 {
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."
} else {
wrapperr_reply.YearStats.YearUsers.Data.UsersDuration = []WrapperrYearUserEntry{}
wrapperr_reply.YearStats.YearUsers.Data.UsersPlays = []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_found, buddy_duration := GetUserShowBuddy(config, wrapperr_reply.User.UserShows.Data.ShowsDuration[0], user_id, wrapperr_data)
var show_buddy 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 {
show_buddy.Message = "Show buddy retrieved."
show_buddy.Error = false
show_buddy.BuddyName = buddy_name
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 *WrapperrConfig, top_show TautulliEntry, user_id int, wrapperr_data []WrapperrDay) (error, string, bool, int) {
var top_show_users []WrapperrYearUserEntry
var top_show_buddy_name = "Something went wrong."
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 = 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", 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_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_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_duration = user.Duration
top_show_buddy_found = true
error_bool = nil
break
}
}
return error_bool, top_show_buddy_name, top_show_buddy_found, top_show_buddy_duration
}