From a67d27e0c42dce7be46c590a9f70e36b1aa27127 Mon Sep 17 00:00:00 2001 From: Zed Date: Tue, 2 Jul 2019 00:52:50 +0200 Subject: [PATCH] Support "Replying to" and "Show thread" --- public/style.css | 5 +++ src/parser.nim | 8 +++- src/parserutils.nim | 14 +++++-- src/types.nim | 10 +++-- src/views/tweet.nimf | 89 +++++++++++++++++++++++++++----------------- 5 files changed, 83 insertions(+), 43 deletions(-) diff --git a/public/style.css b/public/style.css index 45d89b1..4e6ed68 100644 --- a/public/style.css +++ b/public/style.css @@ -138,6 +138,11 @@ a:hover { margin-left: 4px; } +.replying-to { + color: hsla(240,1%,73%,.7); + margin: 4px 0; +} + .status-el .status-content { font-family: sans-serif; line-height: 1.4em; diff --git a/src/parser.nim b/src/parser.nim index b6decbc..0f6d06b 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -41,8 +41,10 @@ proc parseTweetProfile*(profile: XmlNode): Profile = proc parseQuote*(quote: XmlNode): Quote = result = Quote( - id: quote.attr("data-item-id"), - text: getQuoteText(quote) + id: quote.attr("data-item-id"), + text: getQuoteText(quote), + reply: parseTweetReply(quote), + hasThread: quote.select(".self-thread-context") != nil, ) result.profile = Profile( @@ -64,6 +66,8 @@ proc parseTweet*(node: XmlNode): Tweet = shortTime: getShortTime(tweet), profile: parseTweetProfile(tweet), stats: parseTweetStats(tweet), + reply: parseTweetReply(tweet), + hasThread: tweet.select(".self-thread-context") != nil, pinned: "pinned" in tweet.attr("class"), available: true ) diff --git a/src/parserutils.nim b/src/parserutils.nim index f7ba651..a1f8c0a 100644 --- a/src/parserutils.nim +++ b/src/parserutils.nim @@ -120,11 +120,17 @@ proc parseTweetStats*(node: XmlNode): TweetStats = of "rep": result.replies = text[0] of "lik": result.likes = text[0] +proc parseTweetReply*(node: XmlNode): seq[string] = + let reply = node.select(".ReplyingToContextBelowAuthor") + if reply == nil: return + for username in reply.selectAll("a"): + result.add username.selectText("b") + proc getGif(player: XmlNode): Gif = let thumb = player.attr("style").replace(thumbRegex, "$1") id = thumb.replace(gifRegex, "$1") - url = fmt"https://video.twimg.com/tweet_video/{id}.mp4" + url = &"https://video.twimg.com/tweet_video/{id}.mp4" Gif(url: url, thumb: thumb) proc getTweetMedia*(tweet: Tweet; node: XmlNode) = @@ -146,15 +152,15 @@ proc getQuoteMedia*(quote: var Quote; node: XmlNode) = let media = node.select(".QuoteMedia") if media != nil: - quote.thumb = some(media.selectAttr("img", "src")) + quote.thumb = media.selectAttr("img", "src") let badge = node.select(".AdaptiveMedia-badgeText") let gifBadge = node.select(".Icon--gifBadge") if badge != nil: - quote.badge = some(badge.innerText()) + quote.badge = badge.innerText() elif gifBadge != nil: - quote.badge = some("GIF") + quote.badge = "GIF" proc getTweetCards*(tweet: Tweet; node: XmlNode) = if node.attr("data-has-cards") == "false": return diff --git a/src/types.nim b/src/types.nim index 4758f80..639aee2 100644 --- a/src/types.nim +++ b/src/types.nim @@ -58,9 +58,11 @@ type id*: string profile*: Profile text*: string + reply*: seq[string] + hasThread*: bool sensitive*: bool - thumb*: Option[string] - badge*: Option[string] + thumb*: string + badge*: string Retweet* = object by*: string @@ -77,8 +79,10 @@ type text*: string time*: Time shortTime*: string - available*: bool + reply*: seq[string] pinned*: bool + available*: bool + hasThread*: bool stats*: TweetStats retweet*: Option[Retweet] quote*: Option[Quote] diff --git a/src/views/tweet.nimf b/src/views/tweet.nimf index 9f9e7a7..e2fc056 100644 --- a/src/views/tweet.nimf +++ b/src/views/tweet.nimf @@ -1,5 +1,5 @@ #? stdtmpl(subsChar = '$', metaChar = '#') -#import xmltree, strutils, times, sequtils, uri +#import xmltree, strutils, strformat, sequtils, times, uri #import ../types, ../formatters, ../utils # #proc renderHeading(tweet: Tweet): string = @@ -29,38 +29,6 @@ #end proc # -#proc renderQuote(quote: Quote): string = -#let hasMedia = quote.thumb.isSome() or quote.sensitive -
-
- - #if hasMedia: -
-
- #if quote.thumb.isSome(): - ${genImg(quote.thumb.get())} - #if quote.badge.isSome(): -
-
${quote.badge.get()}
-
- #end if - #elif quote.sensitive: -
- -
- #end if -
-
- #end if -
- ${linkUser(quote.profile, class="fullname")} - ${linkUser(quote.profile, class="username")} -
-
${linkifyText(quote.text)}
-
-
-#end proc -# #proc renderMediaGroup(tweet: Tweet): string = #let groups = if tweet.photos.len > 2: tweet.photos.distribute(2) else: @[tweet.photos] #let class = if groups.len == 1 and groups[0].len == 1: "single-image" else: "" @@ -140,6 +108,53 @@ #end proc # +#proc renderShowThread(tweet: Tweet | Quote): string = +Show this thread +#end proc +# +#proc renderReply(tweet: Tweet | Quote): string = +#let usernames = tweet.reply.mapIt(&"""@{it}""") +
Replying to ${usernames.join(" ")}
+#end proc +# +#proc renderQuote(quote: Quote): string = +#let hasMedia = quote.thumb.len > 0 or quote.sensitive +
+
+ + #if hasMedia: +
+
+ #if quote.thumb.len > 0: + ${genImg(quote.thumb)} + #if quote.badge.len > 0: +
+
${quote.badge}
+
+ #end if + #elif quote.sensitive: +
+ +
+ #end if +
+
+ #end if +
+ ${linkUser(quote.profile, class="fullname")} + ${linkUser(quote.profile, class="username")} +
+ #if quote.reply.len > 0: + ${renderReply(quote)} + #end if +
${linkifyText(quote.text)}
+ #if quote.hasThread: + ${renderShowThread(quote)} + #end if +
+
+#end proc +# #proc renderTweet*(tweet: Tweet; class=""; last=false): string = #var divClass = if last: "thread-last " & class else: class #if divClass.len > 0: @@ -149,6 +164,9 @@
${renderHeading(tweet)} + #if tweet.reply.len > 0: + ${renderReply(tweet)} + #end if
${linkifyText(tweet.text)}
@@ -161,9 +179,12 @@ #elif tweet.quote.isSome: ${renderQuote(tweet.quote.get())} #elif tweet.poll.isSome: - ${renderPoll(tweet.poll.get())} + ${renderPoll(tweet.poll.get())} #end if ${renderStats(tweet.stats)} + #if tweet.hasThread and "timeline" in class: + ${renderShowThread(tweet)} + #end if
#else: