mirror of
https://github.com/zedeus/nitter.git
synced 2025-03-04 10:01:24 +00:00
Replace old v1 photo rail API with gql
This commit is contained in:
parent
c6edec0490
commit
19569bb19f
8 changed files with 42 additions and 70 deletions
12
src/api.nim
12
src/api.nim
|
@ -136,13 +136,13 @@ proc getGraphUserSearch*(query: Query; after=""): Future[Result[User]] {.async.}
|
||||||
result = parseGraphSearch[User](await fetch(url, Api.search), after)
|
result = parseGraphSearch[User](await fetch(url, Api.search), after)
|
||||||
result.query = query
|
result.query = query
|
||||||
|
|
||||||
proc getPhotoRail*(name: string): Future[PhotoRail] {.async.} =
|
proc getPhotoRail*(id: string): Future[PhotoRail] {.async.} =
|
||||||
if name.len == 0: return
|
if id.len == 0: return
|
||||||
let
|
let
|
||||||
ps = genParams({"screen_name": name, "trim_user": "true"},
|
variables = userTweetsVariables % [id, ""]
|
||||||
count="18", ext=false)
|
params = {"variables": variables, "features": gqlFeatures}
|
||||||
url = photoRail ? ps
|
url = graphUserMedia ? params
|
||||||
result = parsePhotoRail(await fetch(url, Api.photoRail))
|
result = parseGraphPhotoRail(await fetch(url, Api.userMedia))
|
||||||
|
|
||||||
proc resolve*(url: string; prefs: Prefs): Future[string] {.async.} =
|
proc resolve*(url: string; prefs: Prefs): Future[string] {.async.} =
|
||||||
let client = newAsyncHttpClient(maxRedirects=0)
|
let client = newAsyncHttpClient(maxRedirects=0)
|
||||||
|
|
|
@ -10,25 +10,6 @@ const
|
||||||
|
|
||||||
var pool: HttpPool
|
var pool: HttpPool
|
||||||
|
|
||||||
proc genParams*(pars: openArray[(string, string)] = @[]; cursor="";
|
|
||||||
count="20"; ext=true): seq[(string, string)] =
|
|
||||||
result = timelineParams
|
|
||||||
for p in pars:
|
|
||||||
result &= p
|
|
||||||
if ext:
|
|
||||||
result &= ("include_ext_alt_text", "1")
|
|
||||||
result &= ("include_ext_media_stats", "1")
|
|
||||||
result &= ("include_ext_media_availability", "1")
|
|
||||||
if count.len > 0:
|
|
||||||
result &= ("count", count)
|
|
||||||
if cursor.len > 0:
|
|
||||||
# The raw cursor often has plus signs, which sometimes get turned into spaces,
|
|
||||||
# so we need to turn them back into a plus
|
|
||||||
if " " in cursor:
|
|
||||||
result &= ("cursor", cursor.replace(" ", "+"))
|
|
||||||
else:
|
|
||||||
result &= ("cursor", cursor)
|
|
||||||
|
|
||||||
proc getOauthHeader(url, oauthToken, oauthTokenSecret: string): string =
|
proc getOauthHeader(url, oauthToken, oauthTokenSecret: string): string =
|
||||||
let
|
let
|
||||||
encodedUrl = url.replace(",", "%2C").replace("+", "%20")
|
encodedUrl = url.replace(",", "%2C").replace("+", "%20")
|
||||||
|
|
|
@ -10,7 +10,6 @@ const
|
||||||
apiMaxReqs: Table[Api, int] = {
|
apiMaxReqs: Table[Api, int] = {
|
||||||
Api.search: 50,
|
Api.search: 50,
|
||||||
Api.tweetDetail: 150,
|
Api.tweetDetail: 150,
|
||||||
Api.photoRail: 180,
|
|
||||||
Api.userTweets: 500,
|
Api.userTweets: 500,
|
||||||
Api.userTweetsAndReplies: 500,
|
Api.userTweetsAndReplies: 500,
|
||||||
Api.userMedia: 500,
|
Api.userMedia: 500,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import uri, sequtils, strutils
|
import uri, strutils
|
||||||
|
|
||||||
const
|
const
|
||||||
consumerKey* = "3nVuSoBZnx6U4vzUxf5w"
|
consumerKey* = "3nVuSoBZnx6U4vzUxf5w"
|
||||||
|
@ -8,8 +8,6 @@ const
|
||||||
api = parseUri("https://api.twitter.com")
|
api = parseUri("https://api.twitter.com")
|
||||||
activate* = $(api / "1.1/guest/activate.json")
|
activate* = $(api / "1.1/guest/activate.json")
|
||||||
|
|
||||||
photoRail* = api / "1.1/statuses/media_timeline.json"
|
|
||||||
|
|
||||||
graphql = api / "graphql"
|
graphql = api / "graphql"
|
||||||
graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery"
|
graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery"
|
||||||
graphUserById* = graphql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery"
|
graphUserById* = graphql / "oPppcargziU1uDQHAUmH-A/UserResultByIdQuery"
|
||||||
|
@ -24,22 +22,6 @@ const
|
||||||
graphListMembers* = graphql / "P4NpVZDqUD_7MEM84L-8nw/ListMembers"
|
graphListMembers* = graphql / "P4NpVZDqUD_7MEM84L-8nw/ListMembers"
|
||||||
graphListTweets* = graphql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline"
|
graphListTweets* = graphql / "BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline"
|
||||||
|
|
||||||
timelineParams* = {
|
|
||||||
"include_can_media_tag": "1",
|
|
||||||
"include_cards": "1",
|
|
||||||
"include_entities": "1",
|
|
||||||
"include_profile_interstitial_type": "0",
|
|
||||||
"include_quote_count": "0",
|
|
||||||
"include_reply_count": "0",
|
|
||||||
"include_user_entities": "0",
|
|
||||||
"include_ext_reply_count": "0",
|
|
||||||
"include_ext_media_color": "0",
|
|
||||||
"cards_platform": "Web-13",
|
|
||||||
"tweet_mode": "extended",
|
|
||||||
"send_error_codes": "1",
|
|
||||||
"simple_quoted_tweet": "1"
|
|
||||||
}.toSeq
|
|
||||||
|
|
||||||
gqlFeatures* = """{
|
gqlFeatures* = """{
|
||||||
"android_graphql_skip_api_media_color_palette": false,
|
"android_graphql_skip_api_media_color_palette": false,
|
||||||
"blue_business_profile_image_shape_enabled": false,
|
"blue_business_profile_image_shape_enabled": false,
|
||||||
|
|
|
@ -289,23 +289,6 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet =
|
||||||
result.text.removeSuffix(" Learn more.")
|
result.text.removeSuffix(" Learn more.")
|
||||||
result.available = false
|
result.available = false
|
||||||
|
|
||||||
proc parsePhotoRail*(js: JsonNode): PhotoRail =
|
|
||||||
with error, js{"error"}:
|
|
||||||
if error.getStr == "Not authorized.":
|
|
||||||
return
|
|
||||||
|
|
||||||
for tweet in js:
|
|
||||||
let
|
|
||||||
t = parseTweet(tweet, js{"tweet_card"})
|
|
||||||
url = if t.photos.len > 0: t.photos[0]
|
|
||||||
elif t.video.isSome: get(t.video).thumb
|
|
||||||
elif t.gif.isSome: get(t.gif).thumb
|
|
||||||
elif t.card.isSome: get(t.card).image
|
|
||||||
else: ""
|
|
||||||
|
|
||||||
if url.len == 0: continue
|
|
||||||
result.add GalleryPhoto(url: url, tweetId: $t.id)
|
|
||||||
|
|
||||||
proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet =
|
proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet =
|
||||||
if js.kind == JNull:
|
if js.kind == JNull:
|
||||||
return Tweet()
|
return Tweet()
|
||||||
|
@ -445,6 +428,34 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile =
|
||||||
tweet.id = parseBiggestInt(entryId)
|
tweet.id = parseBiggestInt(entryId)
|
||||||
result.pinned = some tweet
|
result.pinned = some tweet
|
||||||
|
|
||||||
|
proc parseGraphPhotoRail*(js: JsonNode): PhotoRail =
|
||||||
|
result = @[]
|
||||||
|
|
||||||
|
let instructions =
|
||||||
|
? js{"data", "user_result", "result", "timeline_response", "timeline", "instructions"}
|
||||||
|
|
||||||
|
for i in instructions:
|
||||||
|
if i{"__typename"}.getStr == "TimelineAddEntries":
|
||||||
|
for e in i{"entries"}:
|
||||||
|
let entryId = e{"entryId"}.getStr
|
||||||
|
if entryId.startsWith("tweet"):
|
||||||
|
with tweetResult, e{"content", "content", "tweetResult", "result"}:
|
||||||
|
let t = parseGraphTweet(tweetResult, false)
|
||||||
|
if not t.available:
|
||||||
|
t.id = parseBiggestInt(entryId.getId())
|
||||||
|
|
||||||
|
let url =
|
||||||
|
if t.photos.len > 0: t.photos[0]
|
||||||
|
elif t.video.isSome: get(t.video).thumb
|
||||||
|
elif t.gif.isSome: get(t.gif).thumb
|
||||||
|
elif t.card.isSome: get(t.card).image
|
||||||
|
else: ""
|
||||||
|
|
||||||
|
result.add GalleryPhoto(url: url, tweetId: $t.id)
|
||||||
|
|
||||||
|
if result.len == 16:
|
||||||
|
break
|
||||||
|
|
||||||
proc parseGraphSearch*[T: User | Tweets](js: JsonNode; after=""): Result[T] =
|
proc parseGraphSearch*[T: User | Tweets](js: JsonNode; after=""): Result[T] =
|
||||||
result = Result[T](beginning: after.len == 0)
|
result = Result[T](beginning: after.len == 0)
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,7 @@ proc cache*(data: List) {.async.} =
|
||||||
await setEx(data.listKey, listCacheTime, compress(toFlatty(data)))
|
await setEx(data.listKey, listCacheTime, compress(toFlatty(data)))
|
||||||
|
|
||||||
proc cache*(data: PhotoRail; name: string) {.async.} =
|
proc cache*(data: PhotoRail; name: string) {.async.} =
|
||||||
await setEx("pr:" & toLower(name), baseCacheTime * 2, compress(toFlatty(data)))
|
await setEx("pr2:" & toLower(name), baseCacheTime * 2, compress(toFlatty(data)))
|
||||||
|
|
||||||
proc cache*(data: User) {.async.} =
|
proc cache*(data: User) {.async.} =
|
||||||
if data.username.len == 0: return
|
if data.username.len == 0: return
|
||||||
|
@ -158,14 +158,14 @@ proc getCachedUsername*(userId: string): Future[string] {.async.} =
|
||||||
# if not result.isNil:
|
# if not result.isNil:
|
||||||
# await cache(result)
|
# await cache(result)
|
||||||
|
|
||||||
proc getCachedPhotoRail*(name: string): Future[PhotoRail] {.async.} =
|
proc getCachedPhotoRail*(id: string): Future[PhotoRail] {.async.} =
|
||||||
if name.len == 0: return
|
if id.len == 0: return
|
||||||
let rail = await get("pr:" & toLower(name))
|
let rail = await get("pr2:" & toLower(id))
|
||||||
if rail != redisNil:
|
if rail != redisNil:
|
||||||
rail.deserialize(PhotoRail)
|
rail.deserialize(PhotoRail)
|
||||||
else:
|
else:
|
||||||
result = await getPhotoRail(name)
|
result = await getPhotoRail(id)
|
||||||
await cache(result, name)
|
await cache(result, id)
|
||||||
|
|
||||||
proc getCachedList*(username=""; slug=""; id=""): Future[List] {.async.} =
|
proc getCachedList*(username=""; slug=""; id=""): Future[List] {.async.} =
|
||||||
let list = if id.len == 0: redisNil
|
let list = if id.len == 0: redisNil
|
||||||
|
|
|
@ -47,7 +47,7 @@ proc fetchProfile*(after: string; query: Query; skipRail=false;
|
||||||
let
|
let
|
||||||
rail =
|
rail =
|
||||||
skipIf(skipRail or query.kind == media, @[]):
|
skipIf(skipRail or query.kind == media, @[]):
|
||||||
getCachedPhotoRail(name)
|
getCachedPhotoRail(userId)
|
||||||
|
|
||||||
user = getCachedUser(name)
|
user = getCachedUser(name)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ type
|
||||||
Api* {.pure.} = enum
|
Api* {.pure.} = enum
|
||||||
tweetDetail
|
tweetDetail
|
||||||
tweetResult
|
tweetResult
|
||||||
photoRail
|
|
||||||
search
|
search
|
||||||
list
|
list
|
||||||
listBySlug
|
listBySlug
|
||||||
|
|
Loading…
Reference in a new issue