From 4738ec33854f46c0c23a1181fbbdac291bb9a5a3 Mon Sep 17 00:00:00 2001 From: Zed Date: Wed, 26 Jan 2022 20:27:11 +0100 Subject: [PATCH] Add experimental user search parser --- src/api.nim | 5 ++++- src/experimental/parser/timeline.nim | 28 ++++++++++++++++++++++++++++ src/experimental/parser/user.nim | 10 ++++++---- src/experimental/types/timeline.nim | 23 +++++++++++++++++++++++ src/experimental/types/user.nim | 5 +++-- src/parser.nim | 20 -------------------- src/sass/profile/_base.scss | 14 +++++++++----- src/views/general.nim | 2 +- 8 files changed, 74 insertions(+), 33 deletions(-) create mode 100644 src/experimental/parser/timeline.nim create mode 100644 src/experimental/types/timeline.nim diff --git a/src/api.nim b/src/api.nim index 8fdaf3d..f421bb7 100644 --- a/src/api.nim +++ b/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) diff --git a/src/experimental/parser/timeline.nim b/src/experimental/parser/timeline.nim new file mode 100644 index 0000000..351ca85 --- /dev/null +++ b/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 diff --git a/src/experimental/parser/user.nim b/src/experimental/parser/user.nim index b9aaa3c..dc760f0 100644 --- a/src/experimental/parser/user.nim +++ b/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( diff --git a/src/experimental/types/timeline.nim b/src/experimental/types/timeline.nim new file mode 100644 index 0000000..28239ad --- /dev/null +++ b/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] diff --git a/src/experimental/types/user.nim b/src/experimental/types/user.nim index 430bc8e..1c8a5c3 100644 --- a/src/experimental/types/user.nim +++ b/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 diff --git a/src/parser.nim b/src/parser.nim index 580f449..ae5e505 100644 --- a/src/parser.nim +++ b/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) diff --git a/src/sass/profile/_base.scss b/src/sass/profile/_base.scss index ae6b801..b7f33e6 100644 --- a/src/sass/profile/_base.scss +++ b/src/sass/profile/_base.scss @@ -42,12 +42,16 @@ top: 50px; } -.profile-result .username { - margin: 0 !important; -} +.profile-result { + min-height: 54px; -.profile-result .tweet-header { - margin-bottom: unset; + .username { + margin: 0 !important; + } + + .tweet-header { + margin-bottom: unset; + } } @media(max-width: 700px) { diff --git a/src/views/general.nim b/src/views/general.nim index 7054fd4..82902d4 100644 --- a/src/views/general.nim +++ b/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: