← back to Tiktok Oauth Catcher
TikTok OAuth token catcher (one-shot :9276)
457fad3a72b6ff1854a882b63bae298dc792f976 · 2026-07-20 15:07:49 -0700 · Steve
Files touched
Diff
commit 457fad3a72b6ff1854a882b63bae298dc792f976
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jul 20 15:07:49 2026 -0700
TikTok OAuth token catcher (one-shot :9276)
---
.gitignore | 5 +++++
catcher.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 68 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ff2422c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
diff --git a/catcher.py b/catcher.py
new file mode 100644
index 0000000..ab4c6ea
--- /dev/null
+++ b/catcher.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+"""One-shot TikTok OAuth token catcher.
+Listens on :9276, catches /tiktok/callback?code=..., exchanges the code for a
+user access_token + refresh_token, writes them to /tmp/tiktok-tokens.json, shows
+a success page, then exits so the parent can route the tokens via the secrets skill.
+Reads client_key/secret DIRECTLY from the master .env (avoids the source bug).
+"""
+import http.server, urllib.parse, urllib.request, json, os, sys, re
+
+ENV = os.path.expanduser("~/Projects/secrets-manager/.env")
+REDIRECT = "http://localhost:9276/tiktok/callback"
+OUT = "/tmp/tiktok-tokens.json"
+
+def envval(key):
+ for l in open(ENV):
+ if l.startswith(key + "="):
+ return l.partition("=")[2].strip().strip('"').strip("'")
+ return ""
+
+CK, CS = envval("TIKTOK_CLIENT_KEY"), envval("TIKTOK_CLIENT_SECRET")
+
+class H(http.server.BaseHTTPRequestHandler):
+ def log_message(self, *a): pass
+ def do_GET(self):
+ u = urllib.parse.urlparse(self.path)
+ if not u.path.startswith("/tiktok/callback"):
+ self.send_response(404); self.end_headers(); return
+ q = urllib.parse.parse_qs(u.query)
+ code = (q.get("code") or [""])[0]
+ err = (q.get("error") or [""])[0]
+ if err or not code:
+ self._html(f"<h2>OAuth error</h2><pre>{err or 'no code returned'}</pre>")
+ self.server._result = {"error": err or "no_code"}; return
+ data = urllib.parse.urlencode({
+ "client_key": CK, "client_secret": CS, "code": code,
+ "grant_type": "authorization_code", "redirect_uri": REDIRECT,
+ }).encode()
+ req = urllib.request.Request("https://open.tiktokapis.com/v2/oauth/token/",
+ data=data, headers={"Content-Type": "application/x-www-form-urlencoded"})
+ try:
+ resp = json.load(urllib.request.urlopen(req, timeout=20))
+ except Exception as e:
+ resp = {"error": "token_exchange_failed", "detail": str(e)}
+ json.dump(resp, open(OUT, "w"), indent=2)
+ self.server._result = resp
+ if resp.get("access_token"):
+ self._html("<h2>✅ TikTok connected</h2><p>Token captured. You can close this tab — Claude will finish wiring it.</p>")
+ else:
+ self._html(f"<h2>Token exchange failed</h2><pre>{json.dumps(resp)[:400]}</pre>")
+ def _html(self, body):
+ self.send_response(200); self.send_header("Content-Type","text/html"); self.end_headers()
+ self.wfile.write(f"<html><body style='font-family:system-ui;max-width:640px;margin:80px auto'>{body}</body></html>".encode())
+
+if not CK or not CS:
+ print("ERROR: TIKTOK_CLIENT_KEY/SECRET not found in .env"); sys.exit(2)
+srv = http.server.HTTPServer(("127.0.0.1", 9276), H)
+srv._result = None
+print(f"listening on {REDIRECT} — waiting for one callback…", flush=True)
+while srv._result is None:
+ srv.handle_request()
+r = srv._result
+print("RESULT:", "OK access_token captured" if r.get("access_token") else json.dumps(r)[:300], flush=True)
+sys.exit(0 if r.get("access_token") else 1)
(oldest)
·
back to Tiktok Oauth Catcher
·
auto-save: 2026-07-20T16:33:10 (2 files) — catcher.py catche d107334 →