mirror of
https://github.com/zedeus/nitter.git
synced 2024-12-12 02:56:29 +00:00
Optional base64 support for proxy urls
This commit is contained in:
parent
1b9fa40237
commit
310c5e936d
7 changed files with 61 additions and 39 deletions
|
@ -20,6 +20,7 @@ redisMaxConnections = 30
|
||||||
|
|
||||||
[Config]
|
[Config]
|
||||||
hmacKey = "secretkey" # random key for cryptographic signing of video urls
|
hmacKey = "secretkey" # random key for cryptographic signing of video urls
|
||||||
|
base64Media = false # use base64 encoding for proxied media urls
|
||||||
tokenCount = 10
|
tokenCount = 10
|
||||||
# minimum amount of usable tokens. tokens are used to authorize API requests,
|
# minimum amount of usable tokens. tokens are used to authorize API requests,
|
||||||
# but they expire after ~1 hour, and have a limit of 187 requests.
|
# but they expire after ~1 hour, and have a limit of 187 requests.
|
||||||
|
|
|
@ -20,6 +20,10 @@ proc getConfig*(path: string): (Config, parseCfg.Config) =
|
||||||
hostname: cfg.get("Server", "hostname", "nitter.net"),
|
hostname: cfg.get("Server", "hostname", "nitter.net"),
|
||||||
staticDir: cfg.get("Server", "staticDir", "./public"),
|
staticDir: cfg.get("Server", "staticDir", "./public"),
|
||||||
|
|
||||||
|
hmacKey: cfg.get("Config", "hmacKey", "secretkey"),
|
||||||
|
base64Media: cfg.get("Config", "base64Media", false),
|
||||||
|
minTokens: cfg.get("Config", "tokenCount", 10),
|
||||||
|
|
||||||
cacheDir: cfg.get("Cache", "directory", "/tmp/nitter"),
|
cacheDir: cfg.get("Cache", "directory", "/tmp/nitter"),
|
||||||
listCacheTime: cfg.get("Cache", "listMinutes", 120),
|
listCacheTime: cfg.get("Cache", "listMinutes", 120),
|
||||||
rssCacheTime: cfg.get("Cache", "rssMinutes", 10),
|
rssCacheTime: cfg.get("Cache", "rssMinutes", 10),
|
||||||
|
@ -27,10 +31,7 @@ proc getConfig*(path: string): (Config, parseCfg.Config) =
|
||||||
redisHost: cfg.get("Cache", "redisHost", "localhost"),
|
redisHost: cfg.get("Cache", "redisHost", "localhost"),
|
||||||
redisPort: cfg.get("Cache", "redisPort", 6379),
|
redisPort: cfg.get("Cache", "redisPort", 6379),
|
||||||
redisConns: cfg.get("Cache", "redisConnections", 20),
|
redisConns: cfg.get("Cache", "redisConnections", 20),
|
||||||
redisMaxConns: cfg.get("Cache", "redisMaxConnections", 30),
|
redisMaxConns: cfg.get("Cache", "redisMaxConnections", 30)
|
||||||
|
|
||||||
hmacKey: cfg.get("Config", "hmacKey", "secretkey"),
|
|
||||||
minTokens: cfg.get("Config", "tokenCount", 10),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return (conf, cfg)
|
return (conf, cfg)
|
||||||
|
|
|
@ -15,6 +15,7 @@ const
|
||||||
nbsp = $Rune(0x000A0)
|
nbsp = $Rune(0x000A0)
|
||||||
|
|
||||||
wwwRegex = re"https?://(www[0-9]?\.)?"
|
wwwRegex = re"https?://(www[0-9]?\.)?"
|
||||||
|
m3u8Regex = re"""url="(.+.m3u8)""""
|
||||||
manifestRegex = re"(.+(.ts|.m3u8|.vmap))"
|
manifestRegex = re"(.+(.ts|.m3u8|.vmap))"
|
||||||
userpicRegex = re"_(normal|bigger|mini|200x200|400x400)(\.[A-z]+)$"
|
userpicRegex = re"_(normal|bigger|mini|200x200|400x400)(\.[A-z]+)$"
|
||||||
extRegex = re"(\.[A-z]+)$"
|
extRegex = re"(\.[A-z]+)$"
|
||||||
|
@ -52,6 +53,11 @@ proc replaceUrl*(url: string; prefs: Prefs; absolute=""): string =
|
||||||
if absolute.len > 0:
|
if absolute.len > 0:
|
||||||
result = result.replace("href=\"/", "href=\"https://" & absolute & "/")
|
result = result.replace("href=\"/", "href=\"https://" & absolute & "/")
|
||||||
|
|
||||||
|
proc getM3u8Url*(content: string): string =
|
||||||
|
var m: RegexMatch
|
||||||
|
if content.find(m3u8Regex, m):
|
||||||
|
result = content[m.group(0)[0]]
|
||||||
|
|
||||||
proc proxifyVideo*(manifest: string; proxy: bool): string =
|
proc proxifyVideo*(manifest: string; proxy: bool): string =
|
||||||
proc cb(m: RegexMatch; s: string): string =
|
proc cb(m: RegexMatch; s: string): string =
|
||||||
result = "https://video.twimg.com" & s[m.group(0)[0]]
|
result = "https://video.twimg.com" & s[m.group(0)[0]]
|
||||||
|
|
|
@ -23,6 +23,7 @@ stdout.flushFile
|
||||||
updateDefaultPrefs(fullCfg)
|
updateDefaultPrefs(fullCfg)
|
||||||
setCacheTimes(cfg)
|
setCacheTimes(cfg)
|
||||||
setHmacKey(cfg.hmacKey)
|
setHmacKey(cfg.hmacKey)
|
||||||
|
setProxyEncoding(cfg.base64Media)
|
||||||
|
|
||||||
waitFor initRedisPool(cfg)
|
waitFor initRedisPool(cfg)
|
||||||
stdout.write &"Connected to Redis at {cfg.redisHost}:{cfg.redisPort}\n"
|
stdout.write &"Connected to Redis at {cfg.redisHost}:{cfg.redisPort}\n"
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import uri, strutils, httpclient, os, hashes
|
import uri, strutils, httpclient, os, hashes, base64, re
|
||||||
import asynchttpserver, asyncstreams, asyncfile, asyncnet
|
import asynchttpserver, asyncstreams, asyncfile, asyncnet
|
||||||
|
|
||||||
import jester, regex
|
import jester
|
||||||
|
|
||||||
import router_utils
|
import router_utils
|
||||||
import ".."/[types, formatters, agents, utils]
|
import ".."/[types, formatters, agents, utils]
|
||||||
import ../views/general
|
import ../views/general
|
||||||
|
|
||||||
export asynchttpserver, asyncstreams, asyncfile, asyncnet
|
export asynchttpserver, asyncstreams, asyncfile, asyncnet
|
||||||
export httpclient, os, strutils, asyncstreams, regex
|
export httpclient, os, strutils, asyncstreams, base64, re
|
||||||
|
|
||||||
const
|
const
|
||||||
m3u8Regex* = re"""url="(.+.m3u8)""""
|
|
||||||
m3u8Mime* = "application/vnd.apple.mpegurl"
|
m3u8Mime* = "application/vnd.apple.mpegurl"
|
||||||
maxAge* = "max-age=604800"
|
maxAge* = "max-age=604800"
|
||||||
|
|
||||||
|
@ -60,13 +59,27 @@ proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} =
|
||||||
finally:
|
finally:
|
||||||
client.safeClose()
|
client.safeClose()
|
||||||
|
|
||||||
|
template check*(code): untyped =
|
||||||
|
if code != Http200:
|
||||||
|
resp code
|
||||||
|
else:
|
||||||
|
enableRawMode()
|
||||||
|
break route
|
||||||
|
|
||||||
|
proc decoded*(req: jester.Request; index: int): string =
|
||||||
|
let
|
||||||
|
based = req.matches[0].len > 1
|
||||||
|
encoded = req.matches[index]
|
||||||
|
if based: decode(encoded)
|
||||||
|
else: decodeUrl(encoded)
|
||||||
|
|
||||||
proc createMediaRouter*(cfg: Config) =
|
proc createMediaRouter*(cfg: Config) =
|
||||||
router media:
|
router media:
|
||||||
get "/pic/?":
|
get "/pic/?":
|
||||||
resp Http404
|
resp Http404
|
||||||
|
|
||||||
get "/pic/@url":
|
get re"^\/pic\/(enc)?\/?(.+)":
|
||||||
var url = decodeUrl(@"url")
|
var url = decoded(request, 1)
|
||||||
if "twimg.com" notin url:
|
if "twimg.com" notin url:
|
||||||
url.insert(twimg)
|
url.insert(twimg)
|
||||||
if not url.startsWith(https):
|
if not url.startsWith(https):
|
||||||
|
@ -75,42 +88,32 @@ proc createMediaRouter*(cfg: Config) =
|
||||||
let uri = parseUri(url)
|
let uri = parseUri(url)
|
||||||
cond isTwitterUrl(uri) == true
|
cond isTwitterUrl(uri) == true
|
||||||
|
|
||||||
enableRawMode()
|
let code = await proxyMedia(request, url)
|
||||||
let code = await proxyMedia(request, $uri)
|
check code
|
||||||
if code == Http200:
|
|
||||||
enableRawMode()
|
|
||||||
break route
|
|
||||||
else:
|
|
||||||
resp code
|
|
||||||
|
|
||||||
get "/video/@sig/@url":
|
get re"^\/video\/(enc)?\/?(.+)\/(.+)$":
|
||||||
cond "http" in @"url"
|
let url = decoded(request, 2)
|
||||||
var url = decodeUrl(@"url")
|
cond "http" in url
|
||||||
let prefs = cookiePrefs()
|
|
||||||
|
|
||||||
if getHmac(url) != @"sig":
|
if getHmac(url) != request.matches[1]:
|
||||||
resp showError("Failed to verify signature", cfg)
|
resp showError("Failed to verify signature", cfg)
|
||||||
|
|
||||||
if ".mp4" in url or ".ts" in url:
|
if ".mp4" in url or ".ts" in url:
|
||||||
let code = await proxyMedia(request, url)
|
let code = await proxyMedia(request, url)
|
||||||
if code == Http200:
|
check code
|
||||||
enableRawMode()
|
|
||||||
break route
|
|
||||||
else:
|
|
||||||
resp code
|
|
||||||
|
|
||||||
var content: string
|
var content: string
|
||||||
if ".vmap" in url:
|
if ".vmap" in url:
|
||||||
var m: RegexMatch
|
let m3u8 = getM3u8Url(await safeFetch(url, mediaAgent))
|
||||||
content = await safeFetch(url, mediaAgent)
|
if m3u8.len > 0:
|
||||||
if content.find(m3u8Regex, m):
|
|
||||||
url = decodeUrl(content[m.group(0)[0]])
|
|
||||||
content = await safeFetch(url, mediaAgent)
|
content = await safeFetch(url, mediaAgent)
|
||||||
else:
|
else:
|
||||||
resp Http404
|
resp Http404
|
||||||
|
|
||||||
if ".m3u8" in url:
|
if ".m3u8" in url:
|
||||||
let vid = await safeFetch(url, mediaAgent)
|
let
|
||||||
|
vid = await safeFetch(url, mediaAgent)
|
||||||
|
prefs = cookiePrefs()
|
||||||
content = proxifyVideo(vid, prefs.proxyVideos)
|
content = proxifyVideo(vid, prefs.proxyVideos)
|
||||||
|
|
||||||
resp content, m3u8Mime
|
resp content, m3u8Mime
|
||||||
|
|
|
@ -201,6 +201,7 @@ type
|
||||||
staticDir*: string
|
staticDir*: string
|
||||||
|
|
||||||
hmacKey*: string
|
hmacKey*: string
|
||||||
|
base64Media*: bool
|
||||||
minTokens*: int
|
minTokens*: int
|
||||||
|
|
||||||
cacheDir*: string
|
cacheDir*: string
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import strutils, strformat, sequtils, uri, tables
|
import strutils, strformat, sequtils, uri, tables, base64
|
||||||
import nimcrypto, regex
|
import nimcrypto, regex
|
||||||
|
|
||||||
var hmacKey = "secretkey"
|
var
|
||||||
|
hmacKey = "secretkey"
|
||||||
|
base64Media = false
|
||||||
|
|
||||||
const
|
const
|
||||||
https* = "https://"
|
https* = "https://"
|
||||||
|
@ -20,18 +22,25 @@ const
|
||||||
proc setHmacKey*(key: string) =
|
proc setHmacKey*(key: string) =
|
||||||
hmacKey = key
|
hmacKey = key
|
||||||
|
|
||||||
|
proc setProxyEncoding*(state: bool) =
|
||||||
|
base64Media = state
|
||||||
|
|
||||||
proc getHmac*(data: string): string =
|
proc getHmac*(data: string): string =
|
||||||
($hmac(sha256, hmacKey, data))[0 .. 12]
|
($hmac(sha256, hmacKey, data))[0 .. 12]
|
||||||
|
|
||||||
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)
|
||||||
sig = getHmac(link)
|
if base64Media:
|
||||||
url = encodeUrl(link)
|
&"/video/enc/{sig}/{encode(link, safe=true)}"
|
||||||
&"/video/{sig}/{url}"
|
else:
|
||||||
|
&"/video/{sig}/{encodeUrl(link)}"
|
||||||
|
|
||||||
proc getPicUrl*(link: string): string =
|
proc getPicUrl*(link: string): string =
|
||||||
&"/pic/{encodeUrl(link)}"
|
if base64Media:
|
||||||
|
&"/pic/enc/{encode(link, safe=true)}"
|
||||||
|
else:
|
||||||
|
&"/pic/{encodeUrl(link)}"
|
||||||
|
|
||||||
proc cleanFilename*(filename: string): string =
|
proc cleanFilename*(filename: string): string =
|
||||||
const reg = re"[^A-Za-z0-9._-]"
|
const reg = re"[^A-Za-z0-9._-]"
|
||||||
|
|
Loading…
Reference in a new issue