plexapi

package module
v1.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: GPL-2.0, GPL-3.0 Imports: 14 Imported by: 0

README

plexapi

Go Reference Go version Test coverage OpenSSF Best Practices OpenSSF Scorecard

Typed, resilient Go client for the Plex Media Server HTTP API

Library metadata, sessions, watch history, server identity and statistics, GUID resolution, and per-user stream selection — over a transport that defends the X-Plex-Token by construction and retries transient failures transparently. Plus a small client for the plex.tv account API (shared-server user tokens).

One runtime dependency: httpx (retry round-tripper, CA pinning, bounded reads).

Install

go get github.com/cplieger/plexapi@latest

Usage

client, err := plexapi.New("http://plex:32400", token)
if err != nil { ... }

// Library indexing: sections and their items (rating keys, GUIDs, years).
sections, err := client.Sections(ctx)
items, err := client.SectionItems(ctx, plexapi.RatingKey(sections[0].Key))

// One item; the endpoint is polymorphic (movie/show/season/episode).
item, err := client.Metadata(ctx, "49915")
episodes, err := client.AllLeaves(ctx, "1345") // every episode of a show

// Live sessions and server-side-filtered watch history.
sessions, err := client.Sessions(ctx)
history, err := client.History(ctx, time.Now().Add(-24*time.Hour).Unix())

// Per-user stream selection: writes are recorded against the REQUESTING
// token's user, so select with that user's token.
userClient := client.ForToken(userToken)
err = userClient.SetSubtitleStream(ctx, partID, streamID)

// plex.tv: the shared users of a server, with their access tokens.
tv := plexapi.NewTV(adminToken)
shared, err := tv.SharedServers(ctx, machineID)

A Plex behind a self-signed certificate pins its CA (verification stays on; there is no skip option):

pem, _ := os.ReadFile(caPath) // the caller owns file I/O
client, err := plexapi.New(serverURL, token, plexapi.WithCACertPEM(pem))

Security model

The token grants full server access; the client defends it on every request:

  • Token in the X-Plex-Token header only — never a query string, so URL logging can't leak it.
  • Redirects are refused outright. Go's default policy forwards custom headers on cross-origin redirects, so a hostile 302 would exfiltrate the token; Plex's API issues no redirects, so none are followed.
  • Request paths must be server-relative: an absolute or scheme-relative reference (which would re-target URL resolution at another host) is rejected before any request is built.
  • Rating keys are validated numeric before URL interpolation.
  • CA pinning for self-signed servers keeps TLS verification on, trusting only the supplied CA; there is deliberately no insecure-skip option.
  • Construction warns when the base URL is plain http:// to a non-local host (the token would transit unencrypted).
  • Transport errors are reduced to their cause so error strings never embed full request URLs.

Resilience model

  • GETs ride an httpx retry round-tripper: 429/502/503/504 and transient transport errors retried with jittered exponential backoff, honoring Retry-After on 429. WithMaxAttempts(1) disables retries.
  • Writes (PUT stream selection) are applied at most once — never retried.
  • A per-attempt response-header timeout makes a stalled attempt fail as a retryable error instead of hanging the sequence; a per-request default timeout (WithTimeout, default 2m) applies only when the caller's context has no deadline, so a caller deadline is always the authoritative budget.
  • Response bodies are size-capped before decode (defaults 10 MB; 40 MB for full section listings; both configurable), with overflow reported as *ResponseTooLargeError rather than a truncated decode.

