Refactor hostname to be a runtime option
Add a `hostname` field under Server in your conf file, see the updated nitter.conf in the repo for an example. The compile-time option (-d:hostname) is no longer used.
This commit is contained in:
		
							parent
							
								
									3218cc4069
								
							
						
					
					
						commit
						de62eedea5
					
				
							
								
								
									
										17
									
								
								README.md
								
								
								
								
							
							
						
						
									
										17
									
								
								README.md
								
								
								
								
							| 
						 | 
				
			
			@ -52,24 +52,27 @@ Twitter account.
 | 
			
		|||
 | 
			
		||||
## Installation
 | 
			
		||||
 | 
			
		||||
To compile Nitter you need a Nim installation, see [nim-lang.org](https://nim-lang.org/install.html) for details. It is possible to install it system-wide or in the user directory you create below.
 | 
			
		||||
To compile Nitter you need a Nim installation, see
 | 
			
		||||
[nim-lang.org](https://nim-lang.org/install.html) for details. It is possible to
 | 
			
		||||
install it system-wide or in the user directory you create below.
 | 
			
		||||
 | 
			
		||||
You also need to install `libsass` to compile the scss files. On Ubuntu and Debian, you can use `libsass-dev`.
 | 
			
		||||
You also need to install `libsass` to compile the scss files. On Ubuntu and
 | 
			
		||||
Debian, you can use `libsass-dev`.
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# useradd -m nitter
 | 
			
		||||
# su nitter
 | 
			
		||||
$ git clone https://github.com/zedeus/nitter
 | 
			
		||||
$ cd nitter
 | 
			
		||||
$ nimble build -d:release -d:hostname="..."
 | 
			
		||||
$ nimble build -d:release
 | 
			
		||||
$ 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](https://github.com/zedeus/nitter/wiki/Nginx) or Apache for better security.
 | 
			
		||||
Set your hostname, port and page title in `nitter.conf`, then run Nitter by
 | 
			
		||||
executing `./nitter`. You should run Nitter behind a reverse proxy such as
 | 
			
		||||
[Nginx](https://github.com/zedeus/nitter/wiki/Nginx) or Apache for better
 | 
			
		||||
security.
 | 
			
		||||
 | 
			
		||||
To build and run Nitter in Docker:
 | 
			
		||||
```bash
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,9 @@
 | 
			
		|||
address = "0.0.0.0"
 | 
			
		||||
port = 8080
 | 
			
		||||
https = true  # disable to enable cookies when not using https
 | 
			
		||||
title = "nitter"
 | 
			
		||||
staticDir = "./public"
 | 
			
		||||
title = "nitter"
 | 
			
		||||
hostname = "nitter.net"
 | 
			
		||||
 | 
			
		||||
[Cache]
 | 
			
		||||
directory = "./tmp"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,8 +16,9 @@ proc getConfig*(path: string): Config =
 | 
			
		|||
    address: cfg.get("Server", "address", "0.0.0.0"),
 | 
			
		||||
    port: cfg.get("Server", "port", 8080),
 | 
			
		||||
    useHttps: cfg.get("Server", "https", true),
 | 
			
		||||
    title: cfg.get("Server", "title", "Nitter"),
 | 
			
		||||
    staticDir: cfg.get("Server", "staticDir", "./public"),
 | 
			
		||||
    title: cfg.get("Server", "title", "Nitter"),
 | 
			
		||||
    hostname: cfg.get("Server", "hostname", "nitter.net"),
 | 
			
		||||
 | 
			
		||||
    cacheDir: cfg.get("Cache", "directory", "/tmp/nitter"),
 | 
			
		||||
    profileCacheTime: cfg.get("Cache", "profileMinutes", 10)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,8 +11,6 @@ const
 | 
			
		|||
  twRegex = re"(www.|mobile.)?twitter.com"
 | 
			
		||||
  nbsp = $Rune(0x000A0)
 | 
			
		||||
 | 
			
		||||
const hostname {.strdefine.} = "nitter.net"
 | 
			
		||||
 | 
			
		||||
proc stripText*(text: string): string =
 | 
			
		||||
  text.replace(nbsp, " ").strip()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -29,14 +27,14 @@ proc shortLink*(text: string; length=28): string =
 | 
			
		|||
  if result.len > length:
 | 
			
		||||
    result = result[0 ..< length] & "…"
 | 
			
		||||
 | 
			
		||||
proc replaceUrl*(url: string; prefs: Prefs; rss=false): string =
 | 
			
		||||
proc replaceUrl*(url: string; prefs: Prefs; absolute=""): string =
 | 
			
		||||
  result = url
 | 
			
		||||
  if prefs.replaceYouTube.len > 0:
 | 
			
		||||
    result = result.replace(ytRegex, prefs.replaceYouTube)
 | 
			
		||||
  if prefs.replaceTwitter.len > 0:
 | 
			
		||||
    result = result.replace(twRegex, prefs.replaceTwitter)
 | 
			
		||||
  if rss:
 | 
			
		||||
    result = result.replace("href=\"/", "href=\"https://" & hostname & "/")
 | 
			
		||||
  if absolute.len > 0:
 | 
			
		||||
    result = result.replace("href=\"/", "href=\"https://" & absolute & "/")
 | 
			
		||||
 | 
			
		||||
proc proxifyVideo*(manifest: string; proxy: bool): string =
 | 
			
		||||
  proc cb(m: RegexMatch; s: string): string =
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,10 +27,10 @@ settings:
 | 
			
		|||
 | 
			
		||||
routes:
 | 
			
		||||
  get "/":
 | 
			
		||||
    resp renderMain(renderSearch(), request, cfg.title)
 | 
			
		||||
    resp renderMain(renderSearch(), request, cfg)
 | 
			
		||||
 | 
			
		||||
  get "/about":
 | 
			
		||||
    resp renderMain(renderAbout(), request, cfg.title)
 | 
			
		||||
    resp renderMain(renderAbout(), request, cfg)
 | 
			
		||||
 | 
			
		||||
  get "/explore":
 | 
			
		||||
    redirect("/about")
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +44,7 @@ routes:
 | 
			
		|||
    redirect(replaceUrl(url, cookiePrefs()))
 | 
			
		||||
 | 
			
		||||
  error Http404:
 | 
			
		||||
    resp showError("Page not found", cfg.title)
 | 
			
		||||
    resp showError("Page not found", cfg)
 | 
			
		||||
 | 
			
		||||
  extend unsupported, ""
 | 
			
		||||
  extend preferences, ""
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,10 @@ withDb:
 | 
			
		|||
  except DbError:
 | 
			
		||||
    discard
 | 
			
		||||
 | 
			
		||||
proc getDefaultPrefs(hostname: string): Prefs =
 | 
			
		||||
  result = genDefaultPrefs()
 | 
			
		||||
  result.replaceTwitter = hostname
 | 
			
		||||
 | 
			
		||||
proc cache*(prefs: var Prefs) =
 | 
			
		||||
  withDb:
 | 
			
		||||
    try:
 | 
			
		||||
| 
						 | 
				
			
			@ -31,17 +35,18 @@ proc cache*(prefs: var Prefs) =
 | 
			
		|||
    except AssertionError, KeyError:
 | 
			
		||||
      prefs.insert()
 | 
			
		||||
 | 
			
		||||
proc getPrefs*(id: string): Prefs =
 | 
			
		||||
  if id.len == 0: return genDefaultPrefs()
 | 
			
		||||
proc getPrefs*(id, hostname: string): Prefs =
 | 
			
		||||
  if id.len == 0:
 | 
			
		||||
    return getDefaultPrefs(hostname)
 | 
			
		||||
 | 
			
		||||
  withDb:
 | 
			
		||||
    try:
 | 
			
		||||
      result.getOne("id = ?", id)
 | 
			
		||||
    except KeyError:
 | 
			
		||||
      result = genDefaultPrefs()
 | 
			
		||||
      result = getDefaultPrefs(hostname)
 | 
			
		||||
 | 
			
		||||
proc resetPrefs*(prefs: var Prefs) =
 | 
			
		||||
  var defPrefs = genDefaultPrefs()
 | 
			
		||||
proc resetPrefs*(prefs: var Prefs; hostname: string) =
 | 
			
		||||
  var defPrefs = getDefaultPrefs(hostname)
 | 
			
		||||
  defPrefs.id = prefs.id
 | 
			
		||||
  cache(defPrefs)
 | 
			
		||||
  prefs = defPrefs
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,5 @@
 | 
			
		|||
import macros, tables, strutils, xmltree
 | 
			
		||||
 | 
			
		||||
const hostname {.strdefine.} = "nitter.net"
 | 
			
		||||
 | 
			
		||||
type
 | 
			
		||||
  PrefKind* = enum
 | 
			
		||||
    checkbox, select, input
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +22,7 @@ const prefList*: OrderedTable[string, seq[Pref]] = {
 | 
			
		|||
  "Privacy": @[
 | 
			
		||||
    Pref(kind: input, name: "replaceTwitter",
 | 
			
		||||
         label: "Replace Twitter links with Nitter (blank to disable)",
 | 
			
		||||
         defaultInput: hostname, placeholder: "Nitter hostname"),
 | 
			
		||||
         defaultInput: "nitter.net", placeholder: "Nitter hostname"),
 | 
			
		||||
 | 
			
		||||
    Pref(kind: input, name: "replaceYouTube",
 | 
			
		||||
         label: "Replace YouTube links with Invidious (blank to disable)",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,10 +8,10 @@ import ../views/[general, timeline, list]
 | 
			
		|||
 | 
			
		||||
template respList*(list, timeline: typed) =
 | 
			
		||||
  if list.minId.len == 0:
 | 
			
		||||
    halt Http404, showError("List \"" & @"list" & "\" not found", cfg.title)
 | 
			
		||||
    halt Http404, showError("List \"" & @"list" & "\" not found", cfg)
 | 
			
		||||
  let html = renderList(timeline, list.query, @"name", @"list")
 | 
			
		||||
  let rss = "/$1/lists/$2/rss" % [@"name", @"list"]
 | 
			
		||||
  resp renderMain(html, request, cfg.title, rss=rss)
 | 
			
		||||
  resp renderMain(html, request, cfg, rss=rss)
 | 
			
		||||
 | 
			
		||||
proc createListRouter*(cfg: Config) =
 | 
			
		||||
  router list:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,7 +68,7 @@ proc createMediaRouter*(cfg: Config) =
 | 
			
		|||
      let prefs = cookiePrefs()
 | 
			
		||||
 | 
			
		||||
      if getHmac(url) != @"sig":
 | 
			
		||||
        resp showError("Failed to verify signature", cfg.title)
 | 
			
		||||
        resp showError("Failed to verify signature", cfg)
 | 
			
		||||
 | 
			
		||||
      let client = newAsyncHttpClient()
 | 
			
		||||
      var content = await client.getContent(url)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ proc createPrefRouter*(cfg: Config) =
 | 
			
		|||
 | 
			
		||||
    get "/settings":
 | 
			
		||||
      let html = renderPreferences(cookiePrefs(), refPath())
 | 
			
		||||
      resp renderMain(html, request, cfg.title, "Preferences")
 | 
			
		||||
      resp renderMain(html, request, cfg, "Preferences")
 | 
			
		||||
 | 
			
		||||
    get "/settings/@i?":
 | 
			
		||||
      redirect("/settings")
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +28,7 @@ proc createPrefRouter*(cfg: Config) =
 | 
			
		|||
 | 
			
		||||
    post "/resetprefs":
 | 
			
		||||
      var prefs = cookiePrefs()
 | 
			
		||||
      resetPrefs(prefs)
 | 
			
		||||
      resetPrefs(prefs, cfg.hostname)
 | 
			
		||||
      savePrefs()
 | 
			
		||||
      redirect($(parseUri("/settings") ? filterParams(request.params)))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@ import ../utils, ../prefs
 | 
			
		|||
export utils, prefs
 | 
			
		||||
 | 
			
		||||
template cookiePrefs*(): untyped {.dirty.} =
 | 
			
		||||
  getPrefs(request.cookies.getOrDefault("preferences"))
 | 
			
		||||
  getPrefs(request.cookies.getOrDefault("preferences"), cfg.hostname)
 | 
			
		||||
 | 
			
		||||
template getPath*(): untyped {.dirty.} =
 | 
			
		||||
  $(parseUri(request.path) ? filterParams(request.params))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,46 +8,46 @@ import ../views/general
 | 
			
		|||
 | 
			
		||||
include "../views/rss.nimf"
 | 
			
		||||
 | 
			
		||||
proc showRss*(name: string; query: Query): Future[string] {.async.} =
 | 
			
		||||
proc showRss*(name, hostname: string; query: Query): Future[string] {.async.} =
 | 
			
		||||
  let (profile, timeline, _) = await fetchSingleTimeline(name, "", getAgent(), query)
 | 
			
		||||
  if timeline != nil:
 | 
			
		||||
    return renderTimelineRss(timeline, profile)
 | 
			
		||||
    return renderTimelineRss(timeline, profile, hostname)
 | 
			
		||||
 | 
			
		||||
template respRss*(rss: typed) =
 | 
			
		||||
  if rss.len == 0:
 | 
			
		||||
    halt Http404, showError("User \"" & @"name" & "\" not found", cfg.title)
 | 
			
		||||
    halt Http404, showError("User \"" & @"name" & "\" not found", cfg)
 | 
			
		||||
  resp rss, "application/rss+xml;charset=utf-8"
 | 
			
		||||
 | 
			
		||||
proc createRssRouter*(cfg: Config) =
 | 
			
		||||
  router rss:
 | 
			
		||||
    get "/search/rss":
 | 
			
		||||
      if @"q".len > 200:
 | 
			
		||||
        resp Http400, showError("Search input too long.", cfg.title)
 | 
			
		||||
        resp Http400, showError("Search input too long.", cfg)
 | 
			
		||||
 | 
			
		||||
      let query = initQuery(params(request))
 | 
			
		||||
      if query.kind != tweets:
 | 
			
		||||
        resp Http400, showError("Only Tweet searches are allowed for RSS feeds.", cfg.title)
 | 
			
		||||
        resp Http400, showError("Only Tweet searches are allowed for RSS feeds.", cfg)
 | 
			
		||||
 | 
			
		||||
      let tweets = await getSearch[Tweet](query, "", getAgent())
 | 
			
		||||
      respRss(renderSearchRss(tweets.content, query.text, genQueryUrl(query)))
 | 
			
		||||
      respRss(renderSearchRss(tweets.content, query.text, genQueryUrl(query), cfg.hostname))
 | 
			
		||||
 | 
			
		||||
    get "/@name/rss":
 | 
			
		||||
      cond '.' notin @"name"
 | 
			
		||||
      respRss(await showRss(@"name", Query()))
 | 
			
		||||
      respRss(await showRss(@"name", cfg.hostname, Query()))
 | 
			
		||||
 | 
			
		||||
    get "/@name/with_replies/rss":
 | 
			
		||||
      cond '.' notin @"name"
 | 
			
		||||
      respRss(await showRss(@"name", getReplyQuery(@"name")))
 | 
			
		||||
      respRss(await showRss(@"name", cfg.hostname, getReplyQuery(@"name")))
 | 
			
		||||
 | 
			
		||||
    get "/@name/media/rss":
 | 
			
		||||
      cond '.' notin @"name"
 | 
			
		||||
      respRss(await showRss(@"name", getMediaQuery(@"name")))
 | 
			
		||||
      respRss(await showRss(@"name", cfg.hostname, getMediaQuery(@"name")))
 | 
			
		||||
 | 
			
		||||
    get "/@name/search/rss":
 | 
			
		||||
      cond '.' notin @"name"
 | 
			
		||||
      respRss(await showRss(@"name", initQuery(params(request), name=(@"name"))))
 | 
			
		||||
      respRss(await showRss(@"name", cfg.hostname, initQuery(params(request), name=(@"name"))))
 | 
			
		||||
 | 
			
		||||
    get "/@name/lists/@list/rss":
 | 
			
		||||
      cond '.' notin @"name"
 | 
			
		||||
      let list = await getListTimeline(@"name", @"list", getAgent(), "")
 | 
			
		||||
      respRss(renderListRss(list.content, @"name", @"list"))
 | 
			
		||||
      respRss(renderListRss(list.content, @"name", @"list", cfg.hostname))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ proc createSearchRouter*(cfg: Config) =
 | 
			
		|||
  router search:
 | 
			
		||||
    get "/search/?":
 | 
			
		||||
      if @"q".len > 200:
 | 
			
		||||
        resp Http400, showError("Search input too long.", cfg.title)
 | 
			
		||||
        resp Http400, showError("Search input too long.", cfg)
 | 
			
		||||
 | 
			
		||||
      let prefs = cookiePrefs()
 | 
			
		||||
      let query = initQuery(params(request))
 | 
			
		||||
| 
						 | 
				
			
			@ -22,14 +22,14 @@ proc createSearchRouter*(cfg: Config) =
 | 
			
		|||
        if "," in @"q":
 | 
			
		||||
          redirect("/" & @"q")
 | 
			
		||||
        let users = await getSearch[Profile](query, @"max_position", getAgent())
 | 
			
		||||
        resp renderMain(renderUserSearch(users, prefs), request, cfg.title)
 | 
			
		||||
        resp renderMain(renderUserSearch(users, prefs), request, cfg)
 | 
			
		||||
      of tweets:
 | 
			
		||||
        let tweets = await getSearch[Tweet](query, @"max_position", getAgent())
 | 
			
		||||
        let rss = "/search/rss?" & genQueryUrl(query)
 | 
			
		||||
        resp renderMain(renderTweetSearch(tweets, prefs, getPath()), request,
 | 
			
		||||
                        cfg.title, rss=rss)
 | 
			
		||||
        resp renderMain(renderTweetSearch(tweets, prefs, getPath()),
 | 
			
		||||
                        request, cfg, rss=rss)
 | 
			
		||||
      else:
 | 
			
		||||
        halt Http404, showError("Invalid search", cfg.title)
 | 
			
		||||
        halt Http404, showError("Invalid search", cfg)
 | 
			
		||||
 | 
			
		||||
    get "/hashtag/@hash":
 | 
			
		||||
      redirect("/search?q=" & encodeUrl("#" & @"hash"))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,7 @@ proc createStatusRouter*(cfg: Config) =
 | 
			
		|||
        var error = "Tweet not found"
 | 
			
		||||
        if conversation != nil and conversation.tweet.tombstone.len > 0:
 | 
			
		||||
          error = conversation.tweet.tombstone
 | 
			
		||||
        halt Http404, showError(error, cfg.title)
 | 
			
		||||
        halt Http404, showError(error, cfg)
 | 
			
		||||
 | 
			
		||||
      let
 | 
			
		||||
        title = pageTitle(conversation.tweet.profile)
 | 
			
		||||
| 
						 | 
				
			
			@ -32,15 +32,15 @@ proc createStatusRouter*(cfg: Config) =
 | 
			
		|||
      if conversation.tweet.video.isSome():
 | 
			
		||||
        let thumb = get(conversation.tweet.video).thumb
 | 
			
		||||
        let vidUrl = getVideoEmbed(conversation.tweet.id)
 | 
			
		||||
        resp renderMain(html, request, cfg.title, title, desc, images = @[thumb],
 | 
			
		||||
        resp renderMain(html, request, cfg, title, desc, images = @[thumb],
 | 
			
		||||
                        `type`="video", video=vidUrl)
 | 
			
		||||
      elif conversation.tweet.gif.isSome():
 | 
			
		||||
        let thumb = get(conversation.tweet.gif).thumb
 | 
			
		||||
        let vidUrl = getVideoEmbed(conversation.tweet.id)
 | 
			
		||||
        resp renderMain(html, request, cfg.title, title, desc, images = @[thumb],
 | 
			
		||||
        resp renderMain(html, request, cfg, title, desc, images = @[thumb],
 | 
			
		||||
                        `type`="video", video=vidUrl)
 | 
			
		||||
      else:
 | 
			
		||||
        resp renderMain(html, request, cfg.title, title, desc,
 | 
			
		||||
        resp renderMain(html, request, cfg, title, desc,
 | 
			
		||||
                        images=conversation.tweet.photos, `type`="photo")
 | 
			
		||||
 | 
			
		||||
    get "/@name/status/@id/photo/@i":
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,7 +51,7 @@ proc get*(req: Request; key: string): string =
 | 
			
		|||
  if key in params(req): params(req)[key]
 | 
			
		||||
  else: ""
 | 
			
		||||
 | 
			
		||||
proc showTimeline*(request: Request; query: Query; title, rss: string): Future[string] {.async.} =
 | 
			
		||||
proc showTimeline*(request: Request; query: Query; cfg: Config; rss: string): Future[string] {.async.} =
 | 
			
		||||
  let
 | 
			
		||||
    agent = getAgent()
 | 
			
		||||
    prefs = cookiePrefs()
 | 
			
		||||
| 
						 | 
				
			
			@ -63,16 +63,16 @@ proc showTimeline*(request: Request; query: Query; title, rss: string): Future[s
 | 
			
		|||
    let (p, t, r) = await fetchSingleTimeline(names[0], after, agent, query)
 | 
			
		||||
    if p.username.len == 0: return
 | 
			
		||||
    let pHtml = renderProfile(p, t, r, prefs, getPath())
 | 
			
		||||
    return renderMain(pHtml, request, title, pageTitle(p), pageDesc(p), rss=rss)
 | 
			
		||||
    return renderMain(pHtml, request, cfg, pageTitle(p), pageDesc(p), rss=rss)
 | 
			
		||||
  else:
 | 
			
		||||
    let
 | 
			
		||||
      timeline = await fetchMultiTimeline(names, after, agent, query)
 | 
			
		||||
      html = renderTweetSearch(timeline, prefs, getPath())
 | 
			
		||||
    return renderMain(html, request, title, "Multi")
 | 
			
		||||
    return renderMain(html, request, cfg, "Multi")
 | 
			
		||||
 | 
			
		||||
template respTimeline*(timeline: typed) =
 | 
			
		||||
  if timeline.len == 0:
 | 
			
		||||
    halt Http404, showError("User \"" & @"name" & "\" not found", cfg.title)
 | 
			
		||||
    halt Http404, showError("User \"" & @"name" & "\" not found", cfg)
 | 
			
		||||
  resp timeline
 | 
			
		||||
 | 
			
		||||
proc createTimelineRouter*(cfg: Config) =
 | 
			
		||||
| 
						 | 
				
			
			@ -82,20 +82,20 @@ proc createTimelineRouter*(cfg: Config) =
 | 
			
		|||
    get "/@name/?":
 | 
			
		||||
      cond '.' notin @"name"
 | 
			
		||||
      let rss = "/$1/rss" % @"name"
 | 
			
		||||
      respTimeline(await showTimeline(request, Query(), cfg.title, rss))
 | 
			
		||||
      respTimeline(await showTimeline(request, Query(), cfg, rss))
 | 
			
		||||
 | 
			
		||||
    get "/@name/with_replies":
 | 
			
		||||
      cond '.' notin @"name"
 | 
			
		||||
      let rss = "/$1/with_replies/rss" % @"name"
 | 
			
		||||
      respTimeline(await showTimeline(request, getReplyQuery(@"name"), cfg.title, rss))
 | 
			
		||||
      respTimeline(await showTimeline(request, getReplyQuery(@"name"), cfg, rss))
 | 
			
		||||
 | 
			
		||||
    get "/@name/media":
 | 
			
		||||
      cond '.' notin @"name"
 | 
			
		||||
      let rss = "/$1/media/rss" % @"name"
 | 
			
		||||
      respTimeline(await showTimeline(request, getMediaQuery(@"name"), cfg.title, rss))
 | 
			
		||||
      respTimeline(await showTimeline(request, getMediaQuery(@"name"), cfg, rss))
 | 
			
		||||
 | 
			
		||||
    get "/@name/search":
 | 
			
		||||
      cond '.' notin @"name"
 | 
			
		||||
      let query = initQuery(params(request), name=(@"name"))
 | 
			
		||||
      let rss = "/$1/search/rss?$2" % [@"name", genQueryUrl(query)]
 | 
			
		||||
      respTimeline(await showTimeline(request, query, cfg.title, rss))
 | 
			
		||||
      respTimeline(await showTimeline(request, query, cfg, rss))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,14 +7,14 @@ import ../views/[general, about]
 | 
			
		|||
proc createUnsupportedRouter*(cfg: Config) =
 | 
			
		||||
  router unsupported:
 | 
			
		||||
    get "/about/feature":
 | 
			
		||||
      resp renderMain(renderFeature(), request, cfg.title)
 | 
			
		||||
      resp renderMain(renderFeature(), request, cfg)
 | 
			
		||||
 | 
			
		||||
    get "/intent/?@i?":
 | 
			
		||||
      resp renderMain(renderFeature(), request, cfg.title)
 | 
			
		||||
      resp renderMain(renderFeature(), request, cfg)
 | 
			
		||||
 | 
			
		||||
    get "/login/?@i?":
 | 
			
		||||
      resp renderMain(renderFeature(), request, cfg.title)
 | 
			
		||||
      resp renderMain(renderFeature(), request, cfg)
 | 
			
		||||
 | 
			
		||||
    get "/i/@i?/?@j?":
 | 
			
		||||
      cond @"i" != "status"
 | 
			
		||||
      resp renderMain(renderFeature(), request, cfg.title)
 | 
			
		||||
      resp renderMain(renderFeature(), request, cfg)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -172,8 +172,9 @@ type
 | 
			
		|||
    address*: string
 | 
			
		||||
    port*: int
 | 
			
		||||
    useHttps*: bool
 | 
			
		||||
    title*: string
 | 
			
		||||
    staticDir*: string
 | 
			
		||||
    title*: string
 | 
			
		||||
    hostname*: string
 | 
			
		||||
    cacheDir*: string
 | 
			
		||||
    profileCacheTime*: int
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,9 +27,9 @@ proc renderNavbar*(title, rss: string; req: Request): VNode =
 | 
			
		|||
        icon "info-circled", title="About", href="/about"
 | 
			
		||||
        iconReferer "cog", "/settings", path, title="Preferences"
 | 
			
		||||
 | 
			
		||||
proc renderMain*(body: VNode; req: Request; title="Nitter"; titleText=""; desc="";
 | 
			
		||||
proc renderMain*(body: VNode; req: Request; cfg: Config; titleText=""; desc="";
 | 
			
		||||
                 rss=""; `type`="article"; video=""; images: seq[string] = @[]): string =
 | 
			
		||||
  let prefs = getPrefs(req.cookies.getOrDefault("preferences"))
 | 
			
		||||
  let prefs = getPrefs(req.cookies.getOrDefault("preferences"), cfg.hostname)
 | 
			
		||||
  let node = buildHtml(html(lang="en")):
 | 
			
		||||
    head:
 | 
			
		||||
      link(rel="stylesheet", `type`="text/css", href="/css/style.css")
 | 
			
		||||
| 
						 | 
				
			
			@ -50,9 +50,9 @@ proc renderMain*(body: VNode; req: Request; title="Nitter"; titleText=""; desc="
 | 
			
		|||
 | 
			
		||||
      title:
 | 
			
		||||
        if titleText.len > 0:
 | 
			
		||||
          text titleText & " | " & title
 | 
			
		||||
          text titleText & " | " & cfg.title
 | 
			
		||||
        else:
 | 
			
		||||
          text title
 | 
			
		||||
          text cfg.title
 | 
			
		||||
 | 
			
		||||
      meta(name="viewport", content="width=device-width, initial-scale=1.0")
 | 
			
		||||
      meta(property="og:type", content=`type`)
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +68,7 @@ proc renderMain*(body: VNode; req: Request; title="Nitter"; titleText=""; desc="
 | 
			
		|||
        meta(property="og:video:secure_url", content=video)
 | 
			
		||||
 | 
			
		||||
    body:
 | 
			
		||||
      renderNavbar(title, rss, req)
 | 
			
		||||
      renderNavbar(cfg.title, rss, req)
 | 
			
		||||
 | 
			
		||||
      tdiv(class="container"):
 | 
			
		||||
        body
 | 
			
		||||
| 
						 | 
				
			
			@ -80,5 +80,5 @@ proc renderError*(error: string): VNode =
 | 
			
		|||
    tdiv(class="error-panel"):
 | 
			
		||||
      span: text error
 | 
			
		||||
 | 
			
		||||
template showError*(error, title: string): string =
 | 
			
		||||
  renderMain(renderError(error), request, title, "Error")
 | 
			
		||||
template showError*(error: string; cfg: Config): string =
 | 
			
		||||
  renderMain(renderError(error), request, cfg, "Error")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,12 @@
 | 
			
		|||
#? stdtmpl(subsChar = '$', metaChad = '#')
 | 
			
		||||
#import strutils, xmltree, strformat
 | 
			
		||||
#import ../types, ../utils, ../formatters
 | 
			
		||||
#const hostname {.strdefine.} = "nitter.net"
 | 
			
		||||
#
 | 
			
		||||
#proc getTitle(tweet: Tweet; prefs: Prefs): string =
 | 
			
		||||
#proc getTitle(tweet: Tweet; prefs: Prefs; hostname: string): string =
 | 
			
		||||
#if tweet.pinned: result = "Pinned: "
 | 
			
		||||
#elif tweet.retweet.isSome: result = "RT: "
 | 
			
		||||
#end if
 | 
			
		||||
#result &= xmltree.escape(replaceUrl(tweet.text, prefs, rss=true))
 | 
			
		||||
#result &= xmltree.escape(replaceUrl(tweet.text, prefs, absolute=hostname))
 | 
			
		||||
#if result.len > 0: return
 | 
			
		||||
#end if
 | 
			
		||||
#if tweet.photos.len > 0:
 | 
			
		||||
| 
						 | 
				
			
			@ -19,8 +18,8 @@
 | 
			
		|||
#end if
 | 
			
		||||
#end proc
 | 
			
		||||
#
 | 
			
		||||
#proc renderRssTweet(tweet: Tweet; prefs: Prefs): string =
 | 
			
		||||
#let text = replaceUrl(tweet.text, prefs, rss=true)
 | 
			
		||||
#proc renderRssTweet(tweet: Tweet; prefs: Prefs; hostname: string): string =
 | 
			
		||||
#let text = replaceUrl(tweet.text, prefs, absolute=hostname)
 | 
			
		||||
#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>
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +38,7 @@
 | 
			
		|||
#end if
 | 
			
		||||
#end proc
 | 
			
		||||
#
 | 
			
		||||
#proc renderRssTweets(tweets: seq[Tweet]; prefs: Prefs): string =
 | 
			
		||||
#proc renderRssTweets(tweets: seq[Tweet]; prefs: Prefs; hostname: string): string =
 | 
			
		||||
#var links: seq[string]
 | 
			
		||||
#for tweet in tweets:
 | 
			
		||||
#let link = getLink(tweet)
 | 
			
		||||
| 
						 | 
				
			
			@ -47,9 +46,9 @@
 | 
			
		|||
#end if
 | 
			
		||||
#links.add link
 | 
			
		||||
<item>
 | 
			
		||||
  <title>${getTitle(tweet, prefs)}</title>
 | 
			
		||||
  <title>${getTitle(tweet, prefs, hostname)}</title>
 | 
			
		||||
  <dc:creator>@${tweet.profile.username}</dc:creator>
 | 
			
		||||
  <description><![CDATA[${renderRssTweet(tweet, prefs).strip(chars={'\n'})}]]></description>
 | 
			
		||||
  <description><![CDATA[${renderRssTweet(tweet, prefs, hostname).strip(chars={'\n'})}]]></description>
 | 
			
		||||
  <pubDate>${getRfc822Time(tweet)}</pubDate>
 | 
			
		||||
  <guid>https://${hostname & link}</guid>
 | 
			
		||||
  <link>https://${hostname & link}</link>
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +56,7 @@
 | 
			
		|||
#end for
 | 
			
		||||
#end proc
 | 
			
		||||
#
 | 
			
		||||
#proc renderTimelineRss*(timeline: Timeline; profile: Profile): string =
 | 
			
		||||
#proc renderTimelineRss*(timeline: Timeline; profile: Profile; hostname: string): string =
 | 
			
		||||
#let prefs = Prefs(replaceTwitter: hostname, replaceYoutube: "invidio.us")
 | 
			
		||||
#result = ""
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
| 
						 | 
				
			
			@ -77,13 +76,13 @@
 | 
			
		|||
      <height>128</height>
 | 
			
		||||
    </image>
 | 
			
		||||
    #if timeline != nil:
 | 
			
		||||
    ${renderRssTweets(timeline.content, prefs)}
 | 
			
		||||
    ${renderRssTweets(timeline.content, prefs, hostname)}
 | 
			
		||||
    #end if
 | 
			
		||||
  </channel>
 | 
			
		||||
</rss>
 | 
			
		||||
#end proc
 | 
			
		||||
#
 | 
			
		||||
#proc renderListRss*(tweets: seq[Tweet]; name, list: string): string =
 | 
			
		||||
#proc renderListRss*(tweets: seq[Tweet]; name, list, hostname: string): string =
 | 
			
		||||
#let prefs = Prefs(replaceTwitter: hostname, replaceYoutube: "invidio.us")
 | 
			
		||||
#let link = &"https://{hostname}/{name}/lists/{list}"
 | 
			
		||||
#result = ""
 | 
			
		||||
| 
						 | 
				
			
			@ -96,12 +95,12 @@
 | 
			
		|||
    <description>Twitter feed for: ${list} by @${name}. Generated by ${hostname}</description>
 | 
			
		||||
    <language>en-us</language>
 | 
			
		||||
    <ttl>40</ttl>
 | 
			
		||||
    ${renderRssTweets(tweets, prefs)}
 | 
			
		||||
    ${renderRssTweets(tweets, prefs, hostname)}
 | 
			
		||||
 </channel>
 | 
			
		||||
</rss>
 | 
			
		||||
#end proc
 | 
			
		||||
#
 | 
			
		||||
#proc renderSearchRss*(tweets: seq[Tweet]; name, param: string): string =
 | 
			
		||||
#proc renderSearchRss*(tweets: seq[Tweet]; name, param, hostname: string): string =
 | 
			
		||||
#let prefs = Prefs(replaceTwitter: hostname, replaceYoutube: "invidio.us")
 | 
			
		||||
#let link = &"https://{hostname}/search"
 | 
			
		||||
#result = ""
 | 
			
		||||
| 
						 | 
				
			
			@ -114,7 +113,7 @@
 | 
			
		|||
    <description>Twitter feed for search "${name}". Generated by ${hostname}</description>
 | 
			
		||||
    <language>en-us</language>
 | 
			
		||||
    <ttl>40</ttl>
 | 
			
		||||
    ${renderRssTweets(tweets, prefs)}
 | 
			
		||||
    ${renderRssTweets(tweets, prefs, hostname)}
 | 
			
		||||
  </channel>
 | 
			
		||||
</rss>
 | 
			
		||||
#end proc
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue