Skip to content

Commit

Permalink
fetch multiple pages from plex friends watchlist (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
nylonee authored Nov 22, 2023
1 parent f94500c commit 8573055
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 35 deletions.
76 changes: 41 additions & 35 deletions src/main/scala/plex/PlexUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ trait PlexUtils {
protected def getOthersWatchlist(config: Configuration, client: HttpClient): EitherT[IO, Throwable, Set[Item]] =
for {
friends <- getFriends(config, client)
watchlistItems <- friends.map(getWatchlistIdsForUser(config, client)).toList.sequence.map(_.flatten)
watchlistItems <- friends.map(getWatchlistIdsForUser(config, client)(_)).toList.sequence.map(_.flatten)
items <- watchlistItems.map(i => toItems(config, client, i)).sequence.map(_.toSet)
} yield items

Expand Down Expand Up @@ -95,46 +95,52 @@ trait PlexUtils {
})
}.getOrElse(EitherT.left(IO.pure(new Throwable("Plex tokens are not configured"))))

protected def getWatchlistIdsForUser(config: Configuration, client: HttpClient)(user: User): EitherT[IO, Throwable, Set[TokenWatchlistItem]] =
protected def getWatchlistIdsForUser(config: Configuration, client: HttpClient)(user: User, page: Option[String] = None): EitherT[IO, Throwable, Set[TokenWatchlistItem]] =
config.plexToken.map { token =>
val url = Uri.unsafeFromString("https://community.plex.tv/api")

val query = GraphQLQuery(
"""query GetWatchlistHub ($uuid: ID = "", $first: PaginationInt !, $after: String) {
user(id: $uuid) {
watchlist(first: $first, after: $after) {
nodes {
...itemFields
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
fragment itemFields on MetadataItem {
id
title
type
}""".stripMargin,
Some(
s"""{
| "first": 100,
| "uuid": "${user.id}"
|}""".stripMargin.asJson))
user(id: $uuid) {
watchlist(first: $first, after: $after) {
nodes {
...itemFields
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
fragment itemFields on MetadataItem {
id
title
type
}""".stripMargin,
if (page.isEmpty) {
Some(
s"""{
| "first": 100,
| "uuid": "${user.id}"
|}""".stripMargin.asJson)
} else {
Some(
s"""{
| "first": 100,
| "after": "${page.getOrElse("")}",
| "uuid": "${user.id}"
|}""".stripMargin.asJson)
})

EitherT(client.httpRequest(Method.POST, url, Some(token), Some(query.asJson)).map {
case Left(err) =>
logger.warn(s"Unable to fetch friends watchlist from Plex: $err")
Left(err)
case Right(json) =>
json.as[TokenWatchlistFriend] match {
// TODO: Fetch the other pages if hasNextPage = true
case Right(v) => Right(v.data.user.watchlist.nodes.map(_.toTokenWatchlistItem).toSet)
case Left(v) => Left(new Throwable(v))
}
})
for {
responseJson <- EitherT(client.httpRequest(Method.POST, url, Some(token), Some(query.asJson)))
watchlist <- EitherT.fromEither[IO](responseJson.as[TokenWatchlistFriend]).leftMap(new Throwable(_))
extraContent <- if (watchlist.data.user.watchlist.pageInfo.hasNextPage)
getWatchlistIdsForUser(config, client)(user, Some(watchlist.data.user.watchlist.pageInfo.endCursor))
else
EitherT.pure[IO, Throwable](Set.empty[TokenWatchlistItem])
} yield watchlist.data.user.watchlist.nodes.map(_.toTokenWatchlistItem).toSet ++ extraContent
}.getOrElse(EitherT.left(IO.pure(new Throwable("Plex tokens are not configured"))))

// We don't have all the information available in TokenWatchlist
Expand Down
26 changes: 26 additions & 0 deletions src/test/resources/plex-get-watchlist-from-friend-page-1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"data": {
"user": {
"watchlist": {
"nodes": [
{
"id": "5d77688b9ab54400214e789b",
"title": "The Twilight Saga: Breaking Dawn - Part 2",
"publicPagesURL": "https://watch.plex.tv/movie/the-twilight-saga-breaking-dawn-part-2",
"type": "MOVIE"
},
{
"id": "5d77688b594b2b001e68f2f0",
"title": "The Twilight Saga: Breaking Dawn - Part 1",
"publicPagesURL": "https://watch.plex.tv/movie/the-twilight-saga-breaking-dawn-part-1",
"type": "MOVIE"
}
],
"pageInfo": {
"hasNextPage": true,
"endCursor": "MTY1NjE4NDgyMzEzMA=="
}
}
}
}
}
24 changes: 24 additions & 0 deletions src/test/scala/plex/PlexUtilsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,30 @@ class PlexUtilsSpec extends AnyFlatSpec with Matchers with PlexUtils with MockFa
result.head shouldBe TokenWatchlistItem("The Twilight Saga: Breaking Dawn - Part 2", "5d77688b9ab54400214e789b", "movie", "/library/metadata/5d77688b9ab54400214e789b")
}

it should "successfully fetch multiple watchlist pages from a friend on Plex" in {
val mockClient = mock[HttpClient]
val config = createConfiguration(Some("test-token"))
(mockClient.httpRequest _).expects(
Method.POST,
Uri.unsafeFromString("https://community.plex.tv/api"),
Some("test-token"),
*
).returning(IO.pure(parse(Source.fromResource("plex-get-watchlist-from-friend-page-1.json").getLines().mkString("\n")))).repeat(13)
(mockClient.httpRequest _).expects(
Method.POST,
Uri.unsafeFromString("https://community.plex.tv/api"),
Some("test-token"),
*
).returning(IO.pure(parse(Source.fromResource("plex-get-watchlist-from-friend.json").getLines().mkString("\n")))).once()

val eitherResult = getWatchlistIdsForUser(config, mockClient)(User("ecdb6as0230e2115", "friend-1")).value.unsafeRunSync()

eitherResult shouldBe a[Right[_, _]]
val result = eitherResult.getOrElse(Set.empty[TokenWatchlistItem])
result.size shouldBe 2
result.head shouldBe TokenWatchlistItem("The Twilight Saga: Breaking Dawn - Part 2", "5d77688b9ab54400214e789b", "movie", "/library/metadata/5d77688b9ab54400214e789b")
}

private def createConfiguration(plexToken: Option[String]): Configuration = Configuration(
refreshInterval = 10.seconds,
sonarrBaseUrl = Uri.unsafeFromString("https://localhost:8989"),
Expand Down

0 comments on commit 8573055

Please sign in to comment.