mirror of
https://github.com/zedeus/nitter.git
synced 2024-05-19 17:48:10 +00:00
Compare commits
11 commits
1befbc50a0
...
22e9bcd801
Author | SHA1 | Date | |
---|---|---|---|
22e9bcd801 | |||
cdff5e9b1c | |||
e3b7d6d571 | |||
8f3b5cafd1 | |||
c8c606f96e | |||
f6ca1a3f2a | |||
a11332b2b5 | |||
ab321c9b10 | |||
98cc15e78c | |||
08604ac8d1 | |||
5bd4e545ce |
|
@ -26,12 +26,14 @@ enableRSS = true # set this to false to disable RSS feeds
|
||||||
enableDebug = false # enable request logs and debug endpoints (/.accounts)
|
enableDebug = false # enable request logs and debug endpoints (/.accounts)
|
||||||
proxy = "" # http/https url, SOCKS proxies are not supported
|
proxy = "" # http/https url, SOCKS proxies are not supported
|
||||||
proxyAuth = ""
|
proxyAuth = ""
|
||||||
tokenCount = 10
|
|
||||||
# minimum amount of usable tokens. tokens are used to authorize API requests,
|
# Instead of guest_accounts.json, fetch guest accounts from an external URL.
|
||||||
# but they expire after ~1 hour, and have a limit of 500 requests per endpoint.
|
# Nitter will re-fetch accounts if it runs out of valid ones.
|
||||||
# the limits reset every 15 minutes, and the pool is filled up so there's
|
[GuestAccounts]
|
||||||
# always at least `tokenCount` usable tokens. only increase this if you receive
|
usePool = false # enable fetching accounts from external pool.
|
||||||
# major bursts all the time and don't have a rate limiting setup via e.g. nginx
|
poolUrl = "" # https://example.com/download
|
||||||
|
poolId = "" # defaults to nitter's hostname
|
||||||
|
poolAuth = "" # random secret string for authentication
|
||||||
|
|
||||||
# Change default preferences here, see src/prefs_impl.nim for a complete list
|
# Change default preferences here, see src/prefs_impl.nim for a complete list
|
||||||
[Preferences]
|
[Preferences]
|
||||||
|
|
5
public/js/hls.light.min.js
vendored
5
public/js/hls.light.min.js
vendored
File diff suppressed because one or more lines are too long
5
public/js/hls.min.js
vendored
Normal file
5
public/js/hls.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
55
src/auth.nim
55
src/auth.nim
|
@ -1,5 +1,6 @@
|
||||||
#SPDX-License-Identifier: AGPL-3.0-only
|
#SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import std/[asyncdispatch, times, json, random, sequtils, strutils, tables, packedsets, os]
|
import std/[httpclient, asyncdispatch, times, json, random, sequtils, strutils, tables, packedsets, os, uri]
|
||||||
|
import nimcrypto
|
||||||
import types
|
import types
|
||||||
import experimental/parser/guestaccount
|
import experimental/parser/guestaccount
|
||||||
|
|
||||||
|
@ -197,13 +198,55 @@ proc initAccountPool*(cfg: Config; path: string) =
|
||||||
elif fileExists(path):
|
elif fileExists(path):
|
||||||
log "Parsing JSON guest accounts file: ", path
|
log "Parsing JSON guest accounts file: ", path
|
||||||
accountPool = parseGuestAccounts(path)
|
accountPool = parseGuestAccounts(path)
|
||||||
else:
|
elif not cfg.guestAccountsUsePool:
|
||||||
echo "[accounts] ERROR: ", path, " not found. This file is required to authenticate API requests."
|
echo "[accounts] ERROR: ", path, " not found. This file is required to authenticate API requests. Alternatively, configure the guest account pool in nitter.conf"
|
||||||
quit 1
|
quit 1
|
||||||
|
|
||||||
let accountsPrePurge = accountPool.len
|
|
||||||
accountPool.keepItIf(not it.hasExpired)
|
accountPool.keepItIf(not it.hasExpired)
|
||||||
|
|
||||||
log "Successfully added ", accountPool.len, " valid accounts."
|
log "Successfully added ", accountPool.len, " valid accounts."
|
||||||
if accountsPrePurge > accountPool.len:
|
|
||||||
log "Purged ", accountsPrePurge - accountPool.len, " expired accounts."
|
proc updateAccountPool*(cfg: Config) {.async.} =
|
||||||
|
if not cfg.guestAccountsUsePool:
|
||||||
|
return
|
||||||
|
|
||||||
|
# wait for a few seconds before fetching guest accounts, so that
|
||||||
|
# /.well-known/... is served correctly
|
||||||
|
await sleepAsync(10 * 1000)
|
||||||
|
|
||||||
|
while true:
|
||||||
|
if accountPool.len == 0:
|
||||||
|
log "fetching more accounts from service"
|
||||||
|
|
||||||
|
let client = newAsyncHttpClient("nitter-accounts")
|
||||||
|
|
||||||
|
try:
|
||||||
|
let resp = await client.get($(cfg.guestAccountsPoolUrl ? {"id": cfg.guestAccountsPoolId, "auth": cfg.guestAccountsPoolAuth}))
|
||||||
|
let guestAccounts = await resp.body
|
||||||
|
|
||||||
|
log "status code from service: ", resp.status
|
||||||
|
|
||||||
|
for line in guestAccounts.splitLines:
|
||||||
|
if line != "":
|
||||||
|
accountPool.add parseGuestAccount(line)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log "failed to fetch from accounts service: ", e.msg
|
||||||
|
finally:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
accountPool.keepItIf(not it.hasExpired)
|
||||||
|
|
||||||
|
await sleepAsync(3600 * 1000)
|
||||||
|
|
||||||
|
proc getAuthHash*(cfg: Config): string =
|
||||||
|
if cfg.guestAccountsPoolAuth.len == 0:
|
||||||
|
# If somebody turns on pool auth and provides a dummy key, we should
|
||||||
|
# prevent third parties from using that mis-configured auth and impersonate
|
||||||
|
# this instance
|
||||||
|
log "poolAuth is empty, authentication with accounts service will fail"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
let hashStr = $sha_256.digest(cfg.guestAccountsPoolAuth)
|
||||||
|
|
||||||
|
return hashStr.toLowerAscii
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import parsecfg except Config
|
import std/parsecfg except Config
|
||||||
|
import std/uri
|
||||||
import types, strutils
|
import types, strutils
|
||||||
|
|
||||||
proc get*[T](config: parseCfg.Config; section, key: string; default: T): T =
|
proc get*[T](config: parseCfg.Config; section, key: string; default: T): T =
|
||||||
|
@ -36,11 +37,16 @@ proc getConfig*(path: string): (Config, parseCfg.Config) =
|
||||||
# Config
|
# Config
|
||||||
hmacKey: cfg.get("Config", "hmacKey", "secretkey"),
|
hmacKey: cfg.get("Config", "hmacKey", "secretkey"),
|
||||||
base64Media: cfg.get("Config", "base64Media", false),
|
base64Media: cfg.get("Config", "base64Media", false),
|
||||||
minTokens: cfg.get("Config", "tokenCount", 10),
|
|
||||||
enableRss: cfg.get("Config", "enableRSS", true),
|
enableRss: cfg.get("Config", "enableRSS", true),
|
||||||
enableDebug: cfg.get("Config", "enableDebug", false),
|
enableDebug: cfg.get("Config", "enableDebug", false),
|
||||||
proxy: cfg.get("Config", "proxy", ""),
|
proxy: cfg.get("Config", "proxy", ""),
|
||||||
proxyAuth: cfg.get("Config", "proxyAuth", "")
|
proxyAuth: cfg.get("Config", "proxyAuth", ""),
|
||||||
|
|
||||||
|
# GuestAccounts
|
||||||
|
guestAccountsUsePool: cfg.get("GuestAccounts", "usePool", false),
|
||||||
|
guestAccountsPoolUrl: parseUri(cfg.get("GuestAccounts", "poolUrl", "")),
|
||||||
|
guestAccountsPoolAuth: cfg.get("GuestAccounts", "poolAuth", ""),
|
||||||
|
guestAccountsPoolId: cfg.get("GuestAccounts", "poolId", cfg.get("Server", "hostname", ""))
|
||||||
)
|
)
|
||||||
|
|
||||||
return (conf, cfg)
|
return (conf, cfg)
|
||||||
|
|
|
@ -82,6 +82,8 @@ proc proxifyVideo*(manifest: string; proxy: bool): string =
|
||||||
for line in manifest.splitLines:
|
for line in manifest.splitLines:
|
||||||
let url =
|
let url =
|
||||||
if line.startsWith("#EXT-X-MAP:URI"): line[16 .. ^2]
|
if line.startsWith("#EXT-X-MAP:URI"): line[16 .. ^2]
|
||||||
|
elif line.startsWith("#EXT-X-MEDIA") and "URI=" in line:
|
||||||
|
line[line.find("URI=") + 5 .. -1 + line.find("\"", start= 5 + line.find("URI="))]
|
||||||
else: line
|
else: line
|
||||||
if url.startsWith('/'):
|
if url.startsWith('/'):
|
||||||
let path = "https://video.twimg.com" & url
|
let path = "https://video.twimg.com" & url
|
||||||
|
|
|
@ -10,7 +10,7 @@ import types, config, prefs, formatters, redis_cache, http_pool, auth
|
||||||
import views/[general, about]
|
import views/[general, about]
|
||||||
import routes/[
|
import routes/[
|
||||||
preferences, timeline, status, media, search, rss, list, debug,
|
preferences, timeline, status, media, search, rss, list, debug,
|
||||||
unsupported, embed, resolver, router_utils]
|
unsupported, embed, resolver, router_utils, auth]
|
||||||
|
|
||||||
const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances"
|
const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances"
|
||||||
const issuesUrl = "https://github.com/zedeus/nitter/issues"
|
const issuesUrl = "https://github.com/zedeus/nitter/issues"
|
||||||
|
@ -22,6 +22,7 @@ let
|
||||||
accountsPath = getEnv("NITTER_ACCOUNTS_FILE", "./guest_accounts.json")
|
accountsPath = getEnv("NITTER_ACCOUNTS_FILE", "./guest_accounts.json")
|
||||||
|
|
||||||
initAccountPool(cfg, accountsPath)
|
initAccountPool(cfg, accountsPath)
|
||||||
|
asyncCheck updateAccountPool(cfg)
|
||||||
|
|
||||||
if not cfg.enableDebug:
|
if not cfg.enableDebug:
|
||||||
# Silence Jester's query warning
|
# Silence Jester's query warning
|
||||||
|
@ -54,6 +55,7 @@ createMediaRouter(cfg)
|
||||||
createEmbedRouter(cfg)
|
createEmbedRouter(cfg)
|
||||||
createRssRouter(cfg)
|
createRssRouter(cfg)
|
||||||
createDebugRouter(cfg)
|
createDebugRouter(cfg)
|
||||||
|
createAuthRouter(cfg)
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
port = Port(cfg.port)
|
port = Port(cfg.port)
|
||||||
|
@ -108,3 +110,4 @@ routes:
|
||||||
extend embed, ""
|
extend embed, ""
|
||||||
extend debug, ""
|
extend debug, ""
|
||||||
extend unsupported, ""
|
extend unsupported, ""
|
||||||
|
extend auth, ""
|
||||||
|
|
12
src/routes/auth.nim
Normal file
12
src/routes/auth.nim
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import jester
|
||||||
|
|
||||||
|
import router_utils
|
||||||
|
import ".."/[types, auth]
|
||||||
|
|
||||||
|
proc createAuthRouter*(cfg: Config) =
|
||||||
|
router auth:
|
||||||
|
get "/.well-known/nitter-auth":
|
||||||
|
cond cfg.guestAccountsUsePool
|
||||||
|
resp Http200, {"content-type": "text/plain"}, getAuthHash(cfg)
|
|
@ -1,5 +1,5 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import times, sequtils, options, tables
|
import std/[times, sequtils, options, tables, uri]
|
||||||
import prefs_impl
|
import prefs_impl
|
||||||
|
|
||||||
genPrefsType()
|
genPrefsType()
|
||||||
|
@ -262,12 +262,16 @@ type
|
||||||
|
|
||||||
hmacKey*: string
|
hmacKey*: string
|
||||||
base64Media*: bool
|
base64Media*: bool
|
||||||
minTokens*: int
|
|
||||||
enableRss*: bool
|
enableRss*: bool
|
||||||
enableDebug*: bool
|
enableDebug*: bool
|
||||||
proxy*: string
|
proxy*: string
|
||||||
proxyAuth*: string
|
proxyAuth*: string
|
||||||
|
|
||||||
|
guestAccountsUsePool*: bool
|
||||||
|
guestAccountsPoolUrl*: Uri
|
||||||
|
guestAccountsPoolId*: string
|
||||||
|
guestAccountsPoolAuth*: string
|
||||||
|
|
||||||
rssCacheTime*: int
|
rssCacheTime*: int
|
||||||
listCacheTime*: int
|
listCacheTime*: int
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,7 @@ proc getHmac*(data: string): string =
|
||||||
|
|
||||||
proc getVidUrl*(link: string): string =
|
proc getVidUrl*(link: string): string =
|
||||||
if link.len == 0: return
|
if link.len == 0: return
|
||||||
let
|
let sig = getHmac(link)
|
||||||
link = link.replace("cmaf", "fmp4")
|
|
||||||
sig = getHmac(link)
|
|
||||||
if base64Media:
|
if base64Media:
|
||||||
&"/video/enc/{sig}/{encode(link, safe=true)}"
|
&"/video/enc/{sig}/{encode(link, safe=true)}"
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -73,7 +73,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
||||||
link(rel="alternate", type="application/rss+xml", href=rss, title="RSS feed")
|
link(rel="alternate", type="application/rss+xml", href=rss, title="RSS feed")
|
||||||
|
|
||||||
if prefs.hlsPlayback:
|
if prefs.hlsPlayback:
|
||||||
script(src="/js/hls.light.min.js", `defer`="")
|
script(src="/js/hls.min.js", `defer`="")
|
||||||
script(src="/js/hlsPlayback.js", `defer`="")
|
script(src="/js/hlsPlayback.js", `defer`="")
|
||||||
|
|
||||||
if prefs.infiniteScroll:
|
if prefs.infiniteScroll:
|
||||||
|
|
Loading…
Reference in a new issue