mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-17 23:53:13 +02:00
feat: add proto file for grpc api in shared/ with generated types
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
import {
|
||||
PaginatedResponse,
|
||||
PaginationParams,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
|
||||
export const PickAndPartial = <T, K extends keyof T>(
|
||||
clazz: Type<T>,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Subtitles,
|
||||
AudioTrack,
|
||||
VideoOptions,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
/*
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
MediaPluginSettings,
|
||||
MediaPluginSettingsResponseDto,
|
||||
mediaPluginVersion,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { ClientProxy } from '@nestjs/microservices';
|
||||
import { firstValueFrom, lastValueFrom } from 'rxjs';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ProfileCondition,
|
||||
SubtitleProfile,
|
||||
TranscodingProfile,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
|
||||
export class DirectPlayProfileDto implements DirectPlayProfile {
|
||||
@ApiProperty({
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
StreamResponse,
|
||||
Subtitles,
|
||||
ValidationResponse,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import {
|
||||
ApiProperty,
|
||||
ApiPropertyOptional,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReiverrPlugin } from '@aleksilassila/reiverr-shared';
|
||||
import { ReiverrPlugin } from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
getReiverrPluginVersion,
|
||||
ReiverrPlugin,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type ViewBase } from '@aleksilassila/reiverr-shared';
|
||||
import { type ViewBase } from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
enum ViewType {
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
SortableProperty,
|
||||
StreamActionElement,
|
||||
MediaSourceProvider,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import { ViewBaseDto } from 'src/source-providers/ui.dto';
|
||||
|
||||
class CatalogueOrderDirectionOption implements DirectionOption {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { SourceProviderSettings } from '@aleksilassila/reiverr-shared';
|
||||
import { SourceProviderSettings } from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import { User } from 'src/users/user.entity';
|
||||
import {
|
||||
Column,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SourceProviderError } from '@aleksilassila/reiverr-shared';
|
||||
import { SourceProviderError } from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import {
|
||||
All,
|
||||
BadRequestException,
|
||||
@@ -59,9 +59,8 @@ export class ServiceOwnershipValidator implements CanActivate {
|
||||
|
||||
if (!sourceId) return true;
|
||||
|
||||
const mediaSource = await this.mediaSourcesService.findMediaSource(
|
||||
sourceId,
|
||||
);
|
||||
const mediaSource =
|
||||
await this.mediaSourcesService.findMediaSource(sourceId);
|
||||
|
||||
if (!mediaSource) throw new NotFoundException('Source not found');
|
||||
|
||||
@@ -110,9 +109,8 @@ export class MediaSourcesController {
|
||||
|
||||
const providers = await Promise.all(
|
||||
user.mediaSources.map(async (ms) => {
|
||||
const mediaSourceDto = await this.mediaSourcesService.getMediaSourceDto(
|
||||
ms,
|
||||
);
|
||||
const mediaSourceDto =
|
||||
await this.mediaSourcesService.getMediaSourceDto(ms);
|
||||
|
||||
const connection = await this.getConnection({
|
||||
sourceId: ms.id,
|
||||
@@ -425,9 +423,8 @@ export class MediaSourcesController {
|
||||
@GetAuthToken() token: string,
|
||||
) {
|
||||
const sourceId = params.sourceId;
|
||||
const mediaSource = await this.mediaSourcesService.findMediaSource(
|
||||
sourceId,
|
||||
);
|
||||
const mediaSource =
|
||||
await this.mediaSourcesService.findMediaSource(sourceId);
|
||||
|
||||
if (!mediaSource) throw new NotFoundException('Source not found');
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
CatalogueProvider,
|
||||
MediaSourceProvider,
|
||||
ValidationResponse,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { SourceProvidersService } from 'src/source-providers/source-providers.service';
|
||||
import { User } from 'src/users/user.entity';
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
OrderOption,
|
||||
PaginatedResponse,
|
||||
PaginationParams,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import {
|
||||
ItemSortBy,
|
||||
BaseItemKind,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
SourceProviderSettings,
|
||||
SourceProviderSettingsTemplate,
|
||||
ValidationResponse,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import { JellyfinMediaSourceProvider } from './media-source-provider';
|
||||
import { JellyfinCatalogueProvider } from './catalogue-provider';
|
||||
|
||||
|
||||
@@ -12,24 +12,24 @@ import {
|
||||
UserContext,
|
||||
MediaSourceView,
|
||||
MediaSourceViews,
|
||||
} from "@aleksilassila/reiverr-shared";
|
||||
import { Readable } from "stream";
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import { Readable } from 'stream';
|
||||
import {
|
||||
BaseItemKind,
|
||||
ItemFields,
|
||||
Api as JellyfinApi,
|
||||
} from "./jellyfin.openapi";
|
||||
} from './jellyfin.openapi';
|
||||
import {
|
||||
bitrateQualities,
|
||||
formatSize,
|
||||
formatTicksToTime,
|
||||
getClosestBitrate,
|
||||
JELLYFIN_DEVICE_ID,
|
||||
} from "./utils";
|
||||
} from './utils';
|
||||
|
||||
enum View {
|
||||
StreamMovie = "stream-movie",
|
||||
StreamEpisode = "stream-episode",
|
||||
StreamMovie = 'stream-movie',
|
||||
StreamEpisode = 'stream-episode',
|
||||
}
|
||||
|
||||
export interface JellyfinSettings extends SourceProviderSettings {
|
||||
@@ -72,8 +72,8 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
views: [
|
||||
{
|
||||
id: View.StreamMovie,
|
||||
label: "Stream",
|
||||
type: "list-with-details",
|
||||
label: 'Stream',
|
||||
type: 'list-with-details',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -82,8 +82,8 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
views: [
|
||||
{
|
||||
id: View.StreamEpisode,
|
||||
label: "Stream",
|
||||
type: "list-with-details",
|
||||
label: 'Stream',
|
||||
type: 'list-with-details',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -110,16 +110,16 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
if (id === View.StreamMovie && tmdbMovie) {
|
||||
const candidates = await this.getTmdbMovieCandidates({ tmdbMovie });
|
||||
view = {
|
||||
type: "list-with-details",
|
||||
type: 'list-with-details',
|
||||
id,
|
||||
label: "Stream",
|
||||
label: 'Stream',
|
||||
items: candidates.candidates.map((c) => ({
|
||||
...c,
|
||||
id: c.streamId,
|
||||
label: c.title,
|
||||
actions: c.actions.map((a) => ({
|
||||
label: a.label,
|
||||
type: "action",
|
||||
type: 'action',
|
||||
action: a.type,
|
||||
})),
|
||||
})),
|
||||
@@ -132,16 +132,16 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
});
|
||||
|
||||
view = {
|
||||
type: "list-with-details",
|
||||
type: 'list-with-details',
|
||||
id,
|
||||
label: "Stream",
|
||||
label: 'Stream',
|
||||
items: candidates.candidates.map((c) => ({
|
||||
...c,
|
||||
id: c.streamId,
|
||||
label: c.title,
|
||||
actions: c.actions.map((a) => ({
|
||||
label: a.label,
|
||||
type: "action",
|
||||
type: 'action',
|
||||
action: a.type,
|
||||
})),
|
||||
})),
|
||||
@@ -170,7 +170,7 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
});
|
||||
|
||||
const movie = movies.data.Items.find(
|
||||
(i) => i.ProviderIds?.Tmdb === String(tmdbMovie.id)
|
||||
(i) => i.ProviderIds?.Tmdb === String(tmdbMovie.id),
|
||||
);
|
||||
|
||||
if (!movie || !movie.MediaSources || movie.MediaSources.length === 0) {
|
||||
@@ -182,36 +182,36 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
{
|
||||
id: movie.ProviderIds?.Tmdb,
|
||||
tmdbId: movie.ProviderIds?.Tmdb,
|
||||
mediaType: "movie" as const,
|
||||
mediaType: 'movie' as const,
|
||||
streamId: movie.Id,
|
||||
title: movie.Name,
|
||||
actions: [
|
||||
{
|
||||
label: "Stream",
|
||||
type: "stream",
|
||||
label: 'Stream',
|
||||
type: 'stream',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
label: "Video",
|
||||
label: 'Video',
|
||||
value: movie.MediaSources[0].Bitrate || 0,
|
||||
formatted:
|
||||
movie.MediaSources[0].MediaStreams.find(
|
||||
(s) => s.Type === "Video"
|
||||
)?.DisplayTitle || "Unknown",
|
||||
(s) => s.Type === 'Video',
|
||||
)?.DisplayTitle || 'Unknown',
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
label: 'Size',
|
||||
value: movie.MediaSources[0].Size,
|
||||
formatted: formatSize(movie.MediaSources[0].Size),
|
||||
},
|
||||
{
|
||||
label: "Filename",
|
||||
label: 'Filename',
|
||||
value: movie.MediaSources[0].Name,
|
||||
formatted: undefined,
|
||||
},
|
||||
{
|
||||
label: "Runtime",
|
||||
label: 'Runtime',
|
||||
value: movie.MediaSources[0].RunTimeTicks,
|
||||
formatted: formatTicksToTime(movie.MediaSources[0].RunTimeTicks),
|
||||
},
|
||||
@@ -251,14 +251,14 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
});
|
||||
|
||||
const show = series.data.Items.find(
|
||||
(i) => i.ProviderIds?.Tmdb === String(tmdbSeries.id)
|
||||
(i) => i.ProviderIds?.Tmdb === String(tmdbSeries.id),
|
||||
);
|
||||
|
||||
if (!show) {
|
||||
console.error(
|
||||
"series not found",
|
||||
'series not found',
|
||||
series.data?.Items?.map((i) => i.ProviderIds),
|
||||
tmdbSeries
|
||||
tmdbSeries,
|
||||
);
|
||||
throw SourceProviderError.StreamNotFound;
|
||||
}
|
||||
@@ -281,7 +281,7 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
(e) =>
|
||||
e.SeriesId === show.Id &&
|
||||
e.ParentIndexNumber === tmdbEpisode.season_number &&
|
||||
e.IndexNumber === tmdbEpisode.episode_number
|
||||
e.IndexNumber === tmdbEpisode.episode_number,
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -289,7 +289,7 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
!episode.MediaSources ||
|
||||
episode.MediaSources.length === 0
|
||||
) {
|
||||
console.error("episode not found", episode, episodes.data.Items.length);
|
||||
console.error('episode not found', episode, episodes.data.Items.length);
|
||||
throw SourceProviderError.StreamNotFound;
|
||||
}
|
||||
|
||||
@@ -298,39 +298,39 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
{
|
||||
id: episode.ProviderIds?.Tmdb,
|
||||
tmdbId: episode.ProviderIds?.Tmdb,
|
||||
mediaType: "episode" as const,
|
||||
mediaType: 'episode' as const,
|
||||
streamId: episode.Id,
|
||||
title: episode.Name,
|
||||
actions: [
|
||||
{
|
||||
label: "Stream",
|
||||
type: "stream",
|
||||
label: 'Stream',
|
||||
type: 'stream',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
label: "Video",
|
||||
label: 'Video',
|
||||
value: episode.MediaSources[0].Bitrate || 0,
|
||||
formatted:
|
||||
episode.MediaSources[0].MediaStreams.find(
|
||||
(s) => s.Type === "Video"
|
||||
)?.DisplayTitle || "Unknown",
|
||||
(s) => s.Type === 'Video',
|
||||
)?.DisplayTitle || 'Unknown',
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
label: 'Size',
|
||||
value: episode.MediaSources[0].Size,
|
||||
formatted: formatSize(episode.MediaSources[0].Size),
|
||||
},
|
||||
{
|
||||
label: "Filename",
|
||||
label: 'Filename',
|
||||
value: episode.MediaSources[0].Name,
|
||||
formatted: undefined,
|
||||
},
|
||||
{
|
||||
label: "Runtime",
|
||||
label: 'Runtime',
|
||||
value: episode.MediaSources[0].RunTimeTicks,
|
||||
formatted: formatTicksToTime(
|
||||
episode.MediaSources[0].RunTimeTicks
|
||||
episode.MediaSources[0].RunTimeTicks,
|
||||
),
|
||||
},
|
||||
],
|
||||
@@ -376,7 +376,7 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
|
||||
return {
|
||||
error: {
|
||||
message: "Action not supported",
|
||||
message: 'Action not supported',
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -445,7 +445,7 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
// deviceId: JELLYFIN_DEVICE_ID,
|
||||
// mediaSourceId: movie.MediaSources[0].Id,
|
||||
// maxBitrate: 8000000,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const mediasSource = playbackInfo.data?.MediaSources?.[0];
|
||||
@@ -456,19 +456,19 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
`/Videos/${mediasSource?.Id}/stream.mp4?Static=true&mediaSourceId=${mediasSource?.Id}&deviceId=${JELLYFIN_DEVICE_ID}&api_key=${this.settings.apiKey}&Tag=${mediasSource?.ETag}`) +
|
||||
`&reiverr_token=${this.token}`;
|
||||
|
||||
const audioStreams: Stream["audioStreams"] =
|
||||
mediasSource?.MediaStreams.filter((s) => s.Type === "Audio").map((s) => ({
|
||||
const audioStreams: Stream['audioStreams'] =
|
||||
mediasSource?.MediaStreams.filter((s) => s.Type === 'Audio').map((s) => ({
|
||||
bitrate: s.BitRate,
|
||||
label: s.Language,
|
||||
codec: s.Codec,
|
||||
index: s.Index,
|
||||
})) ?? [];
|
||||
|
||||
const qualities: Stream["qualities"] = [
|
||||
const qualities: Stream['qualities'] = [
|
||||
...bitrateQualities,
|
||||
{
|
||||
bitrate: mediasSource.Bitrate,
|
||||
label: "Original",
|
||||
label: 'Original',
|
||||
codec: undefined,
|
||||
original: true,
|
||||
},
|
||||
@@ -480,37 +480,37 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
const bitrate = Math.min(maxStreamingBitrate, mediasSource.Bitrate);
|
||||
|
||||
const subtitles: Subtitles[] = mediasSource.MediaStreams.filter(
|
||||
(s) => s.Type === "Subtitle" && s.DeliveryUrl
|
||||
(s) => s.Type === 'Subtitle' && s.DeliveryUrl,
|
||||
).map((s, i) => ({
|
||||
src: this.getProxyUrl() + `${s.DeliveryUrl}&reiverr_token=${this.token}`,
|
||||
lang: s.Language,
|
||||
kind: "subtitles",
|
||||
kind: 'subtitles',
|
||||
label: s.DisplayTitle,
|
||||
}));
|
||||
|
||||
const stream = {
|
||||
streamId: "0",
|
||||
streamId: '0',
|
||||
title: movie.Name,
|
||||
properties: [
|
||||
{
|
||||
label: "Video",
|
||||
label: 'Video',
|
||||
value: mediasSource.Bitrate || 0,
|
||||
formatted:
|
||||
mediasSource.MediaStreams.find((s) => s.Type === "Video")
|
||||
?.DisplayTitle || "Unknown",
|
||||
mediasSource.MediaStreams.find((s) => s.Type === 'Video')
|
||||
?.DisplayTitle || 'Unknown',
|
||||
},
|
||||
{
|
||||
label: "Size",
|
||||
label: 'Size',
|
||||
value: mediasSource.Size,
|
||||
formatted: formatSize(mediasSource.Size),
|
||||
},
|
||||
{
|
||||
label: "Filename",
|
||||
label: 'Filename',
|
||||
value: mediasSource.Name,
|
||||
formatted: undefined,
|
||||
},
|
||||
{
|
||||
label: "Runtime",
|
||||
label: 'Runtime',
|
||||
value: mediasSource.RunTimeTicks,
|
||||
formatted: formatTicksToTime(mediasSource.RunTimeTicks),
|
||||
},
|
||||
@@ -554,19 +554,19 @@ export class JellyfinMediaSourceProvider extends MediaSourceProvider {
|
||||
|
||||
const headers = {};
|
||||
for (const key in req.headers) {
|
||||
if (key === "host") continue;
|
||||
if (key === 'host') continue;
|
||||
headers[key] = req.headers[key];
|
||||
}
|
||||
|
||||
const proxyRes = await fetch(url, {
|
||||
method: req.method || "GET",
|
||||
method: req.method || 'GET',
|
||||
headers: {
|
||||
...headers,
|
||||
Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${this.settings.apiKey}"`,
|
||||
},
|
||||
}).catch((e) => {
|
||||
console.error("error fetching proxy response", e);
|
||||
res.status(500).send("Error fetching proxy response");
|
||||
console.error('error fetching proxy response', e);
|
||||
res.status(500).send('Error fetching proxy response');
|
||||
});
|
||||
|
||||
if (!proxyRes) return;
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
"types": "./dist/src/index.d.ts",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build": "protoc --plugin=..\\node_modules\\.bin\\protoc-gen-ts_proto --ts_proto_out=.\\src --ts_proto_opt=outputEncodeMethods=false,outputJsonMethods=false,outputClientImpl=false --proto_path=. *.proto&& tsc",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@protobuf-ts/protoc": "^2.11.1",
|
||||
"ts-proto": "^2.11.1",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"description": "Shared types and utilities for Reiverr",
|
||||
|
||||
540
shared/reiverr-plugin.proto
Normal file
540
shared/reiverr-plugin.proto
Normal file
@@ -0,0 +1,540 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package aleksilassila.reiverr.plugin.v1;
|
||||
|
||||
// Plugin Service - handles plugin metadata and configuration
|
||||
service PluginService {
|
||||
// Get plugin metadata and version
|
||||
rpc GetInfo(Empty) returns (PluginInfo);
|
||||
}
|
||||
|
||||
// Media Source Provider Service - handles user-specific requests
|
||||
service MediaSourceProviderService {
|
||||
// Get available views for a media item
|
||||
rpc GetMediaSourceViews(MediaSourceViewsRequest) returns (MediaSourceViewsResponse);
|
||||
|
||||
// Get a specific view
|
||||
rpc GetMediaSourceView(MediaSourceViewRequest) returns (MediaSourceViewResponse);
|
||||
|
||||
// Get autoplay stream
|
||||
rpc GetAutoplayStream(AutoplayStreamRequest) returns (AutoplayStreamResponse);
|
||||
|
||||
// Get stream details
|
||||
rpc GetStream(GetStreamRequest) returns (StreamResponse);
|
||||
|
||||
// Handle stream actions (download, delete, etc.)
|
||||
rpc HandleAction(HandleActionRequest) returns (ActionResponse);
|
||||
|
||||
// Proxy handler for streaming (bidirectional for video/subtitle streaming)
|
||||
rpc ProxyStream(stream ProxyRequest) returns (stream ProxyResponse);
|
||||
|
||||
// Legacy methods (deprecated)
|
||||
rpc GetTmdbMovieCandidates(TmdbMovieRequest) returns (StreamCandidatesResponse);
|
||||
rpc GetTmdbEpisodeCandidates(TmdbEpisodeRequest) returns (StreamCandidatesResponse);
|
||||
}
|
||||
|
||||
// Catalogue Provider Service - handles library catalogues
|
||||
service CatalogueProviderService {
|
||||
// Get catalogue capabilities
|
||||
rpc GetCatalogueCapabilities(Empty) returns (CatalogueCapabilities);
|
||||
|
||||
// Get combined catalogue
|
||||
rpc GetCatalogue(CatalogueRequest) returns (CatalogueResponse);
|
||||
|
||||
// Get movies catalogue
|
||||
rpc GetMovieCatalogue(CatalogueRequest) returns (CatalogueResponse);
|
||||
|
||||
// Get series catalogue
|
||||
rpc GetSeriesCatalogue(CatalogueRequest) returns (CatalogueResponse);
|
||||
|
||||
// Get missing items in catalogue
|
||||
rpc GetMissingInCatalogue(MissingCatalogueRequest) returns (MissingCatalogueResponse);
|
||||
}
|
||||
|
||||
// Management Profile Service - NEW for monitoring/management profiles
|
||||
service ManagementProfileService {
|
||||
// Get available management profiles
|
||||
rpc GetManagementProfiles(Empty) returns (ManagementProfilesResponse);
|
||||
|
||||
// Get management profile for specific media
|
||||
rpc GetMediaManagementProfile(MediaManagementProfileRequest) returns (ManagementProfile);
|
||||
|
||||
// Update management profile for media
|
||||
rpc UpdateMediaManagementProfile(UpdateManagementProfileRequest) returns (ManagementProfile);
|
||||
}
|
||||
|
||||
// Job Management Service - NEW for monitoring downloads, transcoding, etc.
|
||||
service JobManagementService {
|
||||
// Get all active jobs
|
||||
rpc GetActiveJobs(Empty) returns (JobsResponse);
|
||||
|
||||
// Get job details
|
||||
rpc GetJobDetails(JobDetailsRequest) returns (JobDetails);
|
||||
|
||||
// Cancel a job
|
||||
rpc CancelJob(CancelJobRequest) returns (ActionResponse);
|
||||
|
||||
// Get disk space usage
|
||||
rpc GetDiskSpaceUsage(Empty) returns (DiskSpaceUsage);
|
||||
|
||||
// Get cleanup history
|
||||
rpc GetCleanupHistory(CleanupHistoryRequest) returns (CleanupHistoryResponse);
|
||||
|
||||
// Trigger cleanup
|
||||
rpc TriggerCleanup(TriggerCleanupRequest) returns (ActionResponse);
|
||||
}
|
||||
|
||||
// Common Messages
|
||||
|
||||
message Empty {}
|
||||
|
||||
message PluginInfo {
|
||||
string name = 1;
|
||||
string version = 2;
|
||||
string description = 3;
|
||||
bool streaming_supported = 4;
|
||||
bool catalogue_supported = 5;
|
||||
}
|
||||
|
||||
message SettingsTemplate {
|
||||
map<string, SettingField> fields = 1;
|
||||
}
|
||||
|
||||
message SettingField {
|
||||
string type = 1; // "string", "number", "boolean", "password", "link"
|
||||
string label = 2;
|
||||
string placeholder = 3;
|
||||
bool required = 4;
|
||||
|
||||
// For link type
|
||||
string url = 5;
|
||||
}
|
||||
|
||||
message ValidateSettingsRequest {
|
||||
map<string, string> settings = 1;
|
||||
}
|
||||
|
||||
message ValidationResponse {
|
||||
bool is_valid = 1;
|
||||
map<string, string> errors = 2;
|
||||
map<string, string> validated_settings = 3;
|
||||
}
|
||||
|
||||
// User Context - passed with most requests
|
||||
message UserContext {
|
||||
string user_id = 1;
|
||||
string token = 2;
|
||||
string source_id = 3;
|
||||
map<string, string> settings = 4;
|
||||
}
|
||||
|
||||
// Playable Context
|
||||
message PlayableContext {
|
||||
optional string tmdb_movie_json = 1;
|
||||
optional string tmdb_series_json = 2;
|
||||
optional string tmdb_episode_json = 3;
|
||||
}
|
||||
|
||||
// Media Source Views
|
||||
|
||||
message MediaSourceViewsRequest {
|
||||
UserContext user_context = 1;
|
||||
PlayableContext playable_context = 2;
|
||||
}
|
||||
|
||||
message MediaSourceViewsResponse {
|
||||
repeated MediaSourceView views = 1;
|
||||
}
|
||||
|
||||
message MediaSourceView {
|
||||
string id = 1;
|
||||
string title = 2;
|
||||
repeated StreamProperty properties = 3;
|
||||
repeated StreamAction actions = 4;
|
||||
}
|
||||
|
||||
message MediaSourceViewRequest {
|
||||
UserContext user_context = 1;
|
||||
PlayableContext playable_context = 2;
|
||||
string view_id = 3;
|
||||
}
|
||||
|
||||
message MediaSourceViewResponse {
|
||||
optional MediaSourceView view = 1;
|
||||
}
|
||||
|
||||
// Autoplay Stream
|
||||
|
||||
message AutoplayStreamRequest {
|
||||
UserContext user_context = 1;
|
||||
PlayableContext playable_context = 2;
|
||||
}
|
||||
|
||||
message AutoplayStreamResponse {
|
||||
optional StreamBase candidate = 1;
|
||||
}
|
||||
|
||||
// Stream Messages
|
||||
|
||||
message GetStreamRequest {
|
||||
UserContext user_context = 1;
|
||||
string stream_id = 2;
|
||||
optional PlaybackConfig config = 3;
|
||||
}
|
||||
|
||||
message StreamResponse {
|
||||
optional Stream stream = 1;
|
||||
optional Toast toast = 2;
|
||||
optional ErrorMessage error = 3;
|
||||
}
|
||||
|
||||
message Stream {
|
||||
string stream_id = 1;
|
||||
string title = 2;
|
||||
repeated StreamProperty properties = 3;
|
||||
string src = 4;
|
||||
bool direct_play = 5;
|
||||
double progress = 6;
|
||||
double duration = 7;
|
||||
repeated AudioStream audio_streams = 8;
|
||||
int32 audio_stream_index = 9;
|
||||
repeated Quality qualities = 10;
|
||||
int32 quality_index = 11;
|
||||
repeated Subtitle subtitles = 12;
|
||||
}
|
||||
|
||||
message StreamBase {
|
||||
string stream_id = 1;
|
||||
string title = 2;
|
||||
repeated StreamProperty properties = 3;
|
||||
}
|
||||
|
||||
message StreamProperty {
|
||||
string label = 1;
|
||||
string value = 2;
|
||||
optional string formatted = 3;
|
||||
}
|
||||
|
||||
message AudioStream {
|
||||
int32 index = 1;
|
||||
string label = 2;
|
||||
optional string codec = 3;
|
||||
optional int32 bitrate = 4;
|
||||
}
|
||||
|
||||
message Quality {
|
||||
int32 index = 1;
|
||||
int32 bitrate = 2;
|
||||
string label = 3;
|
||||
optional string codec = 4;
|
||||
bool original = 5;
|
||||
}
|
||||
|
||||
message Subtitle {
|
||||
string src = 1;
|
||||
string lang = 2;
|
||||
string kind = 3; // "subtitles", "captions", "descriptions"
|
||||
string label = 4;
|
||||
}
|
||||
|
||||
message PlaybackConfig {
|
||||
optional int32 bitrate = 1;
|
||||
optional int32 audio_stream_index = 2;
|
||||
optional double progress = 3;
|
||||
optional string device_profile_json = 4;
|
||||
optional string default_language = 5;
|
||||
}
|
||||
|
||||
// Actions
|
||||
|
||||
message StreamAction {
|
||||
string label = 1;
|
||||
string type = 2;
|
||||
}
|
||||
|
||||
message HandleActionRequest {
|
||||
UserContext user_context = 1;
|
||||
string target_id = 2;
|
||||
string action = 3;
|
||||
}
|
||||
|
||||
message ActionResponse {
|
||||
optional Toast toast = 1;
|
||||
optional ErrorMessage error = 2;
|
||||
optional ActionResult result = 3;
|
||||
}
|
||||
|
||||
message ActionResult {
|
||||
bool success = 1;
|
||||
optional string message = 2;
|
||||
}
|
||||
|
||||
message Toast {
|
||||
string title = 1;
|
||||
string message = 2;
|
||||
string type = 3; // "info", "success", "error"
|
||||
}
|
||||
|
||||
message ErrorMessage {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
// Proxy Streaming
|
||||
|
||||
message ProxyRequest {
|
||||
UserContext user_context = 1;
|
||||
string uri = 2;
|
||||
optional string target_url = 3;
|
||||
map<string, string> headers = 4;
|
||||
bytes body = 5;
|
||||
}
|
||||
|
||||
message ProxyResponse {
|
||||
int32 status_code = 1;
|
||||
map<string, string> headers = 2;
|
||||
bytes chunk = 3;
|
||||
bool is_final = 4;
|
||||
}
|
||||
|
||||
// Legacy Stream Candidates
|
||||
|
||||
message TmdbMovieRequest {
|
||||
UserContext user_context = 1;
|
||||
string tmdb_movie_json = 2;
|
||||
}
|
||||
|
||||
message TmdbEpisodeRequest {
|
||||
UserContext user_context = 1;
|
||||
string tmdb_series_json = 2;
|
||||
string tmdb_episode_json = 3;
|
||||
}
|
||||
|
||||
message StreamCandidatesResponse {
|
||||
repeated StreamCandidate candidates = 1;
|
||||
}
|
||||
|
||||
message StreamCandidate {
|
||||
string stream_id = 1;
|
||||
string title = 2;
|
||||
repeated StreamProperty properties = 3;
|
||||
repeated StreamAction actions = 4;
|
||||
}
|
||||
|
||||
// Catalogue Messages
|
||||
|
||||
message CatalogueCapabilities {
|
||||
CatalogueCapability movies_catalogue = 1;
|
||||
CatalogueCapability series_catalogue = 2;
|
||||
CatalogueCapability combined_catalogue = 3;
|
||||
CatalogueCapability missing_catalogue = 4;
|
||||
}
|
||||
|
||||
message CatalogueCapability {
|
||||
bool is_supported = 1;
|
||||
repeated OrderOption order_options = 2;
|
||||
}
|
||||
|
||||
message OrderOption {
|
||||
string label = 1;
|
||||
string value = 2;
|
||||
repeated DirectionOption directions = 3;
|
||||
}
|
||||
|
||||
message DirectionOption {
|
||||
string label = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message CatalogueRequest {
|
||||
UserContext user_context = 1;
|
||||
PaginationParams pagination = 2;
|
||||
optional string order = 3;
|
||||
optional string direction = 4;
|
||||
}
|
||||
|
||||
message CatalogueResponse {
|
||||
repeated CatalogueItem items = 1;
|
||||
int32 total = 2;
|
||||
int32 page = 3;
|
||||
int32 items_per_page = 4;
|
||||
}
|
||||
|
||||
message CatalogueItem {
|
||||
string tmdb_id = 1;
|
||||
string media_type = 2; // "movie" or "series"
|
||||
}
|
||||
|
||||
message PaginationParams {
|
||||
int32 page = 1;
|
||||
int32 items_per_page = 2;
|
||||
}
|
||||
|
||||
message MissingCatalogueRequest {
|
||||
UserContext user_context = 1;
|
||||
PaginationParams pagination = 2;
|
||||
optional string order = 3;
|
||||
optional string direction = 4;
|
||||
map<string, string> my_list_items_json = 5;
|
||||
}
|
||||
|
||||
message MissingCatalogueResponse {
|
||||
repeated string items_json = 1;
|
||||
int32 total = 2;
|
||||
int32 page = 3;
|
||||
int32 items_per_page = 4;
|
||||
}
|
||||
|
||||
// Management Profile Messages (NEW)
|
||||
|
||||
message ManagementProfilesResponse {
|
||||
repeated ManagementProfile profiles = 1;
|
||||
}
|
||||
|
||||
message ManagementProfile {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
string description = 3;
|
||||
AutoRequestOptions auto_requests = 4;
|
||||
AutoRemoveOptions auto_remove = 5;
|
||||
}
|
||||
|
||||
message AutoRequestOptions {
|
||||
// Episodes: Next up + x episodes (0-n)
|
||||
optional int32 next_episodes_count = 1;
|
||||
|
||||
// Episodes: Current season + x next seasons
|
||||
optional int32 next_seasons_count = 2;
|
||||
|
||||
// Watch RSS feed
|
||||
bool watch_rss = 3;
|
||||
}
|
||||
|
||||
message AutoRemoveOptions {
|
||||
// After watched + x days
|
||||
optional int32 days_after_watched = 1;
|
||||
|
||||
// After downloaded + x days
|
||||
optional int32 days_after_downloaded = 2;
|
||||
|
||||
// When seeding ratio > x
|
||||
optional double min_seeding_ratio = 3;
|
||||
|
||||
// Lazy deletion (delete when disk space needed)
|
||||
bool lazy_deletion = 4;
|
||||
|
||||
// Priority for lazy deletion
|
||||
optional int32 deletion_priority = 5;
|
||||
}
|
||||
|
||||
message MediaManagementProfileRequest {
|
||||
UserContext user_context = 1;
|
||||
string tmdb_id = 2;
|
||||
string media_type = 3; // "movie" or "series"
|
||||
}
|
||||
|
||||
message UpdateManagementProfileRequest {
|
||||
UserContext user_context = 1;
|
||||
string tmdb_id = 2;
|
||||
string media_type = 3;
|
||||
string profile_id = 4;
|
||||
}
|
||||
|
||||
// Job Management Messages (NEW)
|
||||
|
||||
message JobsResponse {
|
||||
repeated Job jobs = 1;
|
||||
}
|
||||
|
||||
message Job {
|
||||
string id = 1;
|
||||
string type = 2; // "download", "transcode", "seed"
|
||||
string status = 3; // "pending", "active", "completed", "failed", "cancelled"
|
||||
string tmdb_id = 4;
|
||||
string media_type = 5;
|
||||
optional int32 season = 6;
|
||||
optional int32 episode = 7;
|
||||
double progress = 8;
|
||||
optional string eta = 9;
|
||||
map<string, string> metadata = 10;
|
||||
}
|
||||
|
||||
message JobDetailsRequest {
|
||||
UserContext user_context = 1;
|
||||
string job_id = 2;
|
||||
}
|
||||
|
||||
message JobDetails {
|
||||
Job job = 1;
|
||||
repeated JobLogEntry logs = 2;
|
||||
JobStats stats = 3;
|
||||
}
|
||||
|
||||
message JobLogEntry {
|
||||
string timestamp = 1;
|
||||
string level = 2; // "info", "warning", "error"
|
||||
string message = 3;
|
||||
}
|
||||
|
||||
message JobStats {
|
||||
optional double download_speed = 1;
|
||||
optional double upload_speed = 2;
|
||||
optional int32 seeders = 3;
|
||||
optional int32 peers = 4;
|
||||
optional double seeding_ratio = 5;
|
||||
optional int64 size_bytes = 6;
|
||||
optional int64 downloaded_bytes = 7;
|
||||
}
|
||||
|
||||
message CancelJobRequest {
|
||||
UserContext user_context = 1;
|
||||
string job_id = 2;
|
||||
}
|
||||
|
||||
message DiskSpaceUsage {
|
||||
int64 total_bytes = 1;
|
||||
int64 used_bytes = 2;
|
||||
int64 available_bytes = 3;
|
||||
repeated MediaFileInfo media_files = 4;
|
||||
}
|
||||
|
||||
message MediaFileInfo {
|
||||
string id = 1;
|
||||
string tmdb_id = 2;
|
||||
string media_type = 3;
|
||||
optional int32 season = 4;
|
||||
optional int32 episode = 5;
|
||||
int64 size_bytes = 6;
|
||||
string file_path = 7;
|
||||
bool watched = 8;
|
||||
optional double seeding_ratio = 9;
|
||||
optional string added_date = 10;
|
||||
optional string watched_date = 11;
|
||||
bool marked_for_deletion = 12;
|
||||
}
|
||||
|
||||
message CleanupHistoryRequest {
|
||||
UserContext user_context = 1;
|
||||
PaginationParams pagination = 2;
|
||||
}
|
||||
|
||||
message CleanupHistoryResponse {
|
||||
repeated CleanupHistoryEntry entries = 1;
|
||||
int32 total = 2;
|
||||
int32 page = 3;
|
||||
int32 items_per_page = 4;
|
||||
}
|
||||
|
||||
message CleanupHistoryEntry {
|
||||
string timestamp = 1;
|
||||
string tmdb_id = 2;
|
||||
string media_type = 3;
|
||||
optional int32 season = 4;
|
||||
optional int32 episode = 5;
|
||||
int64 size_bytes = 6;
|
||||
string reason = 7; // "watched_timeout", "download_timeout", "seeding_complete", "disk_space_needed", "manual"
|
||||
}
|
||||
|
||||
message TriggerCleanupRequest {
|
||||
UserContext user_context = 1;
|
||||
optional int64 target_free_space_bytes = 2;
|
||||
}
|
||||
@@ -1,7 +1,2 @@
|
||||
export * from './catalogue';
|
||||
export * from './common';
|
||||
export * from './dtos';
|
||||
export * from './permissions';
|
||||
export * from './settings';
|
||||
export * from './video';
|
||||
export * from './plugin';
|
||||
export * as old from './old';
|
||||
export * from './reiverr-plugin';
|
||||
|
||||
7
shared/src/old.ts
Normal file
7
shared/src/old.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from './catalogue';
|
||||
export * from './common';
|
||||
export * from './dtos';
|
||||
export * from './permissions';
|
||||
export * from './settings';
|
||||
export * from './video';
|
||||
export * from './plugin';
|
||||
568
shared/src/reiverr-plugin.ts
Normal file
568
shared/src/reiverr-plugin.ts
Normal file
@@ -0,0 +1,568 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.11.1
|
||||
// protoc v6.33.5
|
||||
// source: reiverr-plugin.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
export const protobufPackage = "aleksilassila.reiverr.plugin.v1";
|
||||
|
||||
export interface Empty {
|
||||
}
|
||||
|
||||
export interface PluginInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
description: string;
|
||||
streamingSupported: boolean;
|
||||
catalogueSupported: boolean;
|
||||
}
|
||||
|
||||
export interface SettingsTemplate {
|
||||
fields: { [key: string]: SettingField };
|
||||
}
|
||||
|
||||
export interface SettingsTemplate_FieldsEntry {
|
||||
key: string;
|
||||
value: SettingField | undefined;
|
||||
}
|
||||
|
||||
export interface SettingField {
|
||||
/** "string", "number", "boolean", "password", "link" */
|
||||
type: string;
|
||||
label: string;
|
||||
placeholder: string;
|
||||
required: boolean;
|
||||
/** For link type */
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface ValidateSettingsRequest {
|
||||
settings: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface ValidateSettingsRequest_SettingsEntry {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ValidationResponse {
|
||||
isValid: boolean;
|
||||
errors: { [key: string]: string };
|
||||
validatedSettings: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface ValidationResponse_ErrorsEntry {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ValidationResponse_ValidatedSettingsEntry {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** User Context - passed with most requests */
|
||||
export interface UserContext {
|
||||
userId: string;
|
||||
token: string;
|
||||
sourceId: string;
|
||||
settings: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface UserContext_SettingsEntry {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** Playable Context */
|
||||
export interface PlayableContext {
|
||||
tmdbMovieJson?: string | undefined;
|
||||
tmdbSeriesJson?: string | undefined;
|
||||
tmdbEpisodeJson?: string | undefined;
|
||||
}
|
||||
|
||||
export interface MediaSourceViewsRequest {
|
||||
userContext: UserContext | undefined;
|
||||
playableContext: PlayableContext | undefined;
|
||||
}
|
||||
|
||||
export interface MediaSourceViewsResponse {
|
||||
views: MediaSourceView[];
|
||||
}
|
||||
|
||||
export interface MediaSourceView {
|
||||
id: string;
|
||||
title: string;
|
||||
properties: StreamProperty[];
|
||||
actions: StreamAction[];
|
||||
}
|
||||
|
||||
export interface MediaSourceViewRequest {
|
||||
userContext: UserContext | undefined;
|
||||
playableContext: PlayableContext | undefined;
|
||||
viewId: string;
|
||||
}
|
||||
|
||||
export interface MediaSourceViewResponse {
|
||||
view?: MediaSourceView | undefined;
|
||||
}
|
||||
|
||||
export interface AutoplayStreamRequest {
|
||||
userContext: UserContext | undefined;
|
||||
playableContext: PlayableContext | undefined;
|
||||
}
|
||||
|
||||
export interface AutoplayStreamResponse {
|
||||
candidate?: StreamBase | undefined;
|
||||
}
|
||||
|
||||
export interface GetStreamRequest {
|
||||
userContext: UserContext | undefined;
|
||||
streamId: string;
|
||||
config?: PlaybackConfig | undefined;
|
||||
}
|
||||
|
||||
export interface StreamResponse {
|
||||
stream?: Stream | undefined;
|
||||
toast?: Toast | undefined;
|
||||
error?: ErrorMessage | undefined;
|
||||
}
|
||||
|
||||
export interface Stream {
|
||||
streamId: string;
|
||||
title: string;
|
||||
properties: StreamProperty[];
|
||||
src: string;
|
||||
directPlay: boolean;
|
||||
progress: number;
|
||||
duration: number;
|
||||
audioStreams: AudioStream[];
|
||||
audioStreamIndex: number;
|
||||
qualities: Quality[];
|
||||
qualityIndex: number;
|
||||
subtitles: Subtitle[];
|
||||
}
|
||||
|
||||
export interface StreamBase {
|
||||
streamId: string;
|
||||
title: string;
|
||||
properties: StreamProperty[];
|
||||
}
|
||||
|
||||
export interface StreamProperty {
|
||||
label: string;
|
||||
value: string;
|
||||
formatted?: string | undefined;
|
||||
}
|
||||
|
||||
export interface AudioStream {
|
||||
index: number;
|
||||
label: string;
|
||||
codec?: string | undefined;
|
||||
bitrate?: number | undefined;
|
||||
}
|
||||
|
||||
export interface Quality {
|
||||
index: number;
|
||||
bitrate: number;
|
||||
label: string;
|
||||
codec?: string | undefined;
|
||||
original: boolean;
|
||||
}
|
||||
|
||||
export interface Subtitle {
|
||||
src: string;
|
||||
lang: string;
|
||||
/** "subtitles", "captions", "descriptions" */
|
||||
kind: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface PlaybackConfig {
|
||||
bitrate?: number | undefined;
|
||||
audioStreamIndex?: number | undefined;
|
||||
progress?: number | undefined;
|
||||
deviceProfileJson?: string | undefined;
|
||||
defaultLanguage?: string | undefined;
|
||||
}
|
||||
|
||||
export interface StreamAction {
|
||||
label: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface HandleActionRequest {
|
||||
userContext: UserContext | undefined;
|
||||
targetId: string;
|
||||
action: string;
|
||||
}
|
||||
|
||||
export interface ActionResponse {
|
||||
toast?: Toast | undefined;
|
||||
error?: ErrorMessage | undefined;
|
||||
result?: ActionResult | undefined;
|
||||
}
|
||||
|
||||
export interface ActionResult {
|
||||
success: boolean;
|
||||
message?: string | undefined;
|
||||
}
|
||||
|
||||
export interface Toast {
|
||||
title: string;
|
||||
message: string;
|
||||
/** "info", "success", "error" */
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface ErrorMessage {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface ProxyRequest {
|
||||
userContext: UserContext | undefined;
|
||||
uri: string;
|
||||
targetUrl?: string | undefined;
|
||||
headers: { [key: string]: string };
|
||||
body: Uint8Array;
|
||||
}
|
||||
|
||||
export interface ProxyRequest_HeadersEntry {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ProxyResponse {
|
||||
statusCode: number;
|
||||
headers: { [key: string]: string };
|
||||
chunk: Uint8Array;
|
||||
isFinal: boolean;
|
||||
}
|
||||
|
||||
export interface ProxyResponse_HeadersEntry {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface TmdbMovieRequest {
|
||||
userContext: UserContext | undefined;
|
||||
tmdbMovieJson: string;
|
||||
}
|
||||
|
||||
export interface TmdbEpisodeRequest {
|
||||
userContext: UserContext | undefined;
|
||||
tmdbSeriesJson: string;
|
||||
tmdbEpisodeJson: string;
|
||||
}
|
||||
|
||||
export interface StreamCandidatesResponse {
|
||||
candidates: StreamCandidate[];
|
||||
}
|
||||
|
||||
export interface StreamCandidate {
|
||||
streamId: string;
|
||||
title: string;
|
||||
properties: StreamProperty[];
|
||||
actions: StreamAction[];
|
||||
}
|
||||
|
||||
export interface CatalogueCapabilities {
|
||||
moviesCatalogue: CatalogueCapability | undefined;
|
||||
seriesCatalogue: CatalogueCapability | undefined;
|
||||
combinedCatalogue: CatalogueCapability | undefined;
|
||||
missingCatalogue: CatalogueCapability | undefined;
|
||||
}
|
||||
|
||||
export interface CatalogueCapability {
|
||||
isSupported: boolean;
|
||||
orderOptions: OrderOption[];
|
||||
}
|
||||
|
||||
export interface OrderOption {
|
||||
label: string;
|
||||
value: string;
|
||||
directions: DirectionOption[];
|
||||
}
|
||||
|
||||
export interface DirectionOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface CatalogueRequest {
|
||||
userContext: UserContext | undefined;
|
||||
pagination: PaginationParams | undefined;
|
||||
order?: string | undefined;
|
||||
direction?: string | undefined;
|
||||
}
|
||||
|
||||
export interface CatalogueResponse {
|
||||
items: CatalogueItem[];
|
||||
total: number;
|
||||
page: number;
|
||||
itemsPerPage: number;
|
||||
}
|
||||
|
||||
export interface CatalogueItem {
|
||||
tmdbId: string;
|
||||
/** "movie" or "series" */
|
||||
mediaType: string;
|
||||
}
|
||||
|
||||
export interface PaginationParams {
|
||||
page: number;
|
||||
itemsPerPage: number;
|
||||
}
|
||||
|
||||
export interface MissingCatalogueRequest {
|
||||
userContext: UserContext | undefined;
|
||||
pagination: PaginationParams | undefined;
|
||||
order?: string | undefined;
|
||||
direction?: string | undefined;
|
||||
myListItemsJson: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface MissingCatalogueRequest_MyListItemsJsonEntry {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface MissingCatalogueResponse {
|
||||
itemsJson: string[];
|
||||
total: number;
|
||||
page: number;
|
||||
itemsPerPage: number;
|
||||
}
|
||||
|
||||
export interface ManagementProfilesResponse {
|
||||
profiles: ManagementProfile[];
|
||||
}
|
||||
|
||||
export interface ManagementProfile {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
autoRequests: AutoRequestOptions | undefined;
|
||||
autoRemove: AutoRemoveOptions | undefined;
|
||||
}
|
||||
|
||||
export interface AutoRequestOptions {
|
||||
/** Episodes: Next up + x episodes (0-n) */
|
||||
nextEpisodesCount?:
|
||||
| number
|
||||
| undefined;
|
||||
/** Episodes: Current season + x next seasons */
|
||||
nextSeasonsCount?:
|
||||
| number
|
||||
| undefined;
|
||||
/** Watch RSS feed */
|
||||
watchRss: boolean;
|
||||
}
|
||||
|
||||
export interface AutoRemoveOptions {
|
||||
/** After watched + x days */
|
||||
daysAfterWatched?:
|
||||
| number
|
||||
| undefined;
|
||||
/** After downloaded + x days */
|
||||
daysAfterDownloaded?:
|
||||
| number
|
||||
| undefined;
|
||||
/** When seeding ratio > x */
|
||||
minSeedingRatio?:
|
||||
| number
|
||||
| undefined;
|
||||
/** Lazy deletion (delete when disk space needed) */
|
||||
lazyDeletion: boolean;
|
||||
/** Priority for lazy deletion */
|
||||
deletionPriority?: number | undefined;
|
||||
}
|
||||
|
||||
export interface MediaManagementProfileRequest {
|
||||
userContext: UserContext | undefined;
|
||||
tmdbId: string;
|
||||
/** "movie" or "series" */
|
||||
mediaType: string;
|
||||
}
|
||||
|
||||
export interface UpdateManagementProfileRequest {
|
||||
userContext: UserContext | undefined;
|
||||
tmdbId: string;
|
||||
mediaType: string;
|
||||
profileId: string;
|
||||
}
|
||||
|
||||
export interface JobsResponse {
|
||||
jobs: Job[];
|
||||
}
|
||||
|
||||
export interface Job {
|
||||
id: string;
|
||||
/** "download", "transcode", "seed" */
|
||||
type: string;
|
||||
/** "pending", "active", "completed", "failed", "cancelled" */
|
||||
status: string;
|
||||
tmdbId: string;
|
||||
mediaType: string;
|
||||
season?: number | undefined;
|
||||
episode?: number | undefined;
|
||||
progress: number;
|
||||
eta?: string | undefined;
|
||||
metadata: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface Job_MetadataEntry {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface JobDetailsRequest {
|
||||
userContext: UserContext | undefined;
|
||||
jobId: string;
|
||||
}
|
||||
|
||||
export interface JobDetails {
|
||||
job: Job | undefined;
|
||||
logs: JobLogEntry[];
|
||||
stats: JobStats | undefined;
|
||||
}
|
||||
|
||||
export interface JobLogEntry {
|
||||
timestamp: string;
|
||||
/** "info", "warning", "error" */
|
||||
level: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface JobStats {
|
||||
downloadSpeed?: number | undefined;
|
||||
uploadSpeed?: number | undefined;
|
||||
seeders?: number | undefined;
|
||||
peers?: number | undefined;
|
||||
seedingRatio?: number | undefined;
|
||||
sizeBytes?: number | undefined;
|
||||
downloadedBytes?: number | undefined;
|
||||
}
|
||||
|
||||
export interface CancelJobRequest {
|
||||
userContext: UserContext | undefined;
|
||||
jobId: string;
|
||||
}
|
||||
|
||||
export interface DiskSpaceUsage {
|
||||
totalBytes: number;
|
||||
usedBytes: number;
|
||||
availableBytes: number;
|
||||
mediaFiles: MediaFileInfo[];
|
||||
}
|
||||
|
||||
export interface MediaFileInfo {
|
||||
id: string;
|
||||
tmdbId: string;
|
||||
mediaType: string;
|
||||
season?: number | undefined;
|
||||
episode?: number | undefined;
|
||||
sizeBytes: number;
|
||||
filePath: string;
|
||||
watched: boolean;
|
||||
seedingRatio?: number | undefined;
|
||||
addedDate?: string | undefined;
|
||||
watchedDate?: string | undefined;
|
||||
markedForDeletion: boolean;
|
||||
}
|
||||
|
||||
export interface CleanupHistoryRequest {
|
||||
userContext: UserContext | undefined;
|
||||
pagination: PaginationParams | undefined;
|
||||
}
|
||||
|
||||
export interface CleanupHistoryResponse {
|
||||
entries: CleanupHistoryEntry[];
|
||||
total: number;
|
||||
page: number;
|
||||
itemsPerPage: number;
|
||||
}
|
||||
|
||||
export interface CleanupHistoryEntry {
|
||||
timestamp: string;
|
||||
tmdbId: string;
|
||||
mediaType: string;
|
||||
season?: number | undefined;
|
||||
episode?: number | undefined;
|
||||
sizeBytes: number;
|
||||
/** "watched_timeout", "download_timeout", "seeding_complete", "disk_space_needed", "manual" */
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface TriggerCleanupRequest {
|
||||
userContext: UserContext | undefined;
|
||||
targetFreeSpaceBytes?: number | undefined;
|
||||
}
|
||||
|
||||
/** Plugin Service - handles plugin metadata and configuration */
|
||||
export interface PluginService {
|
||||
/** Get plugin metadata and version */
|
||||
GetInfo(request: Empty): Promise<PluginInfo>;
|
||||
}
|
||||
|
||||
/** Media Source Provider Service - handles user-specific requests */
|
||||
export interface MediaSourceProviderService {
|
||||
/** Get available views for a media item */
|
||||
GetMediaSourceViews(request: MediaSourceViewsRequest): Promise<MediaSourceViewsResponse>;
|
||||
/** Get a specific view */
|
||||
GetMediaSourceView(request: MediaSourceViewRequest): Promise<MediaSourceViewResponse>;
|
||||
/** Get autoplay stream */
|
||||
GetAutoplayStream(request: AutoplayStreamRequest): Promise<AutoplayStreamResponse>;
|
||||
/** Get stream details */
|
||||
GetStream(request: GetStreamRequest): Promise<StreamResponse>;
|
||||
/** Handle stream actions (download, delete, etc.) */
|
||||
HandleAction(request: HandleActionRequest): Promise<ActionResponse>;
|
||||
/** Proxy handler for streaming (bidirectional for video/subtitle streaming) */
|
||||
ProxyStream(request: Observable<ProxyRequest>): Observable<ProxyResponse>;
|
||||
/** Legacy methods (deprecated) */
|
||||
GetTmdbMovieCandidates(request: TmdbMovieRequest): Promise<StreamCandidatesResponse>;
|
||||
GetTmdbEpisodeCandidates(request: TmdbEpisodeRequest): Promise<StreamCandidatesResponse>;
|
||||
}
|
||||
|
||||
/** Catalogue Provider Service - handles library catalogues */
|
||||
export interface CatalogueProviderService {
|
||||
/** Get catalogue capabilities */
|
||||
GetCatalogueCapabilities(request: Empty): Promise<CatalogueCapabilities>;
|
||||
/** Get combined catalogue */
|
||||
GetCatalogue(request: CatalogueRequest): Promise<CatalogueResponse>;
|
||||
/** Get movies catalogue */
|
||||
GetMovieCatalogue(request: CatalogueRequest): Promise<CatalogueResponse>;
|
||||
/** Get series catalogue */
|
||||
GetSeriesCatalogue(request: CatalogueRequest): Promise<CatalogueResponse>;
|
||||
/** Get missing items in catalogue */
|
||||
GetMissingInCatalogue(request: MissingCatalogueRequest): Promise<MissingCatalogueResponse>;
|
||||
}
|
||||
|
||||
/** Management Profile Service - NEW for monitoring/management profiles */
|
||||
export interface ManagementProfileService {
|
||||
/** Get available management profiles */
|
||||
GetManagementProfiles(request: Empty): Promise<ManagementProfilesResponse>;
|
||||
/** Get management profile for specific media */
|
||||
GetMediaManagementProfile(request: MediaManagementProfileRequest): Promise<ManagementProfile>;
|
||||
/** Update management profile for media */
|
||||
UpdateMediaManagementProfile(request: UpdateManagementProfileRequest): Promise<ManagementProfile>;
|
||||
}
|
||||
|
||||
/** Job Management Service - NEW for monitoring downloads, transcoding, etc. */
|
||||
export interface JobManagementService {
|
||||
/** Get all active jobs */
|
||||
GetActiveJobs(request: Empty): Promise<JobsResponse>;
|
||||
/** Get job details */
|
||||
GetJobDetails(request: JobDetailsRequest): Promise<JobDetails>;
|
||||
/** Cancel a job */
|
||||
CancelJob(request: CancelJobRequest): Promise<ActionResponse>;
|
||||
/** Get disk space usage */
|
||||
GetDiskSpaceUsage(request: Empty): Promise<DiskSpaceUsage>;
|
||||
/** Get cleanup history */
|
||||
GetCleanupHistory(request: CleanupHistoryRequest): Promise<CleanupHistoryResponse>;
|
||||
/** Trigger cleanup */
|
||||
TriggerCleanup(request: TriggerCleanupRequest): Promise<ActionResponse>;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
SourceProviderSettingsTemplate,
|
||||
UserContext,
|
||||
ValidationResponse,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import { testConnection } from './lib/jackett.api';
|
||||
import { TorrentMediaSourceProvider } from './media-source-provider';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { XMLParser } from 'fast-xml-parser';
|
||||
import { StreamCandidate } from '@aleksilassila/reiverr-shared';
|
||||
import { StreamCandidate } from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import { TorrentSettings } from '../types';
|
||||
import { formatSize, formatBitrate, EPISODE_SEPARATOR } from '../utils';
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Subtitles,
|
||||
UserContext,
|
||||
ViewBase,
|
||||
} from '@aleksilassila/reiverr-shared';
|
||||
} from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
import {
|
||||
getEpisodeTorrents,
|
||||
getMovieTorrents,
|
||||
@@ -274,9 +274,9 @@ export class TorrentMediaSourceProvider extends MediaSourceProvider {
|
||||
throw new Error('Torrent not found');
|
||||
}
|
||||
|
||||
let src = `${this.proxyUrl}/magnet?link=${encodeURIComponent(link)}&reiverr_token=${
|
||||
this.token
|
||||
}`;
|
||||
let src = `${this.proxyUrl}/magnet?link=${encodeURIComponent(
|
||||
link,
|
||||
)}&reiverr_token=${this.token}`;
|
||||
|
||||
if (season && episode) {
|
||||
src += `&season=${season}&episode=${episode}`;
|
||||
@@ -288,11 +288,12 @@ export class TorrentMediaSourceProvider extends MediaSourceProvider {
|
||||
.filter((f) => subtitleExtensions.some((ext) => f.name.endsWith(ext)))
|
||||
.map((f) => ({
|
||||
kind: 'subtitles',
|
||||
src: `${this.proxyUrl}/magnet?link=${encodeURIComponent(link)}&reiverr_token=${
|
||||
this.token
|
||||
}&file=${f.name}`,
|
||||
src: `${this.proxyUrl}/magnet?link=${encodeURIComponent(
|
||||
link,
|
||||
)}&reiverr_token=${this.token}&file=${f.name}`,
|
||||
label: f.name,
|
||||
lang: 'unknown',
|
||||
default: false,
|
||||
}));
|
||||
|
||||
const stream = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SourceProviderSettings } from '@aleksilassila/reiverr-shared';
|
||||
import type { SourceProviderSettings } from '@aleksilassila/reiverr-shared/dist/src/old';
|
||||
|
||||
export interface TorrentSettings extends SourceProviderSettings {
|
||||
apiKey: string;
|
||||
|
||||
Reference in New Issue
Block a user