added feature to view who a user follows or is followed by (won't compile because of a compiler bug)
This commit is contained in:
parent
1150a59e38
commit
2ce3ee6d84
8 changed files with 72 additions and 34 deletions
19
src/api.nim
19
src/api.nim
|
@ -3,7 +3,6 @@ import asyncdispatch, httpclient, uri, strutils, sequtils, sugar
|
||||||
import packedjson
|
import packedjson
|
||||||
import types, query, formatters, consts, apiutils, parser
|
import types, query, formatters, consts, apiutils, parser
|
||||||
import experimental/parser as newParser
|
import experimental/parser as newParser
|
||||||
import config
|
|
||||||
|
|
||||||
proc getGraphUser*(username: string): Future[User] {.async.} =
|
proc getGraphUser*(username: string): Future[User] {.async.} =
|
||||||
if username.len == 0: return
|
if username.len == 0: return
|
||||||
|
@ -112,6 +111,24 @@ proc getGraphRetweeters*(id: string; after=""): Future[UsersTimeline] {.async.}
|
||||||
js = await fetch(graphRetweeters ? params, Api.retweeters)
|
js = await fetch(graphRetweeters ? params, Api.retweeters)
|
||||||
result = parseGraphRetweetersTimeline(js, id)
|
result = parseGraphRetweetersTimeline(js, id)
|
||||||
|
|
||||||
|
proc getGraphFollowing*(id: string; after=""): Future[UsersTimeline] {.async.} =
|
||||||
|
if id.len == 0: return
|
||||||
|
let
|
||||||
|
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""
|
||||||
|
variables = followVariables % [id, cursor]
|
||||||
|
params = {"variables": variables, "features": gqlFeatures}
|
||||||
|
js = await fetch(graphFollowing ? params, Api.following)
|
||||||
|
result = parseGraphFollowTimeline(js, id)
|
||||||
|
|
||||||
|
proc getGraphFollowers*(id: string; after=""): Future[UsersTimeline] {.async.} =
|
||||||
|
if id.len == 0: return
|
||||||
|
let
|
||||||
|
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""
|
||||||
|
variables = followVariables % [id, cursor]
|
||||||
|
params = {"variables": variables, "features": gqlFeatures}
|
||||||
|
js = await fetch(graphFollowers ? params, Api.followers)
|
||||||
|
result = parseGraphFollowTimeline(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
|
||||||
|
|
|
@ -71,8 +71,8 @@ template fetchImpl(result, additional_headers, fetchBody) {.dirty.} =
|
||||||
|
|
||||||
getContent()
|
getContent()
|
||||||
|
|
||||||
if resp.status == $Http429:
|
if resp.status == $Http429:
|
||||||
raise rateLimitError()
|
raise rateLimitError()
|
||||||
|
|
||||||
if resp.status == $Http503:
|
if resp.status == $Http503:
|
||||||
badClient = true
|
badClient = true
|
||||||
|
|
|
@ -28,6 +28,8 @@ const
|
||||||
graphListTweets* = graphql / "jZntL0oVJSdjhmPcdbw_eA/ListLatestTweetsTimeline"
|
graphListTweets* = graphql / "jZntL0oVJSdjhmPcdbw_eA/ListLatestTweetsTimeline"
|
||||||
graphFavoriters* = graphql / "mDc_nU8xGv0cLRWtTaIEug/Favoriters"
|
graphFavoriters* = graphql / "mDc_nU8xGv0cLRWtTaIEug/Favoriters"
|
||||||
graphRetweeters* = graphql / "RCR9gqwYD1NEgi9FWzA50A/Retweeters"
|
graphRetweeters* = graphql / "RCR9gqwYD1NEgi9FWzA50A/Retweeters"
|
||||||
|
graphFollowers* = graphql / "EAqBhgcGr_qPOzhS4Q3scQ/Followers"
|
||||||
|
graphFollowing* = graphql / "JPZiqKjET7_M1r5Tlr8pyA/Following"
|
||||||
|
|
||||||
timelineParams* = {
|
timelineParams* = {
|
||||||
"include_profile_interstitial_type": "0",
|
"include_profile_interstitial_type": "0",
|
||||||
|
@ -129,3 +131,9 @@ const
|
||||||
"count" : 20,
|
"count" : 20,
|
||||||
"includePromotedContent": false
|
"includePromotedContent": false
|
||||||
}"""
|
}"""
|
||||||
|
|
||||||
|
followVariables* = """{
|
||||||
|
"userId" : "$1", $2
|
||||||
|
"count" : 20,
|
||||||
|
"includePromotedContent": false
|
||||||
|
}"""
|
||||||
|
|
|
@ -498,10 +498,10 @@ 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 =
|
proc parseGraphUsersTimeline(timeline: JsonNode; after=""): UsersTimeline =
|
||||||
result = UsersTimeline(beginning: after.len == 0)
|
result = UsersTimeline(beginning: after.len == 0)
|
||||||
|
|
||||||
let instructions = ? js{"data", key, "timeline", "instructions"}
|
let instructions = ? timeline{"instructions"}
|
||||||
|
|
||||||
if instructions.len == 0:
|
if instructions.len == 0:
|
||||||
return
|
return
|
||||||
|
@ -520,10 +520,13 @@ proc parseGraphUsersTimeline(js: JsonNode; root: string; key: string; after=""):
|
||||||
result.top = e{"content", "value"}.getStr
|
result.top = e{"content", "value"}.getStr
|
||||||
|
|
||||||
proc parseGraphFavoritersTimeline*(js: JsonNode; root: string; after=""): UsersTimeline =
|
proc parseGraphFavoritersTimeline*(js: JsonNode; root: string; after=""): UsersTimeline =
|
||||||
return parseGraphUsersTimeline(js, root, "favoriters_timeline", after)
|
return parseGraphUsersTimeline(js{"data", "favoriters_timeline", "timeline"}, after)
|
||||||
|
|
||||||
proc parseGraphRetweetersTimeline*(js: JsonNode; root: string; after=""): UsersTimeline =
|
proc parseGraphRetweetersTimeline*(js: JsonNode; root: string; after=""): UsersTimeline =
|
||||||
return parseGraphUsersTimeline(js, root, "retweeters_timeline", after)
|
return parseGraphUsersTimeline(js{"data", "retweeters_timeline", "timeline"}, after)
|
||||||
|
|
||||||
|
proc parseGraphFollowTimeline*(js: JsonNode; root: string; after=""): UsersTimeline =
|
||||||
|
return parseGraphUsersTimeline(js{"data", "user", "result", "timeline", "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, timeline, search]
|
import ../views/[general, status, search]
|
||||||
|
|
||||||
export uri, sequtils, options, sugar
|
export uri, sequtils, options, sugar
|
||||||
export router_utils
|
export router_utils
|
||||||
|
|
|
@ -127,35 +127,41 @@ proc createTimelineRouter*(cfg: Config) =
|
||||||
get "/@name/?@tab?/?":
|
get "/@name/?@tab?/?":
|
||||||
cond '.' notin @"name"
|
cond '.' notin @"name"
|
||||||
cond @"name" notin ["pic", "gif", "video", "search", "settings", "login", "intent", "i"]
|
cond @"name" notin ["pic", "gif", "video", "search", "settings", "login", "intent", "i"]
|
||||||
cond @"tab" in ["with_replies", "media", "search", "favorites", ""]
|
cond @"tab" in ["with_replies", "media", "search", "favorites", "following", "followers", ""]
|
||||||
let
|
let
|
||||||
prefs = cookiePrefs()
|
prefs = cookiePrefs()
|
||||||
after = getCursor()
|
after = getCursor()
|
||||||
names = getNames(@"name")
|
names = getNames(@"name")
|
||||||
|
|
||||||
var query = request.getQuery(@"tab", @"name")
|
case @"tab":
|
||||||
if names.len != 1:
|
of "followers":
|
||||||
query.fromUser = names
|
resp renderMain(renderUserList(await getGraphFollowers(await getUserId(@"name"), getCursor()), prefs), request, cfg, prefs)
|
||||||
|
of "following":
|
||||||
# used for the infinite scroll feature
|
resp renderMain(renderUserList(await getGraphFollowing(await getUserId(@"name"), getCursor()), prefs), request, cfg, prefs)
|
||||||
if @"scroll".len > 0:
|
|
||||||
if query.fromUser.len != 1:
|
|
||||||
var timeline = await getGraphSearch(query, after)
|
|
||||||
if timeline.content.len == 0: resp Http404
|
|
||||||
timeline.beginning = true
|
|
||||||
resp $renderTweetSearch(timeline, cfg, prefs, getPath())
|
|
||||||
else:
|
else:
|
||||||
var profile = await fetchProfile(after, query, cfg, skipRail=true)
|
var query = request.getQuery(@"tab", @"name")
|
||||||
if profile.tweets.content.len == 0: resp Http404
|
if names.len != 1:
|
||||||
profile.tweets.beginning = true
|
query.fromUser = names
|
||||||
resp $renderTimelineTweets(profile.tweets, prefs, getPath())
|
|
||||||
|
|
||||||
let rss =
|
# used for the infinite scroll feature
|
||||||
if @"tab".len == 0:
|
if @"scroll".len > 0:
|
||||||
"/$1/rss" % @"name"
|
if query.fromUser.len != 1:
|
||||||
elif @"tab" == "search":
|
var timeline = await getGraphSearch(query, after)
|
||||||
"/$1/search/rss?$2" % [@"name", genQueryUrl(query)]
|
if timeline.content.len == 0: resp Http404
|
||||||
else:
|
timeline.beginning = true
|
||||||
"/$1/$2/rss" % [@"name", @"tab"]
|
resp $renderTweetSearch(timeline, cfg, prefs, getPath())
|
||||||
|
else:
|
||||||
|
var profile = await fetchProfile(after, query, cfg, skipRail=true)
|
||||||
|
if profile.tweets.content.len == 0: resp Http404
|
||||||
|
profile.tweets.beginning = true
|
||||||
|
resp $renderTimelineTweets(profile.tweets, prefs, getPath())
|
||||||
|
|
||||||
respTimeline(await showTimeline(request, query, cfg, prefs, rss, after))
|
let rss =
|
||||||
|
if @"tab".len == 0:
|
||||||
|
"/$1/rss" % @"name"
|
||||||
|
elif @"tab" == "search":
|
||||||
|
"/$1/search/rss?$2" % [@"name", genQueryUrl(query)]
|
||||||
|
else:
|
||||||
|
"/$1/$2/rss" % [@"name", @"tab"]
|
||||||
|
|
||||||
|
respTimeline(await showTimeline(request, query, cfg, prefs, rss, after))
|
||||||
|
|
|
@ -32,6 +32,8 @@ type
|
||||||
userMedia
|
userMedia
|
||||||
favoriters
|
favoriters
|
||||||
retweeters
|
retweeters
|
||||||
|
following
|
||||||
|
followers
|
||||||
|
|
||||||
RateLimit* = object
|
RateLimit* = object
|
||||||
remaining*: int
|
remaining*: int
|
||||||
|
|
|
@ -59,8 +59,10 @@ proc renderUserCard*(user: User; prefs: Prefs): VNode =
|
||||||
tdiv(class="profile-card-extra-links"):
|
tdiv(class="profile-card-extra-links"):
|
||||||
ul(class="profile-statlist"):
|
ul(class="profile-statlist"):
|
||||||
renderStat(user.tweets, "posts", text="Tweets")
|
renderStat(user.tweets, "posts", text="Tweets")
|
||||||
renderStat(user.following, "following")
|
a(href="/" & user.username & "/following"):
|
||||||
renderStat(user.followers, "followers")
|
renderStat(user.following, "following")
|
||||||
|
a(href="/" & user.username & "/followers"):
|
||||||
|
renderStat(user.followers, "followers")
|
||||||
renderStat(user.likes, "likes")
|
renderStat(user.likes, "likes")
|
||||||
|
|
||||||
proc renderPhotoRail(profile: Profile): VNode =
|
proc renderPhotoRail(profile: Profile): VNode =
|
||||||
|
|
Loading…
Reference in a new issue