added favoriters and retweeters endpoints

This commit is contained in:
PrivacyDev 2023-06-02 23:47:05 -04:00
parent 00deddb438
commit f4baef9a9b
7 changed files with 86 additions and 2 deletions

View File

@ -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

View File

@ -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",
@ -122,3 +124,9 @@ const
"withReactionsPerspective": false,
"withVoice": false
}"""
reactorsVariables* = """{
"tweetId" : "$1", $2
"count" : 20,
"includePromotedContent": false
}"""

View File

@ -493,6 +493,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)

View File

@ -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"

View File

@ -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
reqs = maxReqs - token.apis[api].remaining

View File

@ -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

View File

@ -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)