Bläddra i källkod

Add workaround for broken "until" search filter

Fixes #1372
Zed 5 månader sedan
förälder
incheckning
91ff936cb3
4 ändrade filer med 48 tillägg och 17 borttagningar
  1. 10 5
      src/api.nim
  2. 11 10
      src/query.nim
  3. 6 1
      src/utils.nim
  4. 21 1
      src/views/timeline.nim

+ 10 - 5
src/api.nim

@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 import asyncdispatch, httpclient, strutils, sequtils, sugar
 import packedjson
-import types, query, formatters, consts, apiutils, parser
+import types, query, formatters, consts, apiutils, parser, utils
 import experimental/parser as newParser
 
 # Helper to generate params object for GraphQL requests
@@ -146,7 +146,12 @@ proc getGraphEditHistory*(id: string): Future[EditHistory] {.async.} =
   result = parseGraphEditHistory(js, id)
 
 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:
     return Timeline(query: query, beginning: true)
 
@@ -160,9 +165,9 @@ proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} =
       "withReactionsMetadata": false,
       "withReactionsPerspective": false
     }
-  if after.len > 0:
+  if after.len > 0 and maxId.len == 0:
     variables["cursor"] = % after
-  let 
+  let
     url = apiReq(graphSearchTimeline, $variables)
     js = await fetch(url)
   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
   # 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]:
     result.content.setLen(0)
 

+ 11 - 10
src/query.nim

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

+ 6 - 1
src/utils.nim

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

+ 21 - 1
src/views/timeline.nim

@@ -11,6 +11,23 @@ proc getQuery(query: Query): string =
   if result.len > 0:
     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 =
   buildHtml(tdiv(class="top-ref")):
     icon "down", href=focus
@@ -122,6 +139,9 @@ proc renderTimelineTweets*(results: Timeline; prefs: Prefs; path: string;
         else:
           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)
       renderToTop()