diff --git a/backend/tsconfig.build.json b/backend/tsconfig.build.json index 0d362b8..693e1b2 100644 --- a/backend/tsconfig.build.json +++ b/backend/tsconfig.build.json @@ -5,5 +5,6 @@ "test", "dist", "**/*spec.ts", + "plugins/*" ] } diff --git a/shared/src/index.ts b/shared/src/index.ts index 5b43e56..39b9a61 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -1,6 +1 @@ -export * from './plugin/types'; -export * from './plugin/ui.types'; -export * from './plugin/reiverr-plugin'; -export * from './plugin/media-source-provider'; -export * from './plugin/catalogue-provider'; -export * from './plugin/device-profile'; +export * from "./plugin"; diff --git a/shared/src/plugin-api/index.ts b/shared/src/plugin-api/index.ts new file mode 100644 index 0000000..6838705 --- /dev/null +++ b/shared/src/plugin-api/index.ts @@ -0,0 +1,65 @@ +import { CatalogueProvider, MediaSourceProvider } from "src/plugin"; + +// export abstract class ReiverrPlugin { +// abstract name: string; + +// /** +// * This method is called for every user request, and it should return an object that can handle requests that depend on an user that has connected to the plugin / configured it as a source in their settings page. +// */ +// abstract getMediaSourceProvider: ( +// ...args: ConstructorParameters +// ) => MediaSourceProvider; + +// abstract getCatalogueProvider: ( +// ...args: ConstructorParameters +// ) => CatalogueProvider; + +// /** +// * @returns The settings that the plugin supports. @see SourceProviderSettingsTemplate +// */ +// getSettingsTemplate: () => SourceProviderSettingsTemplate = () => ({}); + +// validateSettings: (options: { +// settings: Record; +// }) => Promise = async () => ({ +// isValid: true, +// errors: {}, +// settings: {}, +// }); + +// getPluginVersion(): string { +// return packageJson.version; +// } + +// _isCompatibleWith(version: string): boolean { +// const pluginVersion = this.getPluginVersion(); +// const pluginVersionParts = pluginVersion.split("."); +// const versionParts = version.split("."); + +// if ( +// !pluginVersionParts.length || +// pluginVersionParts.length !== versionParts.length +// ) { +// return false; +// } + +// return ( +// pluginVersionParts[0] === versionParts[0] && +// Number(pluginVersionParts[1]) >= Number(versionParts[1]) +// ); +// } +// } + +// export function getReiverrPluginVersion(): string { +// return packageJson.version; +// } + +export type ReiverrPlugin = { + name: string; + getMediaSourceProvider: ( + ...args: ConstructorParameters + ) => MediaSourceProvider; + getCatalogueProvider?: ( + ...args: ConstructorParameters + ) => CatalogueProvider; +}; diff --git a/shared/src/plugin/catalogue-provider.ts b/shared/src/plugin/catalogue-provider.ts index 70222c5..98cb4de 100644 --- a/shared/src/plugin/catalogue-provider.ts +++ b/shared/src/plugin/catalogue-provider.ts @@ -1,16 +1,10 @@ import { - SourceProviderSettings, - UserContext, - StreamCandidate, - PlaybackConfig, - ActionResponse, - OrderOption, - PaginationParams, - PaginatedResponse, - CatalogueItem, CatalogueCapabilities, -} from './types'; -import { WithMediaSource } from './with-media-source'; + CatalogueItem, + PaginatedResponse, + PaginationParams, +} from "./types"; +import { WithMediaSource } from "./with-media-source"; /** * MediaSourceProvider is a class that handles all requests for Reiverr users that have configured the plugin as MediaSource. A new MediaSourceProvider is instantiated for each request / function call, and it contains data about the Reiverr user that called the function. diff --git a/shared/src/plugin/index.ts b/shared/src/plugin/index.ts new file mode 100644 index 0000000..1333545 --- /dev/null +++ b/shared/src/plugin/index.ts @@ -0,0 +1,6 @@ +export * from "./types"; +export * from "./view/index"; +export * from "./reiverr-plugin"; +export * from "./media-source-provider"; +export * from "./catalogue-provider"; +export * from "./device-profile"; diff --git a/shared/src/plugin/media-source-provider.ts b/shared/src/plugin/media-source-provider.ts index b470c54..6ad18a9 100644 --- a/shared/src/plugin/media-source-provider.ts +++ b/shared/src/plugin/media-source-provider.ts @@ -1,13 +1,13 @@ import { - PlaybackConfig, ActionResponse, - StreamCandidate, + PlaybackConfig, Stream, StreamBase, + StreamCandidate, StreamResponse, -} from './types'; -import { MediaSourceView, MediaSourceViews } from './ui.types'; -import { WithMediaSource } from './with-media-source'; +} from "./types"; +import { MediaSourceView, MediaSourceViews } from "./view"; +import { WithMediaSource } from "./with-media-source"; type PlayableContext = { tmdbMovie?: any; @@ -28,14 +28,14 @@ export class MediaSourceProvider extends WithMediaSource { constructor( options: ConstructorParameters[0] & { token: string; - }, + } ) { super(options); this.token = options.token; } getMeidaSourceViews: ( - options: PlayableContext, + options: PlayableContext ) => Promise<{ views: MediaSourceViews }> = async () => ({ views: [], }); @@ -43,11 +43,11 @@ export class MediaSourceProvider extends WithMediaSource { getMediaSourceView: ( options: PlayableContext & { id: string; - }, + } ) => Promise<{ view?: MediaSourceView }> = async () => ({}); getAutoplayStream: ( - options: PlayableContext, + options: PlayableContext ) => Promise<{ candidate?: StreamBase }> = async () => ({}); getStream: (options: { @@ -65,12 +65,12 @@ export class MediaSourceProvider extends WithMediaSource { action: string; }) => Promise = async () => ({ toast: { - title: 'Not supported', - message: 'This action is not supported by this provider.', - type: 'error', + title: "Not supported", + message: "This action is not supported by this provider.", + type: "error", }, error: { - message: 'Not supported', + message: "Not supported", }, }); diff --git a/shared/src/plugin/ui.types.ts b/shared/src/plugin/ui.types.ts deleted file mode 100644 index 742bb6e..0000000 --- a/shared/src/plugin/ui.types.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { OrderOption } from './types'; - -type Icon = { - type: 'play' | 'download' | 'delete' | 'info' | 'external-link'; - size?: 'lg' | 'md' | 'sm'; -}; - -export type ViewBase = { - id: string; - type: 'general' | 'list-with-details'; - label: string; - priority?: number; -}; - -type GeneralElementBase = { - type: - | 'heading' - | 'toggle' - | 'select' - | 'action' - | 'input' - | 'external-link' - | 'open-view'; -}; - -export interface HeadingElement extends GeneralElementBase { - type: 'heading'; - label: string; - description?: string; -} - -export interface ToggleElement extends GeneralElementBase { - type: 'toggle'; - label: string; - description?: string; - value: boolean; - style: 'checkbox' | 'switch'; -} - -export interface SelectElement extends GeneralElementBase { - type: 'select'; - label: string; - description?: string; - value: string; - options: { - label: string; - value: string; - }[]; - style: 'dropdown' | 'radio'; -} - -export interface ActionElement extends GeneralElementBase { - type: 'action'; - - /** - * The label of the action - * @example "Stream" - */ - label: string; - - /** - * The type of the action - * @example "stream" - */ - action: string; - - disabled?: boolean; - - icon?: Icon; - - // /** - // * The parameters to be passed to the action - // */ - // params: Record; -} - -export interface StreamActionElement extends GeneralElementBase { - type: 'action'; - - /** - * The label of the action - * @example "Stream" - */ - label: 'Stream'; - - /** - * The type of the action - * @example "stream" - */ - action: 'stream'; - - icon: { - type: 'play'; - size?: 'lg' | 'md' | 'sm'; - }; - - disabled?: boolean; -} - -export interface InputElement extends GeneralElementBase { - type: 'input'; - label: string; - description?: string; - value: string; - placeholder?: string; - style: 'text' | 'number' | 'email' | 'password'; - min?: number; - max?: number; - maxLength?: number; - minLength?: number; - disabled?: boolean; -} - -export interface ExternalLinkElement extends GeneralElementBase { - type: 'external-link'; - label: string; - description?: string; - url: string; -} - -export interface OpenViewElement extends GeneralElementBase { - type: 'open-view'; - label: string; - description?: string; - viewId: string; -} - -export interface GeneralView extends ViewBase { - type: 'general'; - elements: ( - | HeadingElement - | ToggleElement - | SelectElement - | StreamActionElement - | ActionElement - | InputElement - | ExternalLinkElement - | OpenViewElement - )[]; -} - -export type SortableProperty = { - /** - * The label of the property - * @example "Resolution" - */ - label: string; - - /** - * Used for sorting and filtering, or displayed if `formatted` is not provided. - * @example 1080 - */ - value: string | number; - - /** - * The formatted value of the property - * @example "1080p" - */ - formatted: string | undefined; - - /** - * Secondary properties are not shown in list views - */ - secondary?: boolean; -}; - -export type ListWithDetailsItem = { - /** - * Unique id for the item, used for referencing it. - */ - id: string; - - /** - * Title of the item. - */ - label: string; - - /** - * A short optional description of the item. - */ - description?: string; - - /** - * A list of properties that are shown to the user in the stream selection UI. - */ - properties: SortableProperty[]; - - /** - * A list of actions that the user can perform on the stream. - */ - actions: (StreamActionElement | ActionElement | OpenViewElement)[]; -}; - -export type ListWithDetailsView = ViewBase & { - type: 'list-with-details'; - items: ListWithDetailsItem[]; - order?: OrderOption; - orderOptions: OrderOption[]; -}; - -export type MediaSourceViews = ViewBase[]; - -export type MediaSourceView = GeneralView | ListWithDetailsView; diff --git a/shared/src/plugin/view/actions.ts b/shared/src/plugin/view/actions.ts new file mode 100644 index 0000000..53e0483 --- /dev/null +++ b/shared/src/plugin/view/actions.ts @@ -0,0 +1,49 @@ +import { GeneralElementBase, Icon } from "./base"; + +export interface ActionElement extends GeneralElementBase { + type: "action"; + + /** + * The label of the action + * @example "Stream" + */ + label: string; + + /** + * The type of the action + * @example "stream" + */ + action: string; + + disabled?: boolean; + + icon?: Icon; + + // /** + // * The parameters to be passed to the action + // */ + // params: Record; +} + +export interface StreamActionElement extends GeneralElementBase { + type: "action"; + + /** + * The label of the action + * @example "Stream" + */ + label: "Stream"; + + /** + * The type of the action + * @example "stream" + */ + action: "stream"; + + icon: { + type: "play"; + size?: "lg" | "md" | "sm"; + }; + + disabled?: boolean; +} diff --git a/shared/src/plugin/view/base.ts b/shared/src/plugin/view/base.ts new file mode 100644 index 0000000..bd9b900 --- /dev/null +++ b/shared/src/plugin/view/base.ts @@ -0,0 +1,22 @@ +export type Icon = { + type: "play" | "download" | "delete" | "info" | "external-link"; + size?: "lg" | "md" | "sm"; +}; + +export type ViewBase = { + id: string; + type: "general" | "list-with-details"; + label: string; + priority?: number; +}; + +export type GeneralElementBase = { + type: + | "heading" + | "toggle" + | "select" + | "action" + | "input" + | "external-link" + | "open-view"; +}; diff --git a/shared/src/plugin/view/elements.ts b/shared/src/plugin/view/elements.ts new file mode 100644 index 0000000..f82e231 --- /dev/null +++ b/shared/src/plugin/view/elements.ts @@ -0,0 +1,55 @@ +import { GeneralElementBase } from "./base"; + +export interface HeadingElement extends GeneralElementBase { + type: "heading"; + label: string; + description?: string; +} + +export interface ToggleElement extends GeneralElementBase { + type: "toggle"; + label: string; + description?: string; + value: boolean; + style: "checkbox" | "switch"; +} + +export interface SelectElement extends GeneralElementBase { + type: "select"; + label: string; + description?: string; + value: string; + options: { + label: string; + value: string; + }[]; + style: "dropdown" | "radio"; +} + +export interface InputElement extends GeneralElementBase { + type: "input"; + label: string; + description?: string; + value: string; + placeholder?: string; + style: "text" | "number" | "email" | "password"; + min?: number; + max?: number; + maxLength?: number; + minLength?: number; + disabled?: boolean; +} + +export interface ExternalLinkElement extends GeneralElementBase { + type: "external-link"; + label: string; + description?: string; + url: string; +} + +export interface OpenViewElement extends GeneralElementBase { + type: "open-view"; + label: string; + description?: string; + viewId: string; +} diff --git a/shared/src/plugin/view/index.ts b/shared/src/plugin/view/index.ts new file mode 100644 index 0000000..e0ba6e7 --- /dev/null +++ b/shared/src/plugin/view/index.ts @@ -0,0 +1,4 @@ +export * from "./base"; +export * from "./elements"; +export * from "./actions"; +export * from "./views"; diff --git a/shared/src/plugin/view/views.ts b/shared/src/plugin/view/views.ts new file mode 100644 index 0000000..a58277e --- /dev/null +++ b/shared/src/plugin/view/views.ts @@ -0,0 +1,88 @@ +import { OrderOption } from "../types"; +import { ViewBase } from "./base"; +import { + HeadingElement, + ToggleElement, + SelectElement, + InputElement, + ExternalLinkElement, + OpenViewElement, +} from "./elements"; +import { StreamActionElement, ActionElement } from "./actions"; + +export interface GeneralView extends ViewBase { + type: "general"; + elements: ( + | HeadingElement + | ToggleElement + | SelectElement + | StreamActionElement + | ActionElement + | InputElement + | ExternalLinkElement + | OpenViewElement + )[]; +} + +export type SortableProperty = { + /** + * The label of the property + * @example "Resolution" + */ + label: string; + + /** + * Used for sorting and filtering, or displayed if `formatted` is not provided. + * @example 1080 + */ + value: string | number; + + /** + * The formatted value of the property + * @example "1080p" + */ + formatted: string | undefined; + + /** + * Secondary properties are not shown in list views + */ + secondary?: boolean; +}; + +export type ListWithDetailsItem = { + /** + * Unique id for the item, used for referencing it. + */ + id: string; + + /** + * Title of the item. + */ + label: string; + + /** + * A short optional description of the item. + */ + description?: string; + + /** + * A list of properties that are shown to the user in the stream selection UI. + */ + properties: SortableProperty[]; + + /** + * A list of actions that the user can perform on the stream. + */ + actions: (StreamActionElement | ActionElement | OpenViewElement)[]; +}; + +export type ListWithDetailsView = ViewBase & { + type: "list-with-details"; + items: ListWithDetailsItem[]; + order?: OrderOption; + orderOptions: OrderOption[]; +}; + +export type MediaSourceViews = ViewBase[]; + +export type MediaSourceView = GeneralView | ListWithDetailsView;