control

package
v1.5.2 Latest Latest
Warning

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

Go to latest
Published: Jul 1, 2026 License: AGPL-3.0 Imports: 16 Imported by: 0

Documentation

Overview

Package control exposes playback control + status over a small HTTP/JSON API. It is the shared foundation for remote control (one OpenDeezer client driving another) and the MCP server (an AI agent driving playback). A frontend wires it like the MPRIS bridge: provide a status snapshot func + a set of command callbacks, plus the Deezer client for read-only browse (search/playlists).

Auth has three modes, picked by Config. Credentials are accepted via request HEADERS only (never the query string, which leaks into logs/history):

  • Token: a bearer token in "X-OpenDeezer-Token". Strongest.
  • Same-account: no token, but a controller must prove it is logged into the SAME Deezer account by sending its OWN Deezer user id in "X-OpenDeezer-Account". A controller learns that id from its own login, not from this server — /whoami deliberately does NOT echo the user id, so a bystander can't read the credential and replay it. Convenience auth for a trusted LAN: the user copies no token; their own devices just connect. The user id is only semi-private, so this is LAN-trust grade, not a secret.
  • Session (web remote): a phone pairs with a 6-digit code minted at enable time; on success it receives a short-lived session token sent as X-OpenDeezer-Session. CSRF-safe because the token lives in localStorage (not a cookie) and custom headers cannot be set cross-origin.
  • None: open (only safe bound to localhost).

Mutating endpoints require POST and reject requests carrying a browser Origin header, so a web page the user happens to visit can't drive playback (CSRF). The exception is requests that also carry a valid X-OpenDeezer-Session token: those come from our own SPA (same origin in the browser), so they are allowed. GET /whoami is unauthenticated so a controller can discover the account NAME (not id) and auth mode of a server before connecting.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Account

type Account struct {
	UserID string
	Name   string
	Offer  string
}

Account is the controlled client's Deezer identity, supplied by a snapshot provider so the HTTP goroutine never reads the deezer.Client's login fields directly (those are written by Login on another goroutine).

type Client

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

Client talks to a control Server over HTTP. It is the shared driver for the MCP server and the TUI's remote-play feature: both point it at another OpenDeezer client's control API and issue the same commands a local user would.

func NewClient

func NewClient(base, token, account string) *Client

NewClient builds a control client. base is the server's URL; token/account are the credentials (send whichever the server requires; empty ones are omitted).

func (*Client) CycleRepeat

func (c *Client) CycleRepeat() (State, error)

func (*Client) Next

func (c *Client) Next() (State, error)

func (*Client) PlayPause

func (c *Client) PlayPause() (State, error)

PlayPause toggles play/pause on the peer.

func (*Client) PlayPlaylist

func (c *Client) PlayPlaylist(id string) (State, error)

func (*Client) PlayTrack

func (c *Client) PlayTrack(id string) (State, error)

func (*Client) Playlists

func (c *Client) Playlists() (json.RawMessage, error)

Playlists returns the raw playlists JSON from the server.

func (*Client) Prev

func (c *Client) Prev() (State, error)

func (*Client) Restart

func (c *Client) Restart() (State, error)

func (*Client) Search

func (c *Client) Search(q string) (json.RawMessage, error)

Search returns the raw search-results JSON from the server.

func (*Client) Seek

func (c *Client) Seek(ms int64) (State, error)

func (*Client) SetRepeat added in v1.0.1

func (c *Client) SetRepeat(mode string) (State, error)

SetRepeat sets the repeat mode on the peer to mode ("off", "all", or "one").

func (*Client) SetShuffle added in v1.0.1

func (c *Client) SetShuffle(on bool) (State, error)

SetShuffle sets shuffle on (true) or off (false) on the peer.

func (*Client) SetVolume

func (c *Client) SetVolume(v float64) (State, error)

func (*Client) Status

func (c *Client) Status() (State, error)

Status returns the current playback snapshot.

func (*Client) Stop

func (c *Client) Stop() (State, error)

func (*Client) ToggleShuffle

func (c *Client) ToggleShuffle() (State, error)

func (*Client) Whoami

func (c *Client) Whoami() (Whoami, error)

Whoami fetches the server's identity (name + auth mode).

