added favoriters and retweeters endpoints
This commit is contained in:
parent
208c39db87
commit
e4eea3d2df
7 changed files with 89 additions and 2 deletions
18
src/api.nim
18
src/api.nim
|
@ -94,6 +94,24 @@ proc getGraphTweet(id: string; after=""): Future[Conversation] {.async.} =
|
|||
js = await fetch(graphTweet ? params, Api.tweetDetail)
|
||||
result = parseGraphConversation(js, id)
|
||||
|
||||
proc getGraphFavoriters*(id: string; after=""): Future[UsersTimeline] {.async.} =
|
||||
if id.len == 0: return
|
||||
let
|
||||
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""
|
||||
variables = reactorsVariables % [id, cursor]
|
||||
params = {"variables": variables, "features": gqlFeatures}
|
||||
js = await fetch(graphFavoriters ? params, Api.favoriters)
|
||||
result = parseGraphFavoritersTimeline(js, id)
|
||||
|
||||
proc getGraphRetweeters*(id: string; after=""): Future[UsersTimeline] {.async.} =
|
||||
if id.len == 0: return
|
||||
let
|
||||
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""
|
||||
variables = reactorsVariables % [id, cursor]
|
||||
params = {"variables": variables, "features": gqlFeatures}
|
||||
js = await fetch(graphRetweeters ? params, Api.retweeters)
|
||||
result = parseGraphRetweetersTimeline(js, id)
|
||||
|
||||
proc getReplies*(id, after: string): Future[Result[Chain]] {.async.} =
|
||||
result = (await getGraphTweet(id, after)).replies
|
||||
result.beginning = after.len == 0
|
||||
|
|
|
@ -26,6 +26,8 @@ const
|
|||
graphListBySlug* = graphql / "-kmqNvm5Y-cVrfvBy6docg/ListBySlug"
|
||||
graphListMembers* = graphql / "P4NpVZDqUD_7MEM84L-8nw/ListMembers"
|
||||
graphListTweets* = graphql / "jZntL0oVJSdjhmPcdbw_eA/ListLatestTweetsTimeline"
|
||||
graphFavoriters* = graphql / "mDc_nU8xGv0cLRWtTaIEug/Favoriters"
|
||||
graphRetweeters* = graphql / "RCR9gqwYD1NEgi9FWzA50A/Retweeters"
|
||||
|
||||
timelineParams* = {
|
||||
"include_profile_interstitial_type": "0",
|
||||
|
@ -53,10 +55,12 @@ const
|
|||
|
||||
gqlFeatures* = """{
|
||||
"blue_business_profile_image_shape_enabled": false,
|
||||
"creator_subscriptions_tweet_preview_api_enabled": false,
|
||||
"freedom_of_speech_not_reach_fetch_enabled": false,
|
||||
"graphql_is_translatable_rweb_tweet_is_translatable_enabled": false,
|
||||
"interactive_text_enabled": false,
|
||||
"longform_notetweets_consumption_enabled": true,
|
||||
"longform_notetweets_inline_media_enabled": false,
|
||||
"longform_notetweets_richtext_consumption_enabled": true,
|
||||
"longform_notetweets_rich_text_read_enabled": false,
|
||||
"responsive_web_edit_tweet_api_enabled": false,
|
||||
|
@ -66,6 +70,7 @@ const
|
|||
"responsive_web_graphql_timeline_navigation_enabled": false,
|
||||
"responsive_web_text_conversations_enabled": false,
|
||||
"responsive_web_twitter_blue_verified_badge_is_enabled": true,
|
||||
"rweb_lists_timeline_redesign_enabled": false,
|
||||
"spaces_2022_h2_clipping": true,
|
||||
"spaces_2022_h2_spaces_communities": true,
|
||||
"standardized_nudges_misinfo": false,
|
||||
|
@ -118,3 +123,9 @@ const
|
|||
"withReactionsPerspective": false,
|
||||
"withVoice": false
|
||||
}"""
|
||||
|
||||
reactorsVariables* = """{
|
||||
"tweetId" : "$1", $2
|
||||
"count" : 20,
|
||||
"includePromotedContent": false
|
||||
}"""
|
||||
|
|
|
@ -498,6 +498,33 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Timeline =
|
|||
elif entryId.startsWith("cursor-bottom"):
|
||||
result.bottom = e{"content", "value"}.getStr
|
||||
|
||||
proc parseGraphUsersTimeline(js: JsonNode; root: string; key: string; after=""): UsersTimeline =
|
||||
result = UsersTimeline(beginning: after.len == 0)
|
||||
|
||||
let instructions = ? js{"data", key, "timeline", "instructions"}
|
||||
|
||||
if instructions.len == 0:
|
||||
return
|
||||
|
||||
for i in instructions:
|
||||
if i{"type"}.getStr == "TimelineAddEntries":
|
||||
for e in i{"entries"}:
|
||||
let entryId = e{"entryId"}.getStr
|
||||
if entryId.startsWith("user"):
|
||||
with graphUser, e{"content", "itemContent"}:
|
||||
let user = parseGraphUser(graphUser)
|
||||
result.content.add user
|
||||
elif entryId.startsWith("cursor-bottom"):
|
||||
result.bottom = e{"content", "value"}.getStr
|
||||
elif entryId.startsWith("cursor-top"):
|
||||
result.top = e{"content", "value"}.getStr
|
||||
|
||||
proc parseGraphFavoritersTimeline*(js: JsonNode; root: string; after=""): UsersTimeline =
|
||||
return parseGraphUsersTimeline(js, root, "favoriters_timeline", after)
|
||||
|
||||
proc parseGraphRetweetersTimeline*(js: JsonNode; root: string; after=""): UsersTimeline =
|
||||
return parseGraphUsersTimeline(js, root, "retweeters_timeline", after)
|
||||
|
||||
proc parseGraphSearch*(js: JsonNode; after=""): Timeline =
|
||||
result = Timeline(beginning: after.len == 0)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import jester, karax/vdom
|
|||
|
||||
import router_utils
|
||||
import ".."/[types, formatters, api]
|
||||
import ../views/[general, status]
|
||||
import ../views/[general, status, timeline, search]
|
||||
|
||||
export uri, sequtils, options, sugar
|
||||
export router_utils
|
||||
|
@ -14,6 +14,29 @@ export status
|
|||
|
||||
proc createStatusRouter*(cfg: Config) =
|
||||
router status:
|
||||
get "/@name/status/@id/@reactors":
|
||||
cond '.' notin @"name"
|
||||
let id = @"id"
|
||||
|
||||
if id.len > 19 or id.any(c => not c.isDigit):
|
||||
resp Http404, showError("Invalid tweet ID", cfg)
|
||||
|
||||
let prefs = cookiePrefs()
|
||||
|
||||
# used for the infinite scroll feature
|
||||
if @"scroll".len > 0:
|
||||
let replies = await getReplies(id, getCursor())
|
||||
if replies.content.len == 0:
|
||||
resp Http404, ""
|
||||
resp $renderReplies(replies, prefs, getPath())
|
||||
|
||||
if @"reactors" == "favoriters":
|
||||
resp renderMain(renderUserList(await getGraphFavoriters(id, getCursor()), prefs),
|
||||
request, cfg, prefs)
|
||||
elif @"reactors" == "retweeters":
|
||||
resp renderMain(renderUserList(await getGraphRetweeters(id, getCursor()), prefs),
|
||||
request, cfg, prefs)
|
||||
|
||||
get "/@name/status/@id/?":
|
||||
cond '.' notin @"name"
|
||||
let id = @"id"
|
||||
|
|
|
@ -45,7 +45,7 @@ proc getPoolJson*(): JsonNode =
|
|||
of Api.listMembers, Api.listBySlug, Api.list, Api.listTweets,
|
||||
Api.userTweets, Api.userTweetsAndReplies, Api.userMedia,
|
||||
Api.userRestId, Api.userScreenName,
|
||||
Api.tweetDetail, Api.tweetResult, Api.search: 500
|
||||
Api.tweetDetail, Api.tweetResult, Api.search, Api.retweeters, Api.favoriters: 500
|
||||
of Api.userSearch: 900
|
||||
else: 180
|
||||
reqs = maxReqs - token.apis[api].remaining
|
||||
|
|
|
@ -30,6 +30,8 @@ type
|
|||
userTweets
|
||||
userTweetsAndReplies
|
||||
userMedia
|
||||
favoriters
|
||||
retweeters
|
||||
|
||||
RateLimit* = object
|
||||
remaining*: int
|
||||
|
@ -224,6 +226,7 @@ type
|
|||
replies*: Result[Chain]
|
||||
|
||||
Timeline* = Result[Tweet]
|
||||
UsersTimeline* = Result[User]
|
||||
|
||||
Profile* = object
|
||||
user*: User
|
||||
|
|
|
@ -121,3 +121,8 @@ proc renderUserSearch*(results: Result[User]; prefs: Prefs): VNode =
|
|||
|
||||
renderSearchTabs(results.query)
|
||||
renderTimelineUsers(results, prefs)
|
||||
|
||||
proc renderUserList*(results: Result[User]; prefs: Prefs): VNode =
|
||||
buildHtml(tdiv(class="timeline-container")):
|
||||
tdiv(class="timeline-header")
|
||||
renderTimelineUsers(results, prefs)
|
||||
|
|
Loading…
Reference in a new issue