mirror of
https://github.com/zedeus/nitter.git
synced 2025-12-06 03:55:36 -05:00
47
src/api.nim
47
src/api.nim
@@ -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.} =
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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}"""
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user