Compare commits
11 Commits
master
...
6005422cea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6005422cea | ||
|
|
d8404496b8 | ||
|
|
41bb2c3a2c | ||
|
|
53d7b56265 | ||
|
|
b012ccb500 | ||
|
|
8eb5dddd7b | ||
|
|
e87b093ea5 | ||
|
|
3fb73e4e62 | ||
|
|
246e40a508 | ||
|
|
2acb4fc4a4 | ||
|
|
9a63f4a4dd |
@@ -5,7 +5,7 @@ import { getSettings, MetadataProviderType } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
|
||||
export const getMetadataProvider = async (
|
||||
mediaType: 'movie' | 'tv' | 'anime'
|
||||
mediaType: 'movie' | 'tv' | 'anime' | 'music' | 'book'
|
||||
): Promise<TvShowProvider> => {
|
||||
try {
|
||||
const settings = await getSettings();
|
||||
|
||||
@@ -100,10 +100,10 @@ class Media {
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Index()
|
||||
public externalServiceId?: string;
|
||||
public foreignId?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public externalServiceTitle?: string;
|
||||
public foreignTitle?: string;
|
||||
|
||||
@Column({ type: 'int', default: MediaStatus.UNKNOWN })
|
||||
@Index()
|
||||
|
||||
@@ -156,7 +156,7 @@ export class MediaRequest {
|
||||
let media = await mediaRepository.findOne({
|
||||
where: isMusicOrBook
|
||||
? {
|
||||
externalServiceId: requestBody.foreignId,
|
||||
foreignId: requestBody.foreignId,
|
||||
mediaType: requestBody.mediaType,
|
||||
}
|
||||
: {
|
||||
@@ -170,8 +170,8 @@ export class MediaRequest {
|
||||
media = new Media({
|
||||
tmdbId: isMusicOrBook ? 0 : tmdbMedia.id,
|
||||
tvdbId: isMusicOrBook ? 0 : (requestBody.tvdbId ?? tmdbMedia.external_ids?.tvdb_id),
|
||||
externalServiceId: isMusicOrBook ? requestBody.foreignId : undefined,
|
||||
externalServiceTitle: isMusicOrBook ? requestBody.foreignTitle : undefined,
|
||||
foreignId: isMusicOrBook ? requestBody.foreignId : undefined,
|
||||
foreignTitle: isMusicOrBook ? requestBody.foreignTitle : undefined,
|
||||
status: !requestBody.is4k ? MediaStatus.PENDING : MediaStatus.UNKNOWN,
|
||||
status4k: requestBody.is4k ? MediaStatus.PENDING : MediaStatus.UNKNOWN,
|
||||
mediaType: requestBody.mediaType,
|
||||
@@ -206,7 +206,7 @@ export class MediaRequest {
|
||||
});
|
||||
|
||||
if (isMusicOrBook) {
|
||||
existingQuery.andWhere('media.externalServiceId = :foreignId', {
|
||||
existingQuery.andWhere('media.foreignId = :foreignId', {
|
||||
foreignId: requestBody.foreignId,
|
||||
});
|
||||
} else {
|
||||
@@ -250,7 +250,8 @@ export class MediaRequest {
|
||||
}
|
||||
|
||||
// Apply overrides if the user is not an admin or has the "advanced request" permission
|
||||
const useOverrides = !user.hasPermission([Permission.MANAGE_REQUESTS], {
|
||||
// Override rules only apply to movie/TV (they use TMDB keywords and Radarr/Sonarr)
|
||||
const useOverrides = !isMusicOrBook && !user.hasPermission([Permission.MANAGE_REQUESTS], {
|
||||
type: 'or',
|
||||
});
|
||||
|
||||
@@ -258,7 +259,7 @@ export class MediaRequest {
|
||||
let profileId = requestBody.profileId;
|
||||
let tags = requestBody.tags;
|
||||
|
||||
if (useOverrides) {
|
||||
if (useOverrides && tmdbMedia) {
|
||||
const defaultRadarrId = requestBody.is4k
|
||||
? settings.radarr.findIndex((r) => r.is4k && r.isDefault)
|
||||
: settings.radarr.findIndex((r) => !r.is4k && r.isDefault);
|
||||
@@ -307,7 +308,7 @@ export class MediaRequest {
|
||||
.split(',')
|
||||
.some((genreId) =>
|
||||
tmdbMedia.genres
|
||||
.map((genre) => genre.id)
|
||||
.map((genre: { id: number }) => genre.id)
|
||||
.includes(Number(genreId))
|
||||
)
|
||||
) {
|
||||
@@ -377,6 +378,11 @@ export class MediaRequest {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle music/book requests via simplified flow
|
||||
if (isMusicOrBook) {
|
||||
return MediaRequest.requestMusicOrBook(requestBody, requestUser, options);
|
||||
}
|
||||
|
||||
if (requestBody.mediaType === MediaType.MOVIE) {
|
||||
await mediaRepository.save(media);
|
||||
|
||||
@@ -570,7 +576,7 @@ export class MediaRequest {
|
||||
|
||||
let media = await mediaRepository.findOne({
|
||||
where: {
|
||||
externalServiceId: requestBody.foreignId,
|
||||
foreignId: requestBody.foreignId,
|
||||
mediaType: requestBody.mediaType,
|
||||
},
|
||||
relations: ['requests'],
|
||||
@@ -580,8 +586,8 @@ export class MediaRequest {
|
||||
media = new Media({
|
||||
tmdbId: 0,
|
||||
tvdbId: 0,
|
||||
externalServiceId: requestBody.foreignId,
|
||||
externalServiceTitle: requestBody.foreignTitle,
|
||||
foreignId: requestBody.foreignId,
|
||||
foreignTitle: requestBody.foreignTitle,
|
||||
status: MediaStatus.PENDING,
|
||||
status4k: MediaStatus.UNKNOWN,
|
||||
mediaType: requestBody.mediaType,
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { PaginatedResponse } from '@server/interfaces/api/common';
|
||||
|
||||
export interface BlocklistItem {
|
||||
tmdbId: number;
|
||||
mediaType: 'movie' | 'tv';
|
||||
mediaType: 'movie' | 'tv' | 'music' | 'book';
|
||||
title?: string;
|
||||
createdAt?: Date;
|
||||
user?: User;
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface WatchlistItem {
|
||||
id: number;
|
||||
ratingKey: string;
|
||||
tmdbId: number;
|
||||
mediaType: 'movie' | 'tv';
|
||||
mediaType: 'movie' | 'tv' | 'music' | 'book';
|
||||
title: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ export interface QuotaStatus {
|
||||
export interface QuotaResponse {
|
||||
movie: QuotaStatus;
|
||||
tv: QuotaStatus;
|
||||
music?: QuotaStatus;
|
||||
book?: QuotaStatus;
|
||||
}
|
||||
|
||||
export interface UserWatchDataResponse {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useIntl } from 'react-intl';
|
||||
|
||||
interface BlocklistModalProps {
|
||||
tmdbId: number;
|
||||
type: 'movie' | 'tv' | 'collection';
|
||||
type: 'movie' | 'tv' | 'music' | 'book' | 'collection';
|
||||
show: boolean;
|
||||
onComplete?: () => void;
|
||||
onCancel?: () => void;
|
||||
|
||||
@@ -56,7 +56,7 @@ const ListView = ({
|
||||
<TmdbTitleCard
|
||||
id={title.tmdbId}
|
||||
tmdbId={title.tmdbId}
|
||||
type={title.mediaType}
|
||||
type={title.mediaType as 'movie' | 'tv'}
|
||||
isAddedToWatchlist={true}
|
||||
canExpand
|
||||
mutateParent={mutateParent}
|
||||
|
||||
@@ -50,7 +50,7 @@ const messages = defineMessages('components.Discover.FilterSlideover', {
|
||||
type FilterSlideoverProps = {
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
type: 'movie' | 'tv';
|
||||
type: 'movie' | 'tv' | 'music' | 'book';
|
||||
currentFilters: FilterOptions;
|
||||
};
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ const PlexWatchlistSlider = () => {
|
||||
id={item.tmdbId}
|
||||
key={`watchlist-slider-item-${item.ratingKey}`}
|
||||
tmdbId={item.tmdbId}
|
||||
type={item.mediaType}
|
||||
type={item.mediaType as 'movie' | 'tv'}
|
||||
isAddedToWatchlist={true}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -43,7 +43,7 @@ const RecentlyAddedSlider = () => {
|
||||
id={item.id}
|
||||
tmdbId={item.tmdbId}
|
||||
tvdbId={item.tvdbId}
|
||||
type={item.mediaType}
|
||||
type={item.mediaType as 'movie' | 'tv'}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
|
||||
@@ -48,7 +48,7 @@ const classNames = (...classes: string[]) => {
|
||||
};
|
||||
|
||||
interface CreateIssueModalProps {
|
||||
mediaType: 'movie' | 'tv';
|
||||
mediaType: 'movie' | 'tv' | 'music' | 'book';
|
||||
tmdbId?: number;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Transition } from '@headlessui/react';
|
||||
interface IssueModalProps {
|
||||
show?: boolean;
|
||||
onCancel: () => void;
|
||||
mediaType: 'movie' | 'tv';
|
||||
mediaType: 'movie' | 'tv' | 'music' | 'book';
|
||||
tmdbId: number;
|
||||
issueId?: never;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => {
|
||||
};
|
||||
|
||||
interface ManageSlideOverProps {
|
||||
// mediaType: 'movie' | 'tv';
|
||||
// mediaType: 'movie' | 'tv' | 'music' | 'book';
|
||||
show?: boolean;
|
||||
onClose: () => void;
|
||||
revalidate: () => void;
|
||||
|
||||
@@ -14,7 +14,7 @@ const messages = defineMessages('components.QuotaSelector', {
|
||||
});
|
||||
|
||||
interface QuotaSelectorProps {
|
||||
mediaType: 'movie' | 'tv';
|
||||
mediaType: 'movie' | 'tv' | 'music' | 'book';
|
||||
defaultDays?: number;
|
||||
defaultLimit?: number;
|
||||
dayOverride?: number;
|
||||
|
||||
@@ -45,7 +45,7 @@ interface ButtonOption {
|
||||
}
|
||||
|
||||
interface RequestButtonProps {
|
||||
mediaType: 'movie' | 'tv';
|
||||
mediaType: 'movie' | 'tv' | 'music' | 'book';
|
||||
onUpdate: () => void;
|
||||
tmdbId: number;
|
||||
media?: Media;
|
||||
|
||||
@@ -51,7 +51,7 @@ type Sort = 'added' | 'modified';
|
||||
|
||||
type SortDirection = 'asc' | 'desc';
|
||||
|
||||
type MediaType = 'all' | 'movie' | 'tv';
|
||||
type MediaType = 'all' | 'movie' | 'tv' | 'music' | 'book';
|
||||
|
||||
const RequestList = () => {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -50,7 +50,7 @@ export type RequestOverrides = {
|
||||
};
|
||||
|
||||
interface AdvancedRequesterProps {
|
||||
type: 'movie' | 'tv';
|
||||
type: 'movie' | 'tv' | 'music' | 'book';
|
||||
is4k: boolean;
|
||||
isAnime?: boolean;
|
||||
defaultOverrides?: RequestOverrides;
|
||||
|
||||
@@ -30,7 +30,7 @@ const messages = defineMessages('components.RequestModal.QuotaDisplay', {
|
||||
|
||||
interface QuotaDisplayProps {
|
||||
quota?: QuotaStatus;
|
||||
mediaType: 'movie' | 'tv';
|
||||
mediaType: 'movie' | 'tv' | 'music' | 'book';
|
||||
userOverride?: number | null;
|
||||
remaining?: number;
|
||||
overLimit?: number;
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { NonFunctionProperties } from '@server/interfaces/api/common';
|
||||
|
||||
interface RequestModalProps {
|
||||
show: boolean;
|
||||
type: 'movie' | 'tv' | 'collection';
|
||||
type: 'movie' | 'tv' | 'music' | 'book' | 'collection';
|
||||
tmdbId: number;
|
||||
is4k?: boolean;
|
||||
editRequest?: NonFunctionProperties<MediaRequest>;
|
||||
|
||||
@@ -144,7 +144,7 @@ export const CompanySelector = ({
|
||||
};
|
||||
|
||||
type GenreSelectorProps = (BaseSelectorMultiProps | BaseSelectorSingleProps) & {
|
||||
type: 'movie' | 'tv';
|
||||
type: 'movie' | 'tv' | 'music' | 'book';
|
||||
};
|
||||
|
||||
export const GenreSelector = ({
|
||||
@@ -369,7 +369,7 @@ export const KeywordSelector = ({
|
||||
};
|
||||
|
||||
type WatchProviderSelectorProps = {
|
||||
type: 'movie' | 'tv';
|
||||
type: 'movie' | 'tv' | 'music' | 'book';
|
||||
region?: string;
|
||||
activeProviders?: number[];
|
||||
onChange: (region: string, value: number[]) => void;
|
||||
|
||||
@@ -10,7 +10,7 @@ interface ErrorCardProps {
|
||||
id: number;
|
||||
tmdbId: number;
|
||||
tvdbId?: number;
|
||||
type: 'movie' | 'tv';
|
||||
type: 'movie' | 'tv' | 'music' | 'book';
|
||||
canExpand?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface TmdbTitleCardProps {
|
||||
id: number;
|
||||
tmdbId: number;
|
||||
tvdbId?: number;
|
||||
type: 'movie' | 'tv';
|
||||
type: 'movie' | 'tv' | 'music' | 'book';
|
||||
canExpand?: boolean;
|
||||
isAddedToWatchlist?: boolean;
|
||||
mutateParent?: () => void;
|
||||
|
||||
@@ -369,7 +369,7 @@ const UserProfile = () => {
|
||||
id={item.tmdbId}
|
||||
key={`watchlist-slider-item-${item.ratingKey}`}
|
||||
tmdbId={item.tmdbId}
|
||||
type={item.mediaType}
|
||||
type={item.mediaType as 'movie' | 'tv'}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
@@ -395,7 +395,7 @@ const UserProfile = () => {
|
||||
id={item.id}
|
||||
tmdbId={item.tmdbId}
|
||||
tvdbId={item.tvdbId}
|
||||
type={item.mediaType}
|
||||
type={item.mediaType as 'movie' | 'tv'}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user