From 36484c73fd0c89ef108d3429fc406b744f90fb1b Mon Sep 17 00:00:00 2001 From: Zed Date: Sun, 15 Sep 2019 12:10:43 +0200 Subject: [PATCH] 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"