Skip to content

Commit

Permalink
Graceful handling of non-plex pass users (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
nylonee committed Feb 15, 2024
1 parent 39d4947 commit 08193fc
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 14 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
11 changes: 7 additions & 4 deletions src/main/scala/PlexTokenSync.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/main/scala/configuration/Configuration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions src/main/scala/configuration/ConfigurationRedactor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
13 changes: 9 additions & 4 deletions src/main/scala/configuration/ConfigurationUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -57,7 +58,8 @@ object ConfigurationUtils {
PlexConfiguration(
plexWatchlistUrls,
plexTokens,
skipFriendSync
skipFriendSync,
hasPlexPass
),
DeleteConfiguration(
deleteMovies,
Expand Down Expand Up @@ -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
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/test/scala/PlexTokenSyncSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 0 additions & 2 deletions src/test/scala/configuration/ConfigurationUtilsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion src/test/scala/plex/PlexUtilsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}

0 comments on commit 08193fc

Please sign in to comment.