Add experimental user search parser
This commit is contained in:
parent
49a2fbb070
commit
4738ec3385
8 changed files with 74 additions and 33 deletions
|
@ -3,6 +3,7 @@ import asyncdispatch, httpclient, uri, strutils, sequtils, sugar
|
||||||
import packedjson
|
import packedjson
|
||||||
import types, query, formatters, consts, apiutils, parser
|
import types, query, formatters, consts, apiutils, parser
|
||||||
import experimental/parser/[user, graphql]
|
import experimental/parser/[user, graphql]
|
||||||
|
import experimental/parser/timeline as timelineParser
|
||||||
|
|
||||||
proc getGraphUser*(id: string): Future[User] {.async.} =
|
proc getGraphUser*(id: string): Future[User] {.async.} =
|
||||||
if id.len == 0 or id.any(c => not c.isDigit): return
|
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
|
const
|
||||||
searchMode = ("result_filter", "user")
|
searchMode = ("result_filter", "user")
|
||||||
parse = parseUsers
|
parse = parseUsers
|
||||||
|
fetchFunc = fetchRaw
|
||||||
else:
|
else:
|
||||||
const
|
const
|
||||||
searchMode = ("tweet_search_mode", "live")
|
searchMode = ("tweet_search_mode", "live")
|
||||||
parse = parseTimeline
|
parse = parseTimeline
|
||||||
|
fetchFunc = fetch
|
||||||
|
|
||||||
let q = genQueryParam(query)
|
let q = genQueryParam(query)
|
||||||
if q.len == 0 or q == emptyQuery:
|
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)
|
let url = search ? genParams(searchParams & @[("q", q), searchMode], after)
|
||||||
try:
|
try:
|
||||||
result = parse(await fetch(url, Api.search), after)
|
result = parse(await fetchFunc(url, Api.search), after)
|
||||||
result.query = query
|
result.query = query
|
||||||
except InternalError:
|
except InternalError:
|
||||||
return Result[T](beginning: true, query: query)
|
return Result[T](beginning: true, query: query)
|
||||||
|
|
28
src/experimental/parser/timeline.nim
Normal file
28
src/experimental/parser/timeline.nim
Normal file
|
@ -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 jsony
|
||||||
import utils, slices
|
import utils, slices
|
||||||
import ../types/user as userType
|
import ../types/user as userType
|
||||||
|
@ -38,9 +38,11 @@ proc getBanner(user: RawUser): string =
|
||||||
if user.profileLinkColor.len > 0:
|
if user.profileLinkColor.len > 0:
|
||||||
return '#' & user.profileLinkColor
|
return '#' & user.profileLinkColor
|
||||||
|
|
||||||
if user.profileImageExtensions.mediaColor.r.ok.palette.len > 0:
|
if user.profileImageExtensions.isSome:
|
||||||
let color = user.profileImageExtensions.mediaColor.r.ok.palette[0].rgb
|
let ext = get(user.profileImageExtensions)
|
||||||
return &"#{color.red:02x}{color.green:02x}{color.blue:02x}"
|
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 =
|
proc toUser*(raw: RawUser): User =
|
||||||
result = User(
|
result = User(
|
||||||
|
|
23
src/experimental/types/timeline.nim
Normal file
23
src/experimental/types/timeline.nim
Normal file
|
@ -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
|
import common
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -16,10 +17,10 @@ type
|
||||||
mediaCount*: int
|
mediaCount*: int
|
||||||
verified*: bool
|
verified*: bool
|
||||||
protected*: bool
|
protected*: bool
|
||||||
|
profileLinkColor*: string
|
||||||
profileBannerUrl*: string
|
profileBannerUrl*: string
|
||||||
profileImageUrlHttps*: string
|
profileImageUrlHttps*: string
|
||||||
profileImageExtensions*: ImageExtensions
|
profileImageExtensions*: Option[ImageExtensions]
|
||||||
profileLinkColor*: string
|
|
||||||
pinnedTweetIdsStr*: seq[string]
|
pinnedTweetIdsStr*: seq[string]
|
||||||
|
|
||||||
Entities* = object
|
Entities* = object
|
||||||
|
|
|
@ -366,26 +366,6 @@ proc parseInstructions[T](res: var Result[T]; global: GlobalObjects; js: JsonNod
|
||||||
elif "bottom" in r{"entryId"}.getStr:
|
elif "bottom" in r{"entryId"}.getStr:
|
||||||
res.bottom = r.getCursor
|
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 =
|
proc parseTimeline*(js: JsonNode; after=""): Timeline =
|
||||||
result = Timeline(beginning: after.len == 0)
|
result = Timeline(beginning: after.len == 0)
|
||||||
let global = parseGlobalObjects(? js)
|
let global = parseGlobalObjects(? js)
|
||||||
|
|
|
@ -42,12 +42,16 @@
|
||||||
top: 50px;
|
top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-result .username {
|
.profile-result {
|
||||||
margin: 0 !important;
|
min-height: 54px;
|
||||||
}
|
|
||||||
|
|
||||||
.profile-result .tweet-header {
|
.username {
|
||||||
margin-bottom: unset;
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-header {
|
||||||
|
margin-bottom: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: 700px) {
|
@media(max-width: 700px) {
|
||||||
|
|
|
@ -52,7 +52,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
||||||
let opensearchUrl = getUrlPrefix(cfg) & "/opensearch"
|
let opensearchUrl = getUrlPrefix(cfg) & "/opensearch"
|
||||||
|
|
||||||
buildHtml(head):
|
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")
|
link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=2")
|
||||||
|
|
||||||
if theme.len > 0:
|
if theme.len > 0:
|
||||||
|
|
Loading…
Reference in a new issue