diff --git a/src/api.nim b/src/api.nim index 886fee6..59bb660 100644 --- a/src/api.nim +++ b/src/api.nim @@ -6,23 +6,41 @@ import experimental/parser as newParser proc getGraphUser*(username: string): Future[User] {.async.} = if username.len == 0: return + + let + headers = newHttpHeaders() + headers.add("Referer", """https://x.com/$1""" % username) + let variables = """{"screen_name":"$1"}""" % username fieldToggles = """{"withAuxiliaryUserLabels":true}""" params = {"variables": variables, "features": gqlFeatures, "fieldToggles": fieldToggles} - js = await fetch(graphUser ? params, Api.userScreenName) + js = await fetch(graphUser ? params, Api.userScreenName, headers) result = parseGraphUser(js) proc getGraphUserById*(id: string): Future[User] {.async.} = if id.len == 0 or id.any(c => not c.isDigit): return + let + headers = newHttpHeaders() + headers.add("Referer", """https://x.com/i/user/$1""" % id) + let variables = """{"userId":"$1"}""" % id params = {"variables": variables, "features": gqlFeatures} - js = await fetch(graphUserById ? params, Api.userRestId) + js = await fetch(graphUserById ? params, Api.userRestId, headers) result = parseGraphUser(js) proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Profile] {.async.} = if id.len == 0: return + + let endpoint = case kind + of TimelineKind.tweets: "" + of TimelineKind.replies: "/with_replies" + of TimelineKind.media: "/media" + let + headers = newHttpHeaders() + headers.add("Referer", """https://x.com/$1$2""" % [id, endpoint]) + let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" variables = if kind == TimelineKind.media: userMediaVariables % [id, cursor] else: userTweetsVariables % [id, cursor] @@ -32,7 +50,7 @@ proc getGraphUserTweets*(id: string; kind: TimelineKind; after=""): Future[Profi of TimelineKind.tweets: (graphUserTweets, Api.userTweets) of TimelineKind.replies: (graphUserTweetsAndReplies, Api.userTweetsAndReplies) of TimelineKind.media: (graphUserMedia, Api.userMedia) - js = await fetch(url ? params, apiId) + js = await fetch(url ? params, apiId, headers) result = parseGraphTimeline(js, if kind == TimelineKind.media: "" else: "user", after) proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = @@ -90,19 +108,27 @@ proc getFavorites*(id: string; cfg: Config; after=""): Future[Profile] {.async.} proc getGraphTweetResult*(id: string): Future[Tweet] {.async.} = if id.len == 0: return + let + headers = newHttpHeaders() + headers.add("Referer", """https://x.com/i/status/$1""" % id) + let variables = """{"rest_id":"$1"}""" % id params = {"variables": variables, "features": gqlFeatures} - js = await fetch(graphTweetResult ? params, Api.tweetResult) + js = await fetch(graphTweetResult ? params, Api.tweetResult, headers) result = parseGraphTweetResult(js) proc getGraphTweet*(id: string; after=""): Future[Conversation] {.async.} = if id.len == 0: return + let + headers = newHttpHeaders() + headers.add("Referer", """https://x.com/i/status/$1""" % id) + let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" variables = tweetVariables % [id, cursor] params = {"variables": variables, "features": gqlFeatures, "fieldToggles": tweetFieldToggles} - js = await fetch(graphTweet ? params, Api.tweetDetail) + js = await fetch(graphTweet ? params, Api.tweetDetail, headers) result = parseGraphConversation(js, id) proc getGraphFavoriters*(id: string; after=""): Future[UsersTimeline] {.async.} = diff --git a/src/apiutils.nim b/src/apiutils.nim index 0d038ed..5336ce5 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -1,5 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-only -import httpclient, asyncdispatch, options, strutils, uri, times, tables +import httpclient, asyncdispatch, options, strutils, uri, times, tables, math import jsony, packedjson, zippy import types, tokens, consts, parserutils, http_pool import experimental/types/common @@ -32,6 +32,9 @@ proc genParams*(pars: openArray[(string, string)] = @[]; cursor=""; #proc genHeaders*(token: Token = nil): HttpHeaders = proc genHeaders*(): HttpHeaders = + let + t = getTime() + ffVersion = floor(124 + ((t.toUnix() / 1000) - 1710892800) / 2419200) result = newHttpHeaders({ "connection": "keep-alive", "authorization": auth, @@ -45,8 +48,12 @@ proc genHeaders*(): HttpHeaders = "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Sec-GPC": "1", - "TE": "trailers" - }) + "TE": "trailers", + "User-Agent": """Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:$1.0) Gecko/20100101 Firefox/$1.0""" % $ffVersion, + "x-twitter-active-user": "yes", + "x-twitter-auth-type": "OAuth2Session", + "x-twitter-client-language": "en" + }, true) #template updateToken() = # if resp.headers.hasKey(rlRemaining): @@ -66,21 +73,16 @@ template fetchImpl(result, additional_headers, fetchBody) {.dirty.} = if len(cfg.cookieHeader) != 0: additional_headers.add("Cookie", cfg.cookieHeader) - additional_headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0") - if len(cfg.xCsrfToken) != 0: additional_headers.add("x-csrf-token", cfg.xCsrfToken) - additional_headers.add("x-twitter-active-user", "yes") - additional_headers.add("x-twitter-auth-type", "OAuth2Session") - additional_headers.add("x-twitter-client-language", "en") - try: var resp: AsyncResponse #var headers = genHeaders(token) var headers = genHeaders() for key, value in additional_headers.pairs(): headers.add(key, value) + pool.use(headers): template getContent = resp = await c.get($url) diff --git a/src/redis_cache.nim b/src/redis_cache.nim index 6c753e7..16f3c25 100644 --- a/src/redis_cache.nim +++ b/src/redis_cache.nim @@ -151,7 +151,7 @@ proc getCachedUsername*(userId: string): Future[string] {.async.} = key = "i:" & userId username = await get(key) - if username != redisNil: + if username != redisNil and username.len > 0: result = username else: let user = await getGraphUserById(userId)