type Commands

type Commands struct {
	PlayPause     func()
	Next          func()
	Prev          func()
	Stop          func()
	Restart       func() // seek to 0
	CycleRepeat   func()
	ToggleShuffle func()
	SetRepeat     func(mode string) // mode: "off"|"all"|"one" (SET variant)
	SetShuffle    func(on bool)     // on: true/false (SET variant)
	Seek          func(ms int64)
	SetVolume     func(v float64)
	PlayTrack     func(id string)
	PlayPlaylist  func(id string)
}

Commands are the mutating actions a controller exposes (each may be nil).

type Config

type Config struct {
	Addr            string // host:port ("127.0.0.1:7654" localhost, ":7654" LAN)
	Token           string // bearer token; "" disables token auth
	SameAccountOnly bool   // when Token=="", require a matching Deezer account id
	WebRemote       bool   // allow LAN bind with session (pairing) as the sole auth
}

Config configures the control server.

type Server

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

Server serves the control API.

func New

func New(cfg Config, status func() State, account func() Account, cmds Commands, client *deezer.Client) *Server

New builds a control server from cfg. status + account are snapshot providers (called from the HTTP goroutine, so they must be race-free reads); client supplies the browse endpoints (search/playlists).

func (*Server) Addr

func (s *Server) Addr() string

Addr returns the actual listen address (valid after Start).

func (*Server) Close

func (s *Server) Close()

Close stops the server.

func (*Server) DisablePairing added in v1.0.1

func (s *Server) DisablePairing()

DisablePairing clears the pairing code so no new phones can pair. Existing valid session tokens remain usable for their remaining TTL.

func (*Server) EnablePairing added in v1.0.1

func (s *Server) EnablePairing() string

EnablePairing mints a fresh 6-digit code, activates pairing, and returns the code. Safe to call multiple times; each call resets the code.

func (*Server) PairingActive added in v1.0.1

func (s *Server) PairingActive() bool

PairingActive reports whether pairing is currently enabled.

func (*Server) PairingCode added in v1.0.1

func (s *Server) PairingCode() string

PairingCode returns the current 6-digit code (empty when not active).

func (*Server) SetClientInfo

func (s *Server) SetClientInfo(client, device string)

SetClientInfo records the client/platform id + device label for /whoami.

func (*Server) SetVersion

func (s *Server) SetVersion(v string)

SetVersion records the app version reported by /whoami.

func (*Server) Start

func (s *Server) Start() error

Start binds the port and serves in a background goroutine.

type State

type State struct {
	State      string  `json:"state"` // playing | paused | stopped | loading | error
	Track      *Track  `json:"track,omitempty"`
	PositionMS int64   `json:"positionMs"`
	DurationMS int64   `json:"durationMs"`
	Volume     float64 `json:"volume"` // 0..1
	Repeat     string  `json:"repeat"` // off | all | one
	Shuffle    bool    `json:"shuffle"`
	Format     string  `json:"format,omitempty"`
	Queue      []Track `json:"queue,omitempty"`
}

State is the playback snapshot returned by GET /status.

type Track

type Track struct {
	ID         string `json:"id"`
	Title      string `json:"title"`
	Artist     string `json:"artist"`
	ArtistID   string `json:"artistId,omitempty"`
	Album      string `json:"album"`
	Explicit   bool   `json:"explicit"`
	DurationMS int64  `json:"durationMs"`
	ArtworkURL string `json:"artworkUrl,omitempty"`
}

Track is a now-playing / queue entry in the API.

type Whoami

type Whoami struct {
	Name    string `json:"name"`
	Offer   string `json:"offer,omitempty"`
	Auth    string `json:"auth"`              // token | account | session | none
	Version string `json:"version,omitempty"` // OpenDeezer version
	Client  string `json:"client,omitempty"`  // client/platform id (tui, macos, gnome…)
	Device  string `json:"device,omitempty"`  // human device label ("OpenDeezer TUI")
}

Whoami is the unauthenticated identity returned by GET /whoami. It carries the account display NAME (for the controller to recognise its own device) but never the user id: in same-account mode that id IS the credential, so echoing it here would let any bystander read and replay it.

Jump to

Keyboard shortcuts

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