many updates

This commit is contained in:
2026-02-11 11:38:26 +11:00
parent 8cb9e43a72
commit b069d5bee8
21 changed files with 1324 additions and 85 deletions

View File

@@ -23,6 +23,7 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
var reloadData = false
var cacheImages = false
var createXEPGFiles = false
var triggerPlexGuideReload = false
var debug string
// -vvv [URL] --sout '#transcode{vcodec=mp4v, acodec=mpga} :standard{access=http, mux=ogg}'
@@ -36,6 +37,21 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
case "tuner":
showWarning(2105)
case "use_plexAPI":
triggerPlexGuideReload = true
case "plex.url":
if v, ok := value.(string); ok {
value = strings.TrimRight(strings.TrimSpace(v), "/")
}
triggerPlexGuideReload = true
case "plex.token":
if v, ok := value.(string); ok {
value = strings.TrimSpace(v)
}
triggerPlexGuideReload = true
case "epgSource":
reloadData = true
@@ -119,22 +135,26 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
oldSettings[key] = value
switch fmt.Sprintf("%T", value) {
if key == "plex.token" {
debug = fmt.Sprintf("Save Setting:Key: %s | Value: ******** (%T)", key, value)
} else {
switch fmt.Sprintf("%T", value) {
case "bool":
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %t (%T)", key, value, value)
case "bool":
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %t (%T)", key, value, value)
case "string":
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %s (%T)", key, value, value)
case "string":
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %s (%T)", key, value, value)
case "[]interface {}":
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %v (%T)", key, value, value)
case "[]interface {}":
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %v (%T)", key, value, value)
case "float64":
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %d (%T)", key, int(value.(float64)), value)
case "float64":
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %d (%T)", key, int(value.(float64)), value)
default:
debug = fmt.Sprintf("%T", value)
default:
debug = fmt.Sprintf("%T", value)
}
}
showDebug(debug, 1)
@@ -250,6 +270,10 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
}
if triggerPlexGuideReload == true {
queuePlexGuideRefresh("settings change")
}
}
return
@@ -980,6 +1004,10 @@ func buildDatabaseDVR() (err error) {
sort.Strings(Data.StreamPreviewUI.Active)
sort.Strings(Data.StreamPreviewUI.Inactive)
if Settings.EpgSource != "XEPG" {
queuePlexGuideRefresh("lineup update")
}
return
}

363
src/plex_api.go Normal file
View File

