mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
@@ -1975,8 +1975,8 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
example: job-name
|
example: job-name
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
enum: [process, command]
|
enum: [process, command]
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
example: A Job Name
|
example: A Job Name
|
||||||
@@ -1984,8 +1984,8 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
example: '2020-09-02T05:02:23.000Z'
|
example: '2020-09-02T05:02:23.000Z'
|
||||||
running:
|
running:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
/settings/jobs/{jobId}/cancel:
|
/settings/jobs/{jobId}/cancel:
|
||||||
get:
|
get:
|
||||||
summary: Cancel a specific job
|
summary: Cancel a specific job
|
||||||
@@ -2010,8 +2010,8 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
example: job-name
|
example: job-name
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
enum: [process, command]
|
enum: [process, command]
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
example: A Job Name
|
example: A Job Name
|
||||||
@@ -2019,8 +2019,8 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
example: '2020-09-02T05:02:23.000Z'
|
example: '2020-09-02T05:02:23.000Z'
|
||||||
running:
|
running:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
/settings/notifications:
|
/settings/notifications:
|
||||||
get:
|
get:
|
||||||
summary: Return notification settings
|
summary: Return notification settings
|
||||||
@@ -3532,6 +3532,41 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: Succesfully removed media item
|
description: Succesfully removed media item
|
||||||
|
/media/{mediaId}/{status}:
|
||||||
|
get:
|
||||||
|
summary: Update media status
|
||||||
|
description: Updates a medias status and returns the media in JSON format
|
||||||
|
tags:
|
||||||
|
- media
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: mediaId
|
||||||
|
description: Media ID
|
||||||
|
required: true
|
||||||
|
example: 1
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: path
|
||||||
|
name: status
|
||||||
|
description: New status
|
||||||
|
required: true
|
||||||
|
example: available
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum: [available, partial, processing, pending, unknown]
|
||||||
|
- in: query
|
||||||
|
name: is4k
|
||||||
|
description: 4K Status
|
||||||
|
example: false
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Returned media
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MediaInfo'
|
||||||
/collection/{collectionId}:
|
/collection/{collectionId}:
|
||||||
get:
|
get:
|
||||||
summary: Get collection details
|
summary: Get collection details
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { getRepository, FindOperator, FindOneOptions, In } from 'typeorm';
|
import { getRepository, FindOperator, FindOneOptions, In } from 'typeorm';
|
||||||
import Media from '../entity/Media';
|
import Media from '../entity/Media';
|
||||||
import { MediaStatus } from '../constants/media';
|
import { MediaStatus, MediaType } from '../constants/media';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { isAuthenticated } from '../middleware/auth';
|
import { isAuthenticated } from '../middleware/auth';
|
||||||
import { Permission } from '../lib/permissions';
|
import { Permission } from '../lib/permissions';
|
||||||
@@ -82,6 +82,63 @@ mediaRoutes.get('/', async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mediaRoutes.get<
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
status: 'available' | 'partial' | 'processing' | 'pending' | 'unknown';
|
||||||
|
},
|
||||||
|
Media
|
||||||
|
>(
|
||||||
|
'/:id/:status',
|
||||||
|
isAuthenticated(Permission.MANAGE_REQUESTS),
|
||||||
|
async (req, res, next) => {
|
||||||
|
const mediaRepository = getRepository(Media);
|
||||||
|
|
||||||
|
const media = await mediaRepository.findOne({
|
||||||
|
where: { id: Number(req.params.id) },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!media) {
|
||||||
|
return next({ status: 404, message: 'Media does not exist.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const is4k = Boolean(req.query.is4k);
|
||||||
|
|
||||||
|
switch (req.params.status) {
|
||||||
|
case 'available':
|
||||||
|
media[is4k ? 'status4k' : 'status'] = MediaStatus.AVAILABLE;
|
||||||
|
if (media.mediaType === MediaType.TV) {
|
||||||
|
// Mark all seasons available
|
||||||
|
media.seasons.forEach((season) => {
|
||||||
|
season[is4k ? 'status4k' : 'status'] = MediaStatus.AVAILABLE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'partial':
|
||||||
|
if (media.mediaType === MediaType.MOVIE) {
|
||||||
|
return next({
|
||||||
|
status: 400,
|
||||||
|
message: 'Only series can be set to be partially available',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
media.status = MediaStatus.PARTIALLY_AVAILABLE;
|
||||||
|
break;
|
||||||
|
case 'processing':
|
||||||
|
media.status = MediaStatus.PROCESSING;
|
||||||
|
break;
|
||||||
|
case 'pending':
|
||||||
|
media.status = MediaStatus.PENDING;
|
||||||
|
break;
|
||||||
|
case 'unknown':
|
||||||
|
media.status = MediaStatus.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
await mediaRepository.save(media);
|
||||||
|
|
||||||
|
return res.status(200).json(media);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
mediaRoutes.delete(
|
mediaRoutes.delete(
|
||||||
'/:id',
|
'/:id',
|
||||||
isAuthenticated(Permission.MANAGE_REQUESTS),
|
isAuthenticated(Permission.MANAGE_REQUESTS),
|
||||||
|
@@ -72,6 +72,8 @@ const messages = defineMessages({
|
|||||||
downloadstatus: 'Download Status',
|
downloadstatus: 'Download Status',
|
||||||
playonplex: 'Play on Plex',
|
playonplex: 'Play on Plex',
|
||||||
play4konplex: 'Play 4K on Plex',
|
play4konplex: 'Play 4K on Plex',
|
||||||
|
markavailable: 'Mark as Available',
|
||||||
|
mark4kavailable: 'Mark 4K as Available',
|
||||||
});
|
});
|
||||||
|
|
||||||
interface MovieDetailsProps {
|
interface MovieDetailsProps {
|
||||||
@@ -118,6 +120,15 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const markAvailable = async (is4k = false) => {
|
||||||
|
await axios.get(`/api/v1/media/${data?.mediaInfo?.id}/available`, {
|
||||||
|
params: {
|
||||||
|
is4k,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
revalidate();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="px-4 pt-4 -mx-4 -mt-2 bg-center bg-cover"
|
className="px-4 pt-4 -mx-4 -mt-2 bg-center bg-cover"
|
||||||
@@ -156,6 +167,56 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{data?.mediaInfo &&
|
||||||
|
(data.mediaInfo.status !== MediaStatus.AVAILABLE ||
|
||||||
|
data.mediaInfo.status4k !== MediaStatus.AVAILABLE) && (
|
||||||
|
<div className="flex flex-col mb-6 sm:flex-row flex-nowrap">
|
||||||
|
{data?.mediaInfo &&
|
||||||
|
data?.mediaInfo.status !== MediaStatus.AVAILABLE && (
|
||||||
|
<Button
|
||||||
|
onClick={() => markAvailable()}
|
||||||
|
className="w-full mb-2 sm:mb-0 sm:mr-1 last:mr-0"
|
||||||
|
buttonType="success"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5 mr-1"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>{intl.formatMessage(messages.markavailable)}</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{data?.mediaInfo &&
|
||||||
|
data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && (
|
||||||
|
<Button
|
||||||
|
onClick={() => markAvailable(true)}
|
||||||
|
className="w-full sm:ml-1 first:ml-0"
|
||||||
|
buttonType="success"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5 mr-1"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>{intl.formatMessage(messages.mark4kavailable)}</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<h3 className="mb-2 text-xl">
|
<h3 className="mb-2 text-xl">
|
||||||
{intl.formatMessage(messages.manageModalRequests)}
|
{intl.formatMessage(messages.manageModalRequests)}
|
||||||
</h3>
|
</h3>
|
||||||
|
@@ -72,6 +72,9 @@ const messages = defineMessages({
|
|||||||
downloadstatus: 'Download Status',
|
downloadstatus: 'Download Status',
|
||||||
playonplex: 'Play on Plex',
|
playonplex: 'Play on Plex',
|
||||||
play4konplex: 'Play 4K on Plex',
|
play4konplex: 'Play 4K on Plex',
|
||||||
|
markavailable: 'Mark as Available',
|
||||||
|
mark4kavailable: 'Mark 4K as Available',
|
||||||
|
allseasonsmarkedavailable: '* All seasons will be marked as available.',
|
||||||
});
|
});
|
||||||
|
|
||||||
interface TvDetailsProps {
|
interface TvDetailsProps {
|
||||||
@@ -120,6 +123,15 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const markAvailable = async (is4k = false) => {
|
||||||
|
await axios.get(`/api/v1/media/${data?.mediaInfo?.id}/available`, {
|
||||||
|
params: {
|
||||||
|
is4k,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
revalidate();
|
||||||
|
};
|
||||||
|
|
||||||
const isComplete =
|
const isComplete =
|
||||||
data.seasons.filter((season) => season.seasonNumber !== 0).length <=
|
data.seasons.filter((season) => season.seasonNumber !== 0).length <=
|
||||||
(
|
(
|
||||||
@@ -183,6 +195,63 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{data?.mediaInfo &&
|
||||||
|
(data.mediaInfo.status !== MediaStatus.AVAILABLE ||
|
||||||
|
data.mediaInfo.status4k !== MediaStatus.AVAILABLE) && (
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="flex flex-col sm:flex-row flex-nowrap">
|
||||||
|
{data?.mediaInfo &&
|
||||||
|
data?.mediaInfo.status !== MediaStatus.AVAILABLE && (
|
||||||
|
<Button
|
||||||
|
onClick={() => markAvailable()}
|
||||||
|
className="w-full mb-2 sm:mb-0 sm:mr-1 last:mr-0"
|
||||||
|
buttonType="success"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5 mr-1"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>{intl.formatMessage(messages.markavailable)}</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{data?.mediaInfo &&
|
||||||
|
data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && (
|
||||||
|
<Button
|
||||||
|
onClick={() => markAvailable(true)}
|
||||||
|
className="w-full sm:ml-1 first:ml-0"
|
||||||
|
buttonType="success"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5 mr-1"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(messages.mark4kavailable)}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 text-xs text-gray-300">
|
||||||
|
{intl.formatMessage(messages.allseasonsmarkedavailable)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<h3 className="mb-2 text-xl">
|
<h3 className="mb-2 text-xl">
|
||||||
{intl.formatMessage(messages.manageModalRequests)}
|
{intl.formatMessage(messages.manageModalRequests)}
|
||||||
</h3>
|
</h3>
|
||||||
|
@@ -53,6 +53,8 @@
|
|||||||
"components.MovieDetails.manageModalNoRequests": "No Requests",
|
"components.MovieDetails.manageModalNoRequests": "No Requests",
|
||||||
"components.MovieDetails.manageModalRequests": "Requests",
|
"components.MovieDetails.manageModalRequests": "Requests",
|
||||||
"components.MovieDetails.manageModalTitle": "Manage Movie",
|
"components.MovieDetails.manageModalTitle": "Manage Movie",
|
||||||
|
"components.MovieDetails.mark4kavailable": "Mark 4K as Available",
|
||||||
|
"components.MovieDetails.markavailable": "Mark as Available",
|
||||||
"components.MovieDetails.openradarr": "Open Movie in Radarr",
|
"components.MovieDetails.openradarr": "Open Movie in Radarr",
|
||||||
"components.MovieDetails.openradarr4k": "Open Movie in 4K Radarr",
|
"components.MovieDetails.openradarr4k": "Open Movie in 4K Radarr",
|
||||||
"components.MovieDetails.originallanguage": "Original Language",
|
"components.MovieDetails.originallanguage": "Original Language",
|
||||||
@@ -494,6 +496,7 @@
|
|||||||
"components.TitleCard.tvshow": "Series",
|
"components.TitleCard.tvshow": "Series",
|
||||||
"components.TvDetails.TvCast.fullseriescast": "Full Series Cast",
|
"components.TvDetails.TvCast.fullseriescast": "Full Series Cast",
|
||||||
"components.TvDetails.TvCrew.fullseriescrew": "Full Series Crew",
|
"components.TvDetails.TvCrew.fullseriescrew": "Full Series Crew",
|
||||||
|
"components.TvDetails.allseasonsmarkedavailable": "* All seasons will be marked as available.",
|
||||||
"components.TvDetails.anime": "Anime",
|
"components.TvDetails.anime": "Anime",
|
||||||
"components.TvDetails.approve": "Approve",
|
"components.TvDetails.approve": "Approve",
|
||||||
"components.TvDetails.areyousure": "Are you sure?",
|
"components.TvDetails.areyousure": "Are you sure?",
|
||||||
@@ -508,6 +511,8 @@
|
|||||||
"components.TvDetails.manageModalNoRequests": "No Requests",
|
"components.TvDetails.manageModalNoRequests": "No Requests",
|
||||||
"components.TvDetails.manageModalRequests": "Requests",
|
"components.TvDetails.manageModalRequests": "Requests",
|
||||||
"components.TvDetails.manageModalTitle": "Manage Series",
|
"components.TvDetails.manageModalTitle": "Manage Series",
|
||||||
|
"components.TvDetails.mark4kavailable": "Mark 4K as Available",
|
||||||
|
"components.TvDetails.markavailable": "Mark as Available",
|
||||||
"components.TvDetails.network": "Network",
|
"components.TvDetails.network": "Network",
|
||||||
"components.TvDetails.opensonarr": "Open Series in Sonarr",
|
"components.TvDetails.opensonarr": "Open Series in Sonarr",
|
||||||
"components.TvDetails.opensonarr4k": "Open Series in 4K Sonarr",
|
"components.TvDetails.opensonarr4k": "Open Series in 4K Sonarr",
|
||||||
|
Reference in New Issue
Block a user