extend

package
v0.3.3 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2026 License: GPL-3.0 Imports: 12 Imported by: 0

Documentation

Overview

Package extend is the descriptor registry the owner console renders from.

Built-ins and plugins describe the same things in the same shape: ranking steps, rule fields, collection and Live TV source kinds, notification channel kinds, integration cards, pages, navigation, commands, settings schemas, slots and agent tools. The console never keeps its own copy of these lists; it asks the registry. A plugin's contribution is therefore indistinguishable from a built-in one except for its Owner and badge.

The registry is in memory. Built-ins register at startup; the plugin host replaces a plugin's descriptors atomically when it is installed, updated, disabled or removed. Nothing here is persisted.

Index

Constants

View Source
const (
	NavGroupTop       = ""
	NavGroupLibrary   = "library"
	NavGroupStreaming = "streaming"
	NavGroupServer    = "server"
)

Nav groups for pages and nav items. The empty group is the unheaded top group in the sidebar.

View Source
const (
	FieldText   = "text"
	FieldSecret = "secret"
	FieldURL    = "url"
	FieldSelect = "select"
	FieldHidden = "hidden"
)

Settings field kinds (SPEC §5.5). The console renders a field from its kind; Secret says the value is stored sealed and never returned.

View Source
const (
	RuleValueText     = "text"     // free text with equals/contains/regex
	RuleValueChoice   = "choice"   // free text with suggested choices
	RuleValueLanguage = "language" // exact declared-language match
	RuleValueNumber   = "number"   // a min/max range
	RuleValueNone     = "none"     // takes no value at all
)

Rule value classes: how a condition value is edited and validated.

View Source
const Builtin = "builtin"

Builtin is the Owner of every descriptor Ether itself registers.

Variables

View Source
var ErrDuplicate = errors.New("descriptor already registered")

ErrDuplicate is returned when a (kind, id) pair is already registered.

Functions

func Handler

func Handler(r *Registry, resolve TextResolver) http.Handler

Handler serves the registry to the owner console. Query parameters: kinds (comma separated; all when absent) and locale (for plugin strings). The ETag hashes the resolved response. A process-local version alone can repeat after restart and wrongly retain old plugin labels or capabilities.

func Known

func Known(k Kind) bool

Known reports whether k is a registered descriptor kind.

func MetaOf

func MetaOf[T any](d Descriptor) (T, error)

MetaOf decodes d.Meta into T. An empty Meta yields T's zero value.

func PluginID

func PluginID(plugin, id string) string

PluginID namespaces a plugin's local id: "plugin:<plugin>/<id>".

Types

type ChannelKindMeta

type ChannelKindMeta struct {
	Fields []SettingsField `json:"fields"`
	// SendTest reports the kind supports the Send test button.
	SendTest bool `json:"sendTest"`
	// Selectable is false for kinds created another way (browser push is
	// created by connecting a device, not picked in the drawer).
	Selectable bool `json:"selectable"`
	// Formats lists payload formats: "text", and "html" for email.
	Formats []string `json:"formats,omitempty"`
	// MaxLength caps the delivered message text, in runes (0 = no cap).
	MaxLength int `json:"maxLength,omitempty"`
}

ChannelKindMeta is the Meta of a channelKind descriptor: the schema the console renders the channel drawer from.

type CollectionSourceMeta

type CollectionSourceMeta struct {
	Params []ParamField `json:"params,omitempty"`
	// Editor names the console widget family that edits this kind today:
	// none, tmdbChart, lookup, urlList, listSelect, discover, titles.
	// WS1f's served-schema renderer replaces this with Params alone.
	Editor string `json:"editor,omitempty"`
	// Defaults is the new-source template: the console creates a source as
	// {"kind": <id>, ...defaults}, then the owner fills required params.
	Defaults json.RawMessage `json:"defaults,omitempty"`
	// MediaTypes lists the collection media types this kind can feed.
	MediaTypes []string `json:"mediaTypes,omitempty"`
}

CollectionSourceMeta is Descriptor.Meta for KindCollectionSource.

type CommandMeta

type CommandMeta struct {
	Route    string `json:"route,omitempty"`
	Action   string `json:"action,omitempty"`
	Keywords Text   `json:"keywords,omitempty"`
	// AgentSuggestion marks the command as an Ask Ether suggestion.
	AgentSuggestion bool `json:"agentSuggestion,omitempty"`
	// RoutePatterns limits an agent suggestion to console routes. A pattern
	// is an exact route or a prefix ending in "/*".
	RoutePatterns []string `json:"routePatterns,omitempty"`
}

CommandMeta is the Meta of a KindCommand descriptor. A command either navigates (Route) or runs a named action (Action). Agent suggestions are commands Ask Ether offers on matching routes instead of ⌘K actions; the descriptor label is the suggestion text.

type Descriptor

type Descriptor struct {
	Kind  Kind   `json:"kind"`
	ID    string `json:"id"`
	Owner string `json:"owner"`
	Label Text   `json:"label"`
	Hint  Text   `json:"hint,omitempty"`
	// Icon is a lucide icon name for built-ins or an asset URL for plugins.
	Icon  string `json:"icon,omitempty"`
	Group string `json:"group,omitempty"`
	Order int    `json:"order,omitempty"`
	// Meta is kind-specific. Each kind's Go type lives in meta_<kind>.go and
	// is mirrored in web/src/lib/extend.ts.
	Meta json.RawMessage `json:"meta,omitempty"`
}

Descriptor is one thing the console can render or pick.

func (Descriptor) Validate

func (d Descriptor) Validate() error

Validate checks the fields every descriptor needs.

func (Descriptor) WithMeta

func (d Descriptor) WithMeta(v any) Descriptor

WithMeta returns d with Meta set to the JSON encoding of v.

type DescriptorOption

type DescriptorOption struct {
	Value string `json:"value"`
	Label Text   `json:"label,omitempty"`
}

DescriptorOption is one selectable value in a descriptor editor. Label is optional: when empty the console shows Value.

func Options

func Options(values ...string) []DescriptorOption

Options builds DescriptorOptions from plain values whose display text is the value itself (codec names, source kinds, language codes).

type EventKindMeta

type EventKindMeta struct {
	// Automatic reports Ether produces the event itself, as opposed to the
	// owner API only.
	Automatic bool `json:"automatic"`
	// Critical marks the events in the Critical alerts preset.
	Critical bool `json:"critical,omitempty"`
	// ChannelKinds, when set, restricts the event to these channel kinds
	// (the weekly newsletter goes by email only).
	ChannelKinds []string `json:"channelKinds,omitempty"`
}

EventKindMeta is the Meta of an eventKind descriptor. The label and hint carry the event's name and description.

type FieldOption

type FieldOption struct {
	Value string `json:"value"`
	Label Text   `json:"label"`
}

FieldOption is one choice of a select field.

type IntegrationMeta

type IntegrationMeta struct {
	// Category is the Integrations page group key: required,
	// watch-tracking, artwork-ratings, playback-extras or smart-features.
	Category string `json:"category"`
	// URL for plugins.
	Logo string `json:"logo,omitempty"`
	// Adds are 1 to 4 short chips naming what the integration adds.
	Adds []Text `json:"adds,omitempty"`
	// Connect is the connect mode: fields, device_code, custom or none.
	Connect string `json:"connect,omitempty"`
	// Tab is the integrations sub-tab key this card configures on.
	Tab string `json:"tab,omitempty"`
	// ConfigKey is the config section key (built-ins only).
	ConfigKey string `json:"configKey,omitempty"`
	// Endpoint is the config section endpoint used to save (built-ins only).
	Endpoint string `json:"endpoint,omitempty"`
	// Test names the test behaviour the console runs (built-ins only); it is
	// a key into the console's compiled test map, like a page component.
	Test string `json:"test,omitempty"`
	// Flag is the boolean field the card switch writes.
	Flag string `json:"flag,omitempty"`
	// RequiredField must be non-empty before the integration can be
	// enabled; the switch opens the setup drawer instead.
	RequiredField string `json:"requiredField,omitempty"`
	// ReadyField is the field whose presence means "configured" for the
	// card status. It defaults to RequiredField.
	ReadyField string `json:"readyField,omitempty"`
	// PublicFallback means the integration works without ReadyField, at
	// public limits; the card says so instead of asking for a key.
	PublicFallback bool `json:"publicFallback,omitempty"`
	// Form names a compiled console form for built-ins whose settings are
	// not a plain schema form (for example "simkl" or "llm").
	Form string `json:"form,omitempty"`
	// Switches are the capability switches the card declares.
	Switches []IntegrationSwitch `json:"switches,omitempty"`
}

IntegrationMeta is the Meta of a KindIntegration descriptor. The descriptor's Group is the card category and Hint the card description. Tab, ConfigKey, Endpoint and Flag are owner-API wiring for built-ins; plugin integrations leave them empty and configure through their plugin.

