Add timeline RSS support
This commit is contained in:
		
							parent
							
								
									4ae5d4fda3
								
							
						
					
					
						commit
						a87e278a68
					
				| 
						 | 
				
			
			@ -15,6 +15,8 @@ const
 | 
			
		|||
  twRegex = re"(www.|mobile.)?twitter.com"
 | 
			
		||||
  nbsp = $Rune(0x000A0)
 | 
			
		||||
 | 
			
		||||
const hostname {.strdefine.} = "nitter.net"
 | 
			
		||||
 | 
			
		||||
proc stripText*(text: string): string =
 | 
			
		||||
  text.replace(nbsp, " ").strip()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -23,12 +25,16 @@ proc shortLink*(text: string; length=28): string =
 | 
			
		|||
  if result.len > length:
 | 
			
		||||
    result = result[0 ..< length] & "…"
 | 
			
		||||
 | 
			
		||||
proc toLink*(url, text: string; class="timeline-link"): string =
 | 
			
		||||
  a(text, class=class, href=url)
 | 
			
		||||
proc toLink*(url, text: string): string =
 | 
			
		||||
  a(text, href=url)
 | 
			
		||||
 | 
			
		||||
proc reUrlToShortLink*(m: RegexMatch; s: string): string =
 | 
			
		||||
  let url = s[m.group(0)[0]]
 | 
			
		||||
  toLink(url, shortLink(url))
 | 
			
		||||
 | 
			
		||||
proc reUrlToLink*(m: RegexMatch; s: string): string =
 | 
			
		||||
  let url = s[m.group(0)[0]]
 | 
			
		||||
  toLink(url, shortLink(url))
 | 
			
		||||
  toLink(url, url.replace(re"https?://(www.)?", ""))
 | 
			
		||||
 | 
			
		||||
proc reEmailToLink*(m: RegexMatch; s: string): string =
 | 
			
		||||
  let url = s[m.group(0)[0]]
 | 
			
		||||
| 
						 | 
				
			
			@ -48,19 +54,9 @@ proc reUsernameToLink*(m: RegexMatch; s: string): string =
 | 
			
		|||
 | 
			
		||||
  pretext & toLink("/" & username, "@" & username)
 | 
			
		||||
 | 
			
		||||
proc linkifyText*(text: string; prefs: Prefs): string =
 | 
			
		||||
  result = xmltree.escape(stripText(text))
 | 
			
		||||
  result = result.replace(ellipsisRegex, "")
 | 
			
		||||
  result = result.replace(emailRegex, reEmailToLink)
 | 
			
		||||
  result = result.replace(urlRegex, reUrlToLink)
 | 
			
		||||
  result = result.replace(usernameRegex, reUsernameToLink)
 | 
			
		||||
  result = result.replace(re"([^\s\(\n%])<a", "$1 <a")
 | 
			
		||||
  result = result.replace(re"</a>\s+([;.,!\)'%]|')", "</a>$1")
 | 
			
		||||
  result = result.replace(re"^\. <a", ".<a")
 | 
			
		||||
  if prefs.replaceYouTube.len > 0:
 | 
			
		||||
    result = result.replace(ytRegex, prefs.replaceYouTube)
 | 
			
		||||
  if prefs.replaceTwitter.len > 0:
 | 
			
		||||
    result = result.replace(twRegex, prefs.replaceTwitter)
 | 
			
		||||
proc reUsernameToFullLink*(m: RegexMatch; s: string): string =
 | 
			
		||||
  result = reUsernameToLink(m, s)
 | 
			
		||||
  result = result.replace("href=\"/", &"href=\"https://{hostname}/")
 | 
			
		||||
 | 
			
		||||
proc replaceUrl*(url: string; prefs: Prefs): string =
 | 
			
		||||
  result = url
 | 
			
		||||
| 
						 | 
				
			
			@ -69,6 +65,21 @@ proc replaceUrl*(url: string; prefs: Prefs): string =
 | 
			
		|||
  if prefs.replaceTwitter.len > 0:
 | 
			
		||||
    result = result.replace(twRegex, prefs.replaceTwitter)
 | 
			
		||||
 | 
			
		||||
proc linkifyText*(text: string; prefs: Prefs; rss=false): string =
 | 
			
		||||
  result = xmltree.escape(stripText(text))
 | 
			
		||||
  result = result.replace(ellipsisRegex, "")
 | 
			
		||||
  result = result.replace(emailRegex, reEmailToLink)
 | 
			
		||||
  if rss:
 | 
			
		||||
    result = result.replace(urlRegex, reUrlToLink)
 | 
			
		||||
    result = result.replace(usernameRegex, reUsernameToFullLink)
 | 
			
		||||
  else:
 | 
			
		||||
    result = result.replace(urlRegex, reUrlToShortLink)
 | 
			
		||||
    result = result.replace(usernameRegex, reUsernameToLink)
 | 
			
		||||
  result = result.replace(re"([^\s\(\n%])<a", "$1 <a")
 | 
			
		||||
  result = result.replace(re"</a>\s+([;.,!\)'%]|')", "</a>$1")
 | 
			
		||||
  result = result.replace(re"^\. <a", ".<a")
 | 
			
		||||
  result = result.replaceUrl(prefs)
 | 
			
		||||
 | 
			
		||||
proc stripTwitterUrls*(text: string): string =
 | 
			
		||||
  result = text
 | 
			
		||||
  result = result.replace(picRegex, "")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,8 @@ import router_utils
 | 
			
		|||
import ".."/[api, prefs, types, utils, cache, formatters, agents, search]
 | 
			
		||||
import ../views/[general, profile, timeline, status]
 | 
			
		||||
 | 
			
		||||
include "../views/rss.nimf"
 | 
			
		||||
 | 
			
		||||
export uri, sequtils
 | 
			
		||||
export router_utils
 | 
			
		||||
export api, cache, formatters, search, agents
 | 
			
		||||
| 
						 | 
				
			
			@ -69,6 +71,26 @@ template respTimeline*(timeline: typed) =
 | 
			
		|||
    resp Http404, showError("User \"" & @"name" & "\" not found", cfg.title)
 | 
			
		||||
  resp timeline
 | 
			
		||||
 | 
			
		||||
proc showRssTimeline*(name: string): Future[string] {.async.} =
 | 
			
		||||
  var timeline: Timeline
 | 
			
		||||
  var profile: Profile
 | 
			
		||||
  var cachedProfile = hasCachedProfile(name)
 | 
			
		||||
  let agent = getAgent()
 | 
			
		||||
 | 
			
		||||
  if cachedProfile.isSome:
 | 
			
		||||
    profile = get(cachedProfile)
 | 
			
		||||
 | 
			
		||||
  if cachedProfile.isSome:
 | 
			
		||||
    timeline = await getTimeline(name, "", agent)
 | 
			
		||||
  else:
 | 
			
		||||
    (profile, timeline) = await getProfileAndTimeline(name, agent, "")
 | 
			
		||||
  cache(profile)
 | 
			
		||||
 | 
			
		||||
  if profile.username.len == 0:
 | 
			
		||||
    return ""
 | 
			
		||||
 | 
			
		||||
  return renderTimelineRss(timeline.content, profile)
 | 
			
		||||
 | 
			
		||||
proc createTimelineRouter*(cfg: Config) =
 | 
			
		||||
  setProfileCacheTime(cfg.profileCacheTime)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -94,6 +116,15 @@ proc createTimelineRouter*(cfg: Config) =
 | 
			
		|||
      respTimeline(await showTimeline(@"name", @"after", some(getMediaQuery(@"name")),
 | 
			
		||||
                                      cookiePrefs(), getPath(), cfg.title))
 | 
			
		||||
 | 
			
		||||
    get "/@name/rss":
 | 
			
		||||
      cond '.' notin @"name"
 | 
			
		||||
      let rss = await showRssTimeline(@"name")
 | 
			
		||||
      if rss.len == 0:
 | 
			
		||||
        resp Http404, showError("User \"" & @"name" & "\" not found", cfg.title)
 | 
			
		||||
      else:
 | 
			
		||||
        resp rss, "application/rss+xml;charset=utf-8"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    get "/@name/status/@id":
 | 
			
		||||
      cond '.' notin @"name"
 | 
			
		||||
      let prefs = cookiePrefs()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,69 @@
 | 
			
		|||
#? stdtmpl(subsChar = '$', metaChad = '#')
 | 
			
		||||
#import strutils, xmltree, strformat
 | 
			
		||||
#import ../types, ../utils, ../formatters
 | 
			
		||||
#const hostname {.strdefine.} = "nitter.net"
 | 
			
		||||
#
 | 
			
		||||
#proc renderRssTweet(tweet: Tweet; prefs: Prefs): string =
 | 
			
		||||
#let text = linkifyText(tweet.text, prefs, rss=true)
 | 
			
		||||
#if tweet.quote.isSome and get(tweet.quote).available:
 | 
			
		||||
#let quoteLink = hostname & getLink(get(tweet.quote))
 | 
			
		||||
<p>${text}<br><a href="https://${quoteLink}">${quoteLink}</a></p>
 | 
			
		||||
#else:
 | 
			
		||||
<p>${text}</p>
 | 
			
		||||
#end if
 | 
			
		||||
#if tweet.photos.len > 0:
 | 
			
		||||
<img src="https://${hostname}${getPicUrl(tweet.photos[0])}" width="250" />
 | 
			
		||||
#elif tweet.video.isSome:
 | 
			
		||||
<img src="https://${hostname}${getPicUrl(get(tweet.video).thumb)}" width="250" />
 | 
			
		||||
#elif tweet.gif.isSome:
 | 
			
		||||
#let thumb = &"https://{hostname}{getPicUrl(get(tweet.gif).thumb)}"
 | 
			
		||||
#let url = &"https://{hostname}{getGifUrl(get(tweet.gif).url)}"
 | 
			
		||||
<video poster="${thumb}" autoplay muted loop width="250">
 | 
			
		||||
  <source src="${url}" type="video/mp4"</source></video>
 | 
			
		||||
#end if
 | 
			
		||||
#end proc
 | 
			
		||||
#
 | 
			
		||||
#proc getTitle(tweet: Tweet; prefs: Prefs): string =
 | 
			
		||||
#if tweet.pinned: result = "Pinned: "
 | 
			
		||||
#elif tweet.retweet.isSome: result = "RT: "
 | 
			
		||||
#end if
 | 
			
		||||
#result &= xmltree.escape(replaceUrl(tweet.text, prefs))
 | 
			
		||||
#if result.len > 0: return
 | 
			
		||||
#end if
 | 
			
		||||
#if tweet.photos.len > 0:
 | 
			
		||||
#  result &= "Image"
 | 
			
		||||
#elif tweet.video.isSome:
 | 
			
		||||
#  result &= "Video"
 | 
			
		||||
#elif tweet.gif.isSome:
 | 
			
		||||
#  result &= "Gif"
 | 
			
		||||
#end if
 | 
			
		||||
#end proc
 | 
			
		||||
#
 | 
			
		||||
#proc renderTimelineRss*(tweets: seq[Tweet]; profile: Profile): string =
 | 
			
		||||
#let prefs = Prefs(replaceTwitter: hostname)
 | 
			
		||||
#result = ""
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:nitter="http://${hostname}" version="2.0">
 | 
			
		||||
  <channel>
 | 
			
		||||
    <atom:link href="http://${hostname}${profile.username}/rss" rel="self" type="application/rss+xml" />
 | 
			
		||||
    <title>${profile.fullname} / @${profile.username}</title>
 | 
			
		||||
    <link>https://${hostname}${profile.username}</link>
 | 
			
		||||
    <description>Twitter feed for: ${profile.username}. Generated by ${hostname}</description>
 | 
			
		||||
    <language>en-us</language>
 | 
			
		||||
    <ttl>40</ttl>
 | 
			
		||||
    <image>
 | 
			
		||||
        <url>https://${hostname}${getPicUrl(profile.getUserPic(style="_400x400"))}</url>
 | 
			
		||||
    </image>
 | 
			
		||||
    #for tweet in tweets:
 | 
			
		||||
    <item>
 | 
			
		||||
      <title>${getTitle(tweet, prefs)}</title>
 | 
			
		||||
      <creator> (@${tweet.profile.username})</creator>
 | 
			
		||||
      <description><![CDATA[${renderRssTweet(tweet, prefs).strip(chars={'\n'})}]]></description>
 | 
			
		||||
      <pubDate>${getTime(tweet)}</pubDate>
 | 
			
		||||
      <guid>https://${hostname}${getLink(tweet)}</guid>
 | 
			
		||||
      <link>https://${hostname}${getLink(tweet)}</link>
 | 
			
		||||
    </item>
 | 
			
		||||
    #end for
 | 
			
		||||
  </channel>
 | 
			
		||||
</rss>
 | 
			
		||||
#end proc
 | 
			
		||||
		Loading…
	
		Reference in New Issue