@@ -0,0 +1,363 @@
package src
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
)
var plexRefreshState = struct {
sync.Mutex
lastRun time.Time
scheduled bool
inProgress bool
queued bool
reason string
missingConfigLogged bool
}{}
// queuePlexGuideRefresh schedules a debounced Plex DVR guide refresh.
func queuePlexGuideRefresh(reason string) {
if Settings.UsePlexAPI == false {
return
}
plexRefreshState.Lock()
if len(strings.TrimSpace(reason)) > 0 {
plexRefreshState.reason = reason
}
if plexRefreshState.scheduled == true || plexRefreshState.inProgress == true {
plexRefreshState.queued = true
plexRefreshState.Unlock()
return
}
plexRefreshState.scheduled = true
delay := plexRefreshDelayLocked()
plexRefreshState.Unlock()
go runPlexGuideRefresh(delay)
}
func runPlexGuideRefresh(initialDelay time.Duration) {
if initialDelay > 0 {
time.Sleep(initialDelay)
}
for {
plexRefreshState.Lock()
reason := strings.TrimSpace(plexRefreshState.reason)
if len(reason) == 0 {
reason = "update"
}
plexRefreshState.scheduled = false
plexRefreshState.inProgress = true
plexRefreshState.queued = false
plexRefreshState.Unlock()
err := refreshPlexGuide(reason)
if err != nil {
ShowError(err, 0)
}
plexRefreshState.Lock()
plexRefreshState.lastRun = time.Now()
runAgain := plexRefreshState.queued
plexRefreshState.inProgress = false
nextDelay := time.Duration(0)
if runAgain == true && Settings.UsePlexAPI == true {
plexRefreshState.scheduled = true
nextDelay = plexRefreshDelayLocked()
}
plexRefreshState.Unlock()
if runAgain == false || Settings.UsePlexAPI == false {
return
}
time.Sleep(nextDelay)
}
}
func plexRefreshDelayLocked() (delay time.Duration) {
const minDelay = 2 * time.Second
const cooldown = 20 * time.Second
if plexRefreshState.lastRun.IsZero() == true {
return minDelay
}
since := time.Since(plexRefreshState.lastRun)
if since >= cooldown {
return minDelay
}
return cooldown - since
}
func refreshPlexGuide(reason string) (err error) {
baseURL, token, ready := getPlexConfig()
if ready == false {
return nil
}
dvrs, err := discoverPlexDVRs(baseURL, token)
if err != nil {
return
}
var refreshed int
var failed int
for _, dvr := range dvrs {
endpoints := getPlexReloadEndpoints(dvr)
if len(endpoints) == 0 {
failed++
continue
}
var endpointErr error
for _, endpoint := range endpoints {
status, _, requestErr := doPlexRequest("POST", baseURL, endpoint, token)
if requestErr != nil {
endpointErr = requestErr
continue
}
if status >= 200 && status < 300 {
refreshed++
endpointErr = nil
break
}
endpointErr = fmt.Errorf("Plex API returned HTTP %d for %s", status, endpoint)
}
if endpointErr != nil {
failed++
}
}
if refreshed == 0 {
if failed == 0 {
return errors.New("Plex API guide refresh failed")
}
return fmt.Errorf("Plex API guide refresh failed for %d DVR(s)", failed)
}
showInfo(fmt.Sprintf("Plex API:Guide reload requested for %d DVR(s) (%s)", refreshed, reason))
if failed > 0 {
showInfo(fmt.Sprintf("Plex API:Guide reload failed for %d DVR(s)", failed))
}
return nil
}
func getPlexConfig() (baseURL, token string, ready bool) {
baseURL = strings.TrimSpace(Settings.PlexURL)
token = strings.TrimSpace(Settings.PlexToken)
if strings.HasPrefix(baseURL, "http://") == false && strings.HasPrefix(baseURL, "https://") == false && len(baseURL) > 0 {
baseURL = "http://" + baseURL
}
baseURL = strings.TrimRight(baseURL, "/")
plexRefreshState.Lock()
defer plexRefreshState.Unlock()
if len(baseURL) == 0 || len(token) == 0 {
if plexRefreshState.missingConfigLogged == false {
showInfo("Plex API:Skipped refresh because plex.url or plex.token is empty")
plexRefreshState.missingConfigLogged = true
}
return "", "", false
}
plexRefreshState.missingConfigLogged = false
return baseURL, token, true
}
func discoverPlexDVRs(baseURL, token string) (dvrs []map[string]interface{}, err error) {
status, body, err := doPlexRequest("GET", baseURL, "/livetv/dvrs", token)
if err != nil {
return
}
if status < 200 || status >= 300 {
err = fmt.Errorf("Plex API returned HTTP %d for /livetv/dvrs", status)
return
}
var payload = make(map[string]interface{})
err = json.Unmarshal(body, &payload)
if err != nil {
return
}
mediaContainer, ok := payload["MediaContainer"].(map[string]interface{})
if ok == false {
err = errors.New("Plex API response missing MediaContainer")
return
}
for _, key := range []string{"Dvr", "DVR", "Directory", "Metadata"} {
if raw, found := mediaContainer[key]; found == true {
if list, ok := raw.([]interface{}); ok == true {
dvrs = convertToPlexMapSlice(list)
if len(dvrs) > 0 {
return
}
}
}
}
err = errors.New("Plex API returned no DVR entries")
return
}
func convertToPlexMapSlice(list []interface{}) (dvrs []map[string]interface{}) {
dvrs = make([]map[string]interface{}, 0, len(list))
for _, item := range list {
if dvr, ok := item.(map[string]interface{}); ok == true {
dvrs = append(dvrs, dvr)
}
}
return
}
func getPlexReloadEndpoints(dvr map[string]interface{}) (endpoints []string) {
endpoints = make([]string, 0, 6)
added := make(map[string]bool)
add := func(endpoint string) {
endpoint = strings.TrimSpace(endpoint)
if len(endpoint) == 0 {
return
}
if strings.HasPrefix(endpoint, "/") == false {
endpoint = "/" + endpoint
}
if added[endpoint] == true {
return
}
added[endpoint] = true
endpoints = append(endpoints, endpoint)
}
if key := getPlexMapString(dvr, "key"); len(key) > 0 {
if strings.HasPrefix(key, "/") == true {
add(strings.TrimRight(key, "/") + "/reloadGuide")
} else {
add("/livetv/dvrs/" + url.PathEscape(key) + "/reloadGuide")
}
}
for _, field := range []string{"uuid", "identifier", "machineIdentifier", "clientIdentifier", "id"} {
if value := getPlexMapString(dvr, field); len(value) > 0 {
add("/livetv/dvrs/" + url.PathEscape(value) + "/reloadGuide")
}
}
return
}
func getPlexMapString(data map[string]interface{}, field string) string {
for key, value := range data {
if strings.EqualFold(key, field) == false {
continue
}
switch v := value.(type) {
case string:
return strings.TrimSpace(v)
case float64:
return strconv.FormatInt(int64(v), 10)
}
}
return ""
}
func doPlexRequest(method, baseURL, endpoint, token string) (status int, body []byte, err error) {
requestURL := strings.TrimRight(baseURL, "/") + "/" + strings.TrimLeft(endpoint, "/")
req, err := http.NewRequest(method, requestURL, nil)
if err != nil {
return
}
query := req.URL.Query()
if len(token) > 0 {
query.Set("X-Plex-Token", token)
req.URL.RawQuery = query.Encode()
}
req.Header.Set("Accept", "application/json")
req.Header.Set("X-Plex-Token", token)
if len(System.DeviceID) > 0 {
req.Header.Set("X-Plex-Client-Identifier", System.DeviceID)
}
if len(System.Name) > 0 {
req.Header.Set("X-Plex-Product", System.Name)
req.Header.Set("X-Plex-Device-Name", System.Name)
}
if len(System.Version) > 0 {
req.Header.Set("X-Plex-Version", System.Version)
}
client := &http.Client{
Timeout: 10 * time.Second,
}
resp, err := client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
status = resp.StatusCode
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return
}
return
}

View File

@@ -254,6 +254,9 @@ type Notification struct {
// SettingsStruct : Inhalt der settings.json
type SettingsStruct struct {
API bool `json:"api"`
UsePlexAPI bool `json:"use_plexAPI"`
PlexURL string `json:"plex.url"`
PlexToken string `json:"plex.token"`
AuthenticationAPI bool `json:"authentication.api"`
AuthenticationM3U bool `json:"authentication.m3u"`
AuthenticationPMS bool `json:"authentication.pms"`

View File

@@ -18,6 +18,9 @@ type RequestStruct struct {
// Neue Werte für die Einstellungen (settings.json)
Settings struct {
API *bool `json:"api,omitempty"`
UsePlexAPI *bool `json:"use_plexAPI,omitempty"`
PlexURL *string `json:"plex.url,omitempty"`
PlexToken *string `json:"plex.token,omitempty"`
AuthenticationAPI *bool `json:"authentication.api,omitempty"`
AuthenticationM3U *bool `json:"authentication.m3u,omitempty"`
AuthenticationPMS *bool `json:"authentication.pms,omitempty"`

View File

@@ -106,6 +106,9 @@ func loadSettings() (settings SettingsStruct, err error) {
dataMap["hdhr"] = make(map[string]interface{})
defaults["api"] = false
defaults["use_plexAPI"] = false
defaults["plex.url"] = ""
defaults["plex.token"] = ""
defaults["authentication.api"] = false
defaults["authentication.m3u"] = false
defaults["authentication.pms"] = false

View File

@@ -68,6 +68,7 @@ func buildXEPG(background bool) {
cleanupXEPG()
createXMLTVFile()
createM3UFile()
queuePlexGuideRefresh("xepg rebuild")
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
@@ -84,6 +85,7 @@ func buildXEPG(background bool) {
createXMLTVFile()
createM3UFile()
queuePlexGuideRefresh("xepg image cache refresh")
System.ImageCachingInProgress = 0
@@ -113,6 +115,7 @@ func buildXEPG(background bool) {
createXMLTVFile()
createM3UFile()
queuePlexGuideRefresh("xepg rebuild")
if Settings.CacheImages == true && System.ImageCachingInProgress == 0 {
@@ -127,6 +130,7 @@ func buildXEPG(background bool) {
createXMLTVFile()
createM3UFile()
queuePlexGuideRefresh("xepg image cache refresh")
System.ImageCachingInProgress = 0
@@ -179,6 +183,7 @@ func updateXEPG(background bool) {
createXMLTVFile()
createM3UFile()
queuePlexGuideRefresh("xepg update")
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
System.ScanInProgress = 0