493 lines
16 KiB
JavaScript
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;
|