type IntegrationSwitch

type IntegrationSwitch struct {
	Key   string `json:"key"`
	Label Text   `json:"label"`
	Hint  Text   `json:"hint,omitempty"`
}

IntegrationSwitch is one capability switch an integration card exposes.

type Kind

type Kind string

Kind names one family of descriptors. Adding a Kind is a contract change.

const (
	KindRankingStep      Kind = "rankingStep"
	KindRuleField        Kind = "ruleField"
	KindCollectionSource Kind = "collectionSource"
	KindRowSource        Kind = "rowSource"
	KindLiveTVSource     Kind = "liveTVSource"
	KindChannelKind      Kind = "channelKind"
	KindEventKind        Kind = "eventKind"
	KindSubtitleProvider Kind = "subtitleProvider"
	KindMetadataProvider Kind = "metadataProvider"
	KindPluginDirectory  Kind = "pluginDirectory"
	KindMarkerSource     Kind = "markerSource"
	KindSourceKind       Kind = "sourceKind"
	KindIntegration      Kind = "integration"
	KindPage             Kind = "page"
	KindNavItem          Kind = "navItem"
	KindCommand          Kind = "command"
	KindSettingsSchema   Kind = "settingsSchema"
	KindSlot             Kind = "slot"
	KindWidget           Kind = "widget"
	KindTool             Kind = "tool"
)

type LiveTVSourceMeta

type LiveTVSourceMeta struct {
	Params         []ParamField `json:"params,omitempty"`
	Editor         string       `json:"editor,omitempty"`
	YieldsChannels bool         `json:"yieldsChannels,omitempty"`
	YieldsGuide    bool         `json:"yieldsGuide,omitempty"`
	// Test reports whether the kind supports a connection test.
	Test bool `json:"test,omitempty"`
}

LiveTVSourceMeta is Descriptor.Meta for KindLiveTVSource.

type NavItemMeta struct {
	Route  string `json:"route"`
	Page   string `json:"page,omitempty"`
	Mobile bool   `json:"mobile,omitempty"`
	Exact  bool   `json:"exact,omitempty"`
}

NavItemMeta is the Meta of a KindNavItem descriptor. The descriptor's Group is the nav group, Icon the lucide icon name (built-ins) or asset URL (plugins), and Order the position inside the group.

type PageMeta

type PageMeta struct {
	// Route is the console path, for example "/releases". Plugin pages omit
	// it; their route is /p/<plugin>/<page>.
	Route string `json:"route,omitempty"`
	// NavGroup places the page in the sidebar when InNav is set, and places
	// a plugin page in its declared group.
	NavGroup string `json:"navGroup,omitempty"`
	// InNav marks a built-in page that belongs in the sidebar. Plugin pages
	// appear in the sidebar whenever they declare a nav group.
	InNav bool `json:"inNav,omitempty"`
	// Mobile keeps the page in the mobile tab bar.
	Mobile bool `json:"mobile,omitempty"`
	// Exact matches only the route itself in the sidebar.
	Exact bool `json:"exact,omitempty"`
	// Keywords are search aliases for the page itself.
	Keywords Text `json:"keywords,omitempty"`
	// Sections are the page's in-page sections in display order.
	Sections []PageSection `json:"sections,omitempty"`
}

PageMeta is the Meta of a KindPage descriptor. Built-in pages stay compiled React components; the console maps the descriptor ID to its component. A plugin page renders through the plugin page route.

type PageSection

type PageSection struct {
	Key string `json:"key"`
	// Label is declared with T for built-ins so the i18n extractor sees it.
	Label Text `json:"label"`
	// Keywords are search aliases, declared with T for built-ins. The
	// console applies its search-terms translation to the source string,
	// keeping the English terms beside the translated ones.
	Keywords Text `json:"keywords,omitempty"`
}

PageSection is one in-page section (tab) the sitemap and ⌘K can target.

type ParamField

type ParamField struct {
	Name     string        `json:"name"`
	Kind     string        `json:"kind"`
	Label    Text          `json:"label"`
	Hint     Text          `json:"hint,omitempty"`
	Required bool          `json:"required,omitempty"`
	Options  []ParamOption `json:"options,omitempty"`
	// Lookup names a catalogue lookup ("person", "keyword", "collection",
	// "company", "network", "watchProvider") for id-valued fields.
	Lookup string `json:"lookup,omitempty"`
	// Placeholder is display-only example text (a URL shape).
	Placeholder string `json:"placeholder,omitempty"`
}

