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))