1
0
mirror of https://github.com/zedeus/nitter.git synced 2026-04-15 18:22:11 -04:00

Add workaround for broken "until" search filter

Fixes #1372
This commit is contained in:
Zed
2026-03-14 04:04:18 +01:00
parent 0fefcf9917
commit 91ff936cb3
4 changed files with 48 additions and 17 deletions

View File

@@ -1,7 +1,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
import asyncdispatch, httpclient, strutils, sequtils, sugar import asyncdispatch, httpclient, strutils, sequtils, sugar
import packedjson import packedjson
import types, query, formatters, consts, apiutils, parser import types, query, formatters, consts, apiutils, parser, utils
import experimental/parser as newParser import experimental/parser as newParser
# Helper to generate params object for GraphQL requests # Helper to generate params object for GraphQL requests
@@ -146,7 +146,12 @@ proc getGraphEditHistory*(id: string): Future[EditHistory] {.async.} =
result = parseGraphEditHistory(js, id) result = parseGraphEditHistory(js, id)
proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} =
let q = genQueryParam(query) # workaround for #1372
let maxId =
if not after.startsWith("maxid:"): ""
else: validateNumber(after[6..^1])
let q = genQueryParam(query, maxId)
if q.len == 0 or q == emptyQuery: if q.len == 0 or q == emptyQuery:
return Timeline(query: query, beginning: true) return Timeline(query: query, beginning: true)
@@ -160,9 +165,9 @@ proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} =
"withReactionsMetadata": false, "withReactionsMetadata": false,
"withReactionsPerspective": false "withReactionsPerspective": false
} }
if after.len > 0: if after.len > 0 and maxId.len == 0:
variables["cursor"] = % after variables["cursor"] = % after
let let
url = apiReq(graphSearchTimeline, $variables) url = apiReq(graphSearchTimeline, $variables)
js = await fetch(url) js = await fetch(url)
result = parseGraphSearch[Tweets](js, after) result = parseGraphSearch[Tweets](js, after)
@@ -170,7 +175,7 @@ proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} =
# when no more items are available the API just returns the last page in # when no more items are available the API just returns the last page in
# full. this detects that and clears the page instead. # full. this detects that and clears the page instead.
if after.len > 0 and result.bottom.len > 0 and if after.len > 0 and result.bottom.len > 0 and maxId.len == 0 and
after[0..<64] == result.bottom[0..<64]: after[0..<64] == result.bottom[0..<64]:
result.content.setLen(0) result.content.setLen(0)

View File

@@ -1,7 +1,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
import strutils, strformat, sequtils, tables, uri import strutils, strformat, sequtils, tables, uri
import types import types, utils
const const
validFilters* = @[ validFilters* = @[
@@ -17,11 +17,6 @@ template `@`(param: string): untyped =
if param in pms: pms[param] if param in pms: pms[param]
else: "" else: ""
proc validateNumber(value: string): string =
if value.anyIt(not it.isDigit):
return ""
return value
proc initQuery*(pms: Table[string, string]; name=""): Query = proc initQuery*(pms: Table[string, string]; name=""): Query =
result = Query( result = Query(
kind: parseEnum[QueryKind](@"f", tweets), kind: parseEnum[QueryKind](@"f", tweets),
@@ -50,7 +45,7 @@ proc getReplyQuery*(name: string): Query =
fromUser: @[name] fromUser: @[name]
) )
proc genQueryParam*(query: Query): string = proc genQueryParam*(query: Query; maxId=""): string =
var var
filters: seq[string] filters: seq[string]
param: string param: string
@@ -58,12 +53,15 @@ proc genQueryParam*(query: Query): string =
if query.kind == users: if query.kind == users:
return query.text return query.text
param = "("
for i, user in query.fromUser: for i, user in query.fromUser:
if i == 0:
param = "("
param &= &"from:{user}" param &= &"from:{user}"
if i < query.fromUser.high: if i < query.fromUser.high:
param &= " OR " param &= " OR "
param &= ")" else:
param &= ")"
if query.fromUser.len > 0 and query.kind in {posts, media}: if query.fromUser.len > 0 and query.kind in {posts, media}:
param &= " (filter:self_threads OR -filter:replies)" param &= " (filter:self_threads OR -filter:replies)"
@@ -86,7 +84,7 @@ proc genQueryParam*(query: Query): string =
if query.since.len > 0: if query.since.len > 0:
result &= " since:" & query.since result &= " since:" & query.since
if query.until.len > 0: if query.until.len > 0 and maxId.len == 0:
result &= " until:" & query.until result &= " until:" & query.until
if query.minLikes.len > 0: if query.minLikes.len > 0:
result &= " min_faves:" & query.minLikes result &= " min_faves:" & query.minLikes
@@ -96,6 +94,9 @@ proc genQueryParam*(query: Query): string =
else: else:
result = query.text result = query.text
if result.len > 0 and maxId.len > 0:
result &= " max_id:" & maxId
proc genQueryUrl*(query: Query): string = proc genQueryUrl*(query: Query): string =
if query.kind notin {tweets, users}: return if query.kind notin {tweets, users}: return

View File

@@ -1,5 +1,5 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
import strutils, strformat, uri, tables, base64 import sequtils, strutils, strformat, uri, tables, base64
import nimcrypto import nimcrypto
var var
@@ -59,3 +59,8 @@ proc isTwitterUrl*(uri: Uri): bool =
proc isTwitterUrl*(url: string): bool = proc isTwitterUrl*(url: string): bool =
isTwitterUrl(parseUri(url)) isTwitterUrl(parseUri(url))
proc validateNumber*(value: string): string =
if value.anyIt(not it.isDigit):
return ""
return value

View File

@@ -11,6 +11,23 @@ proc getQuery(query: Query): string =
if result.len > 0: if result.len > 0:
result &= "&" result &= "&"
proc getSearchMaxId(results: Timeline; path: string): string =
if results.query.kind != tweets or results.content.len == 0 or
results.query.until.len == 0:
return
let lastThread = results.content[^1]
if lastThread.len == 0 or lastThread[^1].id == 0:
return
# 2000000 is the minimum decrement to guarantee no result overlap
var maxId = lastThread[^1].id - 2_000_000'i64
if maxId <= 0:
maxId = lastThread[^1].id - 1
if maxId > 0:
return "maxid:" & $maxId
proc renderToTop*(focus="#"): VNode = proc renderToTop*(focus="#"): VNode =
buildHtml(tdiv(class="top-ref")): buildHtml(tdiv(class="top-ref")):
icon "down", href=focus icon "down", href=focus
@@ -122,6 +139,9 @@ proc renderTimelineTweets*(results: Timeline; prefs: Prefs; path: string;
else: else:
renderThread(thread, prefs, path) renderThread(thread, prefs, path)
if results.bottom.len > 0: var cursor = getSearchMaxId(results, path)
if cursor.len > 0:
renderMore(results.query, cursor)
elif results.bottom.len > 0:
renderMore(results.query, results.bottom) renderMore(results.query, results.bottom)
renderToTop() renderToTop()