Files
last30days-skill/scripts/test_device_auth.py
T
Matt Van Horn 0a9ff16dfc feat: v3.0.0 - intelligent search, GitHub person/project mode, ELI5, 13+ sources
v3 rewrites the search engine from the ground up:

- Intelligent pre-research: resolves X handles, GitHub repos, subreddits,
  TikTok hashtags, and YouTube channels before searching
- GitHub person-mode: PR velocity, top repos by stars, release notes
- GitHub project-mode: live star counts, README, releases, top issues
- ELI5 mode: plain language synthesis, no jargon
- 13+ sources: Reddit, X, YouTube, TikTok, Instagram, HN, Polymarket,
  GitHub, Threads, Pinterest, Perplexity, Bluesky, Web
- Free Reddit comments via public JSON (no API key needed)
- Fun judge v2: humor scoring baked into narrative
- Cookie consent before browser scanning
- 10,000 free ScrapeCreators calls
- 1,012 tests

Thank you to the community contributors whose issues and PRs shaped v3:
@uppinote20 (#143), @zerone0x (#134, #136), @thinkun (#116),
@thomasmktong (#124), @fanispoulinakisai-boop (#100), @pejmanjohn (#78),
@zl190 (#115), @hnshah (#84, #85, #86)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 10:52:23 -07:00

129 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""Test ScrapeCreators GitHub device auth flow from the CLI.
Usage:
python3 scripts/test_device_auth.py
Flow:
1. Starts device code request
2. Shows user code + opens GitHub auth URL in browser
3. Polls for token until you complete auth
4. Fetches your profile and prints your API key
"""
import json
import sys
import time
import webbrowser
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError
BASE = "https://api.scrapecreators.com/v1/github/device"
def _post(url, data=None):
body = json.dumps(data).encode() if data else None
req = Request(url, data=body, method="POST")
req.add_header("Content-Type", "application/json")
with urlopen(req, timeout=15) as resp:
return json.loads(resp.read())
def _get(url, token):
req = Request(url)
req.add_header("Authorization", f"Bearer {token}")
with urlopen(req, timeout=15) as resp:
return json.loads(resp.read())
def main():
# Step 1: Start device flow
print("Starting ScrapeCreators GitHub device auth...\n")
try:
code_resp = _post(f"{BASE}/code")
except (HTTPError, URLError) as e:
print(f"Failed to start device flow: {e}")
sys.exit(1)
device_code = code_resp.get("device_code")
user_code = code_resp.get("user_code")
verification_uri = code_resp.get("verification_uri")
interval = code_resp.get("interval", 5)
expires_in = code_resp.get("expires_in", 900)
if not device_code or not user_code:
print(f"Unexpected response: {json.dumps(code_resp, indent=2)}")
sys.exit(1)
print(f"Your code: {user_code}")
print(f"Open: {verification_uri}")
print(f"Expires in: {expires_in}s\n")
# Open browser
if verification_uri:
webbrowser.open(verification_uri)
print("Opened browser. Enter the code above, then authorize.\n")
# Step 2: Poll for token
print("Waiting for authorization", end="", flush=True)
deadline = time.time() + expires_in
access_token = None
while time.time() < deadline:
time.sleep(interval)
print(".", end="", flush=True)
try:
token_resp = _post(f"{BASE}/token", {"device_code": device_code})
except HTTPError as e:
# Some APIs return 4xx while pending
if e.code in (400, 403, 428):
continue
print(f"\nPoll error: {e}")
sys.exit(1)
except URLError:
continue
if token_resp.get("access_token"):
access_token = token_resp["access_token"]
break
# Check for explicit error states
error = token_resp.get("error")
if error == "authorization_pending" or error == "slow_down":
if error == "slow_down":
interval = min(interval + 2, 30)
continue
if error in ("expired_token", "access_denied"):
print(f"\n\nAuth failed: {error}")
sys.exit(1)
if not access_token:
print("\n\nTimed out waiting for authorization.")
sys.exit(1)
print(f"\n\nAuthorized! Access token: {access_token[:12]}...\n")
# Step 3: Fetch profile
print("Fetching profile...")
try:
profile = _get(f"{BASE}/profile", access_token)
except (HTTPError, URLError) as e:
print(f"Failed to fetch profile: {e}")
print(f"(access_token was: {access_token})")
sys.exit(1)
print(f"\nProfile response:\n{json.dumps(profile, indent=2)}\n")
api_key = profile.get("api_key")
if api_key:
print("=" * 50)
print(f"Your ScrapeCreators API key: {api_key}")
print("=" * 50)
print(f"\nTo use it: echo 'SCRAPECREATORS_API_KEY={api_key}' >> ~/.config/last30days/.env")
else:
print("No api_key in profile response. Full response printed above.")
if __name__ == "__main__":
main()