Jelajahi Sumber

Add experimental user search parser

Zed 4 tahun lalu
induk
melakukan
4738ec3385

+ 4 - 1
src/api.nim

@@ -3,6 +3,7 @@ import asyncdispatch, httpclient, uri, strutils, sequtils, sugar
 import packedjson
 import types, query, formatters, consts, apiutils, parser
 import experimental/parser/[user, graphql]
+import experimental/parser/timeline as timelineParser
 
 proc getGraphUser*(id: string): Future[User] {.async.} =
   if id.len == 0 or id.any(c => not c.isDigit): return
@@ -85,10 +86,12 @@ proc getSearch*[T](query: Query; after=""): Future[Result[T]] {.async.} =
     const
       searchMode = ("result_filter", "user")
       parse = parseUsers
+      fetchFunc = fetchRaw
   else:
     const
       searchMode = ("tweet_search_mode", "live")
       parse = parseTimeline
+      fetchFunc = fetch
 
   let q = genQueryParam(query)
   if q.len == 0 or q == emptyQuery:
@@ -96,7 +99,7 @@ proc getSearch*[T](query: Query; after=""): Future[Result[T]] {.async.} =
 
   let url = search ? genParams(searchParams & @[("q", q), searchMode], after)
   try:
-    result = parse(await fetch(url, Api.search), after)
+    result = parse(await fetchFunc(url, Api.search), after)
     result.query = query
   except InternalError:
     return Result[T](beginning: true, query: query)

+ 28 - 0
src/experimental/parser/timeline.nim

@@ -0,0 +1,28 @@
+import std/[strutils, tables]
+import jsony
+import user, ../types/timeline
+from ../../types import Result, User
+
+proc getId(id: string): string {.inline.} =
+  let start = id.rfind("-")
+  if start < 0: return id
+  id[start + 1 ..< id.len]
+
+proc parseUsers*(json: string; after=""): Result[User] =
+  result = Result[User](beginning: after.len == 0)
+
+  let raw = json.fromJson(Search)
+  if raw.timeline.instructions.len == 0:
+    return
+
+  for e in raw.timeline.instructions[0].addEntries.entries:
+    let id = e.entryId.getId
+    if e.entryId.startsWith("user"):
+      if id in raw.globalObjects.users:
+        result.content.add toUser raw.globalObjects.users[id]
+    elif e.entryId.startsWith("cursor"):
+      let cursor = e.content.operation.cursor
+      if cursor.cursorType == "Top":
+        result.top = cursor.value
+      elif cursor.cursorType == "Bottom":
+        result.bottom = cursor.value

+ 6 - 4
src/experimental/parser/user.nim

@@ -1,4 +1,4 @@
-import std/[algorithm, unicode, re, strutils, strformat]
+import std/[algorithm, unicode, re, strutils, strformat, options]
 import jsony
 import utils, slices
 import ../types/user as userType
@@ -38,9 +38,11 @@ proc getBanner(user: RawUser): string =
   if user.profileLinkColor.len > 0:
     return '#' & user.profileLinkColor
 
-  if user.profileImageExtensions.mediaColor.r.ok.palette.len > 0:
-    let color = user.profileImageExtensions.mediaColor.r.ok.palette[0].rgb
-    return &"#{color.red:02x}{color.green:02x}{color.blue:02x}"
+  if user.profileImageExtensions.isSome:
+    let ext = get(user.profileImageExtensions)
+    if ext.mediaColor.r.ok.palette.len > 0:
+      let color = ext.mediaColor.r.ok.palette[0].rgb
+      return &"#{color.red:02x}{color.green:02x}{color.blue:02x}"
 
 proc toUser*(raw: RawUser): User =
   result = User(

+ 23 - 0
src/experimental/types/timeline.nim

@@ -0,0 +1,23 @@
+import std/tables
+import user
+
+type
+  Search* = object
+    globalObjects*: GlobalObjects
+    timeline*: Timeline
+
+  GlobalObjects = object
+    users*: Table[string, RawUser]
+
+  Timeline = object
+    instructions*: seq[Instructions]
+
+  Instructions = object
+    addEntries*: tuple[entries: seq[Entry]]
+
+  Entry = object
+    entryId*: string
+    content*: tuple[operation: Operation]
+
+  Operation = object
+    cursor*: tuple[value, cursorType: string]

+ 3 - 2
src/experimental/types/user.nim

@@ -1,3 +1,4 @@
+import options
 import common
 
 type
@@ -16,10 +17,10 @@ type
     mediaCount*: int
     verified*: bool
     protected*: bool
+    profileLinkColor*: string
     profileBannerUrl*: string
     profileImageUrlHttps*: string
-    profileImageExtensions*: ImageExtensions
-    profileLinkColor*: string
+    profileImageExtensions*: Option[ImageExtensions]
     pinnedTweetIdsStr*: seq[string]
 
   Entities* = object

+ 0 - 20
src/parser.nim

@@ -366,26 +366,6 @@ proc parseInstructions[T](res: var Result[T]; global: GlobalObjects; js: JsonNod
       elif "bottom" in r{"entryId"}.getStr:
         res.bottom = r.getCursor
 
-proc parseUsers*(js: JsonNode; after=""): Result[User] =
-  result = Result[User](beginning: after.len == 0)
-  let global = parseGlobalObjects(? js)
-
-  let instructions = ? js{"timeline", "instructions"}
-  if instructions.len == 0: return
-
-  result.parseInstructions(global, instructions)
-
-  for e in instructions[0]{"addEntries", "entries"}:
-    let entry = e{"entryId"}.getStr
-    if "user-" in entry:
-      let id = entry.getId
-      if id in global.users:
-        result.content.add global.users[id]
-    elif "cursor-top" in entry:
-      result.top = e.getCursor
-    elif "cursor-bottom" in entry:
-      result.bottom = e.getCursor
-
 proc parseTimeline*(js: JsonNode; after=""): Timeline =
   result = Timeline(beginning: after.len == 0)
   let global = parseGlobalObjects(? js)

+ 9 - 5
src/sass/profile/_base.scss

@@ -42,12 +42,16 @@
     top: 50px;
 }
 
-.profile-result .username {
-    margin: 0 !important;
-}
+.profile-result {
+    min-height: 54px;
+
+    .username {
+        margin: 0 !important;
+    }
 
-.profile-result .tweet-header {
-    margin-bottom: unset;
+    .tweet-header {
+        margin-bottom: unset;
+    }
 }
 
 @media(max-width: 700px) {

+ 1 - 1
src/views/general.nim

@@ -52,7 +52,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=15")
+    link(rel="stylesheet", type="text/css", href="/css/style.css?v=16")
     link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=2")
 
     if theme.len > 0: