Add server config file
This commit is contained in:
parent
b10d894a11
commit
6a9d182249
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in New Issue