Add experimental user search parser

このコミットが含まれているのは:
Zed 2022-01-26 20:27:11 +01:00
コミット 4738ec3385
8個のファイルの変更74行の追加33行の削除

ファイルの表示

@ -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
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

ファイルの表示

@ -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
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]

ファイルの表示

@ -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

ファイルの表示

@ -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)

ファイルの表示

@ -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) {

ファイルの表示

@ -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: