App v0.2.26 and gateway 0.1.20
Client: seek controls, Bazarr subtitle download and cast panel in the player; MDBList ratings strip; episode and schedule detail pages; series pace estimate; what's new panel; install-permission onboarding step; synced per-profile preferences; Emby outage banner. Gateway: rebuilt admin console (one fragment per page), preference history and restore, merged Continue Watching, Emby health probe, subtitle selection and Bazarr download, structured request logging with per-request identity, and embedded build version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2675e6d82b
commit
4a4df7a73c
@@ -16,13 +16,19 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ponzischeme89/memby/server/internal/buildinfo"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
baseURL string
|
||||
publicURL string
|
||||
clientName string
|
||||
http *http.Client
|
||||
// gatewayVersion labels the requests the gateway makes on its own behalf — the
|
||||
// library sync, the health probe, device cleanup — which belong to no television and
|
||||
// so have no app version to report.
|
||||
gatewayVersion string
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
// Credentials identify one signed-in Emby user.
|
||||
@@ -31,6 +37,10 @@ type Credentials struct {
|
||||
Token string
|
||||
DeviceID string
|
||||
DeviceName string
|
||||
// ClientVersion is the app version of the television this request is being made for,
|
||||
// as it reported in X-Memby-Version. Emby shows it beside the device, so a blank one
|
||||
// makes every set in the house look like the same build.
|
||||
ClientVersion string
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
@@ -123,9 +133,10 @@ func (e *APIError) Error() string {
|
||||
|
||||
func New(baseURL, publicURL, clientName string, timeout time.Duration) *Client {
|
||||
return &Client{
|
||||
baseURL: strings.TrimRight(baseURL, "/"),
|
||||
publicURL: strings.TrimRight(publicURL, "/"),
|
||||
clientName: clientName,
|
||||
baseURL: strings.TrimRight(baseURL, "/"),
|
||||
publicURL: strings.TrimRight(publicURL, "/"),
|
||||
clientName: clientName,
|
||||
gatewayVersion: buildinfo.Version(),
|
||||
http: &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: &http.Transport{
|
||||
@@ -137,13 +148,17 @@ func New(baseURL, publicURL, clientName string, timeout time.Duration) *Client {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Authenticate(ctx context.Context, username, password, deviceID, deviceName string) (*AuthResult, error) {
|
||||
// Authenticate signs a television in. clientVersion is the app version that set
|
||||
// reported; it is what Emby stamps on the device record it creates here, so an empty one
|
||||
// leaves the entry claiming the gateway's own build.
|
||||
func (c *Client) Authenticate(ctx context.Context, username, password, deviceID, deviceName, clientVersion string) (*AuthResult, error) {
|
||||
body, err := json.Marshal(map[string]string{"Username": username, "Pw": password})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := c.newRequest(ctx, http.MethodPost, "/Users/AuthenticateByName", nil,
|
||||
Credentials{DeviceID: deviceID, DeviceName: deviceName}, bytes.NewReader(body))
|
||||
Credentials{DeviceID: deviceID, DeviceName: deviceName, ClientVersion: clientVersion},
|
||||
bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -357,6 +372,30 @@ func (c *Client) SetPlayed(ctx context.Context, cred Credentials, itemID string,
|
||||
return c.userDataCall(ctx, method, path, cred)
|
||||
}
|
||||
|
||||
// RefreshItem asks Emby to re-scan one item's files.
|
||||
//
|
||||
// It exists for the subtitle download: Bazarr writes the new .srt beside the media file
|
||||
// and Emby has no idea it is there until something makes it look. Metadata and images are
|
||||
// deliberately left alone (ReplaceAllMetadata=false, ImageRefreshMode=None) — the point is
|
||||
// to notice a new sidecar, not to re-fetch a title's artwork from the internet because
|
||||
// somebody wanted Italian subtitles.
|
||||
func (c *Client) RefreshItem(ctx context.Context, cred Credentials, itemID string) error {
|
||||
params := url.Values{
|
||||
"Recursive": {"false"},
|
||||
"MetadataRefreshMode": {"Default"},
|
||||
"ImageRefreshMode": {"None"},
|
||||
"ReplaceAllMetadata": {"false"},
|
||||
"ReplaceAllImages": {"false"},
|
||||
}
|
||||
req, err := c.newRequest(
|
||||
ctx, http.MethodPost, "/Items/"+url.PathEscape(itemID)+"/Refresh", params, cred, nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.do(req, nil)
|
||||
}
|
||||
|
||||
// ReportPlayback forwards a progress report. phase is "started", "progress" or "stopped".
|
||||
func (c *Client) ReportPlayback(
|
||||
ctx context.Context,
|
||||
@@ -531,10 +570,18 @@ func (c *Client) newRequest(ctx context.Context, method, path string, params url
|
||||
if deviceName == "" {
|
||||
deviceName = "Memby TV"
|
||||
}
|
||||
// The version Emby records is the television's app version, not a constant: every
|
||||
// device in the dashboard read as one build before this, so there was no way to tell
|
||||
// which set was behind. A request the gateway makes for itself reports its own build.
|
||||
version := strings.TrimSpace(cred.ClientVersion)
|
||||
if version == "" {
|
||||
version = c.gatewayVersion
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("X-Emby-Authorization", fmt.Sprintf(
|
||||
`MediaBrowser Client="%s", Device="%s", DeviceId="%s", Version="1.0"`,
|
||||
`MediaBrowser Client="%s", Device="%s", DeviceId="%s", Version="%s"`,
|
||||
c.clientName, strings.ReplaceAll(deviceName, `"`, ""), deviceID,
|
||||
strings.ReplaceAll(version, `"`, ""),
|
||||
))
|
||||
if cred.Token != "" {
|
||||
req.Header.Set("X-Emby-Token", cred.Token)
|
||||
|
||||
Reference in New Issue
Block a user