| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- import strutils, times, macros, htmlgen, unicode, options
- import regex, packedjson
- import types, utils, formatters
- const
- unRegex = re"(^|[^A-z0-9-_./?])@([A-z0-9_]{1,15})"
- unReplace = "$1<a href=\"/$2\">@$2</a>"
- htRegex = re"(^|[^\w-_./?])([#$])([\w_]+)"
- htReplace = "$1<a href=\"/search?q=%23$3\">$2$3</a>"
- template isNull*(js: JsonNode): bool = js.kind == JNull
- template notNull*(js: JsonNode): bool = js.kind != JNull
- template `?`*(js: JsonNode): untyped =
- let j = js
- if j.isNull: return
- else: j
- template `with`*(ident, value, body): untyped =
- block:
- let ident {.inject.} = value
- if ident != nil: body
- template `with`*(ident; value: JsonNode; body): untyped =
- block:
- let ident {.inject.} = value
- if value.notNull: body
- template getCursor*(js: JsonNode): string =
- js{"content", "operation", "cursor", "value"}.getStr
- template getError*(js: JsonNode): Error =
- if js.kind != JArray or js.len == 0: null
- else: Error(js[0]{"code"}.getInt)
- template parseTime(time: string; f: static string; flen: int): Time =
- if time.len != flen: return
- parse(time, f).toTime
- proc getDateTime*(js: JsonNode): Time =
- parseTime(js.getStr, "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'", 20)
- proc getTime*(js: JsonNode): Time =
- parseTime(js.getStr, "ddd MMM dd hh:mm:ss \'+0000\' yyyy", 30)
- proc getId*(id: string): string {.inline.} =
- let start = id.rfind("-")
- if start < 0: return id
- id[start + 1 ..< id.len]
- proc getId*(js: JsonNode): int64 {.inline.} =
- case js.kind
- of JString: return parseBiggestInt(js.getStr("0"))
- of JInt: return js.getBiggestInt()
- else: return 0
- template getStrVal*(js: JsonNode; default=""): string =
- js{"string_value"}.getStr(default)
- proc getImageStr*(js: JsonNode): string =
- result = js.getStr
- result.removePrefix(https)
- result.removePrefix(twimg)
- template getImageVal*(js: JsonNode): string =
- js{"image_value", "url"}.getImageStr
- proc getCardUrl*(js: JsonNode; kind: CardKind): string =
- result = js{"website_url"}.getStrVal
- if kind == promoVideoConvo:
- result = js{"thank_you_url"}.getStrVal(result)
- if result.startsWith("card://"):
- result = ""
- proc getCardDomain*(js: JsonNode; kind: CardKind): string =
- result = js{"vanity_url"}.getStrVal(js{"domain"}.getStr)
- if kind == promoVideoConvo:
- result = js{"thank_you_vanity_url"}.getStrVal(result)
- proc getCardTitle*(js: JsonNode; kind: CardKind): string =
- result = js{"title"}.getStrVal
- if kind == promoVideoConvo:
- result = js{"thank_you_text"}.getStrVal(result)
- elif kind == liveEvent:
- result = js{"event_category"}.getStrVal
- elif kind in {videoDirectMessage, imageDirectMessage}:
- result = js{"cta1"}.getStrVal
- proc getBanner*(js: JsonNode): string =
- let url = js{"profile_banner_url"}.getImageStr
- if url.len > 0:
- return url & "/1500x500"
- let color = js{"profile_link_color"}.getStr
- if color.len > 0:
- return '#' & color
- # use primary color from profile picture color histrogram
- with p, js{"profile_image_extensions", "mediaColor", "r", "ok", "palette"}:
- if p.len > 0:
- let pal = p[0]{"rgb"}
- result = "#"
- result.add toHex(pal{"red"}.getInt, 2)
- result.add toHex(pal{"green"}.getInt, 2)
- result.add toHex(pal{"blue"}.getInt, 2)
- return
- return "#161616"
- proc getTombstone*(js: JsonNode): string =
- result = js{"tombstoneInfo", "richText", "text"}.getStr
- result.removeSuffix(" Learn more")
- template getSlice(text: string; slice: seq[int]): string =
- text.runeSubStr(slice[0], slice[1] - slice[0])
- proc getSlice(text: string; js: JsonNode): string =
- if js.kind != JArray or js.len < 2 or js[0].kind != JInt: return text
- let slice = @[js{0}.getInt, js{1}.getInt]
- text.getSlice(slice)
- proc expandUrl(text: var string; js: JsonNode; tLen: int; hideTwitter=false) =
- let u = js{"url"}.getStr
- if u.len == 0 or u notin text:
- return
- let
- url = js{"expanded_url"}.getStr
- slice = js{"indices"}[1].getInt
- if hideTwitter and slice >= tLen and url.isTwitterUrl:
- text = text.replace(u, "")
- text.removeSuffix(' ')
- text.removeSuffix('\n')
- else:
- text = text.replace(u, a(shortLink(url), href=url))
- proc expandMention(text: var string; orig: string; js: JsonNode) =
- let
- name = js{"name"}.getStr
- href = '/' & js{"screen_name"}.getStr
- uname = orig.getSlice(js{"indices"})
- text = text.replace(uname, a(uname, href=href, title=name))
- proc expandProfileEntities*(profile: var Profile; js: JsonNode) =
- let
- orig = profile.bio
- ent = ? js{"entities"}
- with urls, ent{"url", "urls"}:
- profile.website = urls[0]{"expanded_url"}.getStr
- with urls, ent{"description", "urls"}:
- for u in urls: profile.bio.expandUrl(u, orig.high)
- profile.bio = profile.bio.replace(unRegex, unReplace)
- .replace(htRegex, htReplace)
- for mention in ? ent{"user_mentions"}:
- profile.bio.expandMention(orig, mention)
- proc expandTweetEntities*(tweet: Tweet; js: JsonNode) =
- let
- orig = tweet.text
- textRange = js{"display_text_range"}
- slice = @[textRange{0}.getInt, textRange{1}.getInt]
- hasQuote = js{"is_quote_status"}.getBool
- hasCard = tweet.card.isSome
- tweet.text = tweet.text.getSlice(slice)
- var replyTo = ""
- if tweet.replyId != 0:
- with reply, js{"in_reply_to_screen_name"}:
- tweet.reply.add reply.getStr
- replyTo = reply.getStr
- let ent = ? js{"entities"}
- with urls, ent{"urls"}:
- for u in urls:
- tweet.text.expandUrl(u, slice[1], hasQuote)
- if hasCard and u{"url"}.getStr == get(tweet.card).url:
- get(tweet.card).url = u{"expanded_url"}.getStr
- with media, ent{"media"}:
- for m in media: tweet.text.expandUrl(m, slice[1], hideTwitter=true)
- if "hashtags" in ent or "symbols" in ent:
- tweet.text = tweet.text.replace(htRegex, htReplace)
- for mention in ? ent{"user_mentions"}:
- let
- name = mention{"screen_name"}.getStr
- idx = tweet.reply.find(name)
- if mention{"indices"}[0].getInt >= slice[0]:
- tweet.text.expandMention(orig, mention)
- if idx > -1 and name != replyTo:
- tweet.reply.delete idx
- elif idx == -1 and tweet.replyId != 0:
- tweet.reply.add name
|