Skip to content

Commit f4baef9

Browse files
committed
added favoriters and retweeters endpoints
1 parent 00deddb commit f4baef9

7 files changed

Lines changed: 86 additions & 2 deletions

File tree

src/api.nim

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,24 @@ proc getGraphTweet(id: string; after=""): Future[Conversation] {.async.} =
9494
js = await fetch(graphTweet ? params, Api.tweetDetail)
9595
result = parseGraphConversation(js, id)
9696

97+
proc getGraphFavoriters*(id: string; after=""): Future[UsersTimeline] {.async.} =
98+
if id.len == 0: return
99+
let
100+
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""
101+
variables = reactorsVariables % [id, cursor]
102+
params = {"variables": variables, "features": gqlFeatures}
103+
js = await fetch(graphFavoriters ? params, Api.favoriters)
104+
result = parseGraphFavoritersTimeline(js, id)
105+
106+
proc getGraphRetweeters*(id: string; after=""): Future[UsersTimeline] {.async.} =
107+
if id.len == 0: return
108+
let
109+
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""
110+
variables = reactorsVariables % [id, cursor]
111+
params = {"variables": variables, "features": gqlFeatures}
112+
js = await fetch(graphRetweeters ? params, Api.retweeters)
113+
result = parseGraphRetweetersTimeline(js, id)
114+
97115
proc getReplies*(id, after: string): Future[Result[Chain]] {.async.} =
98116
result = (await getGraphTweet(id, after)).replies
99117
result.beginning = after.len == 0

src/consts.nim

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const
2626
graphListBySlug* = graphql / "-kmqNvm5Y-cVrfvBy6docg/ListBySlug"
2727
graphListMembers* = graphql / "P4NpVZDqUD_7MEM84L-8nw/ListMembers"
2828
graphListTweets* = graphql / "jZntL0oVJSdjhmPcdbw_eA/ListLatestTweetsTimeline"
29+
graphFavoriters* = graphql / "mDc_nU8xGv0cLRWtTaIEug/Favoriters"
30+
graphRetweeters* = graphql / "RCR9gqwYD1NEgi9FWzA50A/Retweeters"
2931

3032
timelineParams* = {
3133
"include_profile_interstitial_type": "0",
@@ -122,3 +124,9 @@ const
122124
"withReactionsPerspective": false,
123125
"withVoice": false
124126
}"""
127+
128+
reactorsVariables* = """{
129+
"tweetId" : "$1", $2
130+
"count" : 20,
131+
"includePromotedContent": false
132+
}"""

src/parser.nim

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,33 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Timeline =
493493
elif entryId.startsWith("cursor-bottom"):
494494
result.bottom = e{"content", "value"}.getStr
495495

496+
proc parseGraphUsersTimeline(js: JsonNode; root: string; key: string; after=""): UsersTimeline =
497+
result = UsersTimeline(beginning: after.len == 0)
498+
499+
let instructions = ? js{"data", key, "timeline", "instructions"}
500+
501+
if instructions.len == 0:
502+
return
503+
504+
for i in instructions:
505+
if i{"type"}.getStr == "TimelineAddEntries":
506+
for e in i{"entries"}:
507+
let entryId = e{"entryId"}.getStr
508+
if entryId.startsWith("user"):
509+
with graphUser, e{"content", "itemContent"}:
510+
let user = parseGraphUser(graphUser)
511+
result.content.add user
512+
elif entryId.startsWith("cursor-bottom"):
513+
result.bottom = e{"content", "value"}.getStr
514+
elif entryId.startsWith("cursor-top"):
515+
result.top = e{"content", "value"}.getStr
516+
517+
proc parseGraphFavoritersTimeline*(js: JsonNode; root: string; after=""): UsersTimeline =
518+
return parseGraphUsersTimeline(js, root, "favoriters_timeline", after)
519+
520+
proc parseGraphRetweetersTimeline*(js: JsonNode; root: string; after=""): UsersTimeline =
521+
return parseGraphUsersTimeline(js, root, "retweeters_timeline", after)
522+
496523
proc parseGraphSearch*(js: JsonNode; after=""): Timeline =
497524
result = Timeline(beginning: after.len == 0)
498525

src/routes/status.nim

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import jester, karax/vdom
55

66
import router_utils
77
import ".."/[types, formatters, api]
8-
import ../views/[general, status]
8+
import ../views/[general, status, timeline, search]
99

1010
export uri, sequtils, options, sugar
1111
export router_utils
@@ -14,6 +14,29 @@ export status
1414

1515
proc createStatusRouter*(cfg: Config) =
1616
router status:
17+
get "/@name/status/@id/@reactors":
18+
cond '.' notin @"name"
19+
let id = @"id"
20+
21+
if id.len > 19 or id.any(c => not c.isDigit):
22+
resp Http404, showError("Invalid tweet ID", cfg)
23+
24+
let prefs = cookiePrefs()
25+
26+
# used for the infinite scroll feature
27+
if @"scroll".len > 0:
28+
let replies = await getReplies(id, getCursor())
29+
if replies.content.len == 0:
30+
resp Http404, ""
31+
resp $renderReplies(replies, prefs, getPath())
32+
33+
if @"reactors" == "favoriters":
34+
resp renderMain(renderUserList(await getGraphFavoriters(id, getCursor()), prefs),
35+
request, cfg, prefs)
36+
elif @"reactors" == "retweeters":
37+
resp renderMain(renderUserList(await getGraphRetweeters(id, getCursor()), prefs),
38+
request, cfg, prefs)
39+
1740
get "/@name/status/@id/?":
1841
cond '.' notin @"name"
1942
let id = @"id"

src/tokens.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ proc getPoolJson*(): JsonNode =
4545
of Api.listMembers, Api.listBySlug, Api.list, Api.listTweets,
4646
Api.userTweets, Api.userTweetsAndReplies, Api.userMedia,
4747
Api.userRestId, Api.userScreenName,
48-
Api.tweetDetail, Api.tweetResult, Api.search: 500
48+
Api.tweetDetail, Api.tweetResult, Api.search, Api.retweeters, Api.favoriters: 500
4949
of Api.userSearch: 900
5050
reqs = maxReqs - token.apis[api].remaining
5151

src/types.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type
3030
userTweets
3131
userTweetsAndReplies
3232
userMedia
33+
favoriters
34+
retweeters
3335

3436
RateLimit* = object
3537
remaining*: int
@@ -224,6 +226,7 @@ type
224226
replies*: Result[Chain]
225227

226228
Timeline* = Result[Tweet]
229+
UsersTimeline* = Result[User]
227230

228231
Profile* = object
229232
user*: User

src/views/search.nim

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,8 @@ proc renderUserSearch*(results: Result[User]; prefs: Prefs): VNode =
121121

122122
renderSearchTabs(results.query)
123123
renderTimelineUsers(results, prefs)
124+
125+
proc renderUserList*(results: Result[User]; prefs: Prefs): VNode =
126+
buildHtml(tdiv(class="timeline-container")):
127+
tdiv(class="timeline-header")
128+
renderTimelineUsers(results, prefs)

0 commit comments

Comments
 (0)