From a87e278a6885b77733014c36d772967b15683c1d Mon Sep 17 00:00:00 2001
From: Zed
Date: Sun, 15 Sep 2019 09:57:45 +0200
Subject: [PATCH 1/7] Add timeline RSS support
---
src/formatters.nim | 43 +++++++++++++++----------
src/routes/timeline.nim | 31 ++++++++++++++++++
src/views/rss.nimf | 69 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 127 insertions(+), 16 deletions(-)
create mode 100644 src/views/rss.nimf
diff --git a/src/formatters.nim b/src/formatters.nim
index a7e8bc1..e276bc3 100644
--- a/src/formatters.nim
+++ b/src/formatters.nim
@@ -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%])\s+([;.,!\)'%]|')", "$1")
- result = result.replace(re"^\. 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%])\s+([;.,!\)'%]|')", "$1")
+ result = result.replace(re"^\. ${text}
${quoteLink}
+#else:
+${text}
+#end if
+#if tweet.photos.len > 0:
+
+#elif tweet.video.isSome:
+
+#elif tweet.gif.isSome:
+#let thumb = &"https://{hostname}{getPicUrl(get(tweet.gif).thumb)}"
+#let url = &"https://{hostname}{getGifUrl(get(tweet.gif).url)}"
+
+#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 = ""
+
+
+
+
+ ${profile.fullname} / @${profile.username}
+ https://${hostname}${profile.username}
+ Twitter feed for: ${profile.username}. Generated by ${hostname}
+ en-us
+ 40
+
+ https://${hostname}${getPicUrl(profile.getUserPic(style="_400x400"))}
+
+ #for tweet in tweets:
+ -
+ ${getTitle(tweet, prefs)}
+ (@${tweet.profile.username})
+
+ ${getTime(tweet)}
+ https://${hostname}${getLink(tweet)}
+ https://${hostname}${getLink(tweet)}
+
+ #end for
+
+
+#end proc
From 8912c53f23eac65f2da2a4de3276ab5be1ea9795 Mon Sep 17 00:00:00 2001
From: Zed
Date: Sun, 15 Sep 2019 11:14:03 +0200
Subject: [PATCH 2/7] Improve RSS validity
---
src/formatters.nim | 5 ++++-
src/views/rss.nimf | 18 +++++++++++-------
2 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/src/formatters.nim b/src/formatters.nim
index e276bc3..42ce2dd 100644
--- a/src/formatters.nim
+++ b/src/formatters.nim
@@ -114,7 +114,10 @@ proc getJoinDateFull*(profile: Profile): string =
profile.joinDate.format("h:mm tt - d MMM YYYY")
proc getTime*(tweet: Tweet): string =
- tweet.time.format("d/M/yyyy', ' HH:mm:ss")
+ tweet.time.format("d/M/yyyy', 'HH:mm:ss")
+
+proc getRfc822Time*(tweet: Tweet): string =
+ tweet.time.format("ddd', 'd MMM yyyy HH:mm:ss 'GMT'")
proc getLink*(tweet: Tweet | Quote): string =
&"/{tweet.profile.username}/status/{tweet.id}"
diff --git a/src/views/rss.nimf b/src/views/rss.nimf
index d9ee844..f9879c7 100644
--- a/src/views/rss.nimf
+++ b/src/views/rss.nimf
@@ -43,23 +43,27 @@
#let prefs = Prefs(replaceTwitter: hostname)
#result = ""
-
+
-
+
${profile.fullname} / @${profile.username}
- https://${hostname}${profile.username}
- Twitter feed for: ${profile.username}. Generated by ${hostname}
+ https://${hostname}/${profile.username}
+ Twitter feed for: @${profile.username}. Generated by ${hostname}
en-us
40
- https://${hostname}${getPicUrl(profile.getUserPic(style="_400x400"))}
+ ${profile.fullname} / @${profile.username}
+ https://${hostname}/${profile.username}
+ https://${hostname}${getPicUrl(profile.getUserPic(style="_400x400"))}
+ 128
+ 128
#for tweet in tweets:
-
${getTitle(tweet, prefs)}
- (@${tweet.profile.username})
+ @${tweet.profile.username}
- ${getTime(tweet)}
+ ${getRfc822Time(tweet)}
https://${hostname}${getLink(tweet)}
https://${hostname}${getLink(tweet)}
From 6c479ff7eccd79bc7be6193563f8ca0b6a37346a Mon Sep 17 00:00:00 2001
From: Zed
Date: Sun, 15 Sep 2019 11:29:14 +0200
Subject: [PATCH 3/7] Add RSS button to navbar
---
public/css/fontello.css | 13 +++++++------
public/fonts/fontello.eot | Bin 8832 -> 9160 bytes
public/fonts/fontello.svg | 2 ++
public/fonts/fontello.ttf | Bin 8664 -> 8992 bytes
public/fonts/fontello.woff | Bin 5532 -> 5772 bytes
public/fonts/fontello.woff2 | Bin 4628 -> 4792 bytes
src/routes/timeline.nim | 3 ++-
src/sass/tweet/thread.scss | 2 +-
src/views/general.nim | 10 ++++++----
9 files changed, 18 insertions(+), 12 deletions(-)
diff --git a/public/css/fontello.css b/public/css/fontello.css
index 3f45019..b58b3c7 100644
--- a/public/css/fontello.css
+++ b/public/css/fontello.css
@@ -1,11 +1,11 @@
@font-face {
font-family: 'fontello';
- src: url('/fonts/fontello.eot?85902121');
- src: url('/fonts/fontello.eot?85902121#iefix') format('embedded-opentype'),
- url('/fonts/fontello.woff2?85902121') format('woff2'),
- url('/fonts/fontello.woff?85902121') format('woff'),
- url('/fonts/fontello.ttf?85902121') format('truetype'),
- url('/fonts/fontello.svg?85902121#fontello') format('svg');
+ src: url('/fonts/fontello.eot?33844470');
+ src: url('/fonts/fontello.eot?33844470#iefix') format('embedded-opentype'),
+ url('/fonts/fontello.woff2?33844470') format('woff2'),
+ url('/fonts/fontello.woff?33844470') format('woff'),
+ url('/fonts/fontello.ttf?33844470') format('truetype'),
+ url('/fonts/fontello.svg?33844470#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
@@ -50,4 +50,5 @@
.icon-search:before { content: '\e80e'; } /* '' */
.icon-pin:before { content: '\e80f'; } /* '' */
.icon-cog:before { content: '\e812'; } /* '' */
+.icon-rss:before { content: '\f143'; } /* '' */
.icon-thumbs-up:before { content: '\f164'; } /* '' */
diff --git a/public/fonts/fontello.eot b/public/fonts/fontello.eot
index fc8567e0256088a5aa3d678f822398f71324f3a2..a3d11e8ce4f9d4317f5ba7268f1f00932895936b 100644
GIT binary patch
delta 773
zcmYL{-%C?r7{{OIJ!j`Vp0oV1A7?^t&Z(Q}+=b{A6Vay3NYINKN-&*nZiUO!euzb2
zi5Frw+HN8Uj3A>sTSP$@;gy9^5E1PdBl@GSBQJ>W1F+@k`HWL~Ut|C_nR4fh9#5{W
zuZ1{888jh`&dyCoTosNn;$Otg$;g6^wJO!-24d%hNHVs2yS9#afEM^lpG#-9
z`rf7hGz;Zl^i)j$*x&LDz;!dXfoCzB+sF9G8X%!a*n4<
zihK+>^5G7Dqr2K`F`LQ^_LjzWx2qhrUbOkpb&=p6sqJ||
zk%Qd05e^)D4ur!2W($N@?9I*HUe=^7J(1AJgIq!B${DU0d1zy4s+*xIA1#?;W;-P-
zDyuyxoCxD&KdUYYD1FoqcOM@6r@2B^Ct7k&(@7l9&6rx%
z^Spwx(qFaYZ<$nq!h2RXLu+o=x>BvC(&^Up#cU)Mi8#@veyu-St@v42%H`3=Cn(xrqhk-Ny@n{1prgOnJ#=
zB?@fU|1&W#h#mmSE2I^q=XOn;5DDZzU|`@bNKY&Vs$~!W@^=7fj`W<$wA1Ms7a15>
zBp4WYnKDunQ^b7wfa(QhfbwP;Kmqnf<^@202#~Lmky}#XcrNV!H=sZTP{1cAKRHpF
zK`tN2KLX@yTaipeNfHSQtPK;Q&IQ
zCtvWt5c-(%|38q$0AqpmF#_e)7}O?jVp0bhz_$1y6QdAN0_X%FcmXDXE}Hy-`8;zQVGTd-F=cU`Cm)<9_P#{5D@1xS7GqZ#FEA0ciu#lO=@B8HFbY2zySR
rE3C!d2zH_H=1aod%s}CLVrrAw#5=e+OEOAxlZtgq3pOtkSK
+
+
diff --git a/public/fonts/fontello.ttf b/public/fonts/fontello.ttf
index a9f94a73499e32ec2fffe0875d47e2a0a5b4103a..847494ed4a67d8df9016daef3bc636f55e72462f 100644
GIT binary patch
delta 767
zcmYL{%TE(g6vofJ^SHxIL+NYUL|ULiTT7{tKp{v-u_zcxV1W(9q)=#SHB`nv2pSV&
z!-B9X&c=;NjSJi%N+(u+#Wh7^D6HCe@Yr3Jam0qQ=bJ`OfeA&g9-ZbKghGXG%v`
zE&`Bo0Eo_I;@X2RW6zN90npXCrF^sau1W#y66VeGS}L=?QJO;b0VrB3p3~58Kt=&6
zwQzO*m*x3103QITccqi@gnD!H8-RHf^H3TMc7wZvyo%hK&g7R*d!yB#XyAk}zOXnK
zcN<-k$a~2COng}*O$yeQR^;Z(@l5i??WPvwAza`~Z84YM3ctw$s8-BBYuTjsaj^Xf
zfcw_Q-UU+7CrE0n1O!8Xb>I%G0IH08ECCNO^ktBM0<9P;=H19{qFM#0!2kO2U2Mlb
zU+vN<^$Hd=MEwLk(8E;Fk8@((_rHA>eSmvyivaB605fXh&j8D|gMX~7@Bq~*ds~;sT~E{jVh<9pN3`3ls@+BKK1n)5Jwe{P;t`#sW>ULVt(g{+Rb(x0
zPq(Qn)YH!8lZkl!Jd+J9S}xA^QKCpkY9^oG$x5<9oAxWGVq|8J
zI%)#juZ&UBxH&K_OdI=ufWlR3AN9lC`^WyN9yaw5tG;YHOQ!UvrViy2Gh$Zz!Pf1s
rCWXWB!0MsIrnlPG97;Bq>&RUx#IwmnT|QmN%;q`@cxJaB+T4cUjyR?b
delta 442
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
qKetZUQjI~L*|O#KW6%C)pcmovIIqZMR~`^1
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
S{`0a*`S71~Pvc+usWJx9gIW3j
literal 4628
zcmV+v66@`EPew8T0RR9101^}c4*&oF03p}_01>(X0RR9100000000000000000000
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
K{XT 0:
+ icon "rss", title="RSS Feed", href=rss
icon "info-circled", title="About", href="/about"
iconReferer "cog", "/settings", path, title="Preferences"
-proc renderMain*(body: VNode; prefs: Prefs; title="Nitter"; titleText=""; desc="";
- path="/"; `type`="article"; video=""; images: seq[string] = @[]): string =
+proc renderMain*(body: VNode; prefs: Prefs; title="Nitter"; titleText=""; desc=""; path="/";
+ rss=""; `type`="article"; video=""; images: seq[string] = @[]): string =
let node = buildHtml(html(lang="en")):
head:
link(rel="stylesheet", `type`="text/css", href="/css/style.css")
@@ -48,7 +50,7 @@ proc renderMain*(body: VNode; prefs: Prefs; title="Nitter"; titleText=""; desc="
meta(property="og:video:secure_url", content=video)
body:
- renderNavbar(title, path)
+ renderNavbar(title, path, rss)
tdiv(id="content", class="container"):
body
From e72b48defc0668238725386ef66444184837b1e4 Mon Sep 17 00:00:00 2001
From: Zed
Date: Sun, 15 Sep 2019 11:31:27 +0200
Subject: [PATCH 4/7] Update readme
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 2b68460..83cdde5 100644
--- a/README.md
+++ b/README.md
@@ -23,11 +23,12 @@ It is possible to install Nim system-wide or in the user directory you create be
# su nitter
$ git clone https://github.com/zedeus/nitter
$ cd nitter
-$ nimble build -d:release
+$ nimble build -d:release -d:hostname="..."
$ nimble scss
$ mkdir ./tmp
```
+Change `-d:hostname="..."` to your instance's domain, eg. `-d:hostname:"nitter.net"`.
Set your port and page title in `nitter.conf`, then run Nitter by executing `./nitter`.
You should run Nitter behind a reverse proxy such as nginx or Apache for better
security.
From 36484c73fd0c89ef108d3429fc406b744f90fb1b Mon Sep 17 00:00:00 2001
From: Zed
Date: Sun, 15 Sep 2019 12:10:43 +0200
Subject: [PATCH 5/7] Support RSS feeds for /media and /replies
---
src/nitter.nim | 4 ++-
src/routes/rss.nim | 35 ++++++++++++++++++
src/routes/timeline.nim | 78 ++++++++++++++---------------------------
3 files changed, 64 insertions(+), 53 deletions(-)
create mode 100644 src/routes/rss.nim
diff --git a/src/nitter.nim b/src/nitter.nim
index dd053c5..7a4f746 100644
--- a/src/nitter.nim
+++ b/src/nitter.nim
@@ -5,7 +5,7 @@ import jester
import types, config, prefs
import views/[general, about]
-import routes/[preferences, timeline, media]
+import routes/[preferences, timeline, media, rss]
const configPath {.strdefine.} = "./nitter.conf"
let cfg = getConfig(configPath)
@@ -13,6 +13,7 @@ let cfg = getConfig(configPath)
createPrefRouter(cfg)
createTimelineRouter(cfg)
createMediaRouter(cfg)
+createRssRouter(cfg)
settings:
port = Port(cfg.port)
@@ -32,6 +33,7 @@ routes:
redirect("/" & @"query")
extend preferences, ""
+ extend rss, ""
extend timeline, ""
extend media, ""
diff --git a/src/routes/rss.nim b/src/routes/rss.nim
new file mode 100644
index 0000000..958f7b2
--- /dev/null
+++ b/src/routes/rss.nim
@@ -0,0 +1,35 @@
+import asyncdispatch, strutils
+
+import jester
+
+import router_utils, timeline
+import ".."/[types, utils, cache, agents, search]
+import ../views/general
+
+include "../views/rss.nimf"
+
+export uri
+export cache, search, agents
+
+proc showRss*(name: string; query: Option[Query]): Future[string] {.async.} =
+ let (profile, timeline, _) = await fetchSingleTimeline(name, "", getAgent(), query)
+ return renderTimelineRss(timeline.content, profile)
+
+template respRss*(rss: typed) =
+ if rss.len == 0:
+ resp Http404, showError("User \"" & @"name" & "\" not found", cfg.title)
+ resp rss, "application/rss+xml;charset=utf-8"
+
+proc createRssRouter*(cfg: Config) =
+ router rss:
+ get "/@name/rss":
+ cond '.' notin @"name"
+ respRss(await showRss(@"name", none(Query)))
+
+ get "/@name/replies/rss":
+ cond '.' notin @"name"
+ respRss(await showRss(@"name", some(getReplyQuery(@"name"))))
+
+ get "/@name/media/rss":
+ cond '.' notin @"name"
+ respRss(await showRss(@"name", some(getMediaQuery(@"name"))))
diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim
index 268bd7a..ad2af4f 100644
--- a/src/routes/timeline.nim
+++ b/src/routes/timeline.nim
@@ -13,8 +13,10 @@ export router_utils
export api, cache, formatters, search, agents
export profile, timeline, status
-proc showSingleTimeline(name, after, agent: string; query: Option[Query];
- prefs: Prefs; path, title: string): Future[string] {.async.} =
+type ProfileTimeline = (Profile, Timeline, seq[GalleryPhoto])
+
+proc fetchSingleTimeline*(name, after, agent: string;
+ query: Option[Query]): Future[ProfileTimeline] {.async.} =
let railFut = getPhotoRail(name, agent)
var timeline: Timeline
@@ -36,95 +38,67 @@ proc showSingleTimeline(name, after, agent: string; query: Option[Query];
profile = await getCachedProfile(name, agent)
timeline = await timelineFut
- if profile.username.len == 0:
- return ""
+ if profile.username.len == 0: return
+ return (profile, timeline, await railFut)
- let rssUrl = profile.username & "/rss"
- let profileHtml = renderProfile(profile, timeline, await railFut, prefs, path)
- return renderMain(profileHtml, prefs, title, pageTitle(profile),
- pageDesc(profile), path, rss=rssUrl)
-
-proc showMultiTimeline(names: seq[string]; after, agent: string; query: Option[Query];
- prefs: Prefs; path, title: string): Future[string] {.async.} =
+proc fetchMultiTimeline*(names: seq[string]; after, agent: string;
+ query: Option[Query]): Future[Timeline] {.async.} =
var q = query
if q.isSome:
get(q).fromUser = names
else:
q = some(Query(kind: multi, fromUser: names, excludes: @["replies"]))
- var timeline = renderMulti(await getTimelineSearch(get(q), after, agent),
- names.join(","), prefs, path)
-
- return renderMain(timeline, prefs, title, "Multi")
+ return await getTimelineSearch(get(q), after, agent)
proc showTimeline*(name, after: string; query: Option[Query];
- prefs: Prefs; path, title: string): Future[string] {.async.} =
+ prefs: Prefs; path, title, rss: string): Future[string] {.async.} =
let agent = getAgent()
let names = name.strip(chars={'/'}).split(",").filterIt(it.len > 0)
if names.len == 1:
- return await showSingleTimeline(names[0], after, agent, query, prefs, path, title)
+ let (p, t, r) = await fetchSingleTimeline(names[0], after, agent, query)
+ if p.username.len == 0: return
+ let pHtml = renderProfile(p, t, r, prefs, path)
+ return renderMain(pHtml, prefs, title, pageTitle(p), pageDesc(p), path, rss=rss)
else:
- return await showMultiTimeline(names, after, agent, query, prefs, path, title)
+ let
+ timeline = await fetchMultiTimeline(names, after, agent, query)
+ html = renderMulti(timeline, names.join(","), prefs, path)
+ return renderMain(html, prefs, title, "Multi")
template respTimeline*(timeline: typed) =
if timeline.len == 0:
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)
router timeline:
get "/@name/?":
cond '.' notin @"name"
- respTimeline(await showTimeline(@"name", @"after", none(Query),
- cookiePrefs(), getPath(), cfg.title))
+ let rss = "/$1/rss" % @"name"
+ respTimeline(await showTimeline(@"name", @"after", none(Query), cookiePrefs(),
+ getPath(), cfg.title, rss))
get "/@name/search":
cond '.' notin @"name"
let query = initQuery(@"filter", @"include", @"not", @"sep", @"name")
respTimeline(await showTimeline(@"name", @"after", some(query),
- cookiePrefs(), getPath(), cfg.title))
+ cookiePrefs(), getPath(), cfg.title, ""))
get "/@name/replies":
cond '.' notin @"name"
+ let rss = "/$1/replies/rss" % @"name"
respTimeline(await showTimeline(@"name", @"after", some(getReplyQuery(@"name")),
- cookiePrefs(), getPath(), cfg.title))
+ cookiePrefs(), getPath(), cfg.title, rss))
get "/@name/media":
cond '.' notin @"name"
+ let rss = "/$1/media/rss" % @"name"
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"
-
+ cookiePrefs(), getPath(), cfg.title, rss))
get "/@name/status/@id":
cond '.' notin @"name"
From 6237460f77a05cfddc34825ed8d018048b8aa623 Mon Sep 17 00:00:00 2001
From: Zed
Date: Sun, 15 Sep 2019 12:57:44 +0200
Subject: [PATCH 6/7] Fix want-my-rss detection
---
README.md | 1 +
src/routes/rss.nim | 5 +----
src/views/general.nim | 3 +++
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 83cdde5..9e8231b 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@ Inspired by the [invidio.us](https://github.com/omarroth/invidious) project.
- AGPLv3 licensed, no proprietary instances permitted
- Dark theme
- Lightweight (for [@nim_lang](https://twitter.com/nim_lang), 36KB vs 580KB from twitter.com)
+- Native RSS feeds
## Installation
diff --git a/src/routes/rss.nim b/src/routes/rss.nim
index 958f7b2..bba16fb 100644
--- a/src/routes/rss.nim
+++ b/src/routes/rss.nim
@@ -3,14 +3,11 @@ import asyncdispatch, strutils
import jester
import router_utils, timeline
-import ".."/[types, utils, cache, agents, search]
+import ".."/[cache, agents, search]
import ../views/general
include "../views/rss.nimf"
-export uri
-export cache, search, agents
-
proc showRss*(name: string; query: Option[Query]): Future[string] {.async.} =
let (profile, timeline, _) = await fetchSingleTimeline(name, "", getAgent(), query)
return renderTimelineRss(timeline.content, profile)
diff --git a/src/views/general.nim b/src/views/general.nim
index fe5f37f..fcf9626 100644
--- a/src/views/general.nim
+++ b/src/views/general.nim
@@ -26,6 +26,9 @@ proc renderMain*(body: VNode; prefs: Prefs; title="Nitter"; titleText=""; desc="
link(rel="stylesheet", `type`="text/css", href="/css/style.css")
link(rel="stylesheet", `type`="text/css", href="/css/fontello.css")
+ if rss.len > 0:
+ link(rel="alternate", `type`="application/rss+xml", href=rss, title="RSS feed")
+
if prefs.hlsPlayback:
script(src="/js/hls.light.min.js")
script(src="/js/hlsPlayback.js")
From 2677782286568834e459ec42e0d2e5cbca987be8 Mon Sep 17 00:00:00 2001
From: Zed
Date: Sun, 15 Sep 2019 14:03:47 +0200
Subject: [PATCH 7/7] Improve web preview
---
src/routes/timeline.nim | 3 ++-
src/views/general.nim | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim
index ad2af4f..63edc01 100644
--- a/src/routes/timeline.nim
+++ b/src/routes/timeline.nim
@@ -127,7 +127,8 @@ proc createTimelineRouter*(cfg: Config) =
resp renderMain(html, prefs, cfg.title, title, desc, path, images = @[thumb],
`type`="video", video=vidUrl)
else:
- resp renderMain(html, prefs, cfg.title, title, desc, path, images=conversation.tweet.photos)
+ resp renderMain(html, prefs, cfg.title, title, desc, path,
+ images=conversation.tweet.photos, `type`="photo")
get "/i/web/status/@id":
redirect("/i/status/" & @"id")
diff --git a/src/views/general.nim b/src/views/general.nim
index fcf9626..5df1269 100644
--- a/src/views/general.nim
+++ b/src/views/general.nim
@@ -43,7 +43,7 @@ proc renderMain*(body: VNode; prefs: Prefs; title="Nitter"; titleText=""; desc="
meta(property="og:type", content=`type`)
meta(property="og:title", content=titleText)
meta(property="og:description", content=desc)
- meta(property="og:site_name", content="Twitter")
+ meta(property="og:site_name", content="Nitter")
for url in images:
meta(property="og:image", content=getPicUrl(url))