apiutils.nim 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. # SPDX-License-Identifier: AGPL-3.0-only
  2. import httpclient, asyncdispatch, options, strutils, uri
  3. import jsony, packedjson, zippy
  4. import types, tokens, consts, parserutils, http_pool
  5. import experimental/types/common
  6. const
  7. rlRemaining = "x-rate-limit-remaining"
  8. rlReset = "x-rate-limit-reset"
  9. var pool: HttpPool
  10. proc genParams*(pars: openArray[(string, string)] = @[]; cursor="";
  11. count="20"; ext=true): seq[(string, string)] =
  12. result = timelineParams
  13. for p in pars:
  14. result &= p
  15. if ext:
  16. result &= ("ext", "mediaStats")
  17. result &= ("include_ext_alt_text", "1")
  18. result &= ("include_ext_media_availability", "1")
  19. if count.len > 0:
  20. result &= ("count", count)
  21. if cursor.len > 0:
  22. # The raw cursor often has plus signs, which sometimes get turned into spaces,
  23. # so we need to turn them back into a plus
  24. if " " in cursor:
  25. result &= ("cursor", cursor.replace(" ", "+"))
  26. else:
  27. result &= ("cursor", cursor)
  28. proc genHeaders*(token: Token = nil): HttpHeaders =
  29. result = newHttpHeaders({
  30. "connection": "keep-alive",
  31. "authorization": auth,
  32. "content-type": "application/json",
  33. "x-guest-token": if token == nil: "" else: token.tok,
  34. "x-twitter-active-user": "yes",
  35. "authority": "api.twitter.com",
  36. "accept-encoding": "gzip",
  37. "accept-language": "en-US,en;q=0.9",
  38. "accept": "*/*",
  39. "DNT": "1"
  40. })
  41. template updateToken() =
  42. if resp.headers.hasKey(rlRemaining):
  43. let
  44. remaining = parseInt(resp.headers[rlRemaining])
  45. reset = parseInt(resp.headers[rlReset])
  46. token.setRateLimit(api, remaining, reset)
  47. template fetchImpl(result, fetchBody) {.dirty.} =
  48. once:
  49. pool = HttpPool()
  50. var token = await getToken(api)
  51. if token.tok.len == 0:
  52. raise rateLimitError()
  53. try:
  54. var resp: AsyncResponse
  55. pool.use(genHeaders(token)):
  56. template getContent =
  57. resp = await c.get($url)
  58. result = await resp.body
  59. getContent()
  60. if resp.status == $Http503:
  61. badClient = true
  62. raise newException(BadClientError, "Bad client")
  63. if result.len > 0:
  64. if resp.headers.getOrDefault("content-encoding") == "gzip":
  65. result = uncompress(result, dfGzip)
  66. else:
  67. echo "non-gzip body, url: ", url, ", body: ", result
  68. fetchBody
  69. release(token, used=true)
  70. if resp.status == $Http400:
  71. raise newException(InternalError, $url)
  72. except InternalError as e:
  73. raise e
  74. except BadClientError as e:
  75. release(token, used=true)
  76. raise e
  77. except Exception as e:
  78. echo "error: ", e.name, ", msg: ", e.msg, ", token: ", token[], ", url: ", url
  79. if "length" notin e.msg and "descriptor" notin e.msg:
  80. release(token, invalid=true)
  81. raise rateLimitError()
  82. proc fetch*(url: Uri; api: Api): Future[JsonNode] {.async.} =
  83. var body: string
  84. fetchImpl body:
  85. if body.startsWith('{') or body.startsWith('['):
  86. result = parseJson(body)
  87. else:
  88. echo resp.status, ": ", body, " --- url: ", url
  89. result = newJNull()
  90. updateToken()
  91. let error = result.getError
  92. if error in {invalidToken, badToken}:
  93. echo "fetch error: ", result.getError
  94. release(token, invalid=true)
  95. raise rateLimitError()
  96. proc fetchRaw*(url: Uri; api: Api): Future[string] {.async.} =
  97. fetchImpl result:
  98. if not (result.startsWith('{') or result.startsWith('[')):
  99. echo resp.status, ": ", result, " --- url: ", url
  100. result.setLen(0)
  101. updateToken()
  102. if result.startsWith("{\"errors"):
  103. let errors = result.fromJson(Errors)
  104. if errors in {invalidToken, badToken}:
  105. echo "fetch error: ", errors
  106. release(token, invalid=true)
  107. raise rateLimitError()