[scrolller] support albums for subreddit results (#7339)

update GraphQL queries
This commit is contained in:
Mike Fährmann
2025-04-26 16:41:54 +02:00
parent 3ac33dce61
commit a44ea6b3d9
2 changed files with 126 additions and 58 deletions

View File

@@ -76,7 +76,7 @@ class ScrolllerExtractor(Extractor):
} }
try: try:
data = self._request_graphql("LoginQuery", variables) data = self._request_graphql("LoginQuery", variables, False)
except exception.HttpError as exc: except exception.HttpError as exc:
if exc.status == 403: if exc.status == 403:
raise exception.AuthenticationError() raise exception.AuthenticationError()
@@ -84,10 +84,9 @@ class ScrolllerExtractor(Extractor):
return data["login"]["token"] return data["login"]["token"]
def _request_graphql(self, opname, variables, admin=False): def _request_graphql(self, opname, variables, admin=True):
url = "https://api.scrolller.com/api/v2/graphql"
headers = { headers = {
"Content-Type" : "text/plain;charset=UTF-8", "Content-Type" : None,
"Origin" : self.root, "Origin" : self.root,
"Sec-Fetch-Dest": "empty", "Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors", "Sec-Fetch-Mode": "cors",
@@ -102,15 +101,19 @@ class ScrolllerExtractor(Extractor):
if admin: if admin:
url = "https://api.scrolller.com/admin" url = "https://api.scrolller.com/admin"
headers["Content-Type"] = "application/json" headers["Content-Type"] = "application/json"
else:
url = "https://api.scrolller.com/api/v2/graphql"
headers["Content-Type"] = "text/plain;charset=UTF-8"
return self.request( return self.request(
url, method="POST", headers=headers, data=util.json_dumps(data), url, method="POST", headers=headers, data=util.json_dumps(data),
).json()["data"] ).json()["data"]
def _pagination(self, opname, variables): def _pagination(self, opname, variables, data=None):
while True: if data is None:
data = self._request_graphql(opname, variables) data = self._request_graphql(opname, variables)
while True:
while "items" not in data: while "items" not in data:
data = data.popitem()[1] data = data.popitem()[1]
yield from data["items"] yield from data["items"]
@@ -119,6 +122,8 @@ class ScrolllerExtractor(Extractor):
return return
variables["iterator"] = data["iterator"] variables["iterator"] = data["iterator"]
data = self._request_graphql(opname, variables)
def _sort_key(self, src): def _sort_key(self, src):
return src["width"], not src["isOptimized"] return src["width"], not src["isOptimized"]
@@ -132,6 +137,7 @@ class ScrolllerSubredditExtractor(ScrolllerExtractor):
def posts(self): def posts(self):
url, query = self.groups url, query = self.groups
filter = None filter = None
sort = "RANDOM"
if query: if query:
params = text.parse_query(query) params = text.parse_query(query)
@@ -139,12 +145,24 @@ class ScrolllerSubredditExtractor(ScrolllerExtractor):
filter = params["filter"].upper().rstrip("S") filter = params["filter"].upper().rstrip("S")
variables = { variables = {
"url" : url, "url" : url,
"iterator" : None, "filter": filter,
"filter" : filter, "sortBy": sort,
"hostsDown": None, "limit" : 50,
} }
return self._pagination("SubredditQuery", variables) subreddit = self._request_graphql(
"SubredditQuery", variables)["getSubreddit"]
variables = {
"subredditId": subreddit["id"],
"iterator": None,
"filter" : filter,
"sortBy" : sort,
"limit" : 50,
"isNsfw" : subreddit["isNsfw"],
}
return self._pagination(
"SubredditChildrenQuery", variables, subreddit["children"])
class ScrolllerFollowingExtractor(ScrolllerExtractor): class ScrolllerFollowingExtractor(ScrolllerExtractor):
@@ -160,11 +178,14 @@ class ScrolllerFollowingExtractor(ScrolllerExtractor):
raise exception.AuthorizationError("Login required") raise exception.AuthorizationError("Login required")
variables = { variables = {
"iterator" : None, "iterator": None,
"hostsDown": None, "filter" : None,
"limit" : 10,
"isNsfw" : False,
"sortBy" : "RANDOM",
} }
for subreddit in self._pagination("FollowingQuery", variables): for subreddit in self._pagination("GetFollowingSubreddits", variables):
url = self.root + subreddit["url"] url = self.root + subreddit["url"]
subreddit["_extractor"] = ScrolllerSubredditExtractor subreddit["_extractor"] = ScrolllerSubredditExtractor
yield Message.Queue, url, subreddit yield Message.Queue, url, subreddit
@@ -174,36 +195,62 @@ class ScrolllerPostExtractor(ScrolllerExtractor):
"""Extractor for media from a single scrolller post""" """Extractor for media from a single scrolller post"""
subcategory = "post" subcategory = "post"
pattern = BASE_PATTERN + r"/(?!r/|following$)([^/?#]+)" pattern = BASE_PATTERN + r"/(?!r/|following$)([^/?#]+)"
example = "https://scrolller.com/title-slug-a1b2c3d4f5" example = "https://scrolller.com/TITLE-SLUG-a1b2c3d4f5"
def posts(self): def posts(self):
variables = {"url": "/" + self.groups[0]} variables = {"url": "/" + self.groups[0]}
data = self._request_graphql("SubredditPostQuery", variables, True) data = self._request_graphql("SubredditPostQuery", variables)
return (data["getPost"],) return (data["getPost"],)
QUERIES = { QUERIES = {
"SubredditPostQuery": """\
query SubredditPostQuery(
$url: String!
) {
getPost(
data: { url: $url }
) {
__typename id url title subredditId subredditTitle subredditUrl
redditPath isNsfw hasAudio fullLengthSource gfycatSource redgifsSource
ownerAvatar username displayName favoriteCount isPaid tags
commentsCount commentsRepliesCount isFavorite
albumContent { mediaSources { url width height isOptimized } }
mediaSources { url width height isOptimized }
blurredMediaSources { url width height isOptimized }
}
}
""",
"SubredditQuery": """\ "SubredditQuery": """\
query SubredditQuery( query SubredditQuery(
$url: String! $url: String!
$filter: SubredditPostFilter
$iterator: String $iterator: String
$sortBy: GallerySortBy
$filter: GalleryFilter
$limit: Int!
) { ) {
getSubreddit( getSubreddit(
url: $url data: {
url: $url,
iterator: $iterator,
filter: $filter,
limit: $limit,
sortBy: $sortBy
}
) { ) {
children( __typename id url title secondaryTitle description createdAt isNsfw
limit: 50 subscribers isComplete itemCount videoCount pictureCount albumCount
iterator: $iterator isPaid username tags isFollowing
filter: $filter banner { url width height isOptimized }
disabledHosts: null children {
) {
iterator items { iterator items {
__typename id url title subredditId subredditTitle __typename id url title subredditId subredditTitle subredditUrl
subredditUrl redditPath isNsfw albumUrl hasAudio redditPath isNsfw hasAudio fullLengthSource gfycatSource
fullLengthSource gfycatSource redgifsSource ownerAvatar redgifsSource ownerAvatar username displayName favoriteCount
username displayName isPaid tags isFavorite isPaid tags commentsCount commentsRepliesCount isFavorite
albumContent { mediaSources { url width height isOptimized } }
mediaSources { url width height isOptimized } mediaSources { url width height isOptimized }
blurredMediaSources { url width height isOptimized } blurredMediaSources { url width height isOptimized }
} }
@@ -212,19 +259,59 @@ query SubredditQuery(
} }
""", """,
"FollowingQuery": """\ "SubredditChildrenQuery": """\
query FollowingQuery( query SubredditChildrenQuery(
$subredditId: Int!
$iterator: String $iterator: String
$filter: GalleryFilter
$sortBy: GallerySortBy
$limit: Int!
$isNsfw: Boolean
) { ) {
getFollowing( getSubredditChildren(
limit: 10 data: {
iterator: $iterator subredditId: $subredditId,
iterator: $iterator,
filter: $filter,
sortBy: $sortBy,
limit: $limit,
isNsfw: $isNsfw
},
) {
iterator items {
__typename id url title subredditId subredditTitle subredditUrl
redditPath isNsfw hasAudio fullLengthSource gfycatSource
redgifsSource ownerAvatar username displayName favoriteCount isPaid
tags commentsCount commentsRepliesCount isFavorite
albumContent { mediaSources { url width height isOptimized } }
mediaSources { url width height isOptimized }
blurredMediaSources { url width height isOptimized }
}
}
}
""",
"GetFollowingSubreddits": """\
query GetFollowingSubreddits(
$iterator: String,
$limit: Int!,
$filter: GalleryFilter,
$isNsfw: Boolean,
$sortBy: GallerySortBy
) {
getFollowingSubreddits(
data: {
isNsfw: $isNsfw
limit: $limit
filter: $filter
iterator: $iterator
sortBy: $sortBy
}
) { ) {
iterator items { iterator items {
__typename id url title secondaryTitle description createdAt isNsfw __typename id url title secondaryTitle description createdAt isNsfw
subscribers isComplete itemCount videoCount pictureCount albumCount subscribers isComplete itemCount videoCount pictureCount albumCount
isPaid username tags isFollowing isFollowing
banner { url width height isOptimized }
} }
} }
} }
@@ -252,24 +339,6 @@ query ItemTypeQuery(
url: $url url: $url
) )
} }
""",
"SubredditPostQuery": """\
query SubredditPostQuery(
$url: String!
) {
getPost(
data: { url: $url }
) {
__typename id url title subredditId subredditTitle subredditUrl
redditPath isNsfw hasAudio fullLengthSource gfycatSource redgifsSource
ownerAvatar username displayName favoriteCount isPaid tags
commentsCount commentsRepliesCount isFavorite
albumContent { mediaSources { url width height isOptimized } }
mediaSources { url width height isOptimized }
blurredMediaSources { url width height isOptimized }
}
}
""", """,
} }

View File

@@ -11,21 +11,20 @@ __tests__ = (
{ {
"#url" : "https://scrolller.com/r/AmateurPhotography", "#url" : "https://scrolller.com/r/AmateurPhotography",
"#class" : scrolller.ScrolllerSubredditExtractor, "#class" : scrolller.ScrolllerSubredditExtractor,
"#pattern": r"https://\w+\.scrolller\.com/(\w+/)?[\w-]+-\w+\.(jpg|png)", "#pattern": r"https://static\.scrolller\.com/\w+/(\w+/)?[\w-]+-\w+\.(jpg|png|webp)",
"#range" : "1-100", "#range" : "1-100",
"#count" : 100, "#count" : 100,
"albumUrl" : None,
"displayName" : None, "displayName" : None,
"fullLengthSource": None, "fullLengthSource": None,
"gfycatSource" : None, "gfycatSource" : None,
"hasAudio" : None, "hasAudio" : bool,
"height" : int, "height" : int,
"id" : int, "id" : int,
"isFavorite" : False, "isFavorite" : False,
"isNsfw" : False, "isNsfw" : False,
"isOptimized" : bool, "isOptimized" : bool,
"isPaid" : None, "isPaid" : bool,
"mediaSources" : list, "mediaSources" : list,
"ownerAvatar" : None, "ownerAvatar" : None,
"redditPath" : r"re:/r/AmateurPhotography/comments/...", "redditPath" : r"re:/r/AmateurPhotography/comments/...",
@@ -36,7 +35,7 @@ __tests__ = (
"tags" : None, "tags" : None,
"title" : str, "title" : str,
"url" : str, "url" : str,
"username" : None, "username" : str,
"width" : int, "width" : int,
}, },