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:
PrivacyDev 2023-06-05 22:38:17 -04:00
parent 1150a59e38
commit 2ce3ee6d84
8 changed files with 72 additions and 34 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -32,6 +32,8 @@ type
userMedia userMedia
favoriters favoriters
retweeters retweeters
following
followers
RateLimit* = object RateLimit* = object
remaining*: int remaining*: int

View file

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