ParamField is one source-kind parameter. Kind uses the settings field kinds from docs/extend/SPEC.md §5.5 (text, url, select, number, bool, list, title, …), so the served-schema form (WS1f) can render it without translation. Name maps onto the persisted source object's JSON field.

type ParamOption

type ParamOption struct {
	Value string   `json:"value"`
	Label Text     `json:"label"`
	Media []string `json:"media,omitempty"`
}

ParamOption is one choice of a select-shaped source parameter.

type RankingEditorKind

type RankingEditorKind string

RankingEditorKind names the inline editor a ranking step offers in the "What matters most" ladder.

const (
	RankingEditorNone        RankingEditorKind = "none"
	RankingEditorOrderedList RankingEditorKind = "ordered_list"
	RankingEditorChoice      RankingEditorKind = "choice"
	RankingEditorRange       RankingEditorKind = "range"
	RankingEditorToggle      RankingEditorKind = "toggle"
)

func (*RankingEditorKind) UnmarshalJSON

func (k *RankingEditorKind) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts a plain editor kind string, null, or a plugin manifest editor object ({"kind": "choice"}).

type RankingStepMeta

type RankingStepMeta struct {
	// Editor is the inline editor kind for this step's own setting.
	Editor RankingEditorKind `json:"editor"`
	// Options are the values the editor offers: the ordered choices for
	// ordered_list and choice editors, the selectable bounds for range.
	Options []DescriptorOption `json:"options,omitempty"`
	// Choices are optional selectable choices when a step distinguishes
	// choices from options.
	Choices []DescriptorOption `json:"choices,omitempty"`
	// Required steps must stay in every ladder (connection, verified).
	Required bool `json:"required,omitempty"`
	// CanMove is false for steps the ladder renders as Automatic: the owner
	// cannot reorder them.
	CanMove bool `json:"canMove"`
	// DefaultPosition is the step's 0-based place in the default ladder.
	// "Add a step" inserts a step ahead of the first ladder step with a
	// larger DefaultPosition. Plugin steps are never auto-inserted; this
	// only says where one lands when the owner adds it.
	DefaultPosition int `json:"defaultPosition"`
	// ConfigKey names the QualityProfile field the editor edits for
	// built-in steps (preferredLanguages, preferredSources,
	// preferredCodecs, preferredAudio, preferLargest, heightRange, hdr,
	// customFormatScores).
	// Plugin steps leave it empty; their settings arrive with WS9h.
	ConfigKey string `json:"configKey,omitempty"`
}

RankingStepMeta is the Meta of a KindRankingStep descriptor.

type Registry

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

Registry holds every descriptor. It is safe for concurrent use.

func New

func New() *Registry

New returns an empty registry.

func (*Registry) Get

func (r *Registry) Get(kind Kind, id string) (Descriptor, bool)

Get returns one descriptor.

func (*Registry) List

func (r *Registry) List(kinds ...Kind) []Descriptor

List returns descriptors of the given kinds (all kinds when none are given), ordered by kind, group, order, then id.

func (*Registry) MustRegister

func (r *Registry) MustRegister(ds ...Descriptor)

MustRegister is Register for built-ins whose descriptors are static: a failure is a programming error caught by tests.

func (*Registry) Owned

func (r *Registry) Owned(owner string) []Descriptor

Owned returns every descriptor owned by owner.

func (*Registry) Register

func (r *Registry) Register(ds ...Descriptor) error

Register adds built-in or plugin descriptors. It is all or nothing: on any error nothing is added.

func (*Registry) RemoveOwner

func (r *Registry) RemoveOwner(owner string)

RemoveOwner drops every descriptor owned by a plugin.

func (*Registry) ReplaceOwner

func (r *Registry) ReplaceOwner(owner string, ds []Descriptor) error

ReplaceOwner atomically swaps every descriptor owned by owner for ds. The plugin host calls it on install, update, enable and disable. Built-ins cannot be replaced.

func (*Registry) Subscribe

func (r *Registry) Subscribe(fn func()) (unsubscribe func())

Subscribe calls fn after every change. fn must not block.

func (*Registry) Version

func (r *Registry) Version() uint64

Version increases on every change. The HTTP handler uses it as an ETag.

type Response

type Response struct {
	Version     uint64       `json:"version"`
	Descriptors []Descriptor `json:"descriptors"`
}

Response is the body of GET /api/extend/registry.

type RowSourceMeta

type RowSourceMeta struct {
	// HubID is the row pattern the Home Designer policy targets.
	HubID string `json:"hubId"`
	// RowKind is the personalization row kind, when the row is kind-backed.
	RowKind string `json:"rowKind,omitempty"`
	// Sections lists the pages this row can appear on (empty = every page).
	Sections []string `json:"sections,omitempty"`
	// Source is the row provenance (core.RowSource* for built-ins).
	Source string `json:"source,omitempty"`
	// CollectionSource links a list-backed row source to the collection
	// source kind that gathers its titles (WS3/WS9 list sources).
	CollectionSource string `json:"collectionSource,omitempty"`
}

RowSourceMeta is Descriptor.Meta for KindRowSource.

type RuleFieldMeta

type RuleFieldMeta struct {
	// ValueClass is one of the RuleValue* constants.
	ValueClass string `json:"valueClass"`
	// Operators are the comparisons the field accepts, in display order
	// (equals, contains, regex, range).
	Operators []string `json:"operators,omitempty"`
	// DefaultOperator is preselected when a condition is created or
	// retargeted to this field.
	DefaultOperator string `json:"defaultOperator,omitempty"`
	// Choices are suggested values for choice and language fields. Choice
	// fields still accept free text; language choices are ISO codes the
	// console labels through Intl.DisplayNames.
	Choices []DescriptorOption `json:"choices,omitempty"`
	// Unit is the value's unit for number fields ("GiB", "p").
	Unit string `json:"unit,omitempty"`
	// SingleValue fields can hold one value per release, so two required
	// rules demanding different values conflict.
	SingleValue bool `json:"singleValue,omitempty"`
	// Editor carries a plugin manifest's raw ruleConditions[].editor
	// (SPEC §5.1.3) when ValueClass was not set directly.
	Editor json.RawMessage `json:"editor,omitempty"`
}

RuleFieldMeta is the Meta of a KindRuleField descriptor.

func (RuleFieldMeta) Resolved

func (m RuleFieldMeta) Resolved() RuleFieldMeta

Resolved folds a plugin manifest Editor declaration into ValueClass, Choices, Unit and SingleValue when those fields were not set directly.

type SettingsField

type SettingsField struct {
	Key      string        `json:"key"`
	Kind     string        `json:"kind"`
	Label    Text          `json:"label"`
	Hint     Text          `json:"hint,omitempty"`
	Required bool          `json:"required,omitempty"`
	Secret   bool          `json:"secret,omitempty"`
	Default  string        `json:"default,omitempty"`
	Options  []FieldOption `json:"options,omitempty"`
	Wide     bool          `json:"wide,omitempty"`
}

SettingsField is one field of a kind's settings schema.

type SettingsSchemaMeta

type SettingsSchemaMeta struct {
	// ConfigKey is the config.json section a built-in form edits, when it
	// edits one. Plugin settings leave it empty.
	ConfigKey string          `json:"configKey,omitempty"`
	Schema    json.RawMessage `json:"schema"`
}

SettingsSchemaMeta is the Meta of a KindSettingsSchema descriptor. Schema is an encoded internal/config/schema.Schema; it stays raw here so this package does not import the schema package (which imports this one for Text). The TypeScript mirror is SettingsSchemaMeta in web/src/pages/settings/SchemaForm.tsx.

type Text

type Text struct {
	Source   string `json:"source,omitempty"`
	Plugin   string `json:"plugin,omitempty"`
	Key      string `json:"key,omitempty"`
	Resolved string `json:"resolved,omitempty"`
}

Text is user-visible copy. Built-ins carry an English source string that the console translates through its catalogue (declare it with T so the i18n extractor finds it). Plugins carry a key into their own strings; the registry handler resolves it for the requested locale into Resolved.

func PluginText

func PluginText(plugin, key string) Text

PluginText refers to key in plugin's strings.

func T

func T(source string) Text

T declares built-in console copy. The argument must be a string literal: web/scripts/i18n.mjs extracts extend.T("...") calls from Go sources into the console catalogue, exactly like t("...") calls in the console.

func (Text) Empty

func (t Text) Empty() bool

Empty reports whether the text carries nothing to show.

type TextResolver

type TextResolver func(plugin, key, locale string) (string, bool)

TextResolver looks up a plugin string for a locale. It returns false when neither the locale nor English has the key.

Jump to

Keyboard shortcuts

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