#!/usr/bin/env python3 import http.server import json import os import random import re import socketserver import string import threading import time import urllib.error import urllib.request PORT = int(os.getenv("ADDRESS_TOOL_PORT", "18990")) WEB_DIR = os.path.dirname(os.path.abspath(__file__)) UPSTREAM_API = "https://www.meiguodizhi.com/api/v1/dz" FNG_API_KEY = os.getenv("FNG_API_KEY", "").strip() FNG_BASE_URL = "https://www.fakenamegenerator.com/api/v1" FIRST_NAMES = [ "James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Charles", "Christopher", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "Steven", "Paul", "Andrew", "Joshua", "Kenneth", "Kevin", "Brian", "George", "Edward", "Ronald", "Timothy", "Jason", "Jeffrey", "Ryan", "Jacob", "Gary", "Nicholas", "Eric", "Jonathan", "Stephen", "Larry", "Justin", "Scott", "Brandon", "Mary", "Patricia", "Jennifer", "Linda", "Elizabeth", "Barbara", "Susan", "Jessica", "Sarah", "Karen", "Lisa", "Nancy", "Betty", "Margaret", "Sandra", "Ashley", "Kimberly", "Emily", "Donna", "Michelle", "Carol", "Amanda", "Melissa", "Deborah", "Stephanie", "Rebecca", "Sharon", "Laura", "Cynthia", "Kathleen" ] LAST_NAMES = [ "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin", "Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", "Walker", "Young", "Allen", "King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores" ] US_STATE_CODES = { "alabama": "AL", "alaska": "AK", "arizona": "AZ", "arkansas": "AR", "california": "CA", "colorado": "CO", "connecticut": "CT", "delaware": "DE", "florida": "FL", "georgia": "GA", "hawaii": "HI", "idaho": "ID", "illinois": "IL", "indiana": "IN", "iowa": "IA", "kansas": "KS", "kentucky": "KY", "louisiana": "LA", "maine": "ME", "maryland": "MD", "massachusetts": "MA", "michigan": "MI", "minnesota": "MN", "mississippi": "MS", "missouri": "MO", "montana": "MT", "nebraska": "NE", "nevada": "NV", "new hampshire": "NH", "new jersey": "NJ", "new mexico": "NM", "new york": "NY", "north carolina": "NC", "north dakota": "ND", "ohio": "OH", "oklahoma": "OK", "oregon": "OR", "pennsylvania": "PA", "rhode island": "RI", "south carolina": "SC", "south dakota": "SD", "tennessee": "TN", "texas": "TX", "utah": "UT", "vermont": "VT", "virginia": "VA", "washington": "WA", "west virginia": "WV", "wisconsin": "WI", "wyoming": "WY", "district of columbia": "DC" } MIN_COOLDOWN_SECONDS = 0.5 REQUEST_TIMEOUT = 10 RATE_LIMIT_COOLDOWN = 15 _lock = threading.Lock() _is_requesting = False _last_request_time = 0.0 _cooldown_until = 0.0 def generate_secure_password(length=14): upper = random.choice(string.ascii_uppercase) lower = random.choice(string.ascii_lowercase) digit = random.choice(string.digits) chars = string.ascii_letters + string.digits rest = "".join(random.choices(chars, k=length - 3)) pwd_list = list(upper + lower + digit + rest) random.shuffle(pwd_list) return "".join(pwd_list) def resolve_us_state(city_str): if not city_str: return "" c_lower = city_str.strip().lower() if len(c_lower) == 2 and c_lower.upper() in US_STATE_CODES.values(): return c_lower.upper() for name, code in US_STATE_CODES.items(): if name in c_lower or c_lower == name: return code return "" def transform_fng_to_dz(item): name_obj = item.get("name", {}) addr_obj = item.get("address", {}) card_obj = item.get("credit_card", {}) bday_obj = item.get("birthday", {}) height_obj = item.get("height", {}) weight_obj = item.get("weight", {}) first = name_obj.get("first", "") last = name_obj.get("last", "") full = name_obj.get("full", f"{first} {last}".strip()) # 格式化过期时间 YYYY-MM -> MM/YYYY expires_raw = card_obj.get("expires", "") expires = expires_raw if "-" in expires_raw: parts = expires_raw.split("-") if len(parts) == 2: expires = f"{parts[1]}/{parts[0]}" height_str = "" if height_obj.get("in") and height_obj.get("cm"): height_str = f"{height_obj.get('in')}\" ({height_obj.get('cm')}cm)" weight_str = "" if weight_obj.get("lb") and weight_obj.get("kg"): weight_str = f"{weight_obj.get('lb')} lbs ({weight_obj.get('kg')}kg)" transformed = { "Address": addr_obj.get("street", ""), "Telephone": item.get("phone", {}).get("number", ""), "Address_Alias": "", "Trans_Address": "", "Trans_Cn_Address": "", "Fax": "", "City": addr_obj.get("city", ""), "Zip_Code": addr_obj.get("postal_code", ""), "rowkey": item.get("id", ""), "State": addr_obj.get("state", ""), "State_Full": addr_obj.get("state", ""), "Expires": expires, "Credit_Card_Type": card_obj.get("network", "Visa"), "Credit_Card_Number": card_obj.get("number", ""), "CVV2": str(card_obj.get("cvv", "")), "Full_Name": full, "Gender": (item.get("gender") or "Male").capitalize(), "Full_Name_Tran": "", "Title": name_obj.get("title", "Mr."), "Birthday": bday_obj.get("date", ""), "Username": item.get("username", ""), "Password": item.get("password", ""), "Height": height_str, "Weight": weight_str, "Temporary_mail": item.get("email", ""), "System": "Windows 11", "GUID": item.get("id", ""), "Blood_Type": item.get("blood_type", "O+"), "Browser_User_Agent": item.get("user_agent", ""), "Occupation": item.get("occupation", ""), "Company_Name": item.get("company", ""), "Social_Security_Number": item.get("national_id", {}).get("value", "") if item.get("national_id") else "", "First_Name": first, "Last_Name": last, "Generated_Password": generate_secure_password(14), "_source": "fakenamegenerator.com" } return transformed def fetch_from_fng(city_or_state): url = f"{FNG_BASE_URL}/identities?count=1&country=us" state_code = resolve_us_state(city_or_state) if state_code: url += f"&state={state_code}" req = urllib.request.Request( url, headers={ "Authorization": f"Bearer {FNG_API_KEY}", "User-Agent": "OpenClaw-AddressTool/2.0" } ) with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as response: if response.status == 200: res_data = json.loads(response.read().decode("utf-8")) if res_data.get("data") and len(res_data["data"]) > 0: addr_transformed = transform_fng_to_dz(res_data["data"][0]) return {"status": "ok", "address": addr_transformed} return None class ProxyHandler(http.server.SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): super().__init__(*args, directory=WEB_DIR, **kwargs) def send_standard_json(self, status_code, data): self.send_response(status_code) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(json.dumps(data, ensure_ascii=False).encode("utf-8")) def do_POST(self): global _is_requesting, _last_request_time, _cooldown_until if self.path == "/api/v1/dz" or self.path == "/api/generate": content_length = int(self.headers.get("Content-Length", 0)) post_data = self.rfile.read(content_length) now = time.time() with _lock: if _is_requesting: self.send_standard_json(429, { "status": "busy", "error_code": "ERR_CONCURRENT_REQUEST", "message": "正在处理上一条生成请求,请稍候", "retry_after": 1 }) return _is_requesting = True try: payload = {} try: payload = json.loads(post_data.decode("utf-8")) except Exception: pass city = payload.get("city", "") # 优先调用官方 FakeNameGenerator API if FNG_API_KEY: try: fng_result = fetch_from_fng(city) if fng_result: self.send_standard_json(200, fng_result) return except Exception as e: print(f"[WARN] FNG API failed, falling back to upstream: {e}") # 降级备用:原有的 meiguodizhi.com 接口 req = urllib.request.Request( UPSTREAM_API, data=post_data, headers={ "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", "Referer": "https://www.meiguodizhi.com/" } ) try: with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as response: raw_body = response.read() data = json.loads(raw_body.decode("utf-8")) if data.get("status") == "ok" and "address" in data: addr = data["address"] orig_fn = addr.get("Full_Name", "") # 格式化 cleaned = re.sub(r"[^a-zA-Z\s]", "", orig_fn).strip() words = [w.capitalize() for w in cleaned.split() if w] if len(words) >= 2: fn = f"{words[0]} {words[-1]}" addr["First_Name"] = words[0] addr["Last_Name"] = words[-1] else: fn = f"{random.choice(FIRST_NAMES)} {random.choice(LAST_NAMES)}" p = fn.split() addr["First_Name"] = p[0] addr["Last_Name"] = p[1] addr["Full_Name"] = fn addr["Generated_Password"] = generate_secure_password(14) addr["_source"] = "meiguodizhi.com" self.send_standard_json(200, data) except Exception as e: self.send_standard_json(500, { "status": "error", "message": f"生成失败: {str(e)}" }) finally: with _lock: _is_requesting = False _last_request_time = time.time() else: self.send_error(404, "Not Found") def do_OPTIONS(self): self.send_response(200) self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") self.send_header("Access-Control-Allow-Headers", "Content-Type") self.end_headers() if __name__ == "__main__": socketserver.TCPServer.allow_reuse_address = True with socketserver.TCPServer(("0.0.0.0", PORT), ProxyHandler) as httpd: print(f"Address tool server running on port {PORT}") httpd.serve_forever()