diff --git a/README.md b/README.md index 54684c4..4d74b86 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ upon startup of the app, where the logs will list the movies/tv shows that are o ### Requirements -* Plex Pass Subscription +* Plex Pass Subscription (Recommended, otherwise see "Plex Pass Alternative" section below) * Sonarr v3 or higher * Radarr v3 or higher * Friends' Watchlists [Account Visibility](https://app.plex.tv/desktop/#!/settings/account) must be changed to 'Friends @@ -124,6 +124,15 @@ in [entrypoint.sh](https://github.com/nylonee/watchlistarr/blob/main/docker/entr | ALLOW_CONTINUING_SHOW_DELETING | false | Boolean flag to enable/disable the full Watchlistarr sync for continuing shows. If enabled, shows that still have planned seasons and are not watchlisted will be deleted from Sonarr | | DELETE_INTERVAL_DAYS | 7 | Number of days to wait before deleting content from the arrs (Deleting must be enabled) | +## Plex Pass Alternative +The Plex Pass subscription is required to generate the RSS Feed URLs. Without a Plex Pass, the normal API calls are too heavy-hitting on Plex's servers. + +If the app detects that you are not a Plex Pass user (i.e. the app tries to generate an RSS URL, and it fails), it will fall back into a periodic sync. + +The periodic sync will run every 19 minutes, ignoring the configuration for REFRESH_INTERVAL_SECONDS + +All other settings will still be valid + ## Developers Corner Build the docker image: diff --git a/src/main/scala/PlexTokenSync.scala b/src/main/scala/PlexTokenSync.scala index 7e5d067..c2b6ea7 100644 --- a/src/main/scala/PlexTokenSync.scala +++ b/src/main/scala/PlexTokenSync.scala @@ -14,19 +14,22 @@ object PlexTokenSync extends PlexUtils with SonarrUtils with RadarrUtils { private val logger = LoggerFactory.getLogger(getClass) def run(config: Configuration, client: HttpClient, firstRun: Boolean): IO[Unit] = { + val runTokenSync = firstRun || !config.plexConfiguration.hasPlexPass + val result = for { - selfWatchlist <- if (firstRun) + selfWatchlist <- if (runTokenSync) getSelfWatchlist(config.plexConfiguration, client) else EitherT.pure[IO, Throwable](Set.empty[Item]) - _ = if (firstRun) logger.info(s"Found ${selfWatchlist.size} items on user's watchlist using the plex token") - othersWatchlist <- if (!firstRun || config.plexConfiguration.skipFriendSync) + _ = if (runTokenSync) + logger.info(s"Found ${selfWatchlist.size} items on user's watchlist using the plex token") + othersWatchlist <- if (config.plexConfiguration.skipFriendSync || !runTokenSync) EitherT.pure[IO, Throwable](Set.empty[Item]) else getOthersWatchlist(config.plexConfiguration, client) watchlistDatas <- EitherT[IO, Throwable, List[Set[Item]]](config.plexConfiguration.plexWatchlistUrls.map(fetchWatchlistFromRss(client)).toList.sequence.map(Right(_))) watchlistData = watchlistDatas.flatten.toSet - _ = if (firstRun) logger.info(s"Found ${othersWatchlist.size} items on other available watchlists using the plex token") + _ = if (runTokenSync) logger.info(s"Found ${othersWatchlist.size} items on other available watchlists using the plex token") movies <- fetchMovies(client)(config.radarrConfiguration.radarrApiKey, config.radarrConfiguration.radarrBaseUrl, config.radarrConfiguration.radarrBypassIgnored) series <- fetchSeries(client)(config.sonarrConfiguration.sonarrApiKey, config.sonarrConfiguration.sonarrBaseUrl, config.sonarrConfiguration.sonarrBypassIgnored) allIds = movies ++ series diff --git a/src/main/scala/configuration/Configuration.scala b/src/main/scala/configuration/Configuration.scala index 49d50b8..dd9b8c7 100644 --- a/src/main/scala/configuration/Configuration.scala +++ b/src/main/scala/configuration/Configuration.scala @@ -35,7 +35,8 @@ case class RadarrConfiguration( case class PlexConfiguration( plexWatchlistUrls: Set[Uri], plexTokens: Set[String], - skipFriendSync: Boolean + skipFriendSync: Boolean, + hasPlexPass: Boolean ) case class DeleteConfiguration( diff --git a/src/main/scala/configuration/ConfigurationRedactor.scala b/src/main/scala/configuration/ConfigurationRedactor.scala index c3c4325..2215b19 100644 --- a/src/main/scala/configuration/ConfigurationRedactor.scala +++ b/src/main/scala/configuration/ConfigurationRedactor.scala @@ -27,6 +27,7 @@ object ConfigurationRedactor { | plexWatchlistUrls: ${config.plexConfiguration.plexWatchlistUrls.mkString(", ")} | plexTokens: ${config.plexConfiguration.plexTokens.map(_ => "REDACTED").mkString(", ")} | skipFriendSync: ${config.plexConfiguration.skipFriendSync} + | hasPlexPass: ${config.plexConfiguration.hasPlexPass} | | DeleteConfiguration: | movieDeleting: ${config.deleteConfiguration.movieDeleting} diff --git a/src/main/scala/configuration/ConfigurationUtils.scala b/src/main/scala/configuration/ConfigurationUtils.scala index 2d16fbd..2283167 100644 --- a/src/main/scala/configuration/ConfigurationUtils.scala +++ b/src/main/scala/configuration/ConfigurationUtils.scala @@ -34,8 +34,9 @@ object ConfigurationUtils { deleteEndedShows = configReader.getConfigOption(Keys.deleteEndedShow).flatMap(_.toBooleanOption).getOrElse(false) deleteContinuingShows = configReader.getConfigOption(Keys.deleteContinuingShow).flatMap(_.toBooleanOption).getOrElse(false) deleteInterval = configReader.getConfigOption(Keys.deleteIntervalDays).flatMap(_.toIntOption).getOrElse(7).days + hasPlexPass = plexWatchlistUrls.nonEmpty } yield Configuration( - refreshInterval, + if (hasPlexPass) refreshInterval else 19.minutes, SonarrConfiguration( sonarrBaseUrl, sonarrApiKey, @@ -57,7 +58,8 @@ object ConfigurationUtils { PlexConfiguration( plexWatchlistUrls, plexTokens, - skipFriendSync + skipFriendSync, + hasPlexPass ), DeleteConfiguration( deleteMovies, @@ -219,8 +221,11 @@ object ConfigurationUtils { watchlistsFromTokenIo.map { watchlistsFromToken => (watchlistsFromConfigDeprecated ++ watchlistsFromToken).toList match { case Nil => - throwError("Missing plex watchlist URL") - case other => other.map(toPlexUri).toSet + logger.warn("Missing RSS URL. Are you an active Plex Pass user?") + logger.warn("Real-time RSS sync disabled") + Set.empty + case other => + other.map(toPlexUri).toSet } } } diff --git a/src/test/scala/PlexTokenSyncSpec.scala b/src/test/scala/PlexTokenSyncSpec.scala index f8d7bc2..3780b29 100644 --- a/src/test/scala/PlexTokenSyncSpec.scala +++ b/src/test/scala/PlexTokenSyncSpec.scala @@ -55,7 +55,8 @@ class PlexTokenSyncSpec extends AnyFlatSpec with Matchers with MockFactory { PlexConfiguration( plexWatchlistUrls = Set(), plexTokens = plexTokens, - skipFriendSync = false + skipFriendSync = false, + hasPlexPass = true ), DeleteConfiguration( movieDeleting = false, diff --git a/src/test/scala/configuration/ConfigurationUtilsSpec.scala b/src/test/scala/configuration/ConfigurationUtilsSpec.scala index e298581..a8e8d58 100644 --- a/src/test/scala/configuration/ConfigurationUtilsSpec.scala +++ b/src/test/scala/configuration/ConfigurationUtilsSpec.scala @@ -8,9 +8,7 @@ import org.http4s.{Method, Uri} import org.scalamock.scalatest.MockFactory import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers -import io.circe.generic.auto._ import io.circe.parser._ -import io.circe.syntax.EncoderOps import scala.io.Source diff --git a/src/test/scala/plex/PlexUtilsSpec.scala b/src/test/scala/plex/PlexUtilsSpec.scala index a142022..17f0c80 100644 --- a/src/test/scala/plex/PlexUtilsSpec.scala +++ b/src/test/scala/plex/PlexUtilsSpec.scala @@ -194,6 +194,7 @@ class PlexUtilsSpec extends AnyFlatSpec with Matchers with PlexUtils with MockFa private def createConfiguration(plexTokens: Set[String]): PlexConfiguration = PlexConfiguration( plexWatchlistUrls = Set(Uri.unsafeFromString("https://localhost:9090")), plexTokens = plexTokens, - skipFriendSync = false + skipFriendSync = false, + hasPlexPass = true ) }