import httpclient, asyncdispatch, times, sequtils, strutils, json, uri import ".."/[types, parser, formatters] 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: Thread | Timeline; agent: string; token="") {.async.} = 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) futs.add convo.replies.mapIt(`multi`(it, agent, token=token)) else: futs.add `single`(convo.tweet, agent) futs.add `multi`(convo.before, agent) futs.add `multi`(convo.after, agent) futs.add convo.replies.mapIt(`multi`(it, agent)) 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 let headers = newHttpHeaders({ "Accept": jsonAccept, "Referer": $base, "User-Agent": agent, "Authorization": auth }) newClient() let url = apiBase / tokenUrl json = parseJson(await client.postContent($url)) result = json["guest_token"].to(string) guestToken = result proc getVideoFetch(tweet: Tweet; agent, token: string) {.async.} = if tweet.video.isNone(): return let headers = newHttpHeaders({ "Accept": jsonAccept, "Referer": $(base / getLink(tweet)), "User-Agent": agent, "Authorization": auth, "x-guest-token": token }) let url = apiBase / (videoUrl % tweet.id) let json = await fetchJson(url, headers) if json == nil: if getTime() - tokenUpdated > initDuration(seconds=1): tokenUpdated = getTime() discard await getGuestToken(agent, force=true) await getVideoFetch(tweet, agent, guestToken) return if tweet.card.isNone: tweet.video = some(parseVideo(json, tweet.id)) else: get(tweet.card).video = some(parseVideo(json, tweet.id)) tweet.video = none(Video) tokenUses.inc proc getVideoVar(tweet: Tweet): var Option[Video] = if tweet.card.isSome(): return get(tweet.card).video else: return tweet.video proc getVideo*(tweet: Tweet; agent, token: string; force=false) {.async.} = withCustomDb("cache.db", "", "", ""): try: getVideoVar(tweet) = some(Video.getOne("videoId = ?", tweet.id)) except KeyError: await getVideoFetch(tweet, agent, token) var video = getVideoVar(tweet) if video.isSome(): get(video).insert() proc getPoll*(tweet: Tweet; agent: string) {.async.} = if tweet.poll.isNone(): return let headers = newHttpHeaders({ "Accept": htmlAccept, "Referer": $(base / getLink(tweet)), "User-Agent": agent, "Authority": "twitter.com", "Accept-Language": lang, }) let url = base / (pollUrl % tweet.id) let html = await fetchHtml(url, headers) if html == nil: return tweet.poll = some(parsePoll(html)) proc getCard*(tweet: Tweet; agent: string) {.async.} = if tweet.card.isNone(): return let headers = newHttpHeaders({ "Accept": htmlAccept, "Referer": $(base / getLink(tweet)), "User-Agent": agent, "Authority": "twitter.com", "Accept-Language": lang, }) let query = get(tweet.card).query.replace("sensitive=true", "sensitive=false") let html = await fetchHtml(base / query, headers) if html == nil: return parseCard(get(tweet.card), html) proc getPhotoRail*(username, agent: string): Future[seq[GalleryPhoto]] {.async.} = let headers = newHttpHeaders({ "Accept": jsonAccept, "Referer": $(base / username), "User-Agent": agent, "X-Requested-With": "XMLHttpRequest" }) let params = { "for_photo_rail": "true", "oldest_unread_id": "0" } let url = base / (timelineMediaUrl % username) ? params let html = await fetchHtml(url, headers, jsonKey="items_html") result = parsePhotoRail(html) genMediaGet(video, token=true) genMediaGet(poll) genMediaGet(card)