1
0
mirror of https://github.com/zedeus/nitter.git synced 2025-12-06 03:55:36 -05:00

Implement temp fix for cookie sessions

Fixes #1319
This commit is contained in:
Zed
2025-11-25 17:23:04 +01:00
parent 404b06b5f3
commit b83227aaf5
7 changed files with 77 additions and 39 deletions

View File

@@ -13,47 +13,54 @@ proc genParams(variables: string; fieldToggles = ""): seq[(string, string)] =
proc mediaUrl(id: string; cursor: string): SessionAwareUrl = proc mediaUrl(id: string; cursor: string): SessionAwareUrl =
let let
cookieVariables = userMediaVariables % [id, cursor] cookieVars = userMediaVars % [id, cursor]
oauthVariables = restIdVariables % [id, cursor] oauthVars = restIdVars % [id, cursor]
result = SessionAwareUrl( result = SessionAwareUrl(
cookieUrl: graphUserMedia ? genParams(cookieVariables), cookieUrl: graphUserMedia ? genParams(cookieVars),
oauthUrl: graphUserMediaV2 ? genParams(oauthVariables) oauthUrl: graphUserMediaV2 ? genParams(oauthVars)
) )
proc userTweetsUrl(id: string; cursor: string): SessionAwareUrl = proc userTweetsUrl(id: string; cursor: string): SessionAwareUrl =
let let
cookieVariables = userTweetsVariables % [id, cursor] cookieVars = userTweetsVars % [id, cursor]
oauthVariables = restIdVariables % [id, cursor] oauthVars = restIdVars % [id, cursor]
result = SessionAwareUrl( result = SessionAwareUrl(
# cookieUrl: graphUserTweets ? genParams(cookieVariables, fieldToggles), # cookieUrl: graphUserTweets ? genParams(cookieVars, userTweetsFieldToggles),
oauthUrl: graphUserTweetsV2 ? genParams(oauthVariables) oauthUrl: graphUserTweetsV2 ? genParams(oauthVars)
) )
# might change this in the future pending testing # might change this in the future pending testing
result.cookieUrl = result.oauthUrl result.cookieUrl = result.oauthUrl
proc userTweetsAndRepliesUrl(id: string; cursor: string): SessionAwareUrl = proc userTweetsAndRepliesUrl(id: string; cursor: string): SessionAwareUrl =
let let
cookieVariables = userTweetsAndRepliesVariables % [id, cursor] cookieVars = userTweetsAndRepliesVars % [id, cursor]
oauthVariables = restIdVariables % [id, cursor] oauthVars = restIdVars % [id, cursor]
result = SessionAwareUrl( result = SessionAwareUrl(
cookieUrl: graphUserTweetsAndReplies ? genParams(cookieVariables, fieldToggles), cookieUrl: graphUserTweetsAndReplies ? genParams(cookieVars, userTweetsFieldToggles),
oauthUrl: graphUserTweetsAndRepliesV2 ? genParams(oauthVariables) oauthUrl: graphUserTweetsAndRepliesV2 ? genParams(oauthVars)
) )
proc tweetDetailUrl(id: string; cursor: string): SessionAwareUrl = proc tweetDetailUrl(id: string; cursor: string): SessionAwareUrl =
let let
cookieVariables = tweetDetailVariables % [id, cursor] cookieVars = tweetDetailVars % [id, cursor]
oauthVariables = tweetVariables % [id, cursor] oauthVars = tweetVars % [id, cursor]
result = SessionAwareUrl( result = SessionAwareUrl(
cookieUrl: graphTweetDetail ? genParams(cookieVariables, tweetDetailFieldToggles), cookieUrl: graphTweetDetail ? genParams(cookieVars, tweetDetailFieldToggles),
oauthUrl: graphTweet ? genParams(oauthVariables) 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.} = proc getGraphUser*(username: string): Future[User] {.async.} =
if username.len == 0: return if username.len == 0: return
let let js = await fetchRaw(userUrl(username), Api.userScreenName)
url = graphUser ? genParams("""{"screen_name": "$1"}""" % username)
js = await fetchRaw(url, Api.userScreenName)
result = parseGraphUser(js) result = parseGraphUser(js)
proc getGraphUserById*(id: string): Future[User] {.async.} = proc getGraphUserById*(id: string): Future[User] {.async.} =
@@ -80,7 +87,7 @@ proc getGraphListTweets*(id: string; after=""): Future[Timeline] {.async.} =
if id.len == 0: return if id.len == 0: return
let let
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" 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 result = parseGraphTimeline(await fetch(url, Api.listTweets), after).tweets
proc getGraphListBySlug*(name, list: string): Future[List] {.async.} = proc getGraphListBySlug*(name, list: string): Future[List] {.async.} =

View File

@@ -50,7 +50,7 @@ proc genHeaders*(session: Session, url: string): HttpHeaders =
of SessionKind.oauth: of SessionKind.oauth:
result["authorization"] = getOauthHeader(url, session.oauthToken, session.oauthSecret) result["authorization"] = getOauthHeader(url, session.oauthToken, session.oauthSecret)
of SessionKind.cookie: of SessionKind.cookie:
result["authorization"] = "Bearer AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF" result["authorization"] = "Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F"
result["x-twitter-auth-type"] = "OAuth2Session" result["x-twitter-auth-type"] = "OAuth2Session"
result["x-csrf-token"] = session.ct0 result["x-csrf-token"] = session.ct0
result["cookie"] = getCookieHeader(session.authToken, session.ct0) result["cookie"] = getCookieHeader(session.authToken, session.ct0)

View File

@@ -7,7 +7,8 @@ const
gql = parseUri("https://api.x.com") / "graphql" gql = parseUri("https://api.x.com") / "graphql"
graphUser* = gql / "WEoGnYB0EG1yGwamDCF6zg/UserResultByScreenNameQuery" graphUser* = gql / "-oaLodhGbbnzJBACb1kk2Q/UserByScreenName"
graphUserV2* = gql / "WEoGnYB0EG1yGwamDCF6zg/UserResultByScreenNameQuery"
graphUserById* = gql / "VN33vKXrPT7p35DgNR27aw/UserResultByIdQuery" graphUserById* = gql / "VN33vKXrPT7p35DgNR27aw/UserResultByIdQuery"
graphUserTweetsV2* = gql / "6QdSuZ5feXxOadEdXa4XZg/UserWithProfileTweetsQueryV2" graphUserTweetsV2* = gql / "6QdSuZ5feXxOadEdXa4XZg/UserWithProfileTweetsQueryV2"
graphUserTweetsAndRepliesV2* = gql / "BDX77Xzqypdt11-mDfgdpQ/UserWithProfileTweetsAndRepliesQueryV2" graphUserTweetsAndRepliesV2* = gql / "BDX77Xzqypdt11-mDfgdpQ/UserWithProfileTweetsAndRepliesQueryV2"
@@ -97,10 +98,14 @@ const
"grok_translations_community_note_auto_translation_is_enabled": false, "grok_translations_community_note_auto_translation_is_enabled": false,
"grok_translations_post_auto_translation_is_enabled": false, "grok_translations_post_auto_translation_is_enabled": false,
"grok_translations_community_note_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", "") }""".replace(" ", "").replace("\n", "")
tweetVariables* = """{ tweetVars* = """{
"postId": "$1", "postId": "$1",
$2 $2
"includeHasBirdwatchNotes": false, "includeHasBirdwatchNotes": false,
@@ -110,7 +115,7 @@ const
"withV2Timeline": true "withV2Timeline": true
}""".replace(" ", "").replace("\n", "") }""".replace(" ", "").replace("\n", "")
tweetDetailVariables* = """{ tweetDetailVars* = """{
"focalTweetId": "$1", "focalTweetId": "$1",
$2 $2
"referrer": "profile", "referrer": "profile",
@@ -123,12 +128,12 @@ const
"withVoice": true "withVoice": true
}""".replace(" ", "").replace("\n", "") }""".replace(" ", "").replace("\n", "")
restIdVariables* = """{ restIdVars* = """{
"rest_id": "$1", $2 "rest_id": "$1", $2
"count": 20 "count": 20
}""" }"""
userMediaVariables* = """{ userMediaVars* = """{
"userId": "$1", $2 "userId": "$1", $2
"count": 20, "count": 20,
"includePromotedContent": false, "includePromotedContent": false,
@@ -137,7 +142,7 @@ const
"withVoice": true "withVoice": true
}""".replace(" ", "").replace("\n", "") }""".replace(" ", "").replace("\n", "")
userTweetsVariables* = """{ userTweetsVars* = """{
"userId": "$1", $2 "userId": "$1", $2
"count": 20, "count": 20,
"includePromotedContent": false, "includePromotedContent": false,
@@ -145,7 +150,7 @@ const
"withVoice": true "withVoice": true
}""".replace(" ", "").replace("\n", "") }""".replace(" ", "").replace("\n", "")
userTweetsAndRepliesVariables* = """{ userTweetsAndRepliesVars* = """{
"userId": "$1", $2 "userId": "$1", $2
"count": 20, "count": 20,
"includePromotedContent": false, "includePromotedContent": false,
@@ -153,5 +158,6 @@ const
"withVoice": true "withVoice": true
}""".replace(" ", "").replace("\n", "") }""".replace(" ", "").replace("\n", "")
fieldToggles* = """{"withArticlePlainText":false}""" userFieldToggles = """{"withPayments":false,"withAuxiliaryUserLabels":true}"""
userTweetsFieldToggles* = """{"withArticlePlainText":false}"""
tweetDetailFieldToggles* = """{"withArticleRichContentState":true,"withArticlePlainText":false,"withGrokAnalyze":false,"withDisallowedReplyControls":false}""" tweetDetailFieldToggles* = """{"withArticleRichContentState":true,"withArticlePlainText":false,"withGrokAnalyze":false,"withDisallowedReplyControls":false}"""

View File

@@ -1,6 +1,6 @@
import options, strutils import options, strutils
import jsony import jsony
import user, ../types/[graphuser, graphlistmembers] import user, utils, ../types/[graphuser, graphlistmembers]
from ../../types import User, VerifiedType, Result, Query, QueryKind from ../../types import User, VerifiedType, Result, Query, QueryKind
proc parseUserResult*(userResult: UserResult): User = proc parseUserResult*(userResult: UserResult): User =
@@ -15,22 +15,36 @@ proc parseUserResult*(userResult: UserResult): User =
result.fullname = userResult.core.name result.fullname = userResult.core.name
result.userPic = userResult.avatar.imageUrl.replace("_normal", "") 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: if userResult.verification.isSome:
let v = userResult.verification.get let v = userResult.verification.get
if v.verifiedType != VerifiedType.none: if v.verifiedType != VerifiedType.none:
result.verifiedType = v.verifiedType result.verifiedType = v.verifiedType
if userResult.profileBio.isSome: if userResult.profileBio.isSome and result.bio.len == 0:
result.bio = userResult.profileBio.get.description result.bio = userResult.profileBio.get.description
proc parseGraphUser*(json: string): User = proc parseGraphUser*(json: string): User =
if json.len == 0 or json[0] != '{': if json.len == 0 or json[0] != '{':
return return
let raw = json.fromJson(GraphUser) let
let userResult = raw.data.userResult.result 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) return User(suspended: true)
result = parseUserResult(userResult) result = parseUserResult(userResult)

View File

@@ -58,11 +58,13 @@ proc toUser*(raw: RawUser): User =
media: raw.mediaCount, media: raw.mediaCount,
verifiedType: raw.verifiedType, verifiedType: raw.verifiedType,
protected: raw.protected, protected: raw.protected,
joinDate: parseTwitterDate(raw.createdAt),
banner: getBanner(raw), banner: getBanner(raw),
userPic: getImageUrl(raw.profileImageUrlHttps).replace("_normal", "") userPic: getImageUrl(raw.profileImageUrlHttps).replace("_normal", "")
) )
if raw.createdAt.len > 0:
result.joinDate = parseTwitterDate(raw.createdAt)
if raw.pinnedTweetIdsStr.len > 0: if raw.pinnedTweetIdsStr.len > 0:
result.pinnedTweet = parseBiggestInt(raw.pinnedTweetIdsStr[0]) result.pinnedTweet = parseBiggestInt(raw.pinnedTweetIdsStr[0])

View File

@@ -3,7 +3,7 @@ from ../../types import User, VerifiedType
type type
GraphUser* = object GraphUser* = object
data*: tuple[userResult: UserData] data*: tuple[userResult: Option[UserData], user: Option[UserData]]
UserData* = object UserData* = object
result*: UserResult result*: UserResult
@@ -22,15 +22,24 @@ type
Verification* = object Verification* = object
verifiedType*: VerifiedType verifiedType*: VerifiedType
Location* = object
location*: string
Privacy* = object
protected*: bool
UserResult* = object UserResult* = object
legacy*: User legacy*: User
restId*: string restId*: string
isBlueVerified*: bool isBlueVerified*: bool
unavailableReason*: Option[string]
core*: UserCore core*: UserCore
avatar*: UserAvatar avatar*: UserAvatar
unavailableReason*: Option[string]
reason*: Option[string]
privacy*: Option[Privacy]
profileBio*: Option[UserBio] profileBio*: Option[UserBio]
verification*: Option[Verification] verification*: Option[Verification]
location*: Option[Location]
proc enumHook*(s: string; v: var VerifiedType) = proc enumHook*(s: string; v: var VerifiedType) =
v = try: v = try:

View File

@@ -21,7 +21,7 @@ proc parseUser(js: JsonNode; id=""): User =
tweets: js{"statuses_count"}.getInt, tweets: js{"statuses_count"}.getInt,
likes: js{"favourites_count"}.getInt, likes: js{"favourites_count"}.getInt,
media: js{"media_count"}.getInt, media: js{"media_count"}.getInt,
protected: js{"protected"}.getBool, protected: js{"protected"}.getBool(js{"privacy", "protected"}.getBool),
joinDate: js{"created_at"}.getTime joinDate: js{"created_at"}.getTime
) )