parserutils.nim 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import xmltree, htmlparser, strtabs, strformat, times
  2. import regex
  3. import ./types, ./formatters, ./api
  4. from q import nil
  5. const
  6. thumbRegex = re".+:url\('([^']+)'\)"
  7. gifRegex = re".+thumb/([^\.']+)\.jpg.*"
  8. proc selectAll*(node: XmlNode; selector: string): seq[XmlNode] =
  9. if node == nil: return
  10. q.select(node, selector)
  11. proc select*(node: XmlNode; selector: string): XmlNode =
  12. if node == nil: return
  13. let nodes = node.selectAll(selector)
  14. if nodes.len > 0: nodes[0] else: nil
  15. proc selectAttr*(node: XmlNode; selector: string; attr: string): string =
  16. let res = node.select(selector)
  17. if res == nil: "" else: res.attr(attr)
  18. proc selectText*(node: XmlNode; selector: string): string =
  19. let res = node.select(selector)
  20. result = if res == nil: "" else: res.innerText()
  21. proc getHeader(profile: XmlNode): XmlNode =
  22. result = profile.select(".permalink-header")
  23. if result == nil:
  24. result = profile.select(".stream-item-header")
  25. if result == nil:
  26. result = profile.select(".ProfileCard-userFields")
  27. proc isVerified*(profile: XmlNode): bool =
  28. getHeader(profile).select(".Icon.Icon--verified") != nil
  29. proc isProtected*(profile: XmlNode): bool =
  30. getHeader(profile).select(".Icon.Icon--protected") != nil
  31. proc getName*(profile: XmlNode; selector: string): string =
  32. profile.selectText(selector).stripText()
  33. proc getUsername*(profile: XmlNode; selector: string): string =
  34. profile.selectText(selector).strip(chars={'@', ' '})
  35. proc emojify*(node: XmlNode) =
  36. for i in node.selectAll(".Emoji"):
  37. i.add newText(i.attr("alt"))
  38. proc getQuoteText*(tweet: XmlNode): string =
  39. let text = tweet.select(".QuoteTweet-text")
  40. emojify(text)
  41. result = stripText(text.innerText())
  42. result = stripTwitterUrls(result)
  43. proc getTweetText*(tweet: XmlNode): string =
  44. let
  45. quote = tweet.select(".QuoteTweet")
  46. text = tweet.select(".tweet-text")
  47. link = text.selectAttr("a.twitter-timeline-link.u-hidden", "data-expanded-url")
  48. emojify(text)
  49. result = stripText(text.innerText())
  50. if quote != nil and link.len > 0:
  51. result = result.replace(link, "")
  52. result = stripTwitterUrls(result)
  53. proc getTime(tweet: XmlNode): XmlNode =
  54. tweet.select(".js-short-timestamp")
  55. proc getTimestamp*(tweet: XmlNode): Time =
  56. let time = getTime(tweet).attr("data-time")
  57. fromUnix(if time.len > 0: parseInt(time) else: 0)
  58. proc getShortTime*(tweet: XmlNode): string =
  59. getTime(tweet).innerText()
  60. proc getBio*(profile: XmlNode; selector: string): string =
  61. profile.selectText(selector).stripText()
  62. proc getAvatar*(profile: XmlNode; selector: string): string =
  63. profile.selectAttr(selector, "src").getUserpic()
  64. proc getBanner*(tweet: XmlNode): string =
  65. let url = tweet.selectAttr("svg > image", "xlink:href")
  66. if url.len > 0:
  67. result = url.replace("600x200", "1500x500")
  68. else:
  69. result = tweet.selectAttr(".ProfileCard-bg", "style")
  70. if result.len == 0:
  71. result = "background-color: #161616"
  72. proc getPopupStats*(profile: var Profile; node: XmlNode) =
  73. for s in node.selectAll( ".ProfileCardStats-statLink"):
  74. let text = s.attr("title").split(" ")[0]
  75. case s.attr("href").split("/")[^1]
  76. of "followers": profile.followers = text
  77. of "following": profile.following = text
  78. else: profile.tweets = text
  79. proc getIntentStats*(profile: var Profile; node: XmlNode) =
  80. profile.tweets = "?"
  81. for s in node.selectAll( "dd.count > a"):
  82. let text = s.innerText()
  83. case s.attr("href").split("/")[^1]
  84. of "followers": profile.followers = text
  85. of "following": profile.following = text
  86. proc parseTweetStats*(node: XmlNode): TweetStats =
  87. result = TweetStats(replies: "0", retweets: "0", likes: "0")
  88. for action in node.selectAll(".ProfileTweet-actionCountForAria"):
  89. let text = action.innerText.split()
  90. case text[1][0 .. 2]
  91. of "ret": result.retweets = text[0]
  92. of "rep": result.replies = text[0]
  93. of "lik": result.likes = text[0]
  94. proc parseTweetReply*(node: XmlNode): seq[string] =
  95. let reply = node.select(".ReplyingToContextBelowAuthor")
  96. if reply == nil: return
  97. let selector = if "Quote" in node.attr("class"): "b"
  98. else: "a b"
  99. for username in reply.selectAll(selector):
  100. result.add username.innerText()
  101. proc getGif(player: XmlNode): Gif =
  102. let
  103. thumb = player.attr("style").replace(thumbRegex, "$1")
  104. id = thumb.replace(gifRegex, "$1")
  105. url = &"https://video.twimg.com/tweet_video/{id}.mp4"
  106. Gif(url: url, thumb: thumb)
  107. proc getTweetMedia*(tweet: Tweet; node: XmlNode) =
  108. for photo in node.selectAll(".AdaptiveMedia-photoContainer"):
  109. tweet.photos.add photo.attrs["data-image-url"]
  110. let player = node.select(".PlayableMedia")
  111. if player == nil: return
  112. if "gif" in player.attr("class"):
  113. tweet.gif = some(getGif(player.select(".PlayableMedia-player")))
  114. elif "video" in player.attr("class"):
  115. tweet.video = some(Video())
  116. proc getQuoteMedia*(quote: var Quote; node: XmlNode) =
  117. if node.select(".QuoteTweet--sensitive") != nil:
  118. quote.sensitive = true
  119. return
  120. let media = node.select(".QuoteMedia")
  121. if media != nil:
  122. quote.thumb = media.selectAttr("img", "src")
  123. let badge = node.select(".AdaptiveMedia-badgeText")
  124. let gifBadge = node.select(".Icon--gifBadge")
  125. if badge != nil:
  126. quote.badge = badge.innerText()
  127. elif gifBadge != nil:
  128. quote.badge = "GIF"
  129. proc getTweetCards*(tweet: Tweet; node: XmlNode) =
  130. if node.attr("data-has-cards") == "false": return
  131. if "poll" in node.attr("data-card2-type"):
  132. tweet.poll = some(Poll())
  133. proc getMoreReplies*(node: XmlNode): int =
  134. let text = node.innerText().strip()
  135. try:
  136. result = parseInt(text.split(" ")[0])
  137. except:
  138. result = -1