wrapperr/modules/tautulli.go
aunefyren 80720039e8 Users from Tautulli fix
Sync details to user from every Tautulli server during user aggregation
Prioritize active status to true if any Tautulli server says so
Sync users before showing statistics and validating active state
Show Tautulli servers on user page
2023-11-16 19:34:22 +01:00

339 lines
12 KiB
Go

package modules
import (
"aunefyren/wrapperr/files"
"aunefyren/wrapperr/models"
"aunefyren/wrapperr/utilities"
"encoding/json"
"errors"
"io"
"log"
"net/http"
"net/url"
"strconv"
"strings"
)
func TautulliTestConnection(TautulliPort int, TautulliIP string, TautulliHttps bool, TautulliRoot string, TautulliApiKey string) (bool, error) {
urlString, err := utilities.BuildURL(TautulliPort, TautulliIP, TautulliHttps, TautulliRoot)
if err != nil {
errString := strings.Replace(err.Error(), TautulliApiKey, "REDACTED", -1)
log.Println("Failed to build Tautulli connection URL. Error: " + errString)
return false, errors.New("Failed to build Tautulli connection URL.")
}
urlString = urlString + "api/v2/" + "?apikey=" + TautulliApiKey + "&cmd=status"
params := url.Values{}
payload := strings.NewReader(params.Encode())
req, err := http.NewRequest("GET", urlString, payload)
if err != nil {
errString := strings.Replace(err.Error(), TautulliApiKey, "REDACTED", -1)
log.Println("Failed to reach Tautulli server. Error: " + errString)
return false, errors.New("Failed to reach Tautulli server. Error: " + errString)
}
req.Header.Add("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
errString := strings.Replace(err.Error(), TautulliApiKey, "REDACTED", -1)
log.Println("Failed to reach Tautulli server. Error: " + errString)
return false, errors.New("Failed to reach Tautulli server. Error: " + errString)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
errString := strings.Replace(err.Error(), TautulliApiKey, "REDACTED", -1)
log.Println("Failed to read Tautulli server response. Error: " + errString)
return false, errors.New("Failed to read Tautulli response. Error: " + errString)
} else if res.StatusCode != 200 {
errString := "Tautulli didn't respond with status code 200, got: " + res.Status + " instead."
reply := string(body)
replyString := strings.Replace(reply, TautulliApiKey, "REDACTED", -1)
log.Println("Failed to connect to Tautulli server. \n\nReply: " + replyString + ". +n\nError: " + errString)
return false, errors.New("Failed to connect to Tautulli server. \n\nReply: " + replyString + ". \n\nError: " + errString)
}
var body_reply models.TautulliStatusReply
err = json.Unmarshal(body, &body_reply)
if err != nil {
errString := strings.Replace(err.Error(), TautulliApiKey, "REDACTED", -1)
reply := string(body)
replyString := strings.Replace(reply, TautulliApiKey, "REDACTED", -1)
log.Println("Failed to parse Tautulli server response. \n\nReply: " + replyString + ". +n\nError: " + errString)
return false, errors.New("Failed to parse Tautulli server response. \n\nReply: " + replyString + ". \n\nError: " + errString)
}
var tautulli_status bool = false
if body_reply.Response.Result == "success" {
tautulli_status = true
} else if body_reply.Response.Result == "error" {
errString := strings.Replace(body_reply.Response.Message, TautulliApiKey, "REDACTED", -1)
log.Println("Tautulli server responded with an error. Error: " + errString)
return false, errors.New("Tautulli server responded with an error. Error: " + errString)
}
return tautulli_status, nil
}
func TautulliGetUserId(TautulliPort int, TautulliIP string, TautulliHttps bool, TautulliRoot string, TautulliApiKey string, PlexUser string) (userID int, userName string, userFriendlyName string, userEmail string, userActive bool, err error) {
userID = 0
userName = ""
userEmail = ""
userFriendlyName = ""
userActive = false
err = nil
body_reply, err := TautulliGetUsers(TautulliPort, TautulliIP, TautulliHttps, TautulliRoot, TautulliApiKey)
if err != nil {
log.Println("Failed to get users from Tautulli. Error: " + err.Error())
return userID, userName, userFriendlyName, userEmail, userActive, errors.New("Failed to get users from Tautulli.")
}
for i := 0; i < len(body_reply.Response.Data); i++ {
if body_reply.Response.Data[i].UserID != 0 && (strings.ToLower(body_reply.Response.Data[i].Username) == strings.ToLower(PlexUser) || strings.ToLower(body_reply.Response.Data[i].Email) == strings.ToLower(PlexUser)) {
ActiveInt := body_reply.Response.Data[i].IsActive
if ActiveInt == 0 {
userActive = false
} else if ActiveInt == 1 {
userActive = true
} else {
return userID, userName, userFriendlyName, userEmail, userActive, errors.New("Invalid IsActive state in Tautulli.")
}
return body_reply.Response.Data[i].UserID, body_reply.Response.Data[i].Username, body_reply.Response.Data[i].FriendlyName, body_reply.Response.Data[i].Email, userActive, nil
}
}
log.Println("Could not find any user that matched the given Plex Identity: '" + PlexUser + "'.")
return userID, userName, userEmail, userFriendlyName, userActive, errors.New("Failed to find user.")
}
func TautulliGetUsers(TautulliPort int, TautulliIP string, TautulliHttps bool, TautulliRoot string, TautulliApiKey string) (usersReply models.TautulliGetUsersReply, err error) {
usersReply = models.TautulliGetUsersReply{}
err = nil
url_string, err := utilities.BuildURL(TautulliPort, TautulliIP, TautulliHttps, TautulliRoot)
if err != nil {
log.Println("Failed to build Tautulli connection URL. Error: " + err.Error())
return usersReply, errors.New("Failed to build Tautulli connection URL.")
}
url_string = url_string + "api/v2/" + "?apikey=" + TautulliApiKey + "&cmd=get_users"
params := url.Values{}
payload := strings.NewReader(params.Encode())
req, err := http.NewRequest("GET", url_string, payload)
if err != nil {
log.Println("Failed to reach Tautulli server. Error: " + err.Error())
return usersReply, errors.New("Failed to reach Tautulli server.")
}
req.Header.Add("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Println("Failed to reach Tautulli server. Error: " + err.Error())
return usersReply, errors.New("Failed to reach Tautulli server.")
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
json.Unmarshal(body, &usersReply)
if err != nil {
log.Println(err)
return usersReply, errors.New("Failed to parse Tautulli response.")
}
return usersReply, err
}
func TautulliDownloadStatistics(TautulliPort int, TautulliIP string, TautulliHttps bool, TautulliRoot string, TautulliApiKey string, TautulliLength int, Libraries string, Grouping string, StartDate string) ([]models.TautulliHistoryItem, error) {
url_string, err := utilities.BuildURL(TautulliPort, TautulliIP, TautulliHttps, TautulliRoot)
if err != nil {
log.Println(err)
return []models.TautulliHistoryItem{}, errors.New("Failed to build Tautulli connection URL.")
}
url_string = url_string + "api/v2/" + "?apikey=" + TautulliApiKey + "&cmd=get_history&order_column=date&order_dir=desc&include_activity=0" + Libraries + "&grouping=" + Grouping + "&length=" + strconv.Itoa(TautulliLength) + "&start_date=" + StartDate
params := url.Values{}
payload := strings.NewReader(params.Encode())
req, err := http.NewRequest("GET", url_string, payload)
if err != nil {
log.Println(err)
return []models.TautulliHistoryItem{}, errors.New("Failed to reach Tautulli server.")
}
req.Header.Add("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Println(err)
return []models.TautulliHistoryItem{}, errors.New("Failed to reach Tautulli server.")
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
var body_reply models.TautulliGetHistoryReply
json.Unmarshal(body, &body_reply)
if err != nil {
log.Println(err)
return []models.TautulliHistoryItem{}, errors.New("Failed to parse Tautulli response.")
}
return body_reply.Response.Data.Data, nil
}
func TautulliTestEveryServer() (err error) {
err = nil
config, err := files.GetConfig()
if err != nil {
log.Println("Failed to load Wrapperr configuration. Error: " + err.Error())
return errors.New("Failed to load Wrapperr configuration.")
}
for i := 0; i < len(config.TautulliConfig); i++ {
log.Println("Checking Tautulli server '" + config.TautulliConfig[i].TautulliName + "'.")
tautulli_state, err := 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())
return errors.New("Failed to reach Tautulli server '" + config.TautulliConfig[i].TautulliName + "'.")
} else if !tautulli_state {
log.Println("Failed to ping Tautulli server '" + config.TautulliConfig[i].TautulliName + "' before retrieving statistics.")
return errors.New("Failed to ping Tautulli server '" + config.TautulliConfig[i].TautulliName + "'.")
}
}
return
}
func TautulliGetUsersFromEveryServer() (tautulliUsers []models.TautulliUserGroup, err error) {
err = nil
tautulliUsers = []models.TautulliUserGroup{}
config, err := files.GetConfig()
if err != nil {
log.Println("Failed to load Wrapperr configuration. Error: " + err.Error())
return tautulliUsers, errors.New("Failed to load Wrapperr configuration.")
}
for i := 0; i < len(config.TautulliConfig); i++ {
log.Println("Getting users from Tautulli server '" + config.TautulliConfig[i].TautulliName + "'.")
tautulliReply, err := TautulliGetUsers(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 get users from Tautulli. Error: " + err.Error())
return tautulliUsers, errors.New("Failed to get users from Tautulli.")
}
for _, user := range tautulliReply.Response.Data {
tautulliUserGroup := models.TautulliUserGroup{
TautulliUser: user,
TautulliServer: config.TautulliConfig[i].TautulliName,
}
tautulliUsers = append(tautulliUsers, tautulliUserGroup)
}
}
return
}
func TautulliSyncUsersToWrapperr() (err error) {
err = nil
// Get all users from Tautulli
users, err := TautulliGetUsersFromEveryServer()
if err != nil {
log.Println("Failed to get users. Error: " + err.Error())
return errors.New("Failed to get users.")
}
// Create Wrapperr user objects array to fill
newUserArray := []models.WrapperrUser{}
// Create new Wrapperr user objects from Tautulli reply
for _, userGroup := range users {
var userMatch bool = false
var userIndex int = 0
for index, wrapperrUser := range newUserArray {
if userGroup.TautulliUser.UserID == wrapperrUser.UserID {
userMatch = true
userIndex = index
break
}
}
// User object is not in array, create and push to array
// If user object is in array, append new server and weigh active status
if !userMatch {
// Create bool from small int
var Active = false
if userGroup.TautulliUser.IsActive == 1 {
Active = true
} else if userGroup.TautulliUser.IsActive == 0 {
Active = false
} else {
log.Println("Failed to convert integer to bool. Error: " + err.Error())
return errors.New("Failed to convert integer to bool.")
}
// Create new object
wrapperrUser := models.WrapperrUser{
FriendlyName: userGroup.TautulliUser.FriendlyName,
User: userGroup.TautulliUser.Username,
UserID: userGroup.TautulliUser.UserID,
Email: userGroup.TautulliUser.Email,
TautulliServers: []string{userGroup.TautulliServer},
Active: Active,
Wrappings: []models.WrapperrHistoryEntry{},
}
// Push to array
newUserArray = append(newUserArray, wrapperrUser)
} else {
// If any server is "active", the user is active
if newUserArray[userIndex].Active == false && userGroup.TautulliUser.IsActive == 1 {
newUserArray[userIndex].Active = true
}
// Add new server
newUserArray[userIndex].TautulliServers = append(newUserArray[userIndex].TautulliServers, userGroup.TautulliServer)
}
}
// Update every new Wrapperr user with new object
for _, user := range newUserArray {
_, err = UsersGetUser(user.UserID)
if err != nil {
err = UsersSaveUserEntry(user)
if err != nil {
log.Println("Failed to save new user. Error: " + err.Error())
return errors.New("Failed to save new user.")
}
} else {
err = UsersUpdateUser(user.UserID, user.FriendlyName, user.User, user.Email, user.Active, user.TautulliServers)
if err != nil {
log.Println("Failed to update user. Error: " + err.Error())
return errors.New("Failed to update user.")
}
}
}
return
}