#!/usr/bin/env python3 """ Query user names for unmapped open_ids. Usage: python3 gen_open_id_mapping.py [input_file] Default input_file: unmapped_open_id_list.json Output: mapped_open_id_list.json Format: {container_id: {open_id: user_name}} Caching: The script now loads existing mappings from mapped_open_id_list.json and only queries IDs that are not already cached. This significantly speeds up subsequent runs. """ import os import sys import json SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) DEFAULT_INPUT = os.path.join(SCRIPT_DIR, "unmapped_open_id_list.json") OUTPUT_FILE = os.path.join(SCRIPT_DIR, "mapped_open_id_list.json") CONFIG_DIR = os.path.expanduser("~/openclaw-config") def load_existing_mappings(): """Load existing mappings from cached file""" if os.path.exists(OUTPUT_FILE): try: with open(OUTPUT_FILE) as f: return json.load(f) except Exception: # Ignore parse errors pass return {} def get_container_app_creds(container): """Get list of (app_id, app_secret) tuples from container's openclaw.json""" config_file = os.path.join(CONFIG_DIR, container, "openclaw.json") if not os.path.exists(config_file): return [] creds_list = [] try: with open(config_file) as f: config = json.load(f) feishu_config = config.get("channels", {}).get("feishu", {}) # First: collect all accounts accounts = feishu_config.get("accounts", {}) for account_name, account_data in accounts.items(): app_id = account_data.get("appId") or account_data.get("app_id") app_secret = account_data.get("appSecret") or account_data.get("app_secret") if app_id and app_secret: creds_list.append((app_id, app_secret)) # Second: check top-level appId/appSecret (if not already added) top_app_id = feishu_config.get("appId") or feishu_config.get("app_id") top_app_secret = feishu_config.get("appSecret") or feishu_config.get("app_secret") if top_app_id and top_app_secret: if not any(cid == top_app_id for cid, _ in creds_list): creds_list.append((top_app_id, top_app_secret)) return creds_list except Exception as e: print(f"Error reading config for {container}: {e}") return [] def query_user_name(app_id, app_secret, user_id): """Query user name from Feishu API""" try: import lark_oapi as lark from lark_oapi.api.contact.v3 import BatchUserRequest client = lark.Client.builder() \ .app_id(app_id) \ .app_secret(app_secret) \ .build() request = BatchUserRequest.builder() \ .user_ids([user_id]) \ .user_id_type("open_id") \ .build() response = client.contact.v3.user.batch(request) if response.success(): data = json.loads(response.raw.content) items = data.get('data', {}).get('items', []) if items: return items[0].get('en_name', '') or items[0].get('name', '') return None except Exception: # Ignore parse errors return None def main(): # Get input file if len(sys.argv) > 1: input_file = sys.argv[1] else: input_file = DEFAULT_INPUT if not os.path.exists(input_file): print(f"Error: Input file not found: {input_file}") sys.exit(1) print(f"Reading from {input_file}...") with open(input_file) as f: unmapped_data = json.load(f) print(f"Found {len(unmapped_data)} containers with unmapped IDs") # Load existing mappings (cache) existing_mappings = load_existing_mappings() cached_count = sum(len(ids) for ids in existing_mappings.values()) print(f"Loaded {cached_count} cached mappings from previous runs") # Query user names for each container result = existing_mappings.copy() # Start with existing mappings total_queried = 0 total_cached = 0 for container, open_ids in unmapped_data.items(): print(f"\nProcessing container: {container}") # Get existing mappings for this container container_cached = result.get(container, {}) print(f" {len(container_cached)} cached mappings for this container") # Get all app credentials for this container creds_list = get_container_app_creds(container) if not creds_list: print(f" Skipping {container}: no app credentials found") continue # Try each credential until we find a match container_result = container_cached.copy() remaining_ids = [oid for oid in open_ids if oid not in container_cached] if not remaining_ids: print(" All IDs already cached, skipping...") total_cached += len(open_ids) continue print(f" Need to query {len(remaining_ids)} new IDs (cached: {len(container_cached)})") for app_id, app_secret in creds_list: if not remaining_ids: break print(f" Trying app: {app_id[:20]}...") for open_id in list(remaining_ids): user_name = query_user_name(app_id, app_secret, open_id) if user_name: container_result[open_id] = user_name print(f" ✓ {open_id[:25]}... -> {user_name}") remaining_ids.remove(open_id) total_queried += 1 else: print(f" ✗ {open_id[:25]}... -> (not found)") if container_result: result[container] = container_result # Write output with open(OUTPUT_FILE, 'w') as f: json.dump(result, f, indent=2) print("\n=== Summary ===") print(f"Total queried this run: {total_queried}") print(f"Total cached (reused): {total_cached}") print(f"Total cached mappings: {cached_count}") print(f"Output: {OUTPUT_FILE}") if __name__ == "__main__": main()