api.nim 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import httpclient, asyncdispatch, htmlparser, times
  2. import sequtils, strutils, strformat, json, xmltree, uri
  3. import nimquery, regex
  4. import ./types, ./parser
  5. const
  6. base = parseUri("https://twitter.com/")
  7. agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
  8. timelineUrl = "i/profiles/show/$1/timeline/tweets?include_available_features=1&include_entities=1&include_new_items_bar=true"
  9. profilePopupUrl = "i/profiles/popup"
  10. profileIntentUrl = "intent/user"
  11. tweetUrl = "i/status/"
  12. proc fetchHtml(url: Uri; headers: HttpHeaders; jsonKey = ""): Future[XmlNode] {.async.} =
  13. var client = newAsyncHttpClient()
  14. defer: client.close()
  15. client.headers = headers
  16. var resp = ""
  17. try:
  18. resp = await client.getContent($url)
  19. except:
  20. return nil
  21. if jsonKey.len > 0:
  22. let json = parseJson(resp)[jsonKey].str
  23. return parseHtml(json)
  24. else:
  25. return parseHtml(resp)
  26. proc getProfileFallback(username: string; headers: HttpHeaders): Future[Profile] {.async.} =
  27. let
  28. url = base / profileIntentUrl ? {"screen_name": username}
  29. html = await fetchHtml(url, headers)
  30. result = parseIntentProfile(html)
  31. proc getProfile*(username: string): Future[Profile] {.async.} =
  32. let headers = newHttpHeaders({
  33. "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9",
  34. "Referer": $(base / username),
  35. "User-Agent": agent,
  36. "X-Twitter-Active-User": "yes",
  37. "X-Requested-With": "XMLHttpRequest",
  38. "Accept-Language": "en-US,en;q=0.9"
  39. })
  40. let
  41. params = {
  42. "screen_name": username,
  43. "wants_hovercard": "true",
  44. "_": $(epochTime().int)
  45. }
  46. url = base / profilePopupUrl ? params
  47. html = await fetchHtml(url, headers, jsonKey="html")
  48. if not html.querySelector(".ProfileCard-sensitiveWarningContainer").isNil:
  49. return await getProfileFallback(username, headers)
  50. result = parsePopupProfile(html)
  51. proc getTimeline*(username: string; after=""): Future[Tweets] {.async.} =
  52. let headers = newHttpHeaders({
  53. "Accept": "application/json, text/javascript, */*; q=0.01",
  54. "Referer": $(base / username),
  55. "User-Agent": agent,
  56. "X-Twitter-Active-User": "yes",
  57. "X-Requested-With": "XMLHttpRequest",
  58. "Accept-Language": "en-US,en;q=0.9"
  59. })
  60. var url = timelineUrl % username
  61. if after.len > 0:
  62. url &= "&max_position=" & after
  63. let html = await fetchHtml(base / url, headers, jsonKey="items_html")
  64. result = parseTweets(html)
  65. proc getTweet*(id: string): Future[Conversation] {.async.} =
  66. let headers = newHttpHeaders({
  67. "Accept": "application/json, text/javascript, */*; q=0.01",
  68. "Referer": $base,
  69. "User-Agent": agent,
  70. "X-Twitter-Active-User": "yes",
  71. "X-Requested-With": "XMLHttpRequest",
  72. "Accept-Language": "en-US,en;q=0.9",
  73. "pragma": "no-cache",
  74. "x-previous-page-name": "profile"
  75. })
  76. let
  77. url = base / tweetUrl / id
  78. html = await fetchHtml(url, headers)
  79. result = parseConversation(html)