From e7d76350ecbdda0ae44d7ebd19046a0cdfe38476 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 29 May 2025 20:19:29 +0300 Subject: [PATCH] New: Release type options for Calendar Feed --- .../iCal/CalendarLinkModalContent.tsx | 53 +++++++++++++++++-- src/NzbDrone.Core/Localization/Core/en.json | 3 ++ .../Calendar/CalendarFeedController.cs | 23 +++++--- .../Calendar/CalendarReleaseType.cs | 8 +++ 4 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 src/Radarr.Api.V3/Calendar/CalendarReleaseType.cs diff --git a/frontend/src/Calendar/iCal/CalendarLinkModalContent.tsx b/frontend/src/Calendar/iCal/CalendarLinkModalContent.tsx index d0f04d7d74..d2e8c4ea22 100644 --- a/frontend/src/Calendar/iCal/CalendarLinkModalContent.tsx +++ b/frontend/src/Calendar/iCal/CalendarLinkModalContent.tsx @@ -4,6 +4,7 @@ import FormGroup from 'Components/Form/FormGroup'; import FormInputButton from 'Components/Form/FormInputButton'; import FormInputGroup from 'Components/Form/FormInputGroup'; import FormLabel from 'Components/Form/FormLabel'; +import { EnhancedSelectInputValue } from 'Components/Form/Select/EnhancedSelectInput'; import Icon from 'Components/Icon'; import Button from 'Components/Link/Button'; import ClipboardButton from 'Components/Link/ClipboardButton'; @@ -15,6 +16,27 @@ import { icons, inputTypes, kinds, sizes } from 'Helpers/Props'; import { InputChanged } from 'typings/inputs'; import translate from 'Utilities/String/translate'; +const releaseTypeOptions: EnhancedSelectInputValue[] = [ + { + key: 'cinemaRelease', + get value() { + return translate('CinemaRelease'); + }, + }, + { + key: 'digitalRelease', + get value() { + return translate('DigitalRelease'); + }, + }, + { + key: 'physicalRelease', + get value() { + return translate('PhysicalRelease'); + }, + }, +]; + interface CalendarLinkModalContentProps { onModalClose: () => void; } @@ -22,13 +44,19 @@ interface CalendarLinkModalContentProps { function CalendarLinkModalContent({ onModalClose, }: CalendarLinkModalContentProps) { - const [state, setState] = useState({ + const [state, setState] = useState<{ + unmonitored: boolean; + asAllDay: boolean; + releaseTypes: string[]; + tags: number[]; + }>({ unmonitored: false, asAllDay: false, + releaseTypes: [], tags: [], }); - const { unmonitored, asAllDay, tags } = state; + const { unmonitored, asAllDay, releaseTypes, tags } = state; const handleInputChange = useCallback(({ name, value }: InputChanged) => { setState((prevState) => ({ ...prevState, [name]: value })); @@ -52,6 +80,12 @@ function CalendarLinkModalContent({ icalUrl += 'asAllDay=true&'; } + if (releaseTypes.length) { + releaseTypes.forEach((releaseType) => { + icalUrl += `releaseTypes=${releaseType}&`; + }); + } + if (tags.length) { icalUrl += `tags=${tags.toString()}&`; } @@ -62,7 +96,7 @@ function CalendarLinkModalContent({ iCalHttpUrl: `${window.location.protocol}//${icalUrl}`, iCalWebCalUrl: `webcal://${icalUrl}`, }; - }, [unmonitored, asAllDay, tags]); + }, [unmonitored, asAllDay, releaseTypes, tags]); return ( @@ -94,6 +128,19 @@ function CalendarLinkModalContent({ /> + + {translate('ICalReleaseTypes')} + + + + {translate('Tags')} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index a426f14ade..9fbef0ecec 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -208,6 +208,7 @@ "ChownGroup": "chown Group", "ChownGroupHelpText": "Group name or gid. Use gid for remote file systems.", "ChownGroupHelpTextWarning": "This only works if the user running {appName} is the owner of the file. It's better to ensure the download client uses the same group as {appName}.", + "CinemaRelease": "Cinema Release", "CleanLibraryLevel": "Clean Library Level", "Clear": "Clear", "ClearBlocklist": "Clear blocklist", @@ -755,6 +756,8 @@ "ICalFeedHelpText": "Copy this URL to your client(s) or click to subscribe if your browser supports webcal", "ICalIncludeUnmonitoredMoviesHelpText": "Include unmonitored movies in the iCal feed", "ICalLink": "iCal Link", + "ICalReleaseTypes": "Release Types", + "ICalReleaseTypesMoviesHelpText": "Include only movies with specific release types in the iCal feed. If unspecified, all options are used.", "ICalShowAsAllDayEvents": "Show as All-Day Events", "ICalShowAsAllDayEventsHelpText": "Events will appear as all-day events in your calendar", "ICalTagsMoviesHelpText": "Applies to movies with at least one matching tag", diff --git a/src/Radarr.Api.V3/Calendar/CalendarFeedController.cs b/src/Radarr.Api.V3/Calendar/CalendarFeedController.cs index c84c0d6357..e7f1634649 100644 --- a/src/Radarr.Api.V3/Calendar/CalendarFeedController.cs +++ b/src/Radarr.Api.V3/Calendar/CalendarFeedController.cs @@ -26,7 +26,7 @@ public CalendarFeedController(IMovieService movieService, ITagService tagService } [HttpGet("Radarr.ics")] - public IActionResult GetCalendarFeed(int pastDays = 7, int futureDays = 28, string tags = "", bool unmonitored = false) + public IActionResult GetCalendarFeed(int pastDays = 7, int futureDays = 28, string tags = "", bool unmonitored = false, IReadOnlyCollection releaseTypes = null) { var start = DateTime.Today.AddDays(-pastDays); var end = DateTime.Today.AddDays(futureDays); @@ -54,9 +54,20 @@ public IActionResult GetCalendarFeed(int pastDays = 7, int futureDays = 28, stri continue; } - CreateEvent(calendar, movie.MovieMetadata, "cinematic"); - CreateEvent(calendar, movie.MovieMetadata, "digital"); - CreateEvent(calendar, movie.MovieMetadata, "physical"); + if (releaseTypes is not { Count: not 0 } || releaseTypes.Contains(CalendarReleaseType.CinemaRelease)) + { + CreateEvent(calendar, movie.MovieMetadata, "cinematic"); + } + + if (releaseTypes is not { Count: not 0 } || releaseTypes.Contains(CalendarReleaseType.DigitalRelease)) + { + CreateEvent(calendar, movie.MovieMetadata, "digital"); + } + + if (releaseTypes is not { Count: not 0 } || releaseTypes.Contains(CalendarReleaseType.PhysicalRelease)) + { + CreateEvent(calendar, movie.MovieMetadata, "physical"); + } } var serializer = (IStringSerializer)new SerializerFactory().Build(calendar.GetType(), new SerializationContext()); @@ -98,9 +109,9 @@ private void CreateEvent(Ical.Net.Calendar calendar, MovieMetadata movie, string occurrence.IsAllDay = true; occurrence.Description = movie.Overview; - occurrence.Categories = new List() { movie.Studio }; + occurrence.Categories = new List { movie.Studio }; - occurrence.Summary = $"{movie.Title} " + summaryText; + occurrence.Summary = $"{movie.Title} {summaryText}"; } } } diff --git a/src/Radarr.Api.V3/Calendar/CalendarReleaseType.cs b/src/Radarr.Api.V3/Calendar/CalendarReleaseType.cs new file mode 100644 index 0000000000..59428cec45 --- /dev/null +++ b/src/Radarr.Api.V3/Calendar/CalendarReleaseType.cs @@ -0,0 +1,8 @@ +namespace Radarr.Api.V3.Calendar; + +public enum CalendarReleaseType +{ + CinemaRelease, + DigitalRelease, + PhysicalRelease +}