Files
tv/rest/openapi.js
T
2026-08-27 16:11:56 -05:00

493 lines
16 KiB
JavaScript

const spec = {
openapi: '3.0.3',
info: {
title: 'bug.tv api',
version: '1.0.0'
},
tags: [
{ name: 'meta', description: 'service and provider metadata' },
{ name: 'search', description: 'TMDB search and episode browsing' },
{ name: 'details', description: 'TMDB media details' },
{ name: 'streams', description: 'm3u8 stream lookup from providers' },
],
paths: {
'/providers': {
get: {
tags: ['meta'],
summary: 'List all providers and which are enabled',
operationId: 'listProviders',
responses: {
200: {
description: 'provider list',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
all: { type: 'array', items: { type: 'string' } },
enabled: { type: 'array', items: { type: 'string' } },
},
},
},
},
},
},
},
},
'/proxy/media': {
get: {
tags: ['meta'],
summary: 'Proxy upstream media or m3u8 playlists with server-side headers',
operationId: 'proxyMedia',
parameters: [
{
name: 'url',
in: 'query',
required: true,
schema: { type: 'string' },
},
{
name: 'h',
in: 'query',
required: false,
description: 'JSON-encoded headers to send to the upstream URL',
schema: { type: 'string' },
},
],
responses: {
200: {
description: 'Proxied upstream response. m3u8 playlist URIs are rewritten to use the proxy.',
},
400: { $ref: '#/components/responses/BadRequest' },
502: { $ref: '#/components/responses/Upstream' },
},
},
},
'/search': {
get: {
tags: ['search'],
summary: 'Search movies + TV via TMDB',
operationId: 'search',
parameters: [
{
name: 'q',
in: 'query',
required: true,
description: 'search text',
schema: { type: 'string' },
example: 'dune',
},
],
responses: {
200: {
description: 'up to 15 results',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
query: { type: 'string' },
count: { type: 'integer' },
results: { type: 'array', items: { $ref: '#/components/schemas/SearchResult' } },
},
},
},
},
},
400: { $ref: '#/components/responses/BadRequest' },
502: { $ref: '#/components/responses/Upstream' },
},
},
},
'/trending': {
get: {
tags: ['search'],
summary: 'Trending movies and TV via TMDB',
operationId: 'getTrending',
parameters: [
{
name: 'type',
in: 'query',
required: false,
schema: { type: 'string', enum: ['all', 'movie', 'tv'], default: 'all' },
},
{
name: 'window',
in: 'query',
required: false,
schema: { type: 'string', enum: ['day', 'week'], default: 'week' },
},
],
responses: {
200: {
description: 'up to 20 trending items',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
type: { type: 'string' },
window: { type: 'string' },
count: { type: 'integer' },
results: { type: 'array', items: { $ref: '#/components/schemas/SearchResult' } },
},
},
},
},
},
400: { $ref: '#/components/responses/BadRequest' },
502: { $ref: '#/components/responses/Upstream' },
},
},
},
'/movie/{id}': {
get: {
tags: ['details'],
summary: 'Movie details (genres, rating, images, description)',
description: 'Passes through the raw TMDB movie object, plus `poster_url`/`backdrop_url` convenience fields.',
operationId: 'getMovieDetails',
parameters: [{ $ref: '#/components/parameters/MovieId' }],
responses: {
200: {
description: 'TMDB movie object',
content: { 'application/json': { schema: { $ref: '#/components/schemas/MediaDetails' } } },
},
404: { $ref: '#/components/responses/NotFound' },
502: { $ref: '#/components/responses/Upstream' },
},
},
},
'/tv/{id}': {
get: {
tags: ['details'],
summary: 'TV show details (genres, rating, images, description)',
description: 'Passes through the raw TMDB tv object, plus `poster_url`/`backdrop_url` convenience fields.',
operationId: 'getTvDetails',
parameters: [{ $ref: '#/components/parameters/TvId' }],
responses: {
200: {
description: 'TMDB tv object',
content: { 'application/json': { schema: { $ref: '#/components/schemas/MediaDetails' } } },
},
404: { $ref: '#/components/responses/NotFound' },
502: { $ref: '#/components/responses/Upstream' },
},
},
},
'/tv/{id}/seasons': {
get: {
tags: ['search'],
summary: 'List seasons for a TV show',
operationId: 'getSeasons',
parameters: [{ $ref: '#/components/parameters/TvId' }],
responses: {
200: {
description: 'season list',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
tmdb_id: { type: 'string' },
count: { type: 'integer' },
seasons: { type: 'array', items: { $ref: '#/components/schemas/Season' } },
},
},
},
},
},
400: { $ref: '#/components/responses/BadRequest' },
502: { $ref: '#/components/responses/Upstream' },
},
},
},
'/tv/{id}/seasons/{season}': {
get: {
tags: ['search'],
summary: 'List episodes for a season',
operationId: 'getEpisodes',
parameters: [
{ $ref: '#/components/parameters/TvId' },
{
name: 'season',
in: 'path',
required: true,
description: 'season number',
schema: { type: 'integer' },
example: 1,
},
],
responses: {
200: {
description: 'episode list',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
tmdb_id: { type: 'string' },
season: { type: 'integer' },
count: { type: 'integer' },
episodes: { type: 'array', items: { $ref: '#/components/schemas/Episode' } },
},
},
},
},
},
400: { $ref: '#/components/responses/BadRequest' },
502: { $ref: '#/components/responses/Upstream' },
},
},
},
'/streams': {
get: {
tags: ['streams'],
summary: 'Fetch m3u8 streams for a movie or TV episode',
operationId: 'getStreams',
description:
'Scrapes every enabled provider in parallel (override with `providers`) and returns deduped, normalized, quality-ranked streams. ' +
'For `type=tv` you must pass `season` and `episode`. Optional filters are applied before ranking.',
parameters: [
{
name: 'tmdb',
in: 'query',
required: true,
description: 'TMDB numeric id',
schema: { type: 'integer' },
example: 438631,
},
{
name: 'type',
in: 'query',
required: false,
description: 'movie (default) or tv',
schema: { type: 'string', enum: ['movie', 'tv'], default: 'movie' },
},
{
name: 'season',
in: 'query',
required: false,
description: 'season number (required when type=tv)',
schema: { type: 'integer' },
},
{
name: 'episode',
in: 'query',
required: false,
description: 'episode number (required when type=tv)',
schema: { type: 'integer' },
},
{
name: 'providers',
in: 'query',
required: false,
description: 'comma-separated provider names; defaults to the enabled set',
schema: { type: 'string' },
example: 'videasy,vidfast',
},
{
name: 'minQuality',
in: 'query',
required: false,
schema: { type: 'string', enum: ['any', '480', '720', '1080', '2160'], default: 'any' },
},
{
name: 'audioCodec',
in: 'query',
required: false,
schema: { type: 'string', enum: ['any', 'aac', 'ac3', 'dts', 'opus', 'mp3', 'flac'], default: 'any' },
},
{
name: 'subtitles',
in: 'query',
required: false,
schema: { type: 'string', enum: ['any', 'required', 'lang'], default: 'any' },
},
{
name: 'subtitleLang',
in: 'query',
required: false,
description: 'two-letter lang code, used when subtitles=lang',
schema: { type: 'string' },
example: 'en',
},
],
responses: {
200: {
description: 'stream list (best quality first)',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/StreamList' },
},
},
},
400: { $ref: '#/components/responses/BadRequest' },
500: {
description: 'no providers available / internal error',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } },
},
},
},
},
},
components: {
parameters: {
MovieId: {
name: 'id',
in: 'path',
required: true,
description: 'TMDB movie id',
schema: { type: 'integer' },
example: 438631,
},
TvId: {
name: 'id',
in: 'path',
required: true,
description: 'TMDB tv id',
schema: { type: 'integer' },
example: 1396,
},
},
responses: {
BadRequest: {
description: 'missing or invalid parameters',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } },
},
NotFound: {
description: 'media id not found on TMDB',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } },
},
Upstream: {
description: 'TMDB request failed',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } },
},
},
schemas: {
Error: {
type: 'object',
properties: { error: { type: 'string' } },
},
SearchResult: {
type: 'object',
properties: {
tmdb_id: { type: 'integer', example: 438631 },
type: { type: 'string', enum: ['movie', 'tv'] },
name: { type: 'string', example: 'Dune' },
year: { type: 'string', example: '2021' },
poster: { type: 'string', format: 'uri' },
overview: { type: 'string' },
},
},
MediaDetails: {
type: 'object',
description: 'raw TMDB media object (movie or tv) with added poster_url/backdrop_url',
properties: {
id: { type: 'integer', example: 438631 },
title: { type: 'string', description: 'movie title' },
name: { type: 'string', description: 'tv show name' },
overview: { type: 'string', description: 'description' },
tagline: { type: 'string' },
genres: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
},
},
},
vote_average: { type: 'number', description: 'rating, 0-10' },
vote_count: { type: 'integer' },
popularity: { type: 'number' },
status: { type: 'string' },
runtime: { type: 'integer', description: 'movie runtime in minutes' },
number_of_seasons: { type: 'integer', description: 'tv' },
number_of_episodes: { type: 'integer', description: 'tv' },
release_date: { type: 'string', format: 'date', description: 'movie' },
first_air_date: { type: 'string', format: 'date', description: 'tv' },
poster_path: { type: 'string' },
backdrop_path: { type: 'string' },
poster_url: { type: 'string', format: 'uri', description: 'full url (original size)' },
backdrop_url: { type: 'string', format: 'uri', description: 'full url (original size)' },
images: {
type: 'object',
description:
'all available images from TMDB (posters, backdrops, logos, profiles); every item includes `url` with the full image url',
},
},
additionalProperties: true,
},
Season: {
type: 'object',
properties: {
season_number: { type: 'integer' },
name: { type: 'string' },
episode_count: { type: 'integer' },
air_date: { type: 'string', format: 'date' },
},
},
Episode: {
type: 'object',
properties: {
episode_number: { type: 'integer' },
name: { type: 'string' },
air_date: { type: 'string', format: 'date' },
},
},
Subtitle: {
type: 'object',
properties: {
url: { type: 'string', format: 'uri' },
language: { type: 'string' },
name: { type: 'string' },
},
},
Stream: {
type: 'object',
properties: {
provider: { type: 'string', example: 'videasy' },
name: { type: 'string' },
url: { type: 'string', format: 'uri', description: 'the m3u8 (or direct) url' },
quality: { type: 'string', example: '2160p' },
pixels: { type: ['integer', 'null'], example: 2160 },
audio: { type: ['string', 'null'], example: 'aac' },
type: { type: 'string', example: 'm3u8' },
headers: {
type: 'object',
description: 'http headers the url needs (e.g. Referer)',
additionalProperties: { type: 'string' },
},
subtitles: { type: 'array', items: { $ref: '#/components/schemas/Subtitle' } },
},
},
StreamList: {
type: 'object',
properties: {
tmdb_id: { type: 'integer' },
type: { type: 'string', enum: ['movie', 'tv'] },
season: { type: ['integer', 'null'] },
episode: { type: ['integer', 'null'] },
total: { type: 'integer', description: 'streams before filters' },
matched: { type: 'integer', description: 'streams after filters' },
streams: { type: 'array', items: { $ref: '#/components/schemas/Stream' } },
providerErrors: {
type: 'object',
description: 'per-provider scrape errors',
additionalProperties: { type: 'string' },
},
loadErrors: {
type: 'object',
description: 'per-provider load errors',
additionalProperties: { type: 'string' },
},
},
},
},
},
};
module.exports = spec;