diff --git a/README.md b/README.md index ae2b254..7612d6d 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ is on implementing missing features. - Search (images/videos, hashtags, etc.) - Custom timeline filter - Nitter link previews -- Server configuration - More caching (waiting for [moigagoo/norm#19](https://github.com/moigagoo/norm/pull/19)) - Simple account system with customizable feed - Video support with hls.js diff --git a/nitter.conf b/nitter.conf new file mode 100644 index 0000000..ba3446d --- /dev/null +++ b/nitter.conf @@ -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 \ No newline at end of file diff --git a/public/style.css b/public/style.css index df421de..fa702be 100644 --- a/public/style.css +++ b/public/style.css @@ -450,6 +450,7 @@ video { text-overflow: ellipsis; max-width: 100%; font-weight: bold; + overflow-wrap: break-word; } .profile-card-username { diff --git a/src/cache.nim b/src/cache.nim index 5871962..63a05ba 100644 --- a/src/cache.nim +++ b/src/cache.nim @@ -26,3 +26,6 @@ proc getCachedProfile*(username: string; force=false): Future[Profile] {.async.} result = await getProfile(username) if result.username.len > 0: result.insert() + +proc setProfileCacheTime*(minutes: int) = + profileCacheTime = initDuration(minutes=minutes) diff --git a/src/formatters.nim b/src/formatters.nim index 5d0de04..e5ffc76 100644 --- a/src/formatters.nim +++ b/src/formatters.nim @@ -69,10 +69,7 @@ proc getUserpic*(profile: Profile; style=""): string = getUserPic(profile.userpic, style) proc pageTitle*(profile: Profile): string = - &"{profile.fullname} (@{profile.username}) | Nitter" - -proc pageTitle*(page: string): string = - &"{page} | Nitter" + &"{profile.fullname} (@{profile.username})" proc getTime*(tweet: Tweet): string = tweet.time.format("d/M/yyyy', ' HH:mm:ss") diff --git a/src/nitter.nim b/src/nitter.nim index 71ba075..7e87a3d 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -1,11 +1,13 @@ import asyncdispatch, asyncfile, httpclient, strutils, strformat, uri, os +from net import Port + import jester, regex -import api, utils, types, cache, formatters, search - +import api, utils, types, cache, formatters, search, config 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.} = let @@ -24,20 +26,27 @@ proc showTimeline(name, after: string; query: Option[Query]): Future[string] {.a return "" 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) = if timeline.len == 0: - resp Http404, showError("User \"" & @"name" & "\" not found") + resp Http404, showError("User \"" & @"name" & "\" not found", cfg.title) resp timeline +setProfileCacheTime(cfg.profileCacheTime) + +settings: + port = Port(cfg.port) + staticDir = cfg.staticDir + bindAddr = cfg.address + routes: get "/": - resp renderMain(renderSearch(), title=pageTitle("Search")) + resp renderMain(renderSearch(), title=cfg.title, titleText="Search") post "/search": if @"query".len == 0: - resp Http404, showError("Please enter a username.") + resp Http404, showError("Please enter a username.", cfg.title) redirect("/" & @"query") get "/@name/?": @@ -62,7 +71,7 @@ routes: let conversation = await getTweet(@"name", @"id") 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) resp renderMain(renderConversation(conversation), title=title) @@ -74,13 +83,13 @@ routes: let uri = parseUri(decodeUrl(@"url")) path = uri.path.split("/")[2 .. ^1].join("/") - filename = cacheDir / cleanFilename(path & uri.query) + filename = cfg.cacheDir / cleanFilename(path & uri.query) if getHmac($uri) != @"sig": - resp showError("Failed to verify signature") + resp showError("Failed to verify signature", cfg.title) - if not existsDir(cacheDir): - createDir(cacheDir) + if not existsDir(cfg.cacheDir): + createDir(cfg.cacheDir) if not existsFile(filename): let client = newAsyncHttpClient() @@ -92,6 +101,7 @@ routes: let file = openAsync(filename) defer: file.close() + resp await readAll(file), mimetype(filename) get "/video/@sig/@url": @@ -100,7 +110,7 @@ routes: let url = decodeUrl(@"url") if getHmac(url) != @"sig": - resp showError("Failed to verify signature") + resp showError("Failed to verify signature", cfg.title) let client = newAsyncHttpClient() diff --git a/src/types.nim b/src/types.nim index 9cde0c7..23ba107 100644 --- a/src/types.nim +++ b/src/types.nim @@ -147,5 +147,13 @@ type beginning*: bool 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 = thread.tweets.anyIt(it.id == tweet.id) diff --git a/src/views/general.nim b/src/views/general.nim index 1c5165a..8a156cb 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -2,17 +2,20 @@ import karax/[karaxdsl, vdom] const doctype = "\n" -proc renderMain*(body: VNode; title="Nitter"): string = +proc renderMain*(body: VNode; title="Nitter"; titleText=""): string = let node = buildHtml(html(lang="en")): 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") body: nav(id="nav", class="nav-bar container"): tdiv(class="inner-nav"): tdiv(class="item"): - a(href="/", class="site-name"): text "nitter" + a(href="/", class="site-name"): text title tdiv(id="content", class="container"): body @@ -31,5 +34,5 @@ proc renderError*(error: string): VNode = tdiv(class="error-panel"): span: text error -proc showError*(error: string): string = - renderMain(renderError(error), title = "Error | Nitter") +proc showError*(error: string; title: string): string = + renderMain(renderError(error), title=title, titleText="Error")