diff --git a/src/apiutils.nim b/src/apiutils.nim index b22de41..91efe8e 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -34,7 +34,7 @@ proc setApiProxy*(url: string) = if "http" notin apiProxy: apiProxy = "http://" & apiProxy -proc toUrl(req: ApiReq; sessionKind: SessionKind): Uri = +proc toUrl*(req: ApiReq; sessionKind: SessionKind): Uri = let url = case sessionKind of oauth: req.oauth of cookie: req.cookie diff --git a/tools/test_sessions.nim b/tools/test_sessions.nim new file mode 100644 index 0000000..e124d02 --- /dev/null +++ b/tools/test_sessions.nim @@ -0,0 +1,136 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Test each cookie session in a JSONL file by hitting the UserMedia endpoint. +# +# Usage: +# nim r --path:src tools/test_sessions.nim [sessions_file] [--delay N] +# +# Examples: +# nim r --path:src tools/test_sessions.nim +# nim r --path:src tools/test_sessions.nim sessions_cookie.jsonl --delay 1 + +import asyncdispatch, httpclient, strutils, uri, os, zippy +import apiutils, auth, consts, types +import experimental/parser/session + +const + # jack (id=12) — public account with media, same target as ratelimit_probe.py + testVars = userMediaVars % ["12", "", "20"] + +# Build the ApiReq once — same for every session +let + testUrl = ApiUrl(endpoint: graphUserMedia, + params: @[("variables", testVars), ("features", gqlFeatures)]) + testReq = ApiReq(cookie: testUrl, oauth: testUrl) + url = testReq.toUrl(SessionKind.cookie) + +proc parseCookieSessions(path: string): seq[Session] = + var skipped = 0 + for line in path.lines: + let s = line.strip() + if s.len == 0: continue + try: + let sess = parseSession(s) + if sess.kind == SessionKind.cookie: + result.add sess + else: + inc skipped + except Exception as e: + echo " [!] Parse error: ", e.msg + if skipped > 0: + echo " (skipped ", skipped, " non-cookie sessions)" + +proc testSession(session: Session): Future[tuple[ok: bool, code: int, + remaining, limit: int, detail: string]] {.async.} = + let headers = await genHeaders(session, url, skipTid = false) + let client = newAsyncHttpClient(headers = headers) + defer: client.close() + + try: + let resp = await client.get($url) + var body = await resp.body + if resp.headers.getOrDefault("content-encoding") == "gzip": + body = uncompress(body, dfGzip) + + var remaining, limit: int + if resp.headers.hasKey("x-rate-limit-remaining"): + remaining = parseInt(resp.headers["x-rate-limit-remaining"]) + if resp.headers.hasKey("x-rate-limit-limit"): + limit = parseInt(resp.headers["x-rate-limit-limit"]) + + if resp.code == Http200: + return (true, resp.code.int, remaining, limit, "") + else: + let detail = if body.len in 1..120: body else: "" + return (false, resp.code.int, remaining, limit, detail) + except Exception as e: + return (false, 0, 0, 0, e.msg[0 ..< min(e.msg.len, 120)]) + +proc main() {.async.} = + var + sessionsPath = "sessions.jsonl" + delay = 500 # ms + i = 1 + + while i <= paramCount(): + let arg = paramStr(i) + case arg + of "--delay": + inc i + if i > paramCount(): + echo "Error: --delay requires a value (seconds)"; quit(1) + delay = int(parseFloat(paramStr(i)) * 1000) + of "--help", "-h": + echo "Usage: nim r --path:src tools/test_sessions.nim [sessions_file] [--delay N]" + return + else: + sessionsPath = arg + inc i + + if not fileExists(sessionsPath): + echo "File not found: ", sessionsPath; quit(1) + + setApiProxy("") + setDisableTid(false) + + let sessions = parseCookieSessions(sessionsPath) + if sessions.len == 0: + echo "No cookie sessions found in ", sessionsPath; quit(0) + + echo "Testing ", sessions.len, " sessions from ", sessionsPath + echo "" + + var + nValid, nFail: int + failures: seq[string] + + for idx, session in sessions: + let + num = idx + 1 + prefix = "[" & $num & "/" & $sessions.len & "] " & session.pretty + res = await testSession(session) + + if res.ok: + inc nValid + var extra = "" + if res.limit > 0: + extra = " remaining=" & $res.remaining & "/" & $res.limit + echo prefix, " ✓ ", res.code, extra + else: + inc nFail + let detail = if res.code > 0: $res.code else: "error: " & res.detail + failures.add session.pretty & " " & detail + echo prefix, " ✗ ", detail + + if num < sessions.len and delay > 0: + await sleepAsync(delay) + + echo "" + echo "Done: ", nValid, " valid, ", nFail, " invalid [", sessions.len, " total]" + + if failures.len > 0: + echo "" + echo "Failures:" + for f in failures: + echo " ", f + +waitFor main()