Store preferences in cookies, add config defaults

This commit is contained in:
Zed 2020-05-08 02:48:47 +02:00
parent 517d9144f6
commit 312ff78628
9 changed files with 109 additions and 109 deletions

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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()

View file

@ -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(

View file

@ -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()

View file

@ -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))

View file

@ -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 =

View file

@ -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):