API

  • Constructor: New(baseURL, token, ...Option) — options WithCACertPEM, WithMaxAttempts (total, default 3), WithBaseDelay, WithTimeout, WithMaxBodyBytes/WithMaxListBodyBytes (read caps), WithLogger (routes the client's own diagnostics; default slog.Default()), WithOnRetry (retry-counter hook), WithHTTPClient (caller-owned transport, tests).
  • Derived clients: (*Client).ForToken(token) — same server + shared connection pool, different token (the per-user write path).
  • Library: Sections, SectionItems(key), RecentlyAdded(key, type, sinceUnix), Metadata(key), Children(key), AllLeaves(key), ItemExists(key) (fail-closed: an undetermined check is an error, never "gone"), ItemsByGUID(guid), ShowForEpisodeGUID(guid) (ambiguity yields "", refusing to guess), ContainerTotalSize(path).
  • Sessions & history: Sessions(), History(sinceUnix) — history and recently-added filters use Plex's literal single-char >= operator (a malformed or encoded operator is silently ignored by Plex, returning the full unfiltered set; the literal form is a pinned wire contract).
  • Server: Identity(), Accounts(), MyPlexUsername(), AdminAccount(), Providers() (per-library duration/storage), StatisticsResources(timespan) / StatisticsBandwidth(timespan) (Plex Pass; 404 → ErrNotFound for graceful degradation).
  • Stream selection: SetAudioStream(partID, streamID), SetSubtitleStream(partID, streamID), DisableSubtitles(partID) — user-scoped by requesting token.
  • plex.tv: NewTV(token, ...TVOption), (*TV).SharedServers(machineID).
  • Types: MC[T] (the MediaContainer envelope, for Get escape-hatch decoding), Item (Plex's polymorphic metadata item: library entries, sessions, and history rows are one wire shape), FlexInt (absorbs Plex's number-or-quoted-string fields), RatingKey (validated identifier), the MediaPartStream graph, Section, ServerIdentity, Account, SharedServer, statistics types.
  • Errors: ErrNotFound + IsNotFound(err), StatusError{Method, Path, Status, Code}, ResponseTooLargeError{Path, Limit}, IsConfigError(err) (a 4xx other than 408/429 is a configuration/authorization failure that will not self-heal; everything else is transient).
  • Escape hatch: Get(ctx, path, &result) for endpoints without a typed method, with the same hardening (path guard, redirect refusal, retries, body caps).

Unsupported by design

Deliberate non-goals, not TODOs:

Feature Rationale
Library management writes (edit metadata, delete items, scan triggers) The consumers are read-and-select tools; the only mutations modeled are stream selections.
WebSocket notifications A different transport with app-specific reconnect policy; consumers own it (the client exposes BaseURL/HTTPClient so a dialer can share the transport and trust settings).
Full plex.tv account surface (devices, friends, PINs) SharedServers is the one account call a consumer needs.
Insecure TLS (InsecureSkipVerify) Pin the CA instead; verification never turns off.
Response caching / request coalescing Callers own their caching layer; the client stays lock-free and stateless per request.

Disclaimer

This project is built with care and follows security best practices, but it is intended for personal / self-hosted use. No guarantees of fitness for production environments. Use at your own risk.

This project was built with AI-assisted tooling using Claude Opus and Kiro. The human maintainer defines architecture, supervises implementation, and makes all final decisions.

License

GPL-3.0 — see LICENSE.

Documentation

Overview

Package plexapi is a typed, resilient client for the Plex Media Server HTTP API, plus a small client for the plex.tv account API.

Security model

The X-Plex-Token grants full server access, so the client defends it in depth, on every request, by construction:

  • The token travels only in the X-Plex-Token header, never a query string, so it cannot leak through URL logging.
  • Redirects are never followed: Go's default policy forwards custom headers (including X-Plex-Token) on cross-origin redirects, so a hostile 302 (MITM, DNS poisoning, compromised upstream) would otherwise exfiltrate the token.
  • Every request path must be server-relative. An absolute or scheme-relative reference would override the configured host via URL resolution and send the token to that origin; the client rejects it.
  • A self-signed Plex is supported by pinning its CA (WithCACertPEM): TLS verification stays ON, trusting only that CA. There is no "insecure skip verify" option.
  • Construction warns (once, via slog) when the base URL is plain http to a non-local host, because the token would transit unencrypted.

Resilience model

GET requests ride an httpx retry round-tripper: 429/502/503/504 and transient transport errors (timeouts, resets, DNS) are retried with jittered exponential backoff, honoring Retry-After on 429. Writes (PUT) go through the same client but are never retried (body replay is not enabled), so a mutation is applied at most once per call. A per-attempt response-header timeout on the transport makes a stalled attempt fail as a retryable error instead of hanging the sequence; a per-request default timeout (WithTimeout) applies only when the caller's context carries no deadline, so a caller deadline is always the authoritative budget. Response bodies are size-capped before decode.

Wire model

Plex wraps every JSON payload in a MediaContainer envelope and returns polymorphic metadata items (an episode, a season, a show, a movie, and a live session all share one shape with different fields populated). The package mirrors that honestly: MC[T] is the envelope, Item is the polymorphic metadata item, and FlexInt absorbs Plex's habit of returning numeric fields as either numbers or quoted strings depending on the endpoint. Typed methods cover the endpoints the consumers use; Get is the documented escape hatch for anything else (for example the Plex Pass statistics endpoints have typed helpers, but a new undocumented endpoint can be reached without waiting for a release).

Index

Constants

View Source
const (
	// DefaultMaxBodyBytes caps metadata, session, history, and server-info
	// responses (10 MB).
	DefaultMaxBodyBytes = 10 << 20
	// DefaultMaxListBodyBytes caps full section listings (40 MB).
	DefaultMaxListBodyBytes = 40 << 20
)

Default read caps per endpoint class. A single item or a session/history page fits well inside the general cap; a full library-section listing can be an order of magnitude larger. Both are configurable (WithMaxBodyBytes, WithMaxListBodyBytes) for deployments whose libraries outgrow them.

View Source
const (
	// SectionTypeMovie and SectionTypeShow are the section "type" values.
	SectionTypeMovie = "movie"
	SectionTypeShow  = "show"
	// TypeEpisode is the metadata "type" string on episode items.
	TypeEpisode = "episode"
	// MetadataTypeEpisode is the numeric type ID for ?type= filters.
	MetadataTypeEpisode = 4
)

Section type strings and metadata type IDs used in section filters.

Variables

View Source
var ErrNotFound = errors.New("not found")

ErrNotFound is returned when Plex answers 404 for an item lookup. Detect with errors.Is(err, plexapi.ErrNotFound).

Functions

func IsConfigError added in v1.1.0

func IsConfigError(err error) bool

IsConfigError reports whether err is a Plex response indicating a configuration or authorization problem — a 4xx StatusError other than 408 (request timeout) and 429 (rate limit, retried transparently and Retry-After-honored). Such a failure will not resolve without operator action (a bad token, a wrong server), unlike a 5xx (Plex up but not ready) or a transport error, both of which may clear on their own. TLS/certificate failures surface as transport errors and remain the caller's concern to classify.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is (or wraps) the ErrNotFound sentinel.

Types

type Account

type Account struct {
	Name string `json:"name"`
	ID   int    `json:"id"`
}

Account is a Plex system account from GET /accounts.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a Plex Media Server API client for one base URL + token. A single Client is safe for concurrent use. Construct with New.

func New

func New(baseURL, token string, opts ...Option) (*Client, error)

New parses and validates baseURL (http/https scheme, non-empty host) and returns a Client. Unless WithHTTPClient overrides it, the transport is: OS trust store or the pinned CA from WithCACertPEM, a per-attempt response-header timeout, an httpx retry round-tripper (429/502/503/504 + transient transport errors, honoring Retry-After), and a refuse-all redirect policy so the token can never ride a hostile 3xx. Construction warns via slog when baseURL is plain http to a non-local host (the token would transit unencrypted).

func (*Client) Accounts

func (c *Client) Accounts(ctx context.Context) ([]Account, error)

Accounts returns the server's system accounts (GET /accounts): the local account IDs history entries reference.

func (*Client) AdminAccount

func (c *Client) AdminAccount(ctx context.Context) (*Account, error)

AdminAccount resolves the server's admin (owner) system account: the owner is always account id 1 in the system accounts list.

It deliberately does not consult /myplex/account. That endpoint's username is the plex.tv account email wrapped in a {"MyPlex":{...}} envelope; the previous implementation decoded a top-level username (silently yielding ""), name-matched it against the id-0 placeholder account (name=""), and returned the placeholder as the admin — so consumers comparing session user ids against the admin id skipped every owner event. Verified live 2026-07 against Plex 1.43.3.

func (*Client) AllLeaves

func (c *Client) AllLeaves(ctx context.Context, key RatingKey) ([]Item, error)

AllLeaves returns an item's leaf descendants (every episode of a show).

func (*Client) BaseURL

func (c *Client) BaseURL() *url.URL

BaseURL returns the configured server base URL (for deriving a websocket URL or logging the host).

func (*Client) Children

func (c *Client) Children(ctx context.Context, key RatingKey) ([]Item, error)

Children returns an item's direct children (a show's seasons, a season's episodes).

func (*Client) ContainerTotalSize

func (c *Client) ContainerTotalSize(ctx context.Context, path string) (int64, error)

ContainerTotalSize returns the totalSize of the container at path (typically a /library/sections/{key}/all?type=N filter) by requesting a single item and reading the totalSize field. The body field is used rather than the X-Plex-Container-Total-Size header because the header is not populated for type-filtered queries.

func (*Client) DisableSubtitles

func (c *Client) DisableSubtitles(ctx context.Context, partID int) error

DisableSubtitles turns subtitles off for a media part (stream ID 0).

func (*Client) ForToken

func (c *Client) ForToken(token string) *Client

ForToken returns a Client for the same server and transport but a different token — the per-user client for user-scoped writes (Plex records a stream-selection PUT against the requesting token's user). The underlying connection pool is shared.

func (*Client) Get

func (c *Client) Get(ctx context.Context, path string, result any) error

Get fetches a server-relative path and decodes the JSON response into result. It is the escape hatch for endpoints without a typed method (decode through MC[T] for container-wrapped payloads); the same hardening (path guard, redirect refusal, retries, body cap) applies.

func (*Client) HTTPClient

func (c *Client) HTTPClient() *http.Client

HTTPClient returns the underlying *http.Client, so a websocket dialer can share the same transport (CA trust, redirect policy).

func (*Client) History

func (c *Client) History(ctx context.Context, sinceUnix int64) ([]Item, error)

History returns watch-history entries viewed at or after sinceUnix, newest first, filtered server-side.

The filter is literally `viewedAt>=N` — one `>`, unencoded. Plex silently ignores a malformed operator (`>>=`, or a URL-encoded one) and returns the FULL history, which on a long-lived server is tens of thousands of entries and blows the read cap; the single-character form is a wire contract, preserved verbatim by url.Parse.

func (*Client) Identity

func (c *Client) Identity(ctx context.Context) (*ServerIdentity, error)

Identity returns the server identity from GET / (name, machine ID, version, platform, Plex Pass subscription, active transcode count).

func (*Client) ItemExists

func (c *Client) ItemExists(ctx context.Context, key RatingKey) (bool, error)

ItemExists reports whether the rating key currently addresses an item: (true, nil) on 200, (false, nil) on a definitive 404. Any other failure (auth, rate limit, 5xx, transport) returns an error — existence could not be determined, and callers deciding "is this item stale?" must fail closed on it. The body is discarded without decoding.

func (*Client) ItemsByGUID

func (c *Client) ItemsByGUID(ctx context.Context, guid string) ([]Item, error)

ItemsByGUID returns every library item matching an external GUID (e.g. "plex://episode/<hash>", "imdb://tt0903747") via /library/all. An unknown GUID yields an empty slice (Plex answers 200 with no items).

func (*Client) Metadata

func (c *Client) Metadata(ctx context.Context, key RatingKey) (*Item, error)

Metadata fetches one library item by rating key. The endpoint is polymorphic: the same call returns a movie, show, season, or episode Item depending on what the key addresses. Returns ErrNotFound when the key no longer exists.

func (*Client) MyPlexUsername

func (c *Client) MyPlexUsername(ctx context.Context) (string, error)

MyPlexUsername returns the username of the plex.tv account the server is signed into (GET /myplex/account). The payload arrives wrapped in a {"MyPlex":{...}} envelope, and on current servers the username field carries the plex.tv account identity in email form — it does NOT match the owner's server-local display name in Accounts, so it must never be used to identify the owner among system accounts (see AdminAccount).

func (*Client) Providers

func (c *Client) Providers(ctx context.Context) (*MediaProviders, error)

Providers returns the media-provider tree (GET /media/providers with storage rollups) — per-library duration and storage totals.

func (*Client) RecentlyAdded

func (c *Client) RecentlyAdded(ctx context.Context, sectionKey RatingKey, metadataType int, sinceUnix int64) ([]Item, error)

RecentlyAdded returns a section's items of the given metadata type added at or after sinceUnix, newest first, filtered server-side.

The filter operator is literally `addedAt>=` — a single `>`, unencoded. Plex silently ignores a malformed or URL-encoded operator and returns the UNFILTERED listing, which on a large library blows the read cap; Go's url.Parse preserves the literal `>=` on the wire.

func (*Client) SectionItems

func (c *Client) SectionItems(ctx context.Context, sectionKey RatingKey) ([]Item, error)

SectionItems returns all items in a library section — the full listing used to index a library (rating keys, titles, years, GUIDs). Uses the large-body cap: a big section's listing is far larger than any other Plex response.

func (*Client) Sections

func (c *Client) Sections(ctx context.Context) ([]Section, error)

Sections returns every library section. Filter by Section.Type (SectionTypeMovie, SectionTypeShow) app-side.

func (*Client) Sessions

func (c *Client) Sessions(ctx context.Context) ([]Item, error)

Sessions returns the currently playing sessions (GET /status/sessions). Session items populate User, Player, Session, TranscodeSession, and the Media graph; a direct-play session has no TranscodeSession.

func (*Client) SetAudioStream

func (c *Client) SetAudioStream(ctx context.Context, partID, streamID int) error

SetAudioStream selects the audio stream for a media part.

Plex records stream-selection writes against the REQUESTING TOKEN's user (unlike reads, which are not user-scoped): selecting for another user requires that user's token — use ForToken. Falling back to the admin token writes to the admin's view and silently drops the target user's preference. Mutations are applied at most once (never retried).

func (*Client) SetSubtitleStream

func (c *Client) SetSubtitleStream(ctx context.Context, partID, streamID int) error

SetSubtitleStream selects the subtitle stream for a media part. Same user-scoping contract as SetAudioStream.

func (*Client) ShowForEpisodeGUID

func (c *Client) ShowForEpisodeGUID(ctx context.Context, episodeGUID string) (string, error)

ShowForEpisodeGUID resolves an episode GUID to the rating key of the show currently containing it — the handle back from a watch-history episode GUID to its show after a library rebuild. Returns ("", nil) when the GUID matches nothing or when matches disagree on their show (an ambiguous GUID that must not drive a decision); a non-nil error means the lookup could not be completed.

func (*Client) StatisticsBandwidth

func (c *Client) StatisticsBandwidth(ctx context.Context, timespan int) ([]StatisticsBandwidth, error)

StatisticsBandwidth returns bandwidth samples from the Plex Pass endpoint /statistics/bandwidth. 404s (ErrNotFound) without Plex Pass.

func (*Client) StatisticsResources

func (c *Client) StatisticsResources(ctx context.Context, timespan int) ([]StatisticsResource, error)

StatisticsResources returns host CPU/memory samples from the Plex Pass endpoint /statistics/resources over the trailing timespan bucket. The endpoint 404s (ErrNotFound) without Plex Pass; callers degrade gracefully.

func (*Client) Token

func (c *Client) Token() string

Token returns the client's token (for cache-eviction comparisons; never log it).

type FlexInt

type FlexInt int

FlexInt decodes a Plex JSON field that may arrive as a number or a quoted numeric string. Plex is inconsistent on numeric fields across endpoints (an episode index can be 14 or "14"); FlexInt absorbs both so callers use a plain int. Null, absent, and empty-string values decode to 0.

func (*FlexInt) UnmarshalJSON

func (f *FlexInt) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts a JSON number, a quoted numeric string, null, or an empty string. Anything else (non-numeric text, floats, objects) is a parse error.

type GUID

type GUID struct {
	ID string `json:"id"`
}

GUID is one external-identity entry from an item's Guid array (e.g. "imdb://tt0903747", "tvdb://81189", "plex://episode/<hash>").

type Item

type Item struct {
	User                 *SessionUser      `json:"User,omitempty"`
	TranscodeSession     *TranscodeSession `json:"TranscodeSession,omitempty"`
	Session              *SessionBandwidth `json:"Session,omitempty"`
	Player               *Player           `json:"Player,omitempty"`
	GrandparentRatingKey string            `json:"grandparentRatingKey"`
	Key                  string            `json:"key"`
	RatingKey            string            `json:"ratingKey"`
	GrandparentKey       string            `json:"grandparentKey"`
	Title                string            `json:"title"`
	ParentTitle          string            `json:"parentTitle"`
	GrandparentTitle     string            `json:"grandparentTitle"`
	Type                 string            `json:"type"`
	GUID                 string            `json:"guid"`
	LibrarySectionTitle  string            `json:"librarySectionTitle"`
	SessionKey           string            `json:"sessionKey"`
	ParentRatingKey      string            `json:"parentRatingKey"`
	GUIDs                []GUID            `json:"Guid"`
	Media                []Media           `json:"Media"`
	Label                []Label           `json:"Label"`
	AddedAt              int64             `json:"addedAt"`
	ViewedAt             int64             `json:"viewedAt"`
	Year                 int               `json:"year"`
	Index                FlexInt           `json:"index"`
	ParentIndex          FlexInt           `json:"parentIndex"`
	LibrarySectionID     FlexInt           `json:"librarySectionID"`
	AccountID            FlexInt           `json:"accountID"`
}

Item is Plex's polymorphic metadata item: episodes, seasons, shows, movies, history entries, and live sessions all share this wire shape with different fields populated. Field presence follows the endpoint: a library listing populates identity + GUIDs, a session adds User / Player / TranscodeSession, a metadata fetch adds the Media→Part→Stream graph.

func (*Item) EpisodeNum

func (i *Item) EpisodeNum() int

EpisodeNum returns the episode index (Index), 0 when absent.

func (*Item) SeasonNum

func (i *Item) SeasonNum() int

SeasonNum returns the season index (ParentIndex), 0 when absent.

type Label

type Label struct {
	Tag string `json:"tag"`
}

Label is a label tag on a metadata item.

type MC

type MC[T any] struct {
	MediaContainer T `json:"MediaContainer"`
}

MC is the generic MediaContainer envelope Plex wraps every JSON response in. Typed methods decode through it internally; it is exported for use with Get on endpoints the typed surface does not model.

type Media

type Media struct {
	VideoResolution string `json:"videoResolution"`
	Part            []Part `json:"Part"`
	ID              int    `json:"id"`
	Bitrate         int    `json:"bitrate"`
}

Media is one media rendition of an item, wrapping its parts.

type MediaProvider

type MediaProvider struct {
	Identifier string            `json:"identifier"`
	Features   []ProviderFeature `json:"Feature"`
}

MediaProvider is one provider entry in MediaProviders.

type MediaProviders

type MediaProviders struct {
	FriendlyName      string          `json:"friendlyName"`
	MachineIdentifier string          `json:"machineIdentifier"`
	Version           string          `json:"version"`
	MediaProviders    []MediaProvider `json:"MediaProvider"`
}

MediaProviders models GET /media/providers?includeStorage=1: the per- library duration/storage rollups behind library metrics.

type Option

type Option func(*options)

Option configures New.

func WithBaseDelay

func WithBaseDelay(d time.Duration) Option

WithBaseDelay sets the initial retry backoff (default 200ms).

func WithCACertPEM

func WithCACertPEM(pem []byte) Option

WithCACertPEM pins the CA(s) in pem as the sole TLS trust anchors, for a Plex behind a self-signed or private CA. Verification stays ON. The caller owns reading the PEM (the library does no file I/O); an empty pem is an error at construction.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient supplies a caller-owned *http.Client, replacing the built-in transport entirely (no retry round-tripper, no CA pinning, no redirect policy are installed — the caller owns all of it). Intended for tests and callers with bespoke transport needs.

func WithLogger added in v1.1.0

func WithLogger(l *slog.Logger) Option

WithLogger sets the slog.Logger for the client's own diagnostics (the construction-time plaintext-URL warning and the over-cap response warning). Defaults to slog.Default(); pass a level-filtered or discard logger to quiet them.

func WithMaxAttempts

func WithMaxAttempts(n int) Option

WithMaxAttempts sets the TOTAL number of attempts per GET including the first (default 3, minimum 1 — 1 disables retries). Writes are never retried regardless.

func WithMaxBodyBytes added in v1.1.0

func WithMaxBodyBytes(n int64) Option

WithMaxBodyBytes sets the read cap for metadata, session, history, and server-info responses (default DefaultMaxBodyBytes). Non-positive values are ignored.

func WithMaxListBodyBytes added in v1.1.0

func WithMaxListBodyBytes(n int64) Option

WithMaxListBodyBytes sets the read cap for full section listings (default DefaultMaxListBodyBytes) — the knob for libraries large enough that a section's listing outgrows the default. Non-positive values are ignored.

func WithOnRetry

func WithOnRetry(fn httpx.OnRetry) Option

WithOnRetry installs a per-retry observability hook (attempt number, request, response, error), forwarded to the underlying round-tripper. Consumers use it to surface a retry counter metric.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-request ceiling applied ONLY when the caller's context has no deadline (default 2m). A caller deadline is always the authoritative budget and is never undercut.

type Part

type Part struct {
	Decision string   `json:"decision"`
	Stream   []Stream `json:"Stream"`
	ID       int      `json:"id"`
}

Part is one file of a Media, wrapping its streams. Decision is populated on session responses (the transcoder's per-part verdict).

type Player

type Player struct {
	Device            string `json:"device"`
	Product           string `json:"product"`
	State             string `json:"state"`
	MachineIdentifier string `json:"machineIdentifier"`
	Local             bool   `json:"local"`
}

Player is the Player element on a session item.

type ProviderDirectory

type ProviderDirectory struct {
	Title         string `json:"title"`
	ID            string `json:"id"`
	Type          string `json:"type"`
	DurationTotal int64  `json:"durationTotal"`
	StorageTotal  int64  `json:"storageTotal"`
}

ProviderDirectory is one library directory entry in MediaProviders.

type ProviderFeature

type ProviderFeature struct {
	Type        string              `json:"type"`
	Directories []ProviderDirectory `json:"Directory"`
}

ProviderFeature is one feature of a MediaProvider; content features carry the library directories.

type RatingKey

type RatingKey string

RatingKey is Plex's opaque numeric-string identifier for a library item (movie, show, season, episode) or section. The wire representation is always a string; the type exists so keys are validated once at the API boundary instead of being interpolated into URL paths unchecked.

func (RatingKey) String

func (r RatingKey) String() string

String returns the key as a plain string.

func (RatingKey) Validate

func (r RatingKey) Validate() error

Validate reports whether the key is a non-empty numeric string. Plex rating keys are always numeric in practice; anything else indicates a programming error (a foreign identifier in a rating-key slot) that must not reach URL construction. The error names the offending value.

type ResponseTooLargeError

type ResponseTooLargeError struct {
	Path  string
	Limit int64
}

ResponseTooLargeError is returned when a response body exceeds the endpoint's read cap. Carries the cap so operators can spot an unfiltered or oversized response class in logs.

func (*ResponseTooLargeError) Error

func (e *ResponseTooLargeError) Error() string

Error implements the error interface.

type Section

type Section struct {
	Key   string `json:"key"`
	Title string `json:"title"`
	Type  string `json:"type"`
}

Section is a library section from GET /library/sections.

type ServerIdentity

type ServerIdentity struct {
	FriendlyName                  string `json:"friendlyName"`
	MachineIdentifier             string `json:"machineIdentifier"`
	Version                       string `json:"version"`
	Platform                      string `json:"platform"`
	PlatformVersion               string `json:"platformVersion"`
	MyPlexSubscription            bool   `json:"myPlexSubscription"`
	TranscoderActiveVideoSessions int    `json:"transcoderActiveVideoSessions"`
}

ServerIdentity is the server info from GET / (the union of the fields the consumers read; the endpoint returns more).

type SessionBandwidth

type SessionBandwidth struct {
	Location  string `json:"location"`
	Bandwidth int    `json:"bandwidth"`
}

SessionBandwidth is the Session element on a session item.

type SessionUser

type SessionUser struct {
	Title string  `json:"title"`
	ID    FlexInt `json:"id"`
}

SessionUser is the User element on a session or history item.

type SharedServer

type SharedServer struct {
	UserID      string `xml:"userID,attr"`
	Username    string `xml:"username,attr"`
	AccessToken string `xml:"accessToken,attr"`
}

SharedServer is one <SharedServer> element from the plex.tv shared_servers endpoint: a user the server is shared with, and the user-scoped access token for it.

type StatisticsBandwidth

type StatisticsBandwidth struct {
	Bytes int64 `json:"bytes"`
	At    int   `json:"at"`
}

StatisticsBandwidth is one bandwidth sample from the Plex Pass endpoint GET /statistics/bandwidth.

type StatisticsResource

type StatisticsResource struct {
	HostCPUUtilization    float64 `json:"hostCpuUtilization"`
	HostMemoryUtilization float64 `json:"hostMemoryUtilization"`
}

StatisticsResource is one host CPU/memory sample from the Plex Pass endpoint GET /statistics/resources.

type StatusError

type StatusError struct {
	Method string
	Path   string
	Status string
	Code   int
}

StatusError is returned for a non-200, non-404 Plex response, after any transparent retries are exhausted. It carries the status code so callers can classify the failure: a 4xx (bad token, wrong server) will not self-heal, while a 5xx (Plex still starting) may recover.

func (*StatusError) Error

func (e *StatusError) Error() string

Error implements the error interface. The path is included (it is server-relative and never carries the token); the message shape follows the consumers' existing log grammar.

type Stream

type Stream struct {
	LanguageCode         string     `json:"languageCode"`
	LanguageTag          string     `json:"languageTag"`
	DisplayTitle         string     `json:"displayTitle"`
	ExtendedDisplayTitle string     `json:"extendedDisplayTitle"`
	Title                string     `json:"title"`
	Codec                string     `json:"codec"`
	AudioChannelLayout   string     `json:"audioChannelLayout"`
	ID                   int        `json:"id"`
	StreamType           StreamType `json:"streamType"`
	Channels             int        `json:"channels"`
	Selected             bool       `json:"selected"`
	Forced               bool       `json:"forced"`
	HearingImpaired      bool       `json:"hearingImpaired"`
	VisualImpaired       bool       `json:"visualImpaired"`
}

Stream is a single video/audio/subtitle stream on a Part.

func (*Stream) IsAudio

func (s *Stream) IsAudio() bool

IsAudio reports whether the stream is an audio track.

func (*Stream) IsSubtitle

func (s *Stream) IsSubtitle() bool

IsSubtitle reports whether the stream is a subtitle track.

type StreamType

type StreamType int

StreamType identifies the kind of stream. The integer values are the Plex wire format.

const (
	StreamTypeVideo    StreamType = 1
	StreamTypeAudio    StreamType = 2
	StreamTypeSubtitle StreamType = 3
)

Stream-type wire values.

type TV

type TV struct {
	// contains filtered or unexported fields
}

TV is a client for the plex.tv account API (as opposed to a local Plex Media Server). It always verifies TLS against the OS trust store — there is no CA-pinning or skip option for a public endpoint — and never follows redirects, so the admin token cannot be forwarded off plex.tv by a compromised redirect or CDN front.

func NewTV

func NewTV(token string, opts ...TVOption) *TV

NewTV returns a plex.tv client authenticated with the given token.

func (*TV) SharedServers

func (t *TV) SharedServers(ctx context.Context, machineIdentifier string) ([]SharedServer, error)

SharedServers returns the users the identified server is shared with, including their user-scoped access tokens. An empty response body (which plex.tv sometimes returns instead of an empty <MediaContainer/>) yields zero servers, not a parse error.

type TVOption

type TVOption func(*TV)

TVOption configures NewTV.

func WithTVBaseURL

func WithTVBaseURL(base string) TVOption

WithTVBaseURL replaces the plex.tv origin (tests).

func WithTVHTTPClient

func WithTVHTTPClient(hc *http.Client) TVOption

WithTVHTTPClient replaces the built-in hardened client (tests).

type TranscodeSession

type TranscodeSession struct {
	VideoDecision    string `json:"videoDecision"`
	AudioDecision    string `json:"audioDecision"`
	SubtitleDecision string `json:"subtitleDecision"`
	SourceVideoCodec string `json:"sourceVideoCodec"`
	SourceAudioCodec string `json:"sourceAudioCodec"`
	VideoCodec       string `json:"videoCodec"`
	AudioCodec       string `json:"audioCodec"`
}

TranscodeSession is the TranscodeSession element on a session item; its decision fields distinguish direct play / direct stream / transcode.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL