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)
|
js = await fetch(graphTweet ? params, Api.tweetDetail)
|
||||||
result = parseGraphConversation(js, id)
|
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.} =
|
proc getReplies*(id, after: string): Future[Result[Chain]] {.async.} =
|
||||||
result = (await getGraphTweet(id, after)).replies
|
result = (await getGraphTweet(id, after)).replies
|
||||||
result.beginning = after.len == 0
|
result.beginning = after.len == 0
|
||||||
|
|
|
@ -26,6 +26,8 @@ const
|
||||||
graphListBySlug* = graphql / "-kmqNvm5Y-cVrfvBy6docg/ListBySlug"
|
graphListBySlug* = graphql / "-kmqNvm5Y-cVrfvBy6docg/ListBySlug"
|
||||||
graphListMembers* = graphql / "P4NpVZDqUD_7MEM84L-8nw/ListMembers"
|
graphListMembers* = graphql / "P4NpVZDqUD_7MEM84L-8nw/ListMembers"
|
||||||
graphListTweets* = graphql / "jZntL0oVJSdjhmPcdbw_eA/ListLatestTweetsTimeline"
|
graphListTweets* = graphql / "jZntL0oVJSdjhmPcdbw_eA/ListLatestTweetsTimeline"
|
||||||
|
graphFavoriters* = graphql / "mDc_nU8xGv0cLRWtTaIEug/Favoriters"
|
||||||
|
graphRetweeters* = graphql / "RCR9gqwYD1NEgi9FWzA50A/Retweeters"
|
||||||
|
|
||||||
timelineParams* = {
|
timelineParams* = {
|
||||||
"include_profile_interstitial_type": "0",
|
"include_profile_interstitial_type": "0",
|
||||||
|
@ -53,10 +55,12 @@ const
|
||||||
|
|
||||||
gqlFeatures* = """{
|
gqlFeatures* = """{
|
||||||
"blue_business_profile_image_shape_enabled": false,
|
"blue_business_profile_image_shape_enabled": false,
|
||||||
|
"creator_subscriptions_tweet_preview_api_enabled": false,
|
||||||
"freedom_of_speech_not_reach_fetch_enabled": false,
|
"freedom_of_speech_not_reach_fetch_enabled": false,
|
||||||
"graphql_is_translatable_rweb_tweet_is_translatable_enabled": false,
|
"graphql_is_translatable_rweb_tweet_is_translatable_enabled": false,
|
||||||
"interactive_text_enabled": false,
|
"interactive_text_enabled": false,
|
||||||
"longform_notetweets_consumption_enabled": true,
|
"longform_notetweets_consumption_enabled": true,
|
||||||
|
"longform_notetweets_inline_media_enabled": false,
|
||||||
"longform_notetweets_richtext_consumption_enabled": true,
|
"longform_notetweets_richtext_consumption_enabled": true,
|
||||||
"longform_notetweets_rich_text_read_enabled": false,
|
"longform_notetweets_rich_text_read_enabled": false,
|
||||||
"responsive_web_edit_tweet_api_enabled": false,
|
"responsive_web_edit_tweet_api_enabled": false,
|
||||||
|
@ -66,6 +70,7 @@ const
|
||||||
"responsive_web_graphql_timeline_navigation_enabled": false,
|
"responsive_web_graphql_timeline_navigation_enabled": false,
|
||||||
"responsive_web_text_conversations_enabled": false,
|
"responsive_web_text_conversations_enabled": false,
|
||||||
"responsive_web_twitter_blue_verified_badge_is_enabled": true,
|
"responsive_web_twitter_blue_verified_badge_is_enabled": true,
|
||||||
|
"rweb_lists_timeline_redesign_enabled": false,
|
||||||
"spaces_2022_h2_clipping": true,
|
"spaces_2022_h2_clipping": true,
|
||||||
"spaces_2022_h2_spaces_communities": true,
|
"spaces_2022_h2_spaces_communities": true,
|
||||||
"standardized_nudges_misinfo": false,
|
"standardized_nudges_misinfo": false,
|
||||||
|
@ -118,3 +123,9 @@ const
|
||||||
"withReactionsPerspective": false,
|
"withReactionsPerspective": false,
|
||||||
"withVoice": 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"):
|
elif entryId.startsWith("cursor-bottom"):
|
||||||
result.bottom = e{"content", "value"}.getStr
|
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 =
|
proc parseGraphSearch*(js: JsonNode; after=""): Timeline =
|
||||||
result = Timeline(beginning: after.len == 0)
|
result = Timeline(beginning: after.len == 0)
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import jester, karax/vdom
|
||||||
|
|
||||||
import router_utils
|
import router_utils
|
||||||
import ".."/[types, formatters, api]
|
import ".."/[types, formatters, api]
|
||||||
import ../views/[general, status]
|
import ../views/[general, status, timeline, search]
|
||||||
|
|
||||||
export uri, sequtils, options, sugar
|
export uri, sequtils, options, sugar
|
||||||
export router_utils
|
export router_utils
|
||||||
|
@ -14,6 +14,29 @@ export status
|
||||||
|
|
||||||
proc createStatusRouter*(cfg: Config) =
|
proc createStatusRouter*(cfg: Config) =
|
||||||
router status:
|
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/?":
|
get "/@name/status/@id/?":
|
||||||
cond '.' notin @"name"
|
cond '.' notin @"name"
|
||||||
let id = @"id"
|
let id = @"id"
|
||||||
|
|
|
@ -45,7 +45,7 @@ proc getPoolJson*(): JsonNode =
|
||||||
of Api.listMembers, Api.listBySlug, Api.list, Api.listTweets,
|
of Api.listMembers, Api.listBySlug, Api.list, Api.listTweets,
|
||||||
Api.userTweets, Api.userTweetsAndReplies, Api.userMedia,
|
Api.userTweets, Api.userTweetsAndReplies, Api.userMedia,
|
||||||
Api.userRestId, Api.userScreenName,
|
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
|
of Api.userSearch: 900
|
||||||
else: 180
|
else: 180
|
||||||
reqs = maxReqs - token.apis[api].remaining
|
reqs = maxReqs - token.apis[api].remaining
|
||||||
|
|
|
@ -30,6 +30,8 @@ type
|
||||||
userTweets
|
userTweets
|
||||||
userTweetsAndReplies
|
userTweetsAndReplies
|
||||||
userMedia
|
userMedia
|
||||||
|
favoriters
|
||||||
|
retweeters
|
||||||
|
|
||||||
RateLimit* = object
|
RateLimit* = object
|
||||||
remaining*: int
|
remaining*: int
|
||||||
|
@ -224,6 +226,7 @@ type
|
||||||
replies*: Result[Chain]
|
replies*: Result[Chain]
|
||||||
|
|
||||||
Timeline* = Result[Tweet]
|
Timeline* = Result[Tweet]
|
||||||
|
UsersTimeline* = Result[User]
|
||||||
|
|
||||||
Profile* = object
|
Profile* = object
|
||||||
user*: User
|
user*: User
|
||||||
|
|
|
@ -121,3 +121,8 @@ proc renderUserSearch*(results: Result[User]; prefs: Prefs): VNode =
|
||||||
|
|
||||||
renderSearchTabs(results.query)
|
renderSearchTabs(results.query)
|
||||||
renderTimelineUsers(results, prefs)
|
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