Store preferences in cookies, add config defaults
This commit is contained in:
parent
517d9144f6
commit
312ff78628
11
nitter.conf
11
nitter.conf
|
@ -11,5 +11,14 @@ directory = "./tmp"
|
||||||
profileMinutes = 10 # how long to cache profiles
|
profileMinutes = 10 # how long to cache profiles
|
||||||
|
|
||||||
[Config]
|
[Config]
|
||||||
defaultTheme = "Nitter"
|
|
||||||
hmacKey = "secretkey" # for signing video urls
|
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
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import parsecfg except Config
|
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)
|
let val = config.getSectionValue(s, v)
|
||||||
if val.len == 0: return default
|
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 bool: parseBool(val)
|
||||||
elif T is string: val
|
elif T is string: val
|
||||||
|
|
||||||
proc getConfig*(path: string): Config =
|
proc getConfig*(path: string): (Config, parseCfg.Config) =
|
||||||
var cfg = loadConfig(path)
|
var cfg = loadConfig(path)
|
||||||
|
|
||||||
Config(
|
let conf = Config(
|
||||||
address: cfg.get("Server", "address", "0.0.0.0"),
|
address: cfg.get("Server", "address", "0.0.0.0"),
|
||||||
port: cfg.get("Server", "port", 8080),
|
port: cfg.get("Server", "port", 8080),
|
||||||
useHttps: cfg.get("Server", "https", true),
|
useHttps: cfg.get("Server", "https", true),
|
||||||
|
@ -23,6 +23,7 @@ proc getConfig*(path: string): Config =
|
||||||
cacheDir: cfg.get("Cache", "directory", "/tmp/nitter"),
|
cacheDir: cfg.get("Cache", "directory", "/tmp/nitter"),
|
||||||
profileCacheTime: cfg.get("Cache", "profileMinutes", 10),
|
profileCacheTime: cfg.get("Cache", "profileMinutes", 10),
|
||||||
|
|
||||||
defaultTheme: cfg.get("Config", "defaultTheme", "Nitter"),
|
|
||||||
hmacKey: cfg.get("Config", "hmacKey", "secretkey")
|
hmacKey: cfg.get("Config", "hmacKey", "secretkey")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return (conf, cfg)
|
||||||
|
|
|
@ -10,7 +10,9 @@ import routes/[
|
||||||
unsupported, embed, resolver]
|
unsupported, embed, resolver]
|
||||||
|
|
||||||
const configPath {.strdefine.} = "./nitter.conf"
|
const configPath {.strdefine.} = "./nitter.conf"
|
||||||
let cfg = getConfig(configPath)
|
let (cfg, fullCfg) = getConfig(configPath)
|
||||||
|
|
||||||
|
updateDefaultPrefs(fullCfg)
|
||||||
|
|
||||||
setHmacKey(cfg.hmacKey)
|
setHmacKey(cfg.hmacKey)
|
||||||
|
|
||||||
|
|
|
@ -1,54 +1,15 @@
|
||||||
import strutils, sequtils, macros
|
import tables
|
||||||
import norm/sqlite
|
import types, prefs_impl
|
||||||
|
from config import get
|
||||||
|
from parsecfg import nil
|
||||||
|
|
||||||
import prefs_impl, types
|
export genUpdatePrefs, genResetPrefs
|
||||||
export genUpdatePrefs
|
|
||||||
|
|
||||||
template safeAddColumn(field: typedesc): untyped =
|
var defaultPrefs*: Prefs
|
||||||
try: field.addColumn
|
|
||||||
except DbError: discard
|
|
||||||
|
|
||||||
dbFromTypes("prefs.db", "", "", "", [Prefs])
|
proc updateDefaultPrefs*(cfg: parsecfg.Config) =
|
||||||
|
genDefaultPrefs()
|
||||||
|
|
||||||
withDb:
|
proc getPrefs*(cookies: Table[string, string]): Prefs =
|
||||||
try:
|
result = defaultPrefs
|
||||||
createTables()
|
genCookiePrefs()
|
||||||
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
|
|
||||||
|
|
|
@ -14,7 +14,9 @@ type
|
||||||
defaultOption*: string
|
defaultOption*: string
|
||||||
defaultInput*: string
|
defaultInput*: string
|
||||||
|
|
||||||
macro genPrefs(prefDsl: untyped) =
|
PrefList* = OrderedTable[string, seq[Pref]]
|
||||||
|
|
||||||
|
macro genPrefs*(prefDsl: untyped) =
|
||||||
var table = nnkTableConstr.newTree()
|
var table = nnkTableConstr.newTree()
|
||||||
for category in prefDsl:
|
for category in prefDsl:
|
||||||
table.add nnkExprColonExpr.newTree(newLit($category[0]))
|
table.add nnkExprColonExpr.newTree(newLit($category[0]))
|
||||||
|
@ -41,7 +43,7 @@ macro genPrefs(prefDsl: untyped) =
|
||||||
|
|
||||||
let name = ident("prefList")
|
let name = ident("prefList")
|
||||||
result = quote do:
|
result = quote do:
|
||||||
const `name`* = toOrderedTable(`table`)
|
const `name`*: PrefList = toOrderedTable(`table`)
|
||||||
|
|
||||||
genPrefs:
|
genPrefs:
|
||||||
Privacy:
|
Privacy:
|
||||||
|
@ -101,46 +103,79 @@ iterator allPrefs*(): Pref =
|
||||||
yield pref
|
yield pref
|
||||||
|
|
||||||
macro genDefaultPrefs*(): untyped =
|
macro genDefaultPrefs*(): untyped =
|
||||||
result = nnkObjConstr.newTree(ident("Prefs"))
|
result = nnkStmtList.newTree()
|
||||||
|
|
||||||
for pref in allPrefs():
|
for pref in allPrefs():
|
||||||
let default =
|
let
|
||||||
|
ident = ident(pref.name)
|
||||||
|
name = newLit(pref.name)
|
||||||
|
default =
|
||||||
case pref.kind
|
case pref.kind
|
||||||
of checkbox: newLit(pref.defaultState)
|
of checkbox: newLit(pref.defaultState)
|
||||||
of select: newLit(pref.defaultOption)
|
of select: newLit(pref.defaultOption)
|
||||||
of input: newLit(pref.defaultInput)
|
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 =
|
macro genUpdatePrefs*(): untyped =
|
||||||
result = nnkStmtList.newTree()
|
result = nnkStmtList.newTree()
|
||||||
|
let req = ident("request")
|
||||||
for pref in allPrefs():
|
for pref in allPrefs():
|
||||||
let ident = ident(pref.name)
|
let
|
||||||
let value = nnkPrefix.newTree(ident("@"), newLit(pref.name))
|
name = newLit(pref.name)
|
||||||
|
kind = newLit(pref.kind)
|
||||||
case pref.kind
|
options = newLit(pref.options)
|
||||||
of checkbox:
|
default = nnkDotExpr.newTree(ident("defaultPrefs"), ident(pref.name))
|
||||||
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:
|
result.add quote do:
|
||||||
cache(prefs)
|
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`
|
||||||
|
|
||||||
|
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 =
|
macro genPrefsType*(): untyped =
|
||||||
let name = nnkPostfix.newTree(ident("*"), ident("Prefs"))
|
let name = nnkPostfix.newTree(ident("*"), ident("Prefs"))
|
||||||
result = quote do:
|
result = quote do:
|
||||||
type `name` = object
|
type `name` = object
|
||||||
id* {.pk, ro.}: int
|
discard
|
||||||
|
|
||||||
for pref in allPrefs():
|
for pref in allPrefs():
|
||||||
result[0][2][2].add nnkIdentDefs.newTree(
|
result[0][2][2].add nnkIdentDefs.newTree(
|
||||||
|
|
|
@ -16,9 +16,6 @@ proc findThemes*(dir: string): seq[string] =
|
||||||
|
|
||||||
proc createPrefRouter*(cfg: Config) =
|
proc createPrefRouter*(cfg: Config) =
|
||||||
router preferences:
|
router preferences:
|
||||||
template savePrefs(): untyped =
|
|
||||||
setCookie("preferences", $prefs.id, daysForward(360), httpOnly=true, secure=cfg.useHttps)
|
|
||||||
|
|
||||||
get "/settings":
|
get "/settings":
|
||||||
let html = renderPreferences(cookiePrefs(), refPath(), findThemes(cfg.staticDir))
|
let html = renderPreferences(cookiePrefs(), refPath(), findThemes(cfg.staticDir))
|
||||||
resp renderMain(html, request, cfg, "Preferences")
|
resp renderMain(html, request, cfg, "Preferences")
|
||||||
|
@ -27,27 +24,14 @@ proc createPrefRouter*(cfg: Config) =
|
||||||
redirect("/settings")
|
redirect("/settings")
|
||||||
|
|
||||||
post "/saveprefs":
|
post "/saveprefs":
|
||||||
var prefs = cookiePrefs()
|
|
||||||
genUpdatePrefs()
|
genUpdatePrefs()
|
||||||
savePrefs()
|
|
||||||
redirect(refPath())
|
redirect(refPath())
|
||||||
|
|
||||||
post "/resetprefs":
|
post "/resetprefs":
|
||||||
var prefs = cookiePrefs()
|
genResetPrefs()
|
||||||
resetPrefs(prefs, cfg)
|
|
||||||
savePrefs()
|
|
||||||
redirect($(parseUri("/settings") ? filterParams(request.params)))
|
redirect($(parseUri("/settings") ? filterParams(request.params)))
|
||||||
|
|
||||||
post "/enablehls":
|
post "/enablehls":
|
||||||
var prefs = cookiePrefs()
|
savePref("hlsPlayback", "on", request)
|
||||||
prefs.hlsPlayback = true
|
|
||||||
cache(prefs)
|
|
||||||
savePrefs()
|
|
||||||
redirect(refPath())
|
redirect(refPath())
|
||||||
|
|
||||||
before:
|
|
||||||
if @"theme".len > 0:
|
|
||||||
var prefs = cookiePrefs()
|
|
||||||
prefs.theme = @"theme".capitalizeAscii.replace("_", " ")
|
|
||||||
cache(prefs)
|
|
||||||
savePrefs()
|
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import strutils, sequtils, asyncdispatch, httpclient
|
import strutils, sequtils, asyncdispatch, httpclient
|
||||||
|
from jester import Request
|
||||||
import ../utils, ../prefs
|
import ../utils, ../prefs
|
||||||
export 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.} =
|
template cookiePrefs*(): untyped {.dirty.} =
|
||||||
getPrefs(request.cookies.getOrDefault("preferences"), cfg)
|
getPrefs(cookies(request))
|
||||||
|
|
||||||
template getPath*(): untyped {.dirty.} =
|
template getPath*(): untyped {.dirty.} =
|
||||||
$(parseUri(request.path) ? filterParams(request.params))
|
$(parseUri(request.path) ? filterParams(request.params))
|
||||||
|
|
|
@ -3,6 +3,8 @@ import norm/sqlite
|
||||||
|
|
||||||
import prefs_impl
|
import prefs_impl
|
||||||
|
|
||||||
|
genPrefsType()
|
||||||
|
|
||||||
type
|
type
|
||||||
VideoType* = enum
|
VideoType* = enum
|
||||||
vmap, m3u8, mp4
|
vmap, m3u8, mp4
|
||||||
|
@ -59,7 +61,6 @@ dbTypes:
|
||||||
formatIt: dbValue(getTime().toUnix())
|
formatIt: dbValue(getTime().toUnix())
|
||||||
.}: Time
|
.}: Time
|
||||||
|
|
||||||
genPrefsType()
|
|
||||||
|
|
||||||
type
|
type
|
||||||
QueryKind* = enum
|
QueryKind* = enum
|
||||||
|
@ -187,7 +188,6 @@ type
|
||||||
hostname*: string
|
hostname*: string
|
||||||
cacheDir*: string
|
cacheDir*: string
|
||||||
profileCacheTime*: int
|
profileCacheTime*: int
|
||||||
defaultTheme*: string
|
|
||||||
hmacKey*: string
|
hmacKey*: string
|
||||||
|
|
||||||
proc contains*(thread: Chain; tweet: Tweet): bool =
|
proc contains*(thread: Chain; tweet: Tweet): bool =
|
||||||
|
|
|
@ -82,8 +82,10 @@ proc renderHead*(prefs: Prefs; cfg: Config; titleText=""; desc=""; video="";
|
||||||
|
|
||||||
proc renderMain*(body: VNode; req: Request; cfg: Config; titleText=""; desc="";
|
proc renderMain*(body: VNode; req: Request; cfg: Config; titleText=""; desc="";
|
||||||
rss=""; video=""; images: seq[string] = @[]; ogTitle=""): string =
|
rss=""; video=""; images: seq[string] = @[]; ogTitle=""): string =
|
||||||
let prefs = getPrefs(req.cookies.getOrDefault("preferences"), cfg)
|
let prefs = getPrefs(req.cookies)
|
||||||
let theme = toLowerAscii(prefs.theme).replace(" ", "_")
|
var theme = toLowerAscii(prefs.theme).replace(" ", "_")
|
||||||
|
if "theme" in req.params:
|
||||||
|
theme = toLowerAscii(req.params["theme"]).replace(" ", "_")
|
||||||
|
|
||||||
let node = buildHtml(html(lang="en")):
|
let node = buildHtml(html(lang="en")):
|
||||||
renderHead(prefs, cfg, titleText, desc, video, images, ogTitle):
|
renderHead(prefs, cfg, titleText, desc, video, images, ogTitle):
|
||||||
|
|
Loading…
Reference in New Issue