Add server config file

This commit is contained in:
Zed 2019-07-31 02:15:43 +02:00
parent b10d894a11
commit 6a9d182249
8 changed files with 53 additions and 23 deletions

View File

@ -28,7 +28,6 @@ is on implementing missing features.
- Search (images/videos, hashtags, etc.) - Search (images/videos, hashtags, etc.)
- Custom timeline filter - Custom timeline filter
- Nitter link previews - Nitter link previews
- Server configuration
- More caching (waiting for [moigagoo/norm#19](https://github.com/moigagoo/norm/pull/19)) - More caching (waiting for [moigagoo/norm#19](https://github.com/moigagoo/norm/pull/19))
- Simple account system with customizable feed - Simple account system with customizable feed
- Video support with hls.js - Video support with hls.js

9
nitter.conf Normal file
View File

@ -0,0 +1,9 @@
[Server]
address = "0.0.0.0"
port = 8080
title = "nitter"
staticDir = "./public"
[Cache]
directory = "/tmp/niter"
profileMinutes = 10 # how long to cache profiles

View File

@ -450,6 +450,7 @@ video {
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: 100%; max-width: 100%;
font-weight: bold; font-weight: bold;
overflow-wrap: break-word;
} }
.profile-card-username { .profile-card-username {

View File

@ -26,3 +26,6 @@ proc getCachedProfile*(username: string; force=false): Future[Profile] {.async.}
result = await getProfile(username) result = await getProfile(username)
if result.username.len > 0: if result.username.len > 0:
result.insert() result.insert()
proc setProfileCacheTime*(minutes: int) =
profileCacheTime = initDuration(minutes=minutes)

View File

@ -69,10 +69,7 @@ proc getUserpic*(profile: Profile; style=""): string =
getUserPic(profile.userpic, style) getUserPic(profile.userpic, style)
proc pageTitle*(profile: Profile): string = proc pageTitle*(profile: Profile): string =
&"{profile.fullname} (@{profile.username}) | Nitter" &"{profile.fullname} (@{profile.username})"
proc pageTitle*(page: string): string =
&"{page} | Nitter"
proc getTime*(tweet: Tweet): string = proc getTime*(tweet: Tweet): string =
tweet.time.format("d/M/yyyy', ' HH:mm:ss") tweet.time.format("d/M/yyyy', ' HH:mm:ss")

View File

@ -1,11 +1,13 @@
import asyncdispatch, asyncfile, httpclient, strutils, strformat, uri, os import asyncdispatch, asyncfile, httpclient, strutils, strformat, uri, os
from net import Port
import jester, regex import jester, regex
import api, utils, types, cache, formatters, search import api, utils, types, cache, formatters, search, config
import views/[general, profile, status] import views/[general, profile, status]
const cacheDir {.strdefine.} = "/tmp/nitter" const configPath {.strdefine.} = "./nitter.conf"
let cfg = getConfig(configPath)
proc showTimeline(name, after: string; query: Option[Query]): Future[string] {.async.} = proc showTimeline(name, after: string; query: Option[Query]): Future[string] {.async.} =
let let
@ -24,20 +26,27 @@ proc showTimeline(name, after: string; query: Option[Query]): Future[string] {.a
return "" return ""
let profileHtml = renderProfile(profile, await timelineFut, await railFut) let profileHtml = renderProfile(profile, await timelineFut, await railFut)
return renderMain(profileHtml, title=pageTitle(profile)) return renderMain(profileHtml, title=cfg.title, titleText=pageTitle(profile))
template respTimeline(timeline: typed) = template respTimeline(timeline: typed) =
if timeline.len == 0: if timeline.len == 0:
resp Http404, showError("User \"" & @"name" & "\" not found") resp Http404, showError("User \"" & @"name" & "\" not found", cfg.title)
resp timeline resp timeline
setProfileCacheTime(cfg.profileCacheTime)
settings:
port = Port(cfg.port)
staticDir = cfg.staticDir
bindAddr = cfg.address
routes: routes:
get "/": get "/":
resp renderMain(renderSearch(), title=pageTitle("Search")) resp renderMain(renderSearch(), title=cfg.title, titleText="Search")
post "/search": post "/search":
if @"query".len == 0: if @"query".len == 0:
resp Http404, showError("Please enter a username.") resp Http404, showError("Please enter a username.", cfg.title)
redirect("/" & @"query") redirect("/" & @"query")
get "/@name/?": get "/@name/?":
@ -62,7 +71,7 @@ routes:
let conversation = await getTweet(@"name", @"id") let conversation = await getTweet(@"name", @"id")
if conversation == nil or conversation.tweet.id.len == 0: if conversation == nil or conversation.tweet.id.len == 0:
resp Http404, showError("Tweet not found") resp Http404, showError("Tweet not found", cfg.title)
let title = pageTitle(conversation.tweet.profile) let title = pageTitle(conversation.tweet.profile)
resp renderMain(renderConversation(conversation), title=title) resp renderMain(renderConversation(conversation), title=title)
@ -74,13 +83,13 @@ routes:
let let
uri = parseUri(decodeUrl(@"url")) uri = parseUri(decodeUrl(@"url"))
path = uri.path.split("/")[2 .. ^1].join("/") path = uri.path.split("/")[2 .. ^1].join("/")
filename = cacheDir / cleanFilename(path & uri.query) filename = cfg.cacheDir / cleanFilename(path & uri.query)
if getHmac($uri) != @"sig": if getHmac($uri) != @"sig":
resp showError("Failed to verify signature") resp showError("Failed to verify signature", cfg.title)
if not existsDir(cacheDir): if not existsDir(cfg.cacheDir):
createDir(cacheDir) createDir(cfg.cacheDir)
if not existsFile(filename): if not existsFile(filename):
let client = newAsyncHttpClient() let client = newAsyncHttpClient()
@ -92,6 +101,7 @@ routes:
let file = openAsync(filename) let file = openAsync(filename)
defer: file.close() defer: file.close()
resp await readAll(file), mimetype(filename) resp await readAll(file), mimetype(filename)
get "/video/@sig/@url": get "/video/@sig/@url":
@ -100,7 +110,7 @@ routes:
let url = decodeUrl(@"url") let url = decodeUrl(@"url")
if getHmac(url) != @"sig": if getHmac(url) != @"sig":
resp showError("Failed to verify signature") resp showError("Failed to verify signature", cfg.title)
let let
client = newAsyncHttpClient() client = newAsyncHttpClient()

View File

@ -147,5 +147,13 @@ type
beginning*: bool beginning*: bool
query*: Option[Query] query*: Option[Query]
Config* = ref object
address*: string
port*: int
title*: string
staticDir*: string
cacheDir*: string
profileCacheTime*: int
proc contains*(thread: Thread; tweet: Tweet): bool = proc contains*(thread: Thread; tweet: Tweet): bool =
thread.tweets.anyIt(it.id == tweet.id) thread.tweets.anyIt(it.id == tweet.id)

View File

@ -2,17 +2,20 @@ import karax/[karaxdsl, vdom]
const doctype = "<!DOCTYPE html>\n" const doctype = "<!DOCTYPE html>\n"
proc renderMain*(body: VNode; title="Nitter"): string = proc renderMain*(body: VNode; title="Nitter"; titleText=""): string =
let node = buildHtml(html(lang="en")): let node = buildHtml(html(lang="en")):
head: head:
title: text title if titleText.len > 0:
title: text titleText & " | " & title
else:
title: text title
link(rel="stylesheet", `type`="text/css", href="/style.css") link(rel="stylesheet", `type`="text/css", href="/style.css")
body: body:
nav(id="nav", class="nav-bar container"): nav(id="nav", class="nav-bar container"):
tdiv(class="inner-nav"): tdiv(class="inner-nav"):
tdiv(class="item"): tdiv(class="item"):
a(href="/", class="site-name"): text "nitter" a(href="/", class="site-name"): text title
tdiv(id="content", class="container"): tdiv(id="content", class="container"):
body body
@ -31,5 +34,5 @@ proc renderError*(error: string): VNode =
tdiv(class="error-panel"): tdiv(class="error-panel"):
span: text error span: text error
proc showError*(error: string): string = proc showError*(error: string; title: string): string =
renderMain(renderError(error), title = "Error | Nitter") renderMain(renderError(error), title=title, titleText="Error")