nitter/src/routes/media.nim

160 lines
4.4 KiB
Nim
Raw Normal View History

2021-12-27 01:37:38 +00:00
# SPDX-License-Identifier: AGPL-3.0-only
2020-06-09 13:04:38 +00:00
import uri, strutils, httpclient, os, hashes, base64, re
import asynchttpserver, asyncstreams, asyncfile, asyncnet
2019-09-06 00:42:35 +00:00
2020-06-09 13:04:38 +00:00
import jester
2019-09-06 00:42:35 +00:00
import router_utils
2022-01-17 03:13:27 +00:00
import ".."/[types, formatters, utils]
2019-09-06 00:42:35 +00:00
export asynchttpserver, asyncstreams, asyncfile, asyncnet
2020-06-09 13:04:38 +00:00
export httpclient, os, strutils, asyncstreams, base64, re
const
m3u8Mime* = "application/vnd.apple.mpegurl"
2022-06-06 20:50:28 +00:00
mp4Mime* = "video/mp4"
2023-05-20 22:47:09 +00:00
maxAge* = "public, max-age=604800, must-revalidate"
2019-09-06 00:42:35 +00:00
2022-01-17 03:13:27 +00:00
proc safeFetch*(url: string): Future[string] {.async.} =
let client = newAsyncHttpClient()
2020-06-09 14:45:21 +00:00
try: result = await client.getContent(url)
except: discard
finally: client.close()
2022-06-06 20:50:28 +00:00
template respond*(req: asynchttpserver.Request; code: HttpCode;
headers: seq[(string, string)]) =
var msg = "HTTP/1.1 " & $code & "\c\L"
for (k, v) in headers:
msg.add(k & ": " & v & "\c\L")
msg.add "\c\L"
2022-06-06 20:50:28 +00:00
yield req.client.send(msg, flags={})
proc getContentLength(res: AsyncResponse): string =
result = "0"
if res.headers.hasKey("content-length"):
result = $res.contentLength
elif res.headers.hasKey("content-range"):
result = res.headers["content-range"]
result = result[result.find('/') + 1 .. ^1]
if result == "*":
result.setLen(0)
proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} =
result = Http200
2022-06-06 20:50:28 +00:00
let
request = req.getNativeReq()
2022-06-06 20:50:28 +00:00
hashed = $hash(url)
if request.headers.getOrDefault("If-None-Match") == hashed:
return Http304
let c = newAsyncHttpClient(headers=newHttpHeaders({
"accept": "*/*",
"range": $req.headers.getOrDefault("range")
}))
try:
2022-06-06 20:50:28 +00:00
var res = await c.get(url)
if not res.status.startsWith("20"):
return Http404
2022-06-06 20:50:28 +00:00
var headers = @{
2023-05-20 22:47:09 +00:00
"accept-ranges": "bytes",
"content-type": $res.headers.getOrDefault("content-type"),
"cache-control": maxAge,
"age": $res.headers.getOrDefault("age"),
"date": $res.headers.getOrDefault("date"),
"last-modified": $res.headers.getOrDefault("last-modified")
2022-06-06 20:50:28 +00:00
}
2022-06-06 20:50:28 +00:00
var tries = 0
while tries <= 10 and res.headers.hasKey("transfer-encoding"):
await sleepAsync(100 + tries * 200)
res = await c.get(url)
tries.inc
2020-11-07 22:02:27 +00:00
2022-06-06 20:50:28 +00:00
let contentLength = res.getContentLength
if contentLength.len > 0:
2023-05-20 22:47:09 +00:00
headers.add ("content-length", contentLength)
2022-06-06 20:50:28 +00:00
if res.headers.hasKey("content-range"):
2023-05-20 22:47:09 +00:00
headers.add ("content-range", $res.headers.getOrDefault("content-range"))
2022-06-06 20:50:28 +00:00
respond(request, Http206, headers)
else:
respond(request, Http200, headers)
var (hasValue, data) = (true, "")
while hasValue:
(hasValue, data) = await res.bodyStream.read()
if hasValue:
2022-06-06 20:50:28 +00:00
await request.client.send(data, flags={})
data.setLen 0
2022-06-06 20:50:28 +00:00
except OSError: discard
except ProtocolError, HttpRequestError:
result = Http404
finally:
2022-06-06 20:50:28 +00:00
c.close()
2022-06-06 20:50:28 +00:00
template check*(c): untyped =
let code = c
2020-06-09 13:04:38 +00:00
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)
2022-06-06 20:50:28 +00:00
proc getPicUrl*(req: jester.Request): string =
result = decoded(req, 1)
if "twimg.com" notin result:
result.insert(twimg)
if not result.startsWith(https):
result.insert(https)
2019-09-06 00:42:35 +00:00
proc createMediaRouter*(cfg: Config) =
router media:
2020-05-26 12:24:41 +00:00
get "/pic/?":
resp Http404
2022-06-08 20:20:28 +00:00
get re"^\/pic\/orig\/(enc)?\/?(.+)":
2022-06-06 20:50:28 +00:00
let url = getPicUrl(request)
cond isTwitterUrl(parseUri(url)) == true
2022-06-08 20:20:28 +00:00
check await proxyMedia(request, url & "?name=orig")
2022-06-08 20:20:28 +00:00
get re"^\/pic\/(enc)?\/?(.+)":
2022-06-06 20:50:28 +00:00
let url = getPicUrl(request)
cond isTwitterUrl(parseUri(url)) == true
2022-06-08 20:20:28 +00:00
check await proxyMedia(request, url)
2020-06-09 13:04:38 +00:00
get re"^\/video\/(enc)?\/?(.+)\/(.+)$":
let url = decoded(request, 2)
cond "http" in url
2019-09-06 00:42:35 +00:00
2020-06-09 13:04:38 +00:00
if getHmac(url) != request.matches[1]:
resp showError("Failed to verify signature", cfg)
2019-09-06 00:42:35 +00:00
if ".mp4" in url or ".ts" in url or ".m4s" in url:
2022-06-06 20:50:28 +00:00
check await proxyMedia(request, url)
2019-09-06 00:42:35 +00:00
var content: string
2019-09-06 00:42:35 +00:00
if ".vmap" in url:
2022-01-17 03:13:27 +00:00
let m3u8 = getM3u8Url(await safeFetch(url))
2020-06-09 13:04:38 +00:00
if m3u8.len > 0:
2022-01-17 03:13:27 +00:00
content = await safeFetch(url)
else:
resp Http404
2019-09-06 00:42:35 +00:00
if ".m3u8" in url:
2022-01-17 03:13:27 +00:00
let vid = await safeFetch(url)
2020-06-09 14:45:21 +00:00
content = proxifyVideo(vid, cookiePref(proxyVideos))
2019-09-06 00:42:35 +00:00
resp content, m3u8Mime