| package bex:plugin@1.0.0; |
|
|
| interface common { |
| record request-context { |
| request-id: string, |
| locale: option<string>, |
| region: option<string>, |
| safe-mode: bool, |
| hints: list<string>, |
| } |
| record attr { key: string, value: string } |
| enum image-layout { portrait, landscape, square, banner, circular, unknown } |
| record image { |
| url: string, |
| layout: image-layout, |
| width: option<u32>, |
| height: option<u32>, |
| blurhash: option<string>, |
| } |
| record image-set { |
| low: option<image>, |
| medium: option<image>, |
| high: option<image>, |
| backdrop: option<image>, |
| logo: option<image>, |
| } |
| record linked-id { source: string, id: string } |
| enum media-kind { movie, series, anime, short, special, documentary, music, podcast, book, live, unknown } |
| enum status { unknown, upcoming, ongoing, completed, cancelled, paused } |
| enum card-layout { carousel, grid, vertical-list, banner, compact } |
| record page-cursor { token: option<string>, limit: option<u32> } |
| record media-card { |
| id: string, |
| title: string, |
| kind: option<media-kind>, |
| images: option<image-set>, |
| original-title: option<string>, |
| tagline: option<string>, |
| year: option<string>, |
| score: option<u32>, |
| genres: list<string>, |
| status: option<status>, |
| content-rating: option<string>, |
| url: option<string>, |
| ids: list<linked-id>, |
| extra: list<attr>, |
| } |
| record category-link { id: string, title: string, subtitle: option<string>, image: option<image> } |
| record home-section { |
| id: string, |
| title: string, |
| subtitle: option<string>, |
| items: list<media-card>, |
| next-page: option<string>, |
| layout: card-layout, |
| show-rank: bool, |
| categories: list<category-link>, |
| extra: list<attr>, |
| } |
| record paged-result { |
| items: list<media-card>, |
| categories: list<category-link>, |
| next-page: option<string>, |
| } |
| record search-filters { kind: option<media-kind>, page: page-cursor, fast-match: bool } |
| record episode { |
| id: string, |
| title: string, |
| number: option<f64>, |
| season: option<f64>, |
| images: option<image-set>, |
| description: option<string>, |
| released: option<string>, |
| score: option<u32>, |
| url: option<string>, |
| tags: list<string>, |
| extra: list<attr>, |
| } |
| record season { id: string, title: string, number: option<f64>, year: option<u32>, episodes: list<episode> } |
| record person { id: string, name: string, image: option<image-set>, role: option<string>, url: option<string> } |
| record media-info { |
| id: string, |
| title: string, |
| kind: media-kind, |
| images: option<image-set>, |
| original-title: option<string>, |
| description: option<string>, |
| score: option<u32>, |
| scored-by: option<u64>, |
| year: option<string>, |
| release-date: option<string>, |
| genres: list<string>, |
| tags: list<string>, |
| status: option<status>, |
| content-rating: option<string>, |
| seasons: list<season>, |
| cast: list<person>, |
| crew: list<person>, |
| runtime-minutes: option<u32>, |
| trailer-url: option<string>, |
| ids: list<linked-id>, |
| studio: option<string>, |
| country: option<string>, |
| language: option<string>, |
| url: option<string>, |
| extra: list<attr>, |
| } |
| record video-resolution { width: u32, height: u32, hdr: bool, label: string } |
| enum stream-format { hls, dash, progressive, unknown } |
| record video-track { |
| resolution: video-resolution, |
| url: string, |
| mime-type: option<string>, |
| bitrate: option<u64>, |
| codecs: option<string>, |
| } |
| record subtitle-track { label: string, url: string, language: option<string>, format: option<string> } |
| record server { id: string, label: string, url: string, priority: u8, extra: list<attr> } |
| record stream-source { |
| id: string, |
| label: string, |
| format: stream-format, |
| manifest-url: option<string>, |
| videos: list<video-track>, |
| subtitles: list<subtitle-track>, |
| headers: list<attr>, |
| extra: list<attr>, |
| } |
| record subtitle-query { |
| title: option<string>, |
| year: option<u32>, |
| season: option<u32>, |
| episode: option<u32>, |
| language: option<string>, |
| fps: option<f32>, |
| file-hash: option<string>, |
| file-size: option<u64>, |
| identifiers: list<linked-id>, |
| } |
| record subtitle-entry { |
| id: string, |
| title: string, |
| language: string, |
| format: string, |
| url: option<string>, |
| release: option<string>, |
| fps: option<f32>, |
| downloads: option<u64>, |
| score: option<u32>, |
| hearing-impaired: bool, |
| machine-translated: bool, |
| file-hash: option<string>, |
| extra: list<attr>, |
| } |
| record subtitle-file { format: string, content: list<u8> } |
| record article { |
| id: string, |
| title: string, |
| summary: option<string>, |
| url: string, |
| published: option<string>, |
| author: option<string>, |
| thumbnail: option<image-set>, |
| tags: list<string>, |
| extra: list<attr>, |
| } |
| record article-section { id: string, title: string, items: list<article>, next-page: option<string> } |
| variant plugin-error { |
| network(string), |
| parse(string), |
| not-found, |
| unauthorized, |
| forbidden, |
| rate-limited(option<u32>), |
| timeout, |
| cancelled, |
| unsupported, |
| invalid-input(string), |
| internal(string), |
| } |
| } |
| |
| interface http { |
| use common.{attr, plugin-error}; |
| |
| enum method { get, post, put, delete, head, patch, options } |
| enum cache-mode { normal, no-store, only-cache, force-refresh } |
| record request { |
| method: method, |
| url: string, |
| headers: list<attr>, |
| body: option<list<u8>>, |
| timeout-ms: option<u32>, |
| follow-redirects: bool, |
| cache-mode: cache-mode, |
| max-bytes: option<u64>, |
| } |
| record response { |
| status: u16, |
| headers: list<attr>, |
| body: list<u8>, |
| cached: bool, |
| final-url: string, |
| } |
| |
| /// Send an HTTP request and return the response. |
| /// This is a simple function-based API (no resource types) for simplicity. |
| send-request: func(req: request) -> result<response, plugin-error>; |
| } |
| |
| interface kv { |
| use common.{attr}; |
| set: func(key: string, value: list<u8>, ttl-seconds: option<u32>) -> bool; |
| get: func(key: string) -> option<list<u8>>; |
| remove: func(key: string) -> bool; |
| keys: func(prefix: string) -> list<string>; |
| } |
| |
| interface secrets { |
| get: func(key: string) -> option<string>; |
| } |
| |
| interface log { |
| use common.{attr}; |
| enum level { trace, debug, info, warn, error } |
| write: func(level: level, msg: string, fields: list<attr>); |
| } |
| |
| interface clock { |
| now-ms: func() -> u64; |
| monotonic: func() -> u64; |
| } |
| |
| interface rng { |
| bytes: func(len: u32) -> list<u8>; |
| } |
| |
| interface js { |
| use common.{plugin-error}; |
| |
| /// Options for JS evaluation with fine-grained control. |
| record js-opts { |
| /// Name for the script (appears in JS error stack traces). |
| filename: option<string>, |
| /// Timeout in milliseconds. 0 = use pool default (10s). |
| timeout-ms: u32, |
| } |
| |
| /// Evaluate JavaScript. `input` is injected as the global variable `input`. |
| /// The last expression value is returned as a JSON string. |
| /// Use `JSON.parse(input)` in JS if structured data is needed. |
| eval-js: func(code: string, input: string) -> result<string, plugin-error>; |
| |
| /// Evaluate JavaScript with options (timeout, filename for error traces). |
| eval-js-opts: func( |
| code: string, |
| input: string, |
| opts: js-opts, |
| ) -> result<string, plugin-error>; |
| |
| /// Register + call a named JS function. Faster than eval-js for repeated calls. |
| /// |
| /// `fn-name` : Function name as defined in `fn-source`. |
| /// `fn-source` : JavaScript defining exactly one function named `fn-name`. |
| /// Parsed and registered on first call; reused on subsequent calls. |
| /// Auto-re-registers if `fn-source` content changes. |
| /// `args-json` : Passed to the function as a single string argument. |
| /// In JS: `function myFn(args) { const d = JSON.parse(args); ... }` |
| call-js-fn: func( |
| fn-name: string, |
| fn-source: string, |
| args-json: string, |
| ) -> result<string, plugin-error>; |
| |
| /// Remove a named JS function from this plugin's context. |
| /// Use when a cipher function is rotated (player update). |
| /// Returns 0 on success. |
| clear-js-fn: func(fn-name: string) -> result<u8, plugin-error>; |
| } |
| |
| world plugin { |
| import http; |
| import kv; |
| import secrets; |
| import log; |
| import clock; |
| import rng; |
| import js; |
| |
| export api: interface { |
| use common.{ |
| request-context, page-cursor, paged-result, search-filters, |
| home-section, media-info, server, stream-source, |
| subtitle-query, subtitle-entry, subtitle-file, |
| article-section, article, plugin-error |
| }; |
| get-home: func(ctx: request-context) -> result<list<home-section>, plugin-error>; |
| get-category: func(ctx: request-context, id: string, page: page-cursor) -> result<paged-result, plugin-error>; |
| search: func(ctx: request-context, query: string, filters: search-filters) -> result<paged-result, plugin-error>; |
| get-info: func(ctx: request-context, id: string) -> result<media-info, plugin-error>; |
| get-servers: func(ctx: request-context, id: string) -> result<list<server>, plugin-error>; |
| resolve-stream: func(ctx: request-context, server: server) -> result<stream-source, plugin-error>; |
| search-subtitles: func(ctx: request-context, query: subtitle-query) -> result<list<subtitle-entry>, plugin-error>; |
| download-subtitle: func(ctx: request-context, id: string) -> result<subtitle-file, plugin-error>; |
| get-articles: func(ctx: request-context) -> result<list<article-section>, plugin-error>; |
| search-articles: func(ctx: request-context, query: string) -> result<list<article>, plugin-error>; |
| } |
| } |
| |