fix(media-requests): incorrect availability mapping (#4520)
This commit is contained in:
@@ -38,7 +38,7 @@ export type {
|
||||
FirewallMemorySummary,
|
||||
} from "./interfaces/firewall-summary/firewall-summary-types";
|
||||
export type { SystemHealthMonitoring } from "./interfaces/health-monitoring/health-monitoring-types";
|
||||
export { MediaRequestStatus } from "./interfaces/media-requests/media-request-types";
|
||||
export { UpstreamMediaRequestStatus } from "./interfaces/media-requests/media-request-types";
|
||||
export type { MediaRequestList, MediaRequestStats } from "./interfaces/media-requests/media-request-types";
|
||||
export type { StreamSession } from "./interfaces/media-server/media-server-types";
|
||||
export type {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { objectKeys } from "@homarr/common";
|
||||
|
||||
interface SerieSeason {
|
||||
id: number;
|
||||
seasonNumber: number;
|
||||
@@ -34,6 +36,64 @@ export interface MediaRequest {
|
||||
requestedBy?: Omit<RequestUser, "requestCount">;
|
||||
}
|
||||
|
||||
export const mediaAvailabilityConfiguration = {
|
||||
available: {
|
||||
color: "green",
|
||||
},
|
||||
partiallyAvailable: {
|
||||
color: "yellow",
|
||||
},
|
||||
processing: {
|
||||
color: "blue",
|
||||
},
|
||||
requested: {
|
||||
color: "violet",
|
||||
},
|
||||
pending: {
|
||||
color: "violet",
|
||||
},
|
||||
unknown: {
|
||||
color: "orange",
|
||||
},
|
||||
deleted: {
|
||||
color: "red",
|
||||
},
|
||||
blacklisted: {
|
||||
color: "gray",
|
||||
},
|
||||
} satisfies Record<string, { color: string }>;
|
||||
|
||||
export const mediaAvailabilities = objectKeys(mediaAvailabilityConfiguration);
|
||||
|
||||
export type MediaAvailability = (typeof mediaAvailabilities)[number];
|
||||
|
||||
export const mediaRequestStatusConfiguration = {
|
||||
pending: {
|
||||
color: "blue",
|
||||
position: 1,
|
||||
},
|
||||
approved: {
|
||||
color: "green",
|
||||
position: 2,
|
||||
},
|
||||
declined: {
|
||||
color: "red",
|
||||
position: 3,
|
||||
},
|
||||
failed: {
|
||||
color: "red",
|
||||
position: 4,
|
||||
},
|
||||
completed: {
|
||||
color: "green",
|
||||
position: 5,
|
||||
},
|
||||
} satisfies Record<string, { color: string; position: number }>;
|
||||
|
||||
export const mediaRequestStatuses = objectKeys(mediaRequestStatusConfiguration);
|
||||
|
||||
export type MediaRequestStatus = (typeof mediaRequestStatuses)[number];
|
||||
|
||||
export interface MediaRequestList {
|
||||
integration: {
|
||||
id: string;
|
||||
@@ -66,7 +126,7 @@ export interface MediaRequestStats {
|
||||
}
|
||||
|
||||
// https://github.com/fallenbagel/jellyseerr/blob/0fd03f38480f853e7015ad9229ed98160e37602e/server/constants/media.ts#L1
|
||||
export enum MediaRequestStatus {
|
||||
export enum UpstreamMediaRequestStatus {
|
||||
PendingApproval = 1,
|
||||
Approved = 2,
|
||||
Declined = 3,
|
||||
@@ -75,12 +135,12 @@ export enum MediaRequestStatus {
|
||||
}
|
||||
|
||||
// https://github.com/fallenbagel/jellyseerr/blob/0fd03f38480f853e7015ad9229ed98160e37602e/server/constants/media.ts#L14
|
||||
export enum MediaAvailability {
|
||||
export enum UpstreamMediaAvailability {
|
||||
Unknown = 1,
|
||||
Pending = 2,
|
||||
Processing = 3,
|
||||
PartiallyAvailable = 4,
|
||||
Available = 5,
|
||||
Blacklisted = 6,
|
||||
Deleted = 7,
|
||||
JellyseerrBlacklistedOrOverseerrDeleted = 6,
|
||||
JellyseerrDeleted = 7,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
import type { MediaAvailability } from "../interfaces/media-requests/media-request-types";
|
||||
import { UpstreamMediaAvailability } from "../interfaces/media-requests/media-request-types";
|
||||
import { OverseerrIntegration } from "../overseerr/overseerr-integration";
|
||||
|
||||
export class JellyseerrIntegration extends OverseerrIntegration {}
|
||||
export class JellyseerrIntegration extends OverseerrIntegration {
|
||||
protected override mapAvailability(availability: UpstreamMediaAvailability, inProgress: boolean): MediaAvailability {
|
||||
// Availability statuses are not exactly the same between Jellyseerr and Overseerr (Jellyseerr has "blacklisted" additionally (deleted is the same value in overseerr))
|
||||
if (availability === UpstreamMediaAvailability.JellyseerrBlacklistedOrOverseerrDeleted) return "blacklisted";
|
||||
if (availability === UpstreamMediaAvailability.JellyseerrDeleted) return "deleted";
|
||||
return super.mapAvailability(availability, inProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { objectEntries } from "@homarr/common";
|
||||
|
||||
import type { IMediaRequestIntegration } from "../../interfaces/media-requests/media-request-integration";
|
||||
import type { MediaInformation, MediaRequest, RequestStats, RequestUser } from "../../types";
|
||||
import { MediaAvailability, MediaRequestStatus } from "../../types";
|
||||
import type {
|
||||
MediaAvailability,
|
||||
MediaInformation,
|
||||
MediaRequest,
|
||||
MediaRequestStatus,
|
||||
RequestStats,
|
||||
RequestUser,
|
||||
} from "../../types";
|
||||
import { mediaAvailabilities, mediaRequestStatuses } from "../../types";
|
||||
|
||||
export class MediaRequestMockService implements IMediaRequestIntegration {
|
||||
public async getSeriesInformationAsync(mediaType: "movie" | "tv", id: number): Promise<MediaInformation> {
|
||||
@@ -86,12 +91,10 @@ export class MediaRequestMockService implements IMediaRequestIntegration {
|
||||
}
|
||||
|
||||
private static randomAvailability(): MediaAvailability {
|
||||
const values = objectEntries(MediaAvailability).filter(([key]) => typeof key === "number");
|
||||
return values[Math.floor(Math.random() * values.length)]?.[1] ?? MediaAvailability.Available;
|
||||
return mediaAvailabilities.at(Math.floor(Math.random() * mediaAvailabilities.length)) ?? "unknown";
|
||||
}
|
||||
|
||||
private static randomStatus(): MediaRequestStatus {
|
||||
const values = objectEntries(MediaRequestStatus).filter(([key]) => typeof key === "number");
|
||||
return values[Math.floor(Math.random() * values.length)]?.[1] ?? MediaRequestStatus.PendingApproval;
|
||||
return mediaRequestStatuses.at(Math.floor(Math.random() * mediaRequestStatuses.length)) ?? "pending";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,17 @@ import type { ISearchableIntegration } from "../base/searchable-integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { IMediaRequestIntegration } from "../interfaces/media-requests/media-request-integration";
|
||||
import type { MediaRequest, RequestStats, RequestUser } from "../interfaces/media-requests/media-request-types";
|
||||
import { MediaAvailability, MediaRequestStatus } from "../interfaces/media-requests/media-request-types";
|
||||
import type {
|
||||
MediaAvailability,
|
||||
MediaRequest,
|
||||
MediaRequestStatus,
|
||||
RequestStats,
|
||||
RequestUser,
|
||||
} from "../interfaces/media-requests/media-request-types";
|
||||
import {
|
||||
UpstreamMediaAvailability,
|
||||
UpstreamMediaRequestStatus,
|
||||
} from "../interfaces/media-requests/media-request-types";
|
||||
|
||||
interface OverseerrSearchResult {
|
||||
id: number;
|
||||
@@ -128,7 +137,7 @@ export class OverseerrIntegration
|
||||
|
||||
if (pendingResults.length > 0 && allResults.length > 0) {
|
||||
requests = pendingResults.concat(
|
||||
allResults.filter(({ status }) => status !== MediaRequestStatus.PendingApproval),
|
||||
allResults.filter(({ status }) => status !== UpstreamMediaRequestStatus.PendingApproval),
|
||||
);
|
||||
} else if (pendingResults.length > 0) requests = pendingResults;
|
||||
else if (allResults.length > 0) requests = allResults;
|
||||
@@ -137,11 +146,15 @@ export class OverseerrIntegration
|
||||
return await Promise.all(
|
||||
requests.map(async (request): Promise<MediaRequest> => {
|
||||
const information = await this.getItemInformationAsync(request.media.tmdbId, request.type);
|
||||
|
||||
// See https://github.com/seerr-team/seerr/blob/af083a3cd5c3e3d5d7917fdf4fdd67fe3f39c46b/src/components/StatusBadge/index.tsx#L40
|
||||
const inProgress = (request.media.downloadStatus ?? []).length >= 1;
|
||||
|
||||
return {
|
||||
id: request.id,
|
||||
name: information.name,
|
||||
status: request.status,
|
||||
availability: request.media.status,
|
||||
status: this.mapRequestStatus(request.status),
|
||||
availability: this.mapAvailability(request.media.status, inProgress),
|
||||
backdropImageUrl: `https://image.tmdb.org/t/p/original/${information.backdropPath}`,
|
||||
posterImagePath: `https://image.tmdb.org/t/p/w600_and_h900_bestv2/${information.posterPath}`,
|
||||
href: this.externalUrl(`/${request.type}/${request.media.tmdbId}`).toString(),
|
||||
@@ -161,6 +174,42 @@ export class OverseerrIntegration
|
||||
);
|
||||
}
|
||||
|
||||
protected mapRequestStatus(status: UpstreamMediaRequestStatus): MediaRequestStatus {
|
||||
switch (status) {
|
||||
case UpstreamMediaRequestStatus.PendingApproval:
|
||||
return "pending";
|
||||
case UpstreamMediaRequestStatus.Approved:
|
||||
return "approved";
|
||||
case UpstreamMediaRequestStatus.Declined:
|
||||
return "declined";
|
||||
case UpstreamMediaRequestStatus.Failed:
|
||||
return "failed";
|
||||
case UpstreamMediaRequestStatus.Completed:
|
||||
return "completed";
|
||||
default:
|
||||
return "failed";
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/seerr-team/seerr/blob/af083a3cd5c3e3d5d7917fdf4fdd67fe3f39c46b/src/components/StatusBadge/index.tsx#L153-L387
|
||||
protected mapAvailability(availability: UpstreamMediaAvailability, inProgress: boolean): MediaAvailability {
|
||||
switch (availability) {
|
||||
case UpstreamMediaAvailability.Available:
|
||||
return inProgress ? "processing" : "available";
|
||||
case UpstreamMediaAvailability.PartiallyAvailable:
|
||||
return inProgress ? "processing" : "partiallyAvailable";
|
||||
case UpstreamMediaAvailability.Processing:
|
||||
return inProgress ? "processing" : "requested";
|
||||
case UpstreamMediaAvailability.Pending:
|
||||
return "pending";
|
||||
case UpstreamMediaAvailability.JellyseerrBlacklistedOrOverseerrDeleted:
|
||||
return "deleted";
|
||||
case UpstreamMediaAvailability.Unknown:
|
||||
default:
|
||||
return inProgress ? "processing" : "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public async getStatsAsync(): Promise<RequestStats> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/request/count"), {
|
||||
headers: {
|
||||
@@ -339,11 +388,12 @@ const getRequestsSchema = z.object({
|
||||
.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
status: z.nativeEnum(MediaRequestStatus),
|
||||
status: z.enum(UpstreamMediaRequestStatus),
|
||||
createdAt: z.string().transform((value) => new Date(value)),
|
||||
media: z.object({
|
||||
status: z.nativeEnum(MediaAvailability),
|
||||
status: z.enum(UpstreamMediaAvailability),
|
||||
tmdbId: z.number(),
|
||||
downloadStatus: z.array(z.unknown()).optional(),
|
||||
}),
|
||||
type: z.enum(["movie", "tv"]),
|
||||
requestedBy: z
|
||||
|
||||
Reference in New Issue
Block a user