From 312ff78628829e3bb2a5a3354e8d85cde616c265 Mon Sep 17 00:00:00 2001 From: Zed Date: Fri, 8 May 2020 02:48:47 +0200 Subject: [PATCH] Store preferences in cookies, add config defaults --- nitter.conf | 11 ++++- src/config.nim | 11 +++-- src/nitter.nim | 4 +- src/prefs.nim | 61 +++++------------------- src/prefs_impl.nim | 93 +++++++++++++++++++++++++------------ src/routes/preferences.nim | 20 +------- src/routes/router_utils.nim | 8 +++- src/types.nim | 4 +- src/views/general.nim | 6 ++- 9 files changed, 109 insertions(+), 109 deletions(-) diff --git a/nitter.conf b/nitter.conf index f01f7b9..ef52f3f 100644 --- a/nitter.conf +++ b/nitter.conf @@ -11,5 +11,14 @@ directory = "./tmp" profileMinutes = 10 # how long to cache profiles [Config] -defaultTheme = "Nitter" hmacKey = "secretkey" # for signing video urls + +# Change default preferences here, see src/prefs_impl.nim for a complete list +[Preferences] +theme = "Nitter" +replaceTwitter = "nitter.net" +replaceYouTube = "invidio.us" +replaceInstagram = "" +proxyVideos = true +hlsPlayback = false +infiniteScroll = false diff --git a/src/config.nim b/src/config.nim index 0a79ada..54d09e4 100644 --- a/src/config.nim +++ b/src/config.nim @@ -1,7 +1,7 @@ import parsecfg except Config -import net, types, strutils +import types, strutils -proc get[T](config: parseCfg.Config; s, v: string; default: T): T = +proc get*[T](config: parseCfg.Config; s, v: string; default: T): T = let val = config.getSectionValue(s, v) if val.len == 0: return default @@ -9,10 +9,10 @@ proc get[T](config: parseCfg.Config; s, v: string; default: T): T = elif T is bool: parseBool(val) elif T is string: val -proc getConfig*(path: string): Config = +proc getConfig*(path: string): (Config, parseCfg.Config) = var cfg = loadConfig(path) - Config( + let conf = Config( address: cfg.get("Server", "address", "0.0.0.0"), port: cfg.get("Server", "port", 8080), useHttps: cfg.get("Server", "https", true), @@ -23,6 +23,7 @@ proc getConfig*(path: string): Config = cacheDir: cfg.get("Cache", "directory", "/tmp/nitter"), profileCacheTime: cfg.get("Cache", "profileMinutes", 10), - defaultTheme: cfg.get("Config", "defaultTheme", "Nitter"), hmacKey: cfg.get("Config", "hmacKey", "secretkey") ) + + return (conf, cfg) diff --git a/src/nitter.nim b/src/nitter.nim index 8ea24ce..457156a 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -10,7 +10,9 @@ import routes/[ unsupported, embed, resolver] const configPath {.strdefine.} = "./nitter.conf" -let cfg = getConfig(configPath) +let (cfg, fullCfg) = getConfig(configPath) + +updateDefaultPrefs(fullCfg) setHmacKey(cfg.hmacKey) diff --git a/src/prefs.nim b/src/prefs.nim index 7a0401c..3f52fd5 100644 --- a/src/prefs.nim +++ b/src/prefs.nim @@ -1,54 +1,15 @@ -import strutils, sequtils, macros -import norm/sqlite +import tables +import types, prefs_impl +from config import get +from parsecfg import nil -import prefs_impl, types -export genUpdatePrefs +export genUpdatePrefs, genResetPrefs -template safeAddColumn(field: typedesc): untyped = - try: field.addColumn - except DbError: discard +var defaultPrefs*: Prefs -dbFromTypes("prefs.db", "", "", "", [Prefs]) +proc updateDefaultPrefs*(cfg: parsecfg.Config) = + genDefaultPrefs() -withDb: - try: - createTables() - except DbError: - discard - safeAddColumn Prefs.theme - safeAddColumn Prefs.hidePins - safeAddColumn Prefs.hideReplies - safeAddColumn Prefs.infiniteScroll - safeAddColumn Prefs.replaceInstagram - -proc getDefaultPrefs(cfg: Config): Prefs = - result = genDefaultPrefs() - result.replaceTwitter = cfg.hostname - result.theme = cfg.defaultTheme - -proc cache*(prefs: var Prefs) = - withDb: - try: - doAssert prefs.id != 0 - discard Prefs.getOne("id = ?", prefs.id) - prefs.update() - except AssertionError, KeyError: - prefs.insert() - -proc getPrefs*(id: string; cfg: Config): Prefs = - if id.len == 0: - return getDefaultPrefs(cfg) - - withDb: - try: - result.getOne("id = ?", id) - if result.theme.len == 0: - result.theme = cfg.defaultTheme - except KeyError: - result = getDefaultPrefs(cfg) - -proc resetPrefs*(prefs: var Prefs; cfg: Config) = - var defPrefs = getDefaultPrefs(cfg) - defPrefs.id = prefs.id - cache(defPrefs) - prefs = defPrefs +proc getPrefs*(cookies: Table[string, string]): Prefs = + result = defaultPrefs + genCookiePrefs() diff --git a/src/prefs_impl.nim b/src/prefs_impl.nim index d38376a..d27a829 100644 --- a/src/prefs_impl.nim +++ b/src/prefs_impl.nim @@ -14,7 +14,9 @@ type defaultOption*: string defaultInput*: string -macro genPrefs(prefDsl: untyped) = + PrefList* = OrderedTable[string, seq[Pref]] + +macro genPrefs*(prefDsl: untyped) = var table = nnkTableConstr.newTree() for category in prefDsl: table.add nnkExprColonExpr.newTree(newLit($category[0])) @@ -41,7 +43,7 @@ macro genPrefs(prefDsl: untyped) = let name = ident("prefList") result = quote do: - const `name`* = toOrderedTable(`table`) + const `name`*: PrefList = toOrderedTable(`table`) genPrefs: Privacy: @@ -101,46 +103,79 @@ iterator allPrefs*(): Pref = yield pref macro genDefaultPrefs*(): untyped = - result = nnkObjConstr.newTree(ident("Prefs")) - + result = nnkStmtList.newTree() for pref in allPrefs(): - let default = - case pref.kind - of checkbox: newLit(pref.defaultState) - of select: newLit(pref.defaultOption) - of input: newLit(pref.defaultInput) + let + ident = ident(pref.name) + name = newLit(pref.name) + default = + case pref.kind + of checkbox: newLit(pref.defaultState) + of select: newLit(pref.defaultOption) + of input: newLit(pref.defaultInput) - result.add nnkExprColonExpr.newTree(ident(pref.name), default) + result.add quote do: + defaultPrefs.`ident` = cfg.get("Preferences", `name`, `default`) + +macro genCookiePrefs*(): untyped = + result = nnkStmtList.newTree() + let cookies = ident("cookies") + for pref in allPrefs(): + let + name = pref.name + ident = ident(pref.name) + kind = newLit(pref.kind) + options = pref.options + + result.add quote do: + if `name` in `cookies`: + let value = `cookies`[`name`] + when `kind` == input or `name` == "theme": + result.`ident` = value + elif `kind` == checkbox: + result.`ident` = value == "on" + else: + if value in `options`: result.`ident` = value macro genUpdatePrefs*(): untyped = result = nnkStmtList.newTree() - + let req = ident("request") for pref in allPrefs(): - let ident = ident(pref.name) - let value = nnkPrefix.newTree(ident("@"), newLit(pref.name)) + let + name = newLit(pref.name) + kind = newLit(pref.kind) + options = newLit(pref.options) + default = nnkDotExpr.newTree(ident("defaultPrefs"), ident(pref.name)) - case pref.kind - of checkbox: - result.add quote do: prefs.`ident` = `value` == "on" - of input: - result.add quote do: prefs.`ident` = xmltree.escape(strip(`value`)) - of select: - let name = pref.name - let options = pref.options - let default = pref.defaultOption - result.add quote do: - if `name` == "theme": prefs.`ident` = `value` - elif `value` in `options`: prefs.`ident` = `value` - else: prefs.`ident` = `default` + result.add quote do: + let val = @`name` + let isDefault = + when `kind` == input or `name` == "theme": + if `default`.len != val.len: false + else: val == `default` + elif `kind` == checkbox: + (val == "on") == `default` + else: + val notin `options` or val == `default` - result.add quote do: - cache(prefs) + if isDefault: + savePref(`name`, "", `req`, expire=true) + else: + savePref(`name`, val, `req`) + +macro genResetPrefs*(): untyped = + result = nnkStmtList.newTree() + let req = ident("request") + for pref in allPrefs(): + let name = newLit(pref.name) + result.add quote do: + savePref(`name`, "", `req`, expire=true) macro genPrefsType*(): untyped = let name = nnkPostfix.newTree(ident("*"), ident("Prefs")) result = quote do: type `name` = object - id* {.pk, ro.}: int + discard for pref in allPrefs(): result[0][2][2].add nnkIdentDefs.newTree( diff --git a/src/routes/preferences.nim b/src/routes/preferences.nim index 08600d9..3c7f2f5 100644 --- a/src/routes/preferences.nim +++ b/src/routes/preferences.nim @@ -16,9 +16,6 @@ proc findThemes*(dir: string): seq[string] = proc createPrefRouter*(cfg: Config) = router preferences: - template savePrefs(): untyped = - setCookie("preferences", $prefs.id, daysForward(360), httpOnly=true, secure=cfg.useHttps) - get "/settings": let html = renderPreferences(cookiePrefs(), refPath(), findThemes(cfg.staticDir)) resp renderMain(html, request, cfg, "Preferences") @@ -27,27 +24,14 @@ proc createPrefRouter*(cfg: Config) = redirect("/settings") post "/saveprefs": - var prefs = cookiePrefs() genUpdatePrefs() - savePrefs() redirect(refPath()) post "/resetprefs": - var prefs = cookiePrefs() - resetPrefs(prefs, cfg) - savePrefs() + genResetPrefs() redirect($(parseUri("/settings") ? filterParams(request.params))) post "/enablehls": - var prefs = cookiePrefs() - prefs.hlsPlayback = true - cache(prefs) - savePrefs() + savePref("hlsPlayback", "on", request) redirect(refPath()) - before: - if @"theme".len > 0: - var prefs = cookiePrefs() - prefs.theme = @"theme".capitalizeAscii.replace("_", " ") - cache(prefs) - savePrefs() diff --git a/src/routes/router_utils.nim b/src/routes/router_utils.nim index 8cfdb65..acbee57 100644 --- a/src/routes/router_utils.nim +++ b/src/routes/router_utils.nim @@ -1,9 +1,15 @@ import strutils, sequtils, asyncdispatch, httpclient +from jester import Request import ../utils, ../prefs export utils, prefs +template savePref*(pref, value: string; req: Request; expire=false): typed = + if not expire or pref in cookies(req): + setCookie(pref, value, daysForward(when expire: -10 else: 360), + httpOnly=true, secure=cfg.useHttps) + template cookiePrefs*(): untyped {.dirty.} = - getPrefs(request.cookies.getOrDefault("preferences"), cfg) + getPrefs(cookies(request)) template getPath*(): untyped {.dirty.} = $(parseUri(request.path) ? filterParams(request.params)) diff --git a/src/types.nim b/src/types.nim index b0a96a0..0be934b 100644 --- a/src/types.nim +++ b/src/types.nim @@ -3,6 +3,8 @@ import norm/sqlite import prefs_impl +genPrefsType() + type VideoType* = enum vmap, m3u8, mp4 @@ -59,7 +61,6 @@ dbTypes: formatIt: dbValue(getTime().toUnix()) .}: Time -genPrefsType() type QueryKind* = enum @@ -187,7 +188,6 @@ type hostname*: string cacheDir*: string profileCacheTime*: int - defaultTheme*: string hmacKey*: string proc contains*(thread: Chain; tweet: Tweet): bool = diff --git a/src/views/general.nim b/src/views/general.nim index 9655c50..3256ba7 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -82,8 +82,10 @@ proc renderHead*(prefs: Prefs; cfg: Config; titleText=""; desc=""; video=""; proc renderMain*(body: VNode; req: Request; cfg: Config; titleText=""; desc=""; rss=""; video=""; images: seq[string] = @[]; ogTitle=""): string = - let prefs = getPrefs(req.cookies.getOrDefault("preferences"), cfg) - let theme = toLowerAscii(prefs.theme).replace(" ", "_") + let prefs = getPrefs(req.cookies) + var theme = toLowerAscii(prefs.theme).replace(" ", "_") + if "theme" in req.params: + theme = toLowerAscii(req.params["theme"]).replace(" ", "_") let node = buildHtml(html(lang="en")): renderHead(prefs, cfg, titleText, desc, video, images, ogTitle):