From b83227aaf5acbd8e8803c85dfd2b8f311a021604 Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 25 Nov 2025 17:23:04 +0100 Subject: [PATCH] Implement temp fix for cookie sessions Fixes #1319 --- src/api.nim | 47 ++++++++++++++++------------ src/apiutils.nim | 2 +- src/consts.nim | 24 ++++++++------ src/experimental/parser/graphql.nim | 24 +++++++++++--- src/experimental/parser/user.nim | 4 ++- src/experimental/types/graphuser.nim | 13 ++++++-- src/parser.nim | 2 +- 7 files changed, 77 insertions(+), 39 deletions(-) diff --git a/src/api.nim b/src/api.nim index ef3a0f9..fb9c516 100644 --- a/src/api.nim +++ b/src/api.nim @@ -13,47 +13,54 @@ proc genParams(variables: string; fieldToggles = ""): seq[(string, string)] = proc mediaUrl(id: string; cursor: string): SessionAwareUrl = let - cookieVariables = userMediaVariables % [id, cursor] - oauthVariables = restIdVariables % [id, cursor] + cookieVars = userMediaVars % [id, cursor] + oauthVars = restIdVars % [id, cursor] result = SessionAwareUrl( - cookieUrl: graphUserMedia ? genParams(cookieVariables), - oauthUrl: graphUserMediaV2 ? genParams(oauthVariables) + cookieUrl: graphUserMedia ? genParams(cookieVars), + oauthUrl: graphUserMediaV2 ? genParams(oauthVars) ) proc userTweetsUrl(id: string; cursor: string): SessionAwareUrl = let - cookieVariables = userTweetsVariables % [id, cursor] - oauthVariables = restIdVariables % [id, cursor] + cookieVars = userTweetsVars % [id, cursor] + oauthVars = restIdVars % [id, cursor] result = SessionAwareUrl( - # cookieUrl: graphUserTweets ? genParams(cookieVariables, fieldToggles), - oauthUrl: graphUserTweetsV2 ? genParams(oauthVariables) + # cookieUrl: graphUserTweets ? genParams(cookieVars, userTweetsFieldToggles), + oauthUrl: graphUserTweetsV2 ? genParams(oauthVars) ) # might change this in the future pending testing result.cookieUrl = result.oauthUrl proc userTweetsAndRepliesUrl(id: string; cursor: string): SessionAwareUrl = let - cookieVariables = userTweetsAndRepliesVariables % [id, cursor] - oauthVariables = restIdVariables % [id, cursor] + cookieVars = userTweetsAndRepliesVars % [id, cursor] + oauthVars = restIdVars % [id, cursor] result = SessionAwareUrl( - cookieUrl: graphUserTweetsAndReplies ? genParams(cookieVariables, fieldToggles), - oauthUrl: graphUserTweetsAndRepliesV2 ? genParams(oauthVariables) + cookieUrl: graphUserTweetsAndReplies ? genParams(cookieVars, userTweetsFieldToggles), + oauthUrl: graphUserTweetsAndRepliesV2 ? genParams(oauthVars) ) proc tweetDetailUrl(id: string; cursor: string): SessionAwareUrl = let - cookieVariables = tweetDetailVariables % [id, cursor] - oauthVariables = tweetVariables % [id, cursor] + cookieVars = tweetDetailVars % [id, cursor] + oauthVars = tweetVars % [id, cursor] result = SessionAwareUrl( - cookieUrl: graphTweetDetail ? genParams(cookieVariables, tweetDetailFieldToggles), - oauthUrl: graphTweet ? genParams(oauthVariables) + cookieUrl: graphTweetDetail ? genParams(cookieVars, tweetDetailFieldToggles), + oauthUrl: graphTweet ? genParams(oauthVars) + ) + +proc userUrl(username: string): SessionAwareUrl = + let + cookieVars = """{"screen_name":"$1","withGrokTranslatedBio":false}""" % username + oauthVars = """{"screen_name": "$1"}""" % username + result = SessionAwareUrl( + cookieUrl: graphUser ? genParams(cookieVars, tweetDetailFieldToggles), + oauthUrl: graphUserV2 ? genParams(oauthVars) ) proc getGraphUser*(username: string): Future[User] {.async.} = if username.len == 0: return - let - url = graphUser ? genParams("""{"screen_name": "$1"}""" % username) - js = await fetchRaw(url, Api.userScreenName) + let js = await fetchRaw(userUrl(username), Api.userScreenName) result = parseGraphUser(js) proc getGraphUserById*(id: string): Future[User] {.async.} = @@ -80,7 +87,7 @@ proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} = if id.len == 0: return let cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" - url = graphListTweets ? genParams(restIdVariables % [id, cursor]) + url = graphListTweets ? genParams(restIdVars % [id, cursor]) result = parseGraphTimeline(await fetch(url, Api.listTweets), after).tweets proc getGraphListBySlug*(name, list: string): Future[List] {.async.} = diff --git a/src/apiutils.nim b/src/apiutils.nim index defffd1..94d4e8a 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -50,7 +50,7 @@ proc genHeaders*(session: Session, url: string): HttpHeaders = of SessionKind.oauth: result["authorization"] = getOauthHeader(url, session.oauthToken, session.oauthSecret) of SessionKind.cookie: - result["authorization"] = "Bearer AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF" + result["authorization"] = "Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F" result["x-twitter-auth-type"] = "OAuth2Session" result["x-csrf-token"] = session.ct0 result["cookie"] = getCookieHeader(session.authToken, session.ct0) diff --git a/src/consts.nim b/src/consts.nim index 792a519..e55ba0e 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -7,7 +7,8 @@ const gql = parseUri("https://api.x.com") / "graphql" - graphUser* = gql / "WEoGnYB0EG1yGwamDCF6zg/UserResultByScreenNameQuery" + graphUser* = gql / "-oaLodhGbbnzJBACb1kk2Q/UserByScreenName" + graphUserV2* = gql / "WEoGnYB0EG1yGwamDCF6zg/UserResultByScreenNameQuery" graphUserById* = gql / "VN33vKXrPT7p35DgNR27aw/UserResultByIdQuery" graphUserTweetsV2* = gql / "6QdSuZ5feXxOadEdXa4XZg/UserWithProfileTweetsQueryV2" graphUserTweetsAndRepliesV2* = gql / "BDX77Xzqypdt11-mDfgdpQ/UserWithProfileTweetsAndRepliesQueryV2" @@ -97,10 +98,14 @@ const "grok_translations_community_note_auto_translation_is_enabled": false, "grok_translations_post_auto_translation_is_enabled": false, "grok_translations_community_note_translation_is_enabled": false, - "grok_translations_timeline_user_bio_auto_translation_is_enabled": false + "grok_translations_timeline_user_bio_auto_translation_is_enabled": false, + "subscriptions_feature_can_gift_premium": false, + "responsive_web_twitter_article_notes_tab_enabled": false, + "subscriptions_verification_info_is_identity_verified_enabled": false, + "hidden_profile_subscriptions_enabled": false }""".replace(" ", "").replace("\n", "") - tweetVariables* = """{ + tweetVars* = """{ "postId": "$1", $2 "includeHasBirdwatchNotes": false, @@ -110,7 +115,7 @@ const "withV2Timeline": true }""".replace(" ", "").replace("\n", "") - tweetDetailVariables* = """{ + tweetDetailVars* = """{ "focalTweetId": "$1", $2 "referrer": "profile", @@ -123,12 +128,12 @@ const "withVoice": true }""".replace(" ", "").replace("\n", "") - restIdVariables* = """{ + restIdVars* = """{ "rest_id": "$1", $2 "count": 20 }""" - userMediaVariables* = """{ + userMediaVars* = """{ "userId": "$1", $2 "count": 20, "includePromotedContent": false, @@ -137,7 +142,7 @@ const "withVoice": true }""".replace(" ", "").replace("\n", "") - userTweetsVariables* = """{ + userTweetsVars* = """{ "userId": "$1", $2 "count": 20, "includePromotedContent": false, @@ -145,7 +150,7 @@ const "withVoice": true }""".replace(" ", "").replace("\n", "") - userTweetsAndRepliesVariables* = """{ + userTweetsAndRepliesVars* = """{ "userId": "$1", $2 "count": 20, "includePromotedContent": false, @@ -153,5 +158,6 @@ const "withVoice": true }""".replace(" ", "").replace("\n", "") - fieldToggles* = """{"withArticlePlainText":false}""" + userFieldToggles = """{"withPayments":false,"withAuxiliaryUserLabels":true}""" + userTweetsFieldToggles* = """{"withArticlePlainText":false}""" tweetDetailFieldToggles* = """{"withArticleRichContentState":true,"withArticlePlainText":false,"withGrokAnalyze":false,"withDisallowedReplyControls":false}""" diff --git a/src/experimental/parser/graphql.nim b/src/experimental/parser/graphql.nim index 045a5d6..65ebc3d 100644 --- a/src/experimental/parser/graphql.nim +++ b/src/experimental/parser/graphql.nim @@ -1,6 +1,6 @@ import options, strutils import jsony -import user, ../types/[graphuser, graphlistmembers] +import user, utils, ../types/[graphuser, graphlistmembers] from ../../types import User, VerifiedType, Result, Query, QueryKind proc parseUserResult*(userResult: UserResult): User = @@ -15,22 +15,36 @@ proc parseUserResult*(userResult: UserResult): User = result.fullname = userResult.core.name result.userPic = userResult.avatar.imageUrl.replace("_normal", "") + if userResult.privacy.isSome: + result.protected = userResult.privacy.get.protected + + if userResult.location.isSome: + result.location = userResult.location.get.location + + if userResult.core.createdAt.len > 0: + result.joinDate = parseTwitterDate(userResult.core.createdAt) + if userResult.verification.isSome: let v = userResult.verification.get if v.verifiedType != VerifiedType.none: result.verifiedType = v.verifiedType - if userResult.profileBio.isSome: + if userResult.profileBio.isSome and result.bio.len == 0: result.bio = userResult.profileBio.get.description proc parseGraphUser*(json: string): User = if json.len == 0 or json[0] != '{': return - let raw = json.fromJson(GraphUser) - let userResult = raw.data.userResult.result + let + raw = json.fromJson(GraphUser) + userResult = + if raw.data.userResult.isSome: raw.data.userResult.get.result + elif raw.data.user.isSome: raw.data.user.get.result + else: UserResult() - if userResult.unavailableReason.get("") == "Suspended": + if userResult.unavailableReason.get("") == "Suspended" or + userResult.reason.get("") == "Suspended": return User(suspended: true) result = parseUserResult(userResult) diff --git a/src/experimental/parser/user.nim b/src/experimental/parser/user.nim index 498757a..8517bdc 100644 --- a/src/experimental/parser/user.nim +++ b/src/experimental/parser/user.nim @@ -58,11 +58,13 @@ proc toUser*(raw: RawUser): User = media: raw.mediaCount, verifiedType: raw.verifiedType, protected: raw.protected, - joinDate: parseTwitterDate(raw.createdAt), banner: getBanner(raw), userPic: getImageUrl(raw.profileImageUrlHttps).replace("_normal", "") ) + if raw.createdAt.len > 0: + result.joinDate = parseTwitterDate(raw.createdAt) + if raw.pinnedTweetIdsStr.len > 0: result.pinnedTweet = parseBiggestInt(raw.pinnedTweetIdsStr[0]) diff --git a/src/experimental/types/graphuser.nim b/src/experimental/types/graphuser.nim index d732b4e..62c6612 100644 --- a/src/experimental/types/graphuser.nim +++ b/src/experimental/types/graphuser.nim @@ -3,7 +3,7 @@ from ../../types import User, VerifiedType type GraphUser* = object - data*: tuple[userResult: UserData] + data*: tuple[userResult: Option[UserData], user: Option[UserData]] UserData* = object result*: UserResult @@ -22,15 +22,24 @@ type Verification* = object verifiedType*: VerifiedType + Location* = object + location*: string + + Privacy* = object + protected*: bool + UserResult* = object legacy*: User restId*: string isBlueVerified*: bool - unavailableReason*: Option[string] core*: UserCore avatar*: UserAvatar + unavailableReason*: Option[string] + reason*: Option[string] + privacy*: Option[Privacy] profileBio*: Option[UserBio] verification*: Option[Verification] + location*: Option[Location] proc enumHook*(s: string; v: var VerifiedType) = v = try: diff --git a/src/parser.nim b/src/parser.nim index 5bf2b0b..7b1e1c0 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -21,7 +21,7 @@ proc parseUser(js: JsonNode; id=""): User = tweets: js{"statuses_count"}.getInt, likes: js{"favourites_count"}.getInt, media: js{"media_count"}.getInt, - protected: js{"protected"}.getBool, + protected: js{"protected"}.getBool(js{"privacy", "protected"}.getBool), joinDate: js{"created_at"}.getTime )