nitter/src/api/media.nim

151 lines
4.8 KiB
Nim
Raw Normal View History

2019-10-26 13:34:30 +00:00
import httpclient, asyncdispatch, times, sequtils, strutils, json, uri
import macros, options
2019-09-06 01:37:12 +00:00
2019-10-26 13:33:38 +00:00
import ".."/[types, parser, formatters, cache]
2019-09-06 01:37:12 +00:00
import utils, consts
var
guestToken = ""
tokenUses = 0
tokenMaxUses = 230
tokenUpdated: Time
tokenLifetime = initDuration(minutes=20)
macro genMediaGet(media: untyped; token=false) =
let
mediaName = capitalizeAscii($media)
multi = ident("get" & mediaName & "s")
convo = ident("getConversation" & mediaName & "s")
single = ident("get" & mediaName)
quote do:
proc `multi`*(thread: Chain | Timeline; agent: string; token="") {.async.} =
2019-09-06 01:37:12 +00:00
if thread == nil: return
var `media` = thread.content.filterIt(it.`media`.isSome)
when `token`:
var gToken = token
if gToken.len == 0: gToken = await getGuestToken(agent)
await all(`media`.mapIt(`single`(it, token, agent)))
else:
await all(`media`.mapIt(`single`(it, agent)))
proc `convo`*(convo: Conversation; agent: string) {.async.} =
var futs: seq[Future[void]]
when `token`:
var token = await getGuestToken(agent)
futs.add `single`(convo.tweet, agent, token)
futs.add `multi`(convo.before, agent, token=token)
futs.add `multi`(convo.after, agent, token=token)
if convo.replies != nil:
futs.add convo.replies.content.mapIt(`multi`(it, agent, token=token))
2019-09-06 01:37:12 +00:00
else:
futs.add `single`(convo.tweet, agent)
futs.add `multi`(convo.before, agent)
futs.add `multi`(convo.after, agent)
if convo.replies != nil:
futs.add convo.replies.content.mapIt(`multi`(it, agent))
2019-09-06 01:37:12 +00:00
await all(futs)
proc getGuestToken(agent: string; force=false): Future[string] {.async.} =
if getTime() - tokenUpdated < tokenLifetime and
not force and tokenUses < tokenMaxUses:
return guestToken
tokenUpdated = getTime()
tokenUses = 0
2019-10-06 14:08:39 +00:00
let headers = genHeaders({"authorization": auth}, agent, base, lang=false)
newClient()
2020-01-10 17:18:22 +00:00
var res: string
try: res = await client.postContent($(apiBase / tokenUrl))
except: return
let json = parseJson(res)
2019-09-06 01:37:12 +00:00
2019-10-02 08:13:17 +00:00
if json != nil:
result = json["guest_token"].to(string)
guestToken = result
2019-09-06 01:37:12 +00:00
2019-10-26 13:33:38 +00:00
proc getVideoVar(tweet: Tweet): var Option[Video] =
if tweet.card.isSome():
return get(tweet.card).video
else:
return tweet.video
proc getVideoFetch(tweet: Tweet; agent, token: string; retry=true): Future[Option[Video]] {.async.} =
2019-09-06 01:37:12 +00:00
if tweet.video.isNone(): return
2019-10-02 08:13:17 +00:00
let
headers = genHeaders({"authorization": auth, "x-guest-token": token},
2019-10-22 07:17:58 +00:00
agent, base / getLink(tweet, focus=false), lang=false)
url = apiBase / (videoUrl % $tweet.id)
2019-10-02 08:13:17 +00:00
json = await fetchJson(url, headers)
2019-09-06 01:37:12 +00:00
if json == nil:
if not retry: return
2019-09-06 01:37:12 +00:00
if getTime() - tokenUpdated > initDuration(seconds=1):
tokenUpdated = getTime()
discard await getGuestToken(agent, force=true)
return await getVideoFetch(tweet, agent, guestToken, retry=false)
2019-09-06 01:37:12 +00:00
2019-10-26 13:33:38 +00:00
var video = parseVideo(json, tweet.id)
video.title = get(tweet.video).title
video.description = get(tweet.video).description
cache(video)
2019-09-06 01:37:12 +00:00
2019-10-26 13:33:38 +00:00
result = some video
tokenUses.inc
2019-09-06 01:37:12 +00:00
proc videoIsInvalid(video: Video): bool =
not video.available and video.url.len == 0
2019-09-06 01:37:12 +00:00
proc getVideo*(tweet: Tweet; agent, token: string; force=false) {.async.} =
2019-12-06 14:15:56 +00:00
let token = if token.len == 0: guestToken else: token
2019-10-26 13:33:38 +00:00
var video = getCachedVideo(tweet.id)
if video.isNone:
video = await getVideoFetch(tweet, agent, token)
elif videoIsInvalid(get(video)) and tweet.gif.isSome:
# gif was mistakenly parsed as a gif
uncache(tweet.id)
return
2019-10-26 13:33:38 +00:00
getVideoVar(tweet) = video
if tweet.card.isSome: tweet.video = none Video
2019-09-06 01:37:12 +00:00
proc getPoll*(tweet: Tweet; agent: string) {.async.} =
if tweet.poll.isNone(): return
2019-10-02 08:13:17 +00:00
let
2019-10-22 07:17:58 +00:00
headers = genHeaders(agent, base / getLink(tweet, focus=false), auth=true)
url = base / (pollUrl % $tweet.id)
2019-10-02 08:13:17 +00:00
html = await fetchHtml(url, headers)
2019-09-06 01:37:12 +00:00
if html == nil: return
2019-09-18 18:54:07 +00:00
tweet.poll = some parsePoll(html)
2019-09-06 01:37:12 +00:00
proc getCard*(tweet: Tweet; agent: string) {.async.} =
if tweet.card.isNone(): return
2019-10-02 08:13:17 +00:00
let
2019-10-22 07:17:58 +00:00
headers = genHeaders(agent, base / getLink(tweet, focus=false), auth=true)
2019-10-02 08:13:17 +00:00
query = get(tweet.card).query.replace("sensitive=true", "sensitive=false")
html = await fetchHtml(base / query, headers)
2019-09-06 01:37:12 +00:00
if html == nil: return
parseCard(get(tweet.card), html)
proc getPhotoRail*(username, agent: string; skip=false): Future[seq[GalleryPhoto]] {.async.} =
if skip: return
2019-10-02 08:13:17 +00:00
let
headers = genHeaders(agent, base / username, xml=true)
2019-10-02 08:13:17 +00:00
params = {"for_photo_rail": "true", "oldest_unread_id": "0"}
url = base / (timelineMediaUrl % username) ? params
html = await fetchHtml(url, headers, jsonKey="items_html")
2019-09-06 01:37:12 +00:00
result = parsePhotoRail(html)
genMediaGet(video, token=true)
genMediaGet(poll)
genMediaGet(card)