From 2da0da826ae1d73467bc8a671fda7cc5ca1f14c9 Mon Sep 17 00:00:00 2001 From: Jakob Ankarhem Date: Fri, 22 Jan 2021 02:49:17 +0100 Subject: [PATCH] fix(requests): handle when tvdbid is null (#657) Co-authored-by: sct --- overseerr-api.yml | 151 ++++++++++++++++++ public/images/overseerr_poster_not_found.png | Bin 0 -> 15944 bytes ...overseerr_poster_not_found_logo_center.png | Bin 0 -> 11106 bytes .../overseerr_poster_not_found_logo_top.png | Bin 0 -> 11169 bytes server/api/sonarr.ts | 22 +++ server/entity/MediaRequest.ts | 24 ++- server/routes/request.ts | 2 +- server/routes/service.ts | 54 ++++++- src/components/MovieDetails/index.tsx | 6 +- .../RequestModal/MovieRequestModal.tsx | 112 +++++++------ .../RequestModal/SearchByNameModal/index.tsx | 114 +++++++++++++ .../RequestModal/TvRequestModal.tsx | 104 ++++++++---- src/components/TitleCard/index.tsx | 4 +- src/i18n/locale/en.json | 9 ++ 14 files changed, 508 insertions(+), 94 deletions(-) create mode 100644 public/images/overseerr_poster_not_found.png create mode 100644 public/images/overseerr_poster_not_found_logo_center.png create mode 100644 public/images/overseerr_poster_not_found_logo_top.png create mode 100644 src/components/RequestModal/SearchByNameModal/index.tsx diff --git a/overseerr-api.yml b/overseerr-api.yml index 32e505602..c85c1a083 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1143,6 +1143,135 @@ components: type: array items: $ref: '#/components/schemas/MovieResult' + SonarrSeries: + type: object + properties: + title: + type: string + example: COVID-25 + sortTitle: + type: string + example: covid 25 + seasonCount: + type: number + example: 1 + status: + type: string + example: upcoming + overview: + type: string + example: The thread is picked up again by Marianne Schmidt which ... + network: + type: string + example: CBS + airTime: + type: string + example: 02:15 + images: + type: array + items: + type: object + properties: + coverType: + type: string + example: banner + url: + type: string + example: /sonarr/MediaCoverProxy/6467f05d9872726ad08cbf920e5fee4bf69198682260acab8eab5d3c2c958e92/5c8f116c6aa5c.jpg + remotePoster: + type: string + example: https://artworks.thetvdb.com/banners/posters/5c8f116129983.jpg + seasons: + type: array + items: + type: object + properties: + seasonNumber: + type: number + example: 1 + monitored: + type: boolean + example: true + year: + type: number + example: 2015 + path: + type: string + profileId: + type: number + languageProfileId: + type: number + seasonFolder: + type: boolean + monitored: + type: boolean + useSceneNumbering: + type: boolean + runtime: + type: number + tvdbId: + type: number + example: 12345 + tvRageId: + type: number + tvMazeId: + type: number + firstAired: + type: string + lastInfoSync: + type: string + nullable: true + seriesType: + type: string + cleanTitle: + type: string + imdbId: + type: string + titleSlug: + type: string + certification: + type: string + genres: + type: array + items: + type: string + tags: + type: array + items: + type: string + added: + type: string + ratings: + type: array + items: + type: object + properties: + votes: + type: number + value: + type: number + qualityProfileId: + type: number + id: + type: number + nullable: true + rootFolderPath: + type: string + nullable: true + addOptions: + type: array + items: + type: object + properties: + ignoreEpisodesWithFiles: + type: boolean + nullable: true + ignoreEpisodesWithoutFiles: + type: boolean + nullable: true + searchForMissingEpisodes: + type: boolean + nullable: true securitySchemes: cookieAuth: type: apiKey @@ -3193,6 +3322,28 @@ paths: $ref: '#/components/schemas/SonarrSettings' profiles: $ref: '#/components/schemas/ServiceProfile' + /service/sonarr/lookup/{tmdbId}: + get: + summary: Returns a list of series from sonarr + description: Returns a list of series returned by searching for the name in sonarr + tags: + - service + parameters: + - in: path + name: tmdbId + required: true + schema: + type: number + example: 0 + responses: + '200': + description: Request successful + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/SonarrSeries' security: - cookieAuth: [] diff --git a/public/images/overseerr_poster_not_found.png b/public/images/overseerr_poster_not_found.png new file mode 100644 index 0000000000000000000000000000000000000000..2f5bc203d883bbc6ebf8c240e87c9612e7ad7156 GIT binary patch literal 15944 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 MLQ%X{)F|lx09i;`*Z=?k literal 0 HcmV?d00001 diff --git a/public/images/overseerr_poster_not_found_logo_top.png b/public/images/overseerr_poster_not_found_logo_top.png new file mode 100644 index 0000000000000000000000000000000000000000..a74b096e1f908304ea724bb5f49cd53c3ca403c1 GIT binary patch literal 11169 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 R&-t&gq?nv&)pz~C{{reR1SbFh literal 0 HcmV?d00001 diff --git a/server/api/sonarr.ts b/server/api/sonarr.ts index e3cd23534..6c8cd9af3 100644 --- a/server/api/sonarr.ts +++ b/server/api/sonarr.ts @@ -94,6 +94,28 @@ class SonarrAPI { }); } + public async getSeriesByTitle(title: string): Promise { + try { + const response = await this.axios.get('/series/lookup', { + params: { + term: title, + }, + }); + + if (!response.data[0]) { + throw new Error('No series found'); + } + + return response.data; + } catch (e) { + logger.error('Error retrieving series by series title', { + label: 'Sonarr API', + message: e.message, + }); + throw new Error('No series found'); + } + } + public async getSeriesByTvdbId(id: number): Promise { try { const response = await this.axios.get('/series/lookup', { diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index 3b64f4727..0e9ca8cac 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -427,9 +427,12 @@ export class MediaRequest { }); logger.info('Sent request to Radarr', { label: 'Media Request' }); } catch (e) { - throw new Error( - `[MediaRequest] Request failed to send to radarr: ${e.message}` - ); + const errorMessage = `Request failed to send to radarr: ${e.message}`; + logger.error('Request failed to send to Radarr', { + label: 'Media Request', + errorMessage, + }); + throw new Error(errorMessage); } } } @@ -501,8 +504,10 @@ export class MediaRequest { }:${sonarrSettings.port}${sonarrSettings.baseUrl ?? ''}/api`, }); const series = await tmdb.getTvShow({ tvId: media.tmdbId }); + const tvdbId = series.external_ids.tvdb_id ?? media.tvdbId; - if (!series.external_ids.tvdb_id) { + if (!tvdbId) { + this.handleRemoveParentUpdate(); throw new Error('Series was missing tvdb id'); } @@ -550,7 +555,7 @@ export class MediaRequest { profileId: qualityProfile, rootFolderPath: rootFolder, title: series.name, - tvdbid: series.external_ids.tvdb_id, + tvdbid: tvdbId, seasons: this.seasons.map((season) => season.seasonNumber), seasonFolder: sonarrSettings.enableSeasonFolders, seriesType, @@ -590,9 +595,12 @@ export class MediaRequest { }); logger.info('Sent request to Sonarr', { label: 'Media Request' }); } catch (e) { - throw new Error( - `[MediaRequest] Request failed to send to sonarr: ${e.message}` - ); + const errorMessage = `Request failed to send to sonarr: ${e.message}`; + logger.error('Request failed to send to Sonarr', { + label: 'Media Request', + errorMessage, + }); + throw new Error(errorMessage); } } } diff --git a/server/routes/request.ts b/server/routes/request.ts index 4750be2da..2a5e7c415 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -109,7 +109,7 @@ requestRoutes.post( if (!media) { media = new Media({ tmdbId: tmdbMedia.id, - tvdbId: tmdbMedia.external_ids.tvdb_id, + tvdbId: req.body.tvdbId ?? tmdbMedia.external_ids.tvdb_id, status: !req.body.is4k ? MediaStatus.PENDING : MediaStatus.UNKNOWN, status4k: req.body.is4k ? MediaStatus.PENDING : MediaStatus.UNKNOWN, mediaType: req.body.mediaType, diff --git a/server/routes/service.ts b/server/routes/service.ts index c163a9403..94b2bc727 100644 --- a/server/routes/service.ts +++ b/server/routes/service.ts @@ -6,6 +6,8 @@ import { ServiceCommonServerWithDetails, } from '../interfaces/api/serviceInterfaces'; import { getSettings } from '../lib/settings'; +import TheMovieDb from '../api/themoviedb'; +import logger from '../logger'; const serviceRoutes = Router(); @@ -100,13 +102,13 @@ serviceRoutes.get<{ sonarrId: string }>( const settings = getSettings(); const sonarrSettings = settings.sonarr.find( - (radarr) => radarr.id === Number(req.params.sonarrId) + (sonarr) => sonarr.id === Number(req.params.sonarrId) ); if (!sonarrSettings) { return next({ status: 404, - message: 'Radarr server with provided ID does not exist.', + message: 'Sonarr server with provided ID does not exist.', }); } @@ -145,4 +147,52 @@ serviceRoutes.get<{ sonarrId: string }>( } ); +serviceRoutes.get<{ tmdbId: string }>( + '/sonarr/lookup/:tmdbId', + async (req, res, next) => { + const settings = getSettings(); + const tmdb = new TheMovieDb(); + + const sonarrSettings = settings.sonarr[0]; + + if (!sonarrSettings) { + logger.error('No sonarr server has been setup', { + label: 'Media Request', + }); + return next({ + status: 404, + message: 'No sonarr server has been setup', + }); + } + + const sonarr = new SonarrAPI({ + apiKey: sonarrSettings.apiKey, + url: `${sonarrSettings.useSsl ? 'https' : 'http'}://${ + sonarrSettings.hostname + }:${sonarrSettings.port}${sonarrSettings.baseUrl ?? ''}/api`, + }); + + try { + const tv = await tmdb.getTvShow({ + tvId: Number(req.params.tmdbId), + language: req.query.language as string, + }); + + const response = await sonarr.getSeriesByTitle(tv.name); + + return res.status(200).json(response); + } catch (e) { + logger.error('Failed to fetch tvdb search results', { + label: 'Media Request', + message: e.message, + }); + + return next({ + status: 500, + message: 'Something went wrong trying to fetch series information', + }); + } + } +); + export default serviceRoutes; diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 22c683681..f87069d1a 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -165,7 +165,11 @@ const MovieDetails: React.FC = ({ movie }) => {
diff --git a/src/components/RequestModal/MovieRequestModal.tsx b/src/components/RequestModal/MovieRequestModal.tsx index fd62b6972..0aa51d62b 100644 --- a/src/components/RequestModal/MovieRequestModal.tsx +++ b/src/components/RequestModal/MovieRequestModal.tsx @@ -39,6 +39,7 @@ const messages = defineMessages({ errorediting: 'Something went wrong editing the request.', requestedited: 'Request edited.', autoapproval: 'Auto Approval', + requesterror: 'Something went wrong when trying to request media.', }); interface RequestModalProps extends React.HTMLAttributes { @@ -78,41 +79,50 @@ const MovieRequestModal: React.FC = ({ const sendRequest = useCallback(async () => { setIsUpdating(true); - let overrideParams = {}; - if (requestOverrides) { - overrideParams = { - serverId: requestOverrides.server, - profileId: requestOverrides.profile, - rootFolder: requestOverrides.folder, - }; - } - const response = await axios.post('/api/v1/request', { - mediaId: data?.id, - mediaType: 'movie', - is4k, - ...overrideParams, - }); - if (response.data) { - if (onComplete) { - onComplete( - hasPermission(Permission.AUTO_APPROVE) || - hasPermission(Permission.AUTO_APPROVE_MOVIE) - ? MediaStatus.PROCESSING - : MediaStatus.PENDING + try { + let overrideParams = {}; + if (requestOverrides) { + overrideParams = { + serverId: requestOverrides.server, + profileId: requestOverrides.profile, + rootFolder: requestOverrides.folder, + }; + } + const response = await axios.post('/api/v1/request', { + mediaId: data?.id, + mediaType: 'movie', + is4k, + ...overrideParams, + }); + + if (response.data) { + if (onComplete) { + onComplete( + hasPermission(Permission.AUTO_APPROVE) || + hasPermission(Permission.AUTO_APPROVE_MOVIE) + ? MediaStatus.PROCESSING + : MediaStatus.PENDING + ); + } + addToast( + + {intl.formatMessage(messages.requestSuccess, { + title: data?.title, + strong: function strong(msg) { + return {msg}; + }, + })} + , + { appearance: 'success', autoDismiss: true } ); } - addToast( - - {intl.formatMessage(messages.requestSuccess, { - title: data?.title, - strong: function strong(msg) { - return {msg}; - }, - })} - , - { appearance: 'success', autoDismiss: true } - ); + } catch (e) { + addToast(intl.formatMessage(messages.requesterror), { + appearance: 'error', + autoDismiss: true, + }); + } finally { setIsUpdating(false); } }, [data, onComplete, addToast, requestOverrides]); @@ -123,25 +133,29 @@ const MovieRequestModal: React.FC = ({ const cancelRequest = async () => { setIsUpdating(true); - const response = await axios.delete( - `/api/v1/request/${activeRequest?.id}` - ); - if (response.status === 204) { - if (onComplete) { - onComplete(MediaStatus.UNKNOWN); - } - addToast( - - {intl.formatMessage(messages.requestCancel, { - title: data?.title, - strong: function strong(msg) { - return {msg}; - }, - })} - , - { appearance: 'success', autoDismiss: true } + try { + const response = await axios.delete( + `/api/v1/request/${activeRequest?.id}` ); + + if (response.status === 204) { + if (onComplete) { + onComplete(MediaStatus.UNKNOWN); + } + addToast( + + {intl.formatMessage(messages.requestCancel, { + title: data?.title, + strong: function strong(msg) { + return {msg}; + }, + })} + , + { appearance: 'success', autoDismiss: true } + ); + } + } catch (e) { setIsUpdating(false); } }; diff --git a/src/components/RequestModal/SearchByNameModal/index.tsx b/src/components/RequestModal/SearchByNameModal/index.tsx new file mode 100644 index 000000000..a7e5f25b8 --- /dev/null +++ b/src/components/RequestModal/SearchByNameModal/index.tsx @@ -0,0 +1,114 @@ +import React from 'react'; +import Alert from '../../Common/Alert'; +import Modal from '../../Common/Modal'; +import { SmallLoadingSpinner } from '../../Common/LoadingSpinner'; +import useSWR from 'swr'; +import { defineMessages, useIntl } from 'react-intl'; +import { SonarrSeries } from '../../../../server/api/sonarr'; + +const messages = defineMessages({ + next: 'Next', + notvdbid: 'Manual match required', + notvdbiddescription: + "We couldn't automatically match your request. Please select the correct match from the list below.", + nosummary: 'No summary for this title was found.', +}); + +interface SearchByNameModalProps { + setTvdbId: (id: number) => void; + tvdbId: number | undefined; + loading: boolean; + onCancel?: () => void; + closeModal: () => void; + modalTitle: string; + tmdbId: number; +} + +const SearchByNameModal: React.FC = ({ + setTvdbId, + tvdbId, + loading, + onCancel, + closeModal, + modalTitle, + tmdbId, +}) => { + const intl = useIntl(); + const { data, error } = useSWR( + `/api/v1/service/sonarr/lookup/${tmdbId}` + ); + + const handleClick = (tvdbId: number) => { + setTvdbId(tvdbId); + }; + + return ( + + + + } + > + + {intl.formatMessage(messages.notvdbiddescription)} + + {!data && !error && } +
+ {data?.slice(0, 6).map((item) => ( + + ))} +
+
+ ); +}; + +export default SearchByNameModal; diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index f16dbb8e1..896746a9a 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -18,6 +18,7 @@ import globalMessages from '../../i18n/globalMessages'; import SeasonRequest from '../../../server/entity/SeasonRequest'; import Alert from '../Common/Alert'; import AdvancedRequester, { RequestOverrides } from './AdvancedRequester'; +import SearchByNameModal from './SearchByNameModal'; const messages = defineMessages({ requestadmin: 'Your request will be immediately approved.', @@ -40,6 +41,12 @@ const messages = defineMessages({ requestedited: 'Request edited.', requestcancelled: 'Request cancelled.', autoapproval: 'Auto Approval', + requesterror: 'Something went wrong when trying to request media.', + next: 'Next', + notvdbid: 'No TVDB id was found connected on TMDB', + notvdbiddescription: + 'Either add the TVDB id to TMDB and come back later, or select the correct match below.', + backbutton: 'Back', }); interface RequestModalProps extends React.HTMLAttributes { @@ -73,6 +80,12 @@ const TvRequestModal: React.FC = ({ ); const intl = useIntl(); const { hasPermission } = useUser(); + const [searchModal, setSearchModal] = useState<{ + show: boolean; + }>({ + show: true, + }); + const [tvdbId, setTvdbId] = useState(undefined); const updateRequest = async () => { if (!editRequest) { @@ -129,38 +142,47 @@ const TvRequestModal: React.FC = ({ if (onUpdating) { onUpdating(true); } - let overrideParams = {}; - if (requestOverrides) { - overrideParams = { - serverId: requestOverrides.server, - profileId: requestOverrides.profile, - rootFolder: requestOverrides.folder, - }; - } - const response = await axios.post('/api/v1/request', { - mediaId: data?.id, - tvdbId: data?.externalIds.tvdbId, - mediaType: 'tv', - is4k, - seasons: selectedSeasons, - ...overrideParams, - }); - if (response.data) { - if (onComplete) { - onComplete(response.data.media.status); + try { + let overrideParams = {}; + if (requestOverrides) { + overrideParams = { + serverId: requestOverrides.server, + profileId: requestOverrides.profile, + rootFolder: requestOverrides.folder, + }; } - addToast( - - {intl.formatMessage(messages.requestSuccess, { - title: data?.name, - strong: function strong(msg) { - return {msg}; - }, - })} - , - { appearance: 'success', autoDismiss: true } - ); + const response = await axios.post('/api/v1/request', { + mediaId: data?.id, + tvdbId: tvdbId ?? data?.externalIds.tvdbId, + mediaType: 'tv', + is4k, + seasons: selectedSeasons, + ...overrideParams, + }); + + if (response.data) { + if (onComplete) { + onComplete(response.data.media.status); + } + addToast( + + {intl.formatMessage(messages.requestSuccess, { + title: data?.name, + strong: function strong(msg) { + return {msg}; + }, + })} + , + { appearance: 'success', autoDismiss: true } + ); + } + } catch (e) { + addToast(intl.formatMessage(messages.requesterror), { + appearance: 'error', + autoDismiss: true, + }); + } finally { if (onUpdating) { onUpdating(false); } @@ -279,11 +301,24 @@ const TvRequestModal: React.FC = ({ return seasonRequest; }; - return ( + return !data?.externalIds.tvdbId && searchModal.show ? ( + setSearchModal({ show: false })} + loading={!data && !error} + onCancel={onCancel} + modalTitle={intl.formatMessage( + is4k ? messages.request4ktitle : messages.requesttitle, + { title: data?.name } + )} + tmdbId={tmdbId} + /> + ) : ( setSearchModal({ show: true }) : onCancel} onOk={() => (editRequest ? updateRequest() : sendRequest())} title={intl.formatMessage( is4k ? messages.request4ktitle : messages.requesttitle, @@ -302,6 +337,11 @@ const TvRequestModal: React.FC = ({ okButtonType={ editRequest && selectedSeasons.length === 0 ? 'danger' : `primary` } + cancelText={ + tvdbId + ? intl.formatMessage(messages.backbutton) + : intl.formatMessage(globalMessages.cancel) + } iconSvg={ = ({ showDetail ? 'scale-105' : '' }`} style={{ - backgroundImage: `url(//image.tmdb.org/t/p/w300_and_h450_face${image})`, + backgroundImage: image + ? `url(//image.tmdb.org/t/p/w300_and_h450_face${image})` + : `url('/images/overseerr_poster_not_found_logo_top.png')`, }} onMouseEnter={() => { if (!isTouch) { diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 6cb399f71..493d3c227 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -134,14 +134,22 @@ "components.RequestModal.AdvancedRequester.loadingprofiles": "Loading profiles…", "components.RequestModal.AdvancedRequester.qualityprofile": "Quality Profile", "components.RequestModal.AdvancedRequester.rootfolder": "Root Folder", + "components.RequestModal.SearchByNameModal.next": "Next", + "components.RequestModal.SearchByNameModal.nosummary": "No summary for this title was found.", + "components.RequestModal.SearchByNameModal.notvdbid": "Manual match required", + "components.RequestModal.SearchByNameModal.notvdbiddescription": "We couldn't automatically match your request. Please select the correct match from the list below.", "components.RequestModal.autoapproval": "Auto Approval", + "components.RequestModal.backbutton": "Back", "components.RequestModal.cancel": "Cancel Request", "components.RequestModal.cancelling": "Cancelling…", "components.RequestModal.cancelrequest": "This will remove your request. Are you sure you want to continue?", "components.RequestModal.close": "Close", "components.RequestModal.errorediting": "Something went wrong editing the request.", "components.RequestModal.extras": "Extras", + "components.RequestModal.next": "Next", "components.RequestModal.notrequested": "Not Requested", + "components.RequestModal.notvdbid": "No TVDB id was found connected on TMDB", + "components.RequestModal.notvdbiddescription": "Either add the TVDB id to TMDB and come back later, or select the correct match below.", "components.RequestModal.numberofepisodes": "# of Episodes", "components.RequestModal.pending4krequest": "Pending request for {title} in 4K", "components.RequestModal.pendingrequest": "Pending request for {title}", @@ -154,6 +162,7 @@ "components.RequestModal.requestadmin": "Your request will be immediately approved.", "components.RequestModal.requestcancelled": "Request cancelled.", "components.RequestModal.requestedited": "Request edited.", + "components.RequestModal.requesterror": "Something went wrong when trying to request media.", "components.RequestModal.requestfrom": "There is currently a pending request from {username}", "components.RequestModal.requesting": "Requesting…", "components.RequestModal.requestseasons": "Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}",