mirror of
https://github.com/zedeus/nitter
synced 2026-09-05 22:59:31 +00:00
parent
7f7092fbf5
commit
2f07fa6151
10 changed files with 117 additions and 21 deletions
18
src/api.nim
18
src/api.nim
|
|
@ -35,8 +35,8 @@ proc userTweetsUrl(id: string; cursor: string): ApiReq =
|
|||
proc userTweetsAndRepliesUrl(id: string; cursor: string): ApiReq =
|
||||
return apiReq(graphUserTweetsAndRepliesV2, restIdVars % [id, cursor, "20"], userTweetsFieldToggles, skipTid=true)
|
||||
|
||||
proc tweetDetailUrl(id: string; cursor: string): ApiReq =
|
||||
return apiReq(graphTweet, tweetVars % [id, cursor])
|
||||
proc tweetDetailUrl(id, cursor: string; mode = Relevance): ApiReq =
|
||||
return apiReq(graphTweet, tweetVars % [id, cursor, $mode])
|
||||
# let cookieVars = tweetDetailVars % [id, cursor]
|
||||
# result = ApiReq(
|
||||
# cookie: apiUrl(graphTweetDetail, cookieVars, tweetDetailFieldToggles),
|
||||
|
|
@ -230,21 +230,21 @@ proc getGraphTweetResult*(id: string): Future[Tweet] {.async.} =
|
|||
js = await fetch(url)
|
||||
result = parseGraphTweetResult(js)
|
||||
|
||||
proc getGraphTweet(id: string; after=""): Future[Conversation] {.async.} =
|
||||
proc getGraphTweet(id: string; after=""; mode = Relevance): Future[Conversation] {.async.} =
|
||||
if id.len == 0: return
|
||||
let
|
||||
cursor = cursorParam(after)
|
||||
js = await fetch(tweetDetailUrl(id, cursor))
|
||||
js = await fetch(tweetDetailUrl(id, cursor, mode))
|
||||
result = parseGraphConversation(js, id)
|
||||
|
||||
proc getReplies*(id, after: string): Future[Result[Chain]] {.async.} =
|
||||
result = (await getGraphTweet(id, after)).replies
|
||||
proc getReplies*(id, after: string; mode = Relevance): Future[Result[Chain]] {.async.} =
|
||||
result = (await getGraphTweet(id, after, mode)).replies
|
||||
result.beginning = after.len == 0
|
||||
|
||||
proc getTweet*(id: string; after=""): Future[Conversation] {.async.} =
|
||||
result = await getGraphTweet(id)
|
||||
proc getTweet*(id: string; after=""; mode = Relevance): Future[Conversation] {.async.} =
|
||||
result = await getGraphTweet(id, mode=mode)
|
||||
if after.len > 0:
|
||||
result.replies = await getReplies(id, after)
|
||||
result.replies = await getReplies(id, after, mode)
|
||||
|
||||
proc getGraphEditHistory*(id: string): Future[EditHistory] {.async.} =
|
||||
if id.len == 0: return
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ const
|
|||
tweetVars* = """{
|
||||
"postId": "$1",
|
||||
$2
|
||||
"ranking_mode": "$3",
|
||||
"includeHasBirdwatchNotes": false,
|
||||
"includePromotedContent": false,
|
||||
"withBirdwatchNotes": true,
|
||||
|
|
|
|||
|
|
@ -21,16 +21,18 @@ proc createStatusRouter*(cfg: Config) =
|
|||
if id.len > 19 or id.any(c => not c.isDigit):
|
||||
resp Http404, showError("Invalid tweet ID", cfg)
|
||||
|
||||
let prefs = requestPrefs()
|
||||
let
|
||||
prefs = requestPrefs()
|
||||
sort = parseEnum[RankingMode](@"sort".toLowerAscii.capitalizeAscii, Relevance)
|
||||
|
||||
# used for the infinite scroll feature
|
||||
if @"scroll".len > 0:
|
||||
let replies = await getReplies(id, getCursor())
|
||||
let replies = await getReplies(id, getCursor(), sort)
|
||||
if replies.content.len == 0:
|
||||
resp Http204
|
||||
resp $renderReplies(replies, prefs, getPath())
|
||||
resp $renderReplies(replies, prefs, getPath(), sort=sort)
|
||||
|
||||
let conv = await getTweet(id, getCursor())
|
||||
let conv = await getTweet(id, getCursor(), sort)
|
||||
|
||||
if conv == nil or conv.tweet == nil or conv.tweet.id == 0:
|
||||
var error = "Tweet not found"
|
||||
|
|
@ -64,7 +66,7 @@ proc createStatusRouter*(cfg: Config) =
|
|||
elif card.video.isSome():
|
||||
images = @[card.video.get().thumb]
|
||||
|
||||
let html = renderConversation(conv, prefs, getPath() & "#m")
|
||||
let html = renderConversation(conv, prefs, getPath() & "#m", sort)
|
||||
resp renderMain(html, request, cfg, prefs, title, desc, ogTitle,
|
||||
images=images, video=video)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,39 @@
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.reply-sort {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px 14px;
|
||||
margin-bottom: 10px;
|
||||
padding: 8px 12px;
|
||||
background-color: var(--bg_panel);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.reply-sort-label {
|
||||
color: var(--fg_faded);
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.reply-sort-option {
|
||||
color: var(--tab);
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
border-bottom: 0.1rem solid transparent;
|
||||
|
||||
&:hover {
|
||||
color: var(--fg_color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--tab_selected);
|
||||
border-bottom-color: var(--tab_selected);
|
||||
}
|
||||
}
|
||||
|
||||
.main-tweet,
|
||||
.replies,
|
||||
.edit-history > div {
|
||||
|
|
|
|||
|
|
@ -176,6 +176,9 @@ type
|
|||
QueryKind* = enum
|
||||
posts, replies, media, users, tweets, userList, followers, following
|
||||
|
||||
RankingMode* = enum
|
||||
Relevance, Recency, Likes
|
||||
|
||||
Query* = object
|
||||
kind*: QueryKind
|
||||
view*: string
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
|||
let opensearchUrl = getUrlPrefix(cfg) & "/opensearch"
|
||||
|
||||
buildHtml(head):
|
||||
link(rel="stylesheet", type="text/css", href="/css/style.css?v=45")
|
||||
link(rel="stylesheet", type="text/css", href="/css/style.css?v=46")
|
||||
link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=7")
|
||||
|
||||
if theme.len > 0:
|
||||
|
|
|
|||
|
|
@ -28,7 +28,22 @@ proc renderReplyThread(thread: Chain; prefs: Prefs; path: string): VNode =
|
|||
if thread.hasMore:
|
||||
renderMoreReplies(thread)
|
||||
|
||||
proc renderReplies*(replies: Result[Chain]; prefs: Prefs; path: string; tweet: Tweet = nil): VNode =
|
||||
proc renderReplySort(sort: RankingMode): VNode =
|
||||
buildHtml(tdiv(class="reply-sort")):
|
||||
span(class="reply-sort-label"): text "Sort replies:"
|
||||
for mode in RankingMode:
|
||||
let
|
||||
cls = if mode == sort: "reply-sort-option active"
|
||||
else: "reply-sort-option"
|
||||
label = case mode
|
||||
of Relevance: "Relevant"
|
||||
of Recency: "Recent"
|
||||
of Likes: "Liked"
|
||||
a(class=cls, href=("?sort=" & $mode & "#r")):
|
||||
text label
|
||||
|
||||
proc renderReplies*(replies: Result[Chain]; prefs: Prefs; path: string;
|
||||
tweet: Tweet = nil; sort = Relevance): VNode =
|
||||
buildHtml(tdiv(class="replies", id="r")):
|
||||
var hasReplies = false
|
||||
var replyCount = 0
|
||||
|
|
@ -40,9 +55,11 @@ proc renderReplies*(replies: Result[Chain]; prefs: Prefs; path: string; tweet: T
|
|||
|
||||
if hasReplies and replies.bottom.len > 0:
|
||||
if tweet == nil or not replies.beginning or replyCount < tweet.stats.replies:
|
||||
renderMore(Query(), replies.bottom, focus="#r")
|
||||
let extra = if sort == Relevance: "" else: "sort=" & $sort & "&"
|
||||
renderMore(Query(), replies.bottom, focus="#r", extra=extra)
|
||||
|
||||
proc renderConversation*(conv: Conversation; prefs: Prefs; path: string): VNode =
|
||||
proc renderConversation*(conv: Conversation; prefs: Prefs; path: string;
|
||||
sort = Relevance): VNode =
|
||||
let hasAfter = conv.after.content.len > 0
|
||||
let threadId = conv.tweet.threadId
|
||||
buildHtml(tdiv(class="conversation")):
|
||||
|
|
@ -75,7 +92,8 @@ proc renderConversation*(conv: Conversation; prefs: Prefs; path: string): VNode
|
|||
if not conv.replies.beginning:
|
||||
renderNewer(Query(), getLink(conv.tweet), focus="#r")
|
||||
if conv.replies.content.len > 0 or conv.replies.bottom.len > 0:
|
||||
renderReplies(conv.replies, prefs, path, conv.tweet)
|
||||
renderReplySort(sort)
|
||||
renderReplies(conv.replies, prefs, path, conv.tweet, sort)
|
||||
|
||||
renderToTop(focus="#m")
|
||||
|
||||
|
|
|
|||
|
|
@ -50,9 +50,9 @@ proc renderNewer*(query: Query; path: string; focus=""): VNode =
|
|||
a(href=(p & url)):
|
||||
text "Load newest"
|
||||
|
||||
proc renderMore*(query: Query; cursor: string; focus=""): VNode =
|
||||
proc renderMore*(query: Query; cursor: string; focus=""; extra=""): VNode =
|
||||
buildHtml(tdiv(class="show-more")):
|
||||
a(href=(&"?{getQuery(query)}cursor={encodeUrl(cursor, usePlus=false)}{focus}")):
|
||||
a(href=(&"?{extra}{getQuery(query)}cursor={encodeUrl(cursor, usePlus=false)}{focus}")):
|
||||
text "Load more"
|
||||
|
||||
proc renderNoMore(): VNode =
|
||||
|
|
|
|||
Loading…
Reference in a new issue