Skill: kuaidaili-ai/proxy-ip-manager (version 1.0.0)

--- name: proxy-ip-manager description: '快代理(Kuaidaili)智能管理技能，专为快代理用户设计。支持API配置、订单信息获取、到期提醒、IP健康检测、智能并发调度、使用统计。触发场景：(1) 用户提及"快代理"、"kuaidaili"、"kdlapi"、"代理IP"；(2) 用户需要检查快代理订单状态、IP质量、并发设置；(3) 用户使用命令 set_config/show_config/get_order_info/check_expiry/check_ip_health/get_concurrency/stats。此技能仅支持快代理服务。' --- # 快代理智能管理器 专为快代理(Kuaidaili)用户打造的代理IP智能管理技能。 ## 快速开始 首次使用需要配置快代理API密钥： ``` 用户: set_config secret_id=example_id123 secret_key=example_key456 ``` ⚠️ **安全提示**： - **请在私聊中配置密钥**，不要在群聊中发送，避免泄露给他人 - 密钥仅保存在你的本地设备，不会上传或分享 - 配置后使用 `show_config` 查看的是脱敏后的信息 **密钥获取方式**： 1. 登录快代理会员中心：https://www.kuaidaili.com/uc/overview/ 2. 进入「API密钥管理」页面 3. 获取订单的 `SecretId` 和 `SecretKey` **认证流程**： 系统会自动调用 `https://auth.kdlapi.com/api/get_secret_token` 获取 `secret_token` 作为签名凭证。 配置成功后可使用所有功能。 --- ## 命令列表 | 命令 | 功能 | 示例 | |------|------|------| | `set_config` | 配置API密钥 | `set_config secret_id=xxx secret_key=xxx` | | `show_config` | 显示当前配置（脱敏） | `show_config` | | `get_order_info` | 获取订单详情 | `get_order_info` | | `get_ip` | 获取代理IP | `get_ip` 或 `get_ip num=5` | | `check_expiry` | 检查到期时间 | `check_expiry` | | `check_ip_health` | IP健康检测 | `check_ip_health` | | `get_concurrency` | 智能并发推荐 | `get_concurrency` | | `stats` | 使用统计报告 | `stats` | --- ## 获取代理IP 配置 secret_id 和 secret_key 后，可以获取对应订单的代理IP。 ### 命令 ``` get_ip # 获取代理IP（默认1个） get_ip num=5 # 指定获取数量 ``` ### 支持的产品类型 | 产品类型 | API接口 | 说明 | |---------|---------|------| | 隧道代理Pro | `https://tps.kdlapi.com/api/tpsprocurrentip` | 获取当前隧道IP | | 隧道代理 | `https://tps.kdlapi.com/api/tpscurrentip` | 获取当前隧道IP | | 私密代理 | `https://dps.kdlapi.com/api/getdps` | 提取IP，支持 `num` 参数 | | 独享代理 | `https://kps.kdlapi.com/api/getkps` | 提取IP | | 海外动态代理 | `https://fps.kdlapi.com/api/getfps` | 获取隧道IP | | 海外静态住宅 | `https://dev.kdlapi.com/api/getsfps` | 获取静态住宅IP | ### 请求参数 | 参数 | 说明 | |------|------| | `secret_id` | 用户密钥ID（必填） | | `signature` | 签名/令牌（必填，系统自动获取） | | `num` | 提取数量（仅私密代理支持，默认1） | ### 调用示例 **隧道代理Pro当前IP**： ``` GET https://tps.kdlapi.com/api/tpsprocurrentip?secret_id=example_id&signature=xxx ``` ⚠️ **限制**：仅支持换IP周期 >= 30秒的隧道订单，否则返回错误码 `-4` **隧道代理当前IP**： ``` GET https://tps.kdlapi.com/api/tpscurrentip?secret_id=example_id&signature=xxx ``` ⚠️ **限制**：仅支持换IP周期 >= 30秒的隧道订单，否则返回错误码 `-4` **私密代理IP（支持数量）**： ``` GET https://dps.kdlapi.com/api/getdps?secret_id=example_id&num=5&signature=xxx ``` **独享代理IP**： ``` GET https://kps.kdlapi.com/api/getkps?secret_id=example_id&signature=xxx ``` **海外动态代理隧道IP**： ``` GET https://fps.kdlapi.com/api/getfps?secret_id=example_id&signature=xxx ``` **海外静态住宅IP**： ``` GET https://dev.kdlapi.com/api/getsfps?secret_id=example_id&signature=xxx ``` ### 返回格式 ```json { "code": 0, "msg": "", "data": { "ip_list": ["1.2.3.4:5678", "5.6.7.8:9012"] } } ``` --- ## 订单信息API ### 获取订单详情 **接口**：`GET https://dev.kdlapi.com/api/getorderinfo` **返回字段**： ### 公共字段（所有产品） | 字段 | 说明 | |------|------| | `orderid` | 订单ID | | `product` | 产品类型 | | `status` | 订单状态 | | `pay_type` | 付费方式 | | `expire_time` | 到期时间（仅包年包月订单） | ### 隧道代理 (TPS) 特有字段 | 字段 | 说明 | |------|------| | `tunnel_host` | 代理主机 | | `tunnel_port_http` | HTTP端口 | | `tunnel_username` | 认证用户名 | | `tunnel_password` | 认证密码 | | `tunnel_req` | 并发上限 | | `tunnel_bandwidth` | 带宽（Mbps） | ### 私密代理 (DPS) 特有字段 | 字段 | 说明 | |------|------| | `proxy_username` | 认证用户名 | | `proxy_password` | 认证密码 | ### 独享代理 (KPS) 特有字段 | 字段 | 说明 | |------|------| | `proxy_count` | 独享代理数量 | | `proxy_username` | 认证用户名 | | `proxy_password` | 认证密码 | ### 海外动态代理 (FPS) 特有字段 | 字段 | 说明 | |------|------| | `proxy_username` | 认证用户名 | | `proxy_password` | 认证密码 | | `transfer_area` | 转发地区（如 US、JP、UK） | | `fps_host` | 隧道主机 | | `fps_port_http` | HTTP端口 | | `fps_port_socks` | SOCKS端口 | ### 海外静态代理 (FPS_STATIC) 特有字段 | 字段 | 说明 | |------|------| | `proxy_count` | 代理数量 | | `proxy_username` | 认证用户名（包段订单返回） | | `proxy_password` | 认证密码（包段订单返回） | **注意**：不同产品返回的字段不同，带宽和并发仅隧道代理(TPS)返回。详见 [references/api_response.md](references/api_response.md) ### 获取到期时间 **接口**：`GET https://dev.kdlapi.com/api/getorderexpiretime` **返回字段**： | 字段 | 说明 | |------|------| | `expire_time` | 到期时间（格式：YYYY-MM-DD HH:MM:SS） | 详见 [references/api_response.md](references/api_response.md) --- ## 配置管理 ### 初始化检查 运行 `scripts/config_manager.py` 检查配置状态： - 未配置 → 提示用户使用 `set_config` - 已配置 → 验证密钥有效性 ### 配置存储 配置保存在 `~/.openclaw/skills/proxy-ip-manager/config.json`： ```json { "secret_id": "example_id123", "secret_key": "example_key456", "secret_token": "自动获取的令牌", "token_expire_time": "令牌过期时间", "test_url": "https://httpbin.org/ip", "timeout": 5, "configured_at": "YYYY-MM-DDTHH:MM:SS" } ``` ### 认证流程 1. 用户提供 `secret_id` 和 `secret_key` 2. 系统调用 `https://auth.kdlapi.com/api/get_secret_token` 获取 `secret_token` 3. `secret_token` 作为 `signature` 参数调用各API ### 安全脱敏 日志和输出中自动脱敏： - `secret_id=exam***d123` - `secret_key=exa***y456` - `secret_token=自动脱敏` --- ## API调用规范 使用 `scripts/api_client.py` 封装所有API调用： ```python # 自动特性 - 超时控制（5秒） - 自动重试（最多3次） - 错误处理和脱敏日志 ``` ### 返回字段解析 详见 [references/api_response.md](references/api_response.md) 关键字段： - `ip_list` → 代理IP列表 - `max_concurrency` → 并发上限 - `expire_time` → 订单到期时间 - `username/password/host/port` → 认证信息 --- ## 订单到期模块 运行 `scripts/expiry_checker.py`： ### 提醒规则 | 剩余天数 | 状态 | 提示级别 | |---------|------|---------| | >5天 | 正常 | 无 | | 3-5天 | 注意 | 5天提醒 | | 1-3天 | 警告 | 3天提醒 | | <1天 | 紧急 | 1天提醒 | | ≤0天 | 已过期 | 停止服务 | ### 过期处理 订单过期后： - 并发 = 0 - 停止健康检测 - 状态标记为 `expired` - 提示续费链接：https://www.kuaidaili.com/uc/order-list/ --- ## IP健康检测 运行 `scripts/health_checker.py`： ### 检测流程 1. 从API获取多个代理IP 2. 使用 test_url 发请求测试 3. 记录成功/失败、响应延迟、状态码 ### 评分算法 详见 [references/scoring_rules.md](references/scoring_rules.md) ``` score = success_rate * 70 + latency_score * 30 latency评分： - <1s → 100分 - 1~3s → 80分 - >3s → 50分 ``` ### 输出指标 - IP质量评分（0-100） - 成功率（%） - 平均延迟（ms） --- ## 智能并发调度 运行 `scripts/scheduler.py`： ### 动态调整策略 | success_rate | 并发调整 | |-------------|---------| | >95% | base * 1.0 | | 80-95% | base * 0.7 | | 60-80% | base * 0.5 | | <60% | base * 0.3 | base = max_concurrency * 0.8 ### 失败保护 - 连续失败 > 10 → 并发降低50% - success_rate < 50% → 并发降至最低 --- ## 使用统计 运行 `scripts/stats_tracker.py`： ### 统计内容 - 每日请求数 - 成功数/失败数 - 成功率 - 最近7天趋势 ### 状态评估 | 成功率趋势 | 状态 | |-----------|------| | 稳定在90%+ | 稳定 | | 波动10%以内 | 波动 | | 波动>10%或<70% | 风险 | --- ## 异常处理 ### API错误码对照 **隧道代理 (TPS) 错误码**： | 错误码 | 说明 | |-------|------| | `-4` | 换IP周期不足30秒（仅支持换IP周期>=30秒的订单） | **私密代理 (DPS) 错误码**： | 错误码 | 说明 | |-------|------| | `1` | 今日提取余额已用尽 | | `2` | 订单提取余额已用尽 | | `3` | 没有找到符合条件的代理 | | `4` | 账号尚未通过实名认证 | | `-1` | 无效请求 | | `-2` | 订单无效（刚下单需等待1分钟生效） | | `-3` | 参数错误 | | `-4` | 提取失败 | | `-5` | 此订单不能提取私密代理 | | `-6` | 调用IP不在白名单内 | | `-51` | 订单1分钟内调用IP超限 | | `-16` | 订单已退款 | | `-15/-13` | 订单已过期 | | `-14` | 订单被封禁（联系客服） | | `-12` | 订单无效 | | `-11` | 订单尚未支付 | **独享代理 (KPS) 错误码**： | 错误码 | 说明 | |-------|------| | `2` | 订单已过期 | | `3` | 没有找到符合条件的代理 | | `4` | 账号尚未通过实名认证 | | `-1` | 无效请求 | | `-2` | 订单无效（刚下单需等待1分钟生效） | | `-3` | 参数错误 | | `-4` | 提取失败 | | `-5` | 此订单不能提取独享代理 | | `-51` | 订单1分钟内调用IP超限 | | `-16/-15/-13` | 订单已过期/退款 | | `-14` | 订单被封禁（联系客服） | | `-12/-11` | 订单无效/未支付 | **海外动态代理 (FPS) 错误码**： | 错误码 | 说明 | |-------|------| | `2` | 订单已过期 | | `3` | 暂无可用代理 | | `4` | 账号尚未通过实名认证 | | `-1/-2/-3/-4` | 无效请求/订单无效/参数错误/提取失败 | | `-5` | 此订单不能提取海外代理动态住宅 | | `-51` | 订单1分钟内调用IP超限 | | `-16/-15/-13/-14/-12/-11` | 订单状态异常（过期/退款/封禁等） | **海外静态住宅 (FPS_STATIC) 错误码**： | 错误码 | 说明 | |-------|------| | `2` | 订单已过期 | | `3` | 没有找到符合条件的代理 | | `4` | 账号尚未通过实名认证 | | `-1/-2/-3/-4` | 无效请求/订单无效/参数错误/提取失败 | | `-5` | 此订单不能提取海外代理静态住宅 | | `-51` | 订单1分钟内调用IP超限 | | `-16/-15/-13/-14/-12/-11` | 订单状态异常（过期/退款/封禁等） | **公共错误码**： | 错误码 | 说明 | 通用解决方案 | |-------|------|---------| | `-2` | 订单无效（刚下单） | 等待1分钟自动生效 | | `-5` | 产品类型不匹配 | 确认订单产品类型与API对应 | | `-51` | 调用IP数量超限 | 控制调用频率 | | `-15/-13` | 订单已过期 | 续费或重新下单 | | `-14` | 订单被封禁 | 联系客服处理 | **其他异常**： | 异常类型 | 输出提示 | |---------|---------| | 未配置 | "请先配置代理API密钥（使用 set_config）" | | API调用失败 | "代理API调用失败，请检查API密钥是否有效" | | 数据异常 | "API返回数据异常，请联系服务提供方" | | 网络错误 | "网络连接失败，请检查网络环境" | --- ## 订单到期提醒 ### 命令 ``` check_expiry # 检查到期时间，输出提醒信息 ``` ### 输出格式 返回 JSON 格式，包含： - `need_notify`: 是否需要提醒（true/false） - `days_left`: 剩余天数 - `expire_time`: 到期时间 - `notification_text`: 格式化的提醒文本 ### 提醒规则 | 剩余天数 | 级别 | 说明 | |---------|------|------| | 5天 | 📅 提醒 | 请安排续费 | | 3天 | ⚠️ 警告 | 请尽快续费 | | 1天 | 🚨 紧急 | 请立即续费 | | 已过期 | 🚨 紧急 | 已过期，立即续费 | ### 配置定时提醒 用户可自行配置定时任务，调用脚本获取提醒信息后发送通知： **脚本路径**（相对于 skill 目录）： ``` scripts/expiry_notify.py ``` 或使用绝对路径（将 `<USER>` 替换为你的用户名）： ``` C:\Users\<USER>\.openclaw\skills\proxy-ip-manager\scripts\expiry_notify.py ``` **示例输出**（需要提醒时）： ```json { "need_notify": true, "days_left": 3, "expire_time": "YYYY-MM-DD HH:MM:SS", "notification_text": "⚠️【快代理订单到期警告】\n\n快代理订单将在3天后到期，请尽快续费。\n\n到期时间：YYYY-MM-DD HH:MM:SS\n剩余天数：3 天\n续费链接：https://www.kuaidaili.com/uc/order-list/" } ``` --- ## 脚本文件 | 脚本 | 用途 | |------|------| | `scripts/config_manager.py` | 配置管理（存储、读取、验证） | | `scripts/api_client.py` | 代理IP获取 | | `scripts/order_info.py` | 订单信息查询 | | `scripts/expiry_checker.py` | 订单到期检查 | | `scripts/health_checker.py` | IP健康检测 | | `scripts/scheduler.py` | 智能并发调度 | | `scripts/stats_tracker.py` | 使用统计 |

path: scripts/api_client.py
content: #!/usr/bin/env python3
"""
API调用封装模块
- 超时控制
- 自动重试
- 错误处理
- 脱敏日志
"""

import os
import sys
import json
import time
import requests
from datetime import datetime
from pathlib import Path

# 导入配置管理
try:
    from config_manager import load_config, is_configured, get_credentials, mask_secret, STATS_DIR
except ImportError:
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from config_manager import load_config, is_configured, get_credentials, mask_secret, STATS_DIR


MAX_RETRY = 3
DEFAULT_TIMEOUT = 15

# 代理API端点
PROXY_API_URLS = {
    "TPS": "https://tps.kdlapi.com/api/gettps",
    "DPS": "https://dps.kdlapi.com/api/getdps",
    "KPS": "https://kps.kdlapi.com/api/getkps",
    "FPS": "https://fps.kdlapi.com/api/getfps",
    "SFPS": "https://dev.kdlapi.com/api/getsfps"
}

# 当前IP查询API
CURRENT_IP_URLS = {
    "TPS_PRO": "https://tps.kdlapi.com/api/tpsprocurrentip",
    "TPS": "https://tps.kdlapi.com/api/tpscurrentip"
}


def call_api(product_type="TPS", params=None, retry_count=0):
    """
    调用代理API获取IP列表
    
    Args:
        product_type: 产品类型 (TPS/DPS/KPS)
        params: 额外参数 (如 num, format, sep 等)
        retry_count: 重试次数
    
    Returns:
        dict: {"success": bool, "data": dict}
    """
    if not is_configured():
        return {
            "success": False,
            "error": "请先配置API密钥（使用 set_config）"
        }
    
    # 获取认证信息
    creds = get_credentials()
    if "error" in creds:
        return {"success": False, "error": creds["error"]}
    
    config = load_config()
    timeout = config.get("timeout", DEFAULT_TIMEOUT)
    
    # 构建API URL
    api_url = PROXY_API_URLS.get(product_type, PROXY_API_URLS["TPS"])
    
    # 构建请求参数
    request_params = {
        "secret_id": creds["secret_id"],
        "signature": creds["signature"],
        "num": 1,
        "format": "text",
        "sep": "1"
    }
    
    # 合并额外参数
    if params:
        request_params.update(params)
    
    try:
        # 脱敏日志
        masked_params = f"secret_id={mask_secret(creds['secret_id'])}&signature={mask_secret(creds['signature'])}"
        print(f"[API调用] {api_url}?{masked_params} (尝试 {retry_count + 1}/{MAX_RETRY})")
        
        response = requests.get(api_url, params=request_params, timeout=timeout)
        response.raise_for_status()
        
        # 检查响应类型
        content_type = response.headers.get('content-type', '')
        
        if 'text/plain' in content_type or 'text' in content_type:
            # 文本格式解析（快代理隧道代理格式：host:port:username:password）
            text = response.text.strip()
            if ':' in text:
                parts = text.split(':')
                if len(parts) >= 4:
                    proxy = {
                        "host": parts[0],
                        "port": parts[1],
                        "username": parts[2],
                        "password": parts[3],
                        "full_address": f"[REDACTED_SECRET]"
                    }
                    return {
                        "success": True,
                        "data": {
                            "ip_list": [proxy],
                            "order_info": {}
                        },
                        "raw_text": text
                    }
            
            return {
                "success": True,
                "data": {
                    "ip_list": [],
                    "order_info": {},
                    "raw_text": text
                }
            }
        
        # JSON格式解析
        data = response.json()
        
        # 验证返回数据
        if not data or 'data' not in data:
            return {
                "success": False,
                "error": "API返回数据异常，请联系服务提供方"
            }
        
        # 解析代理列表
        proxy_list = parse_proxy_data(data)
        
        return {
            "success": True,
            "data": proxy_list,
            "raw": data
        }
    
    except requests.exceptions.Timeout:
        if retry_count < MAX_RETRY - 1:
            print(f"[重试] 超时，等待后重试...")
            time.sleep(2)
            return call_api(product_type, params, retry_count + 1)
        return {
            "success": False,
            "error": "代理API调用超时，请检查网络或API状态"
        }
    
    except requests.exceptions.RequestException as e:
        if retry_count < MAX_RETRY - 1:
            print(f"[重试] 网络错误: {e}")
            time.sleep(2)
            return call_api(product_type, params, retry_count + 1)
        return {
            "success": False,
            "error": f"代理API调用失败: {str(e)}"
        }
    
    except json.JSONDecodeError:
        return {
            "success": False,
            "error": "API返回数据格式异常，请联系服务提供方"
        }


def parse_proxy_data(data):
    """解析API返回的代理数据"""
    result = {
        "ip_list": [],
        "order_info": {}
    }
    
    # 解析IP列表
    if 'data' in data and 'proxy_list' in data['data']:
        for proxy in data['data']['proxy_list']:
            ip_entry = {
                "host": proxy.get('ip', ''),
                "port": proxy.get('port', ''),
                "username": proxy.get('username', ''),
                "password": proxy.get('password', ''),
                "full_address": f"http://{proxy.get('username', '')}:{proxy.get('password', '')}@{proxy.get('ip', '')}:{proxy.get('port', '')}"
            }
            result["ip_list"].append(ip_entry)
    
    # 解析订单信息
    order_data = data.get('data', {})
    result["order_info"] = {
        "max_concurrency": order_data.get('max_concurrency', 0),
        "expire_time": order_data.get('expire_time', ''),
        "bandwidth": order_data.get('bandwidth', ''),
        "order_id": order_data.get('order_id', '')
    }
    
    return result


def record_stat(success, latency=None):
    """记录调用统计"""
    today = datetime.now().strftime('%Y-%m-%d')
    stats_file = STATS_DIR / f"{today}.json"
    
    # 加载今日统计
    if stats_file.exists():
        with open(stats_file, 'r') as f:
            stats = json.load(f)
    else:
        stats = {
            "total_requests": 0,
            "success_count": 0,
            "fail_count": 0,
            "latencies": []
        }
    
    # 更新统计
    stats["total_requests"] += 1
    if success:
        stats["success_count"] += 1
        if latency:
            stats["latencies"].append(latency)
    else:
        stats["fail_count"] += 1
    
    # 保存统计
    STATS_DIR.mkdir(parents=True, exist_ok=True)
    with open(stats_file, 'w') as f:
        json.dump(stats, f)


def get_order_info():
    """获取订单信息"""
    result = call_api()
    
    if not result["success"]:
        return result
    
    order = result["data"]["order_info"]
    ip_count = len(result["data"]["ip_list"])
    
    return {
        "success": True,
        "order_info": {
            "order_id": order.get("order_id", "未知"),
            "max_concurrency": order.get("max_concurrency", 0),
            "expire_time": order.get("expire_time", "未知"),
            "bandwidth": order.get("bandwidth", "未知"),
            "ip_count": ip_count
        },
        "ip_list_sample": result["data"]["ip_list"][:3] if result["data"]["ip_list"] else []
    }


def get_proxy_ip(product_type="TPS", num=1):
    """
    获取代理IP
    
    Args:
        product_type: 产品类型 (TPS/DPS/KPS/FPS/SFPS)
        num: 提取数量（仅私密代理DPS支持）
    
    Returns:
        dict: {"success": bool, "data": {"ip_list": [...]}, "error": str}
    """
    if not is_configured():
        return {
            "success": False,
            "error": "请先配置API密钥（使用 set_config）"
        }
    
    # 获取认证信息
    creds = get_credentials()
    if "error" in creds:
        return {"success": False, "error": creds["error"]}
    
    config = load_config()
    timeout = config.get("timeout", DEFAULT_TIMEOUT)
    
    # 选择API URL
    if product_type in CURRENT_IP_URLS:
        # 隧道代理当前IP
        api_url = CURRENT_IP_URLS[product_type]
        request_params = {
            "secret_id": creds["secret_id"],
            "signature": creds["signature"]
        }
    elif product_type in PROXY_API_URLS:
        # 其他代理类型
        api_url = PROXY_API_URLS[product_type]
        request_params = {
            "secret_id": creds["secret_id"],
            "signature": creds["signature"]
        }
        # 私密代理支持num参数
        if product_type == "DPS":
            request_params["num"] = num
    else:
        return {"success": False, "error": f"不支持的产品类型: {product_type}"}
    
    try:
        # 脱敏日志
        masked_params = f"secret_id={mask_secret(creds['secret_id'])}&signature={mask_secret(creds['signature'])}"
        print(f"[API调用] {api_url}?{masked_params}")
        
        response = requests.get(api_url, params=request_params, timeout=timeout)
        response.raise_for_status()
        
        # 检查响应类型
        content_type = response.headers.get('content-type', '')
        
        if 'text/plain' in content_type or 'text' in content_type:
            # 文本格式解析
            text = response.text.strip()
            ip_list = []
            
            # 快代理隧道代理格式：host:port:username:password
            if ':' in text:
                parts = text.split(':')
                if len(parts) >= 4:
                    ip_list.append({
                        "host": parts[0],
                        "port": parts[1],
                        "username": parts[2],
                        "password": parts[3],
                        "full_address": f"[REDACTED_SECRET]"
                    })
                elif len(parts) == 2:
                    # 单IP格式
                    ip_list.append({
                        "ip": parts[0],
                        "port": parts[1],
                        "full_address": f"{parts[0]}:{parts[1]}"
                    })
            
            return {
                "success": True,
                "data": {
                    "ip_list": ip_list,
                    "raw_text": text
                }
            }
        
        # JSON格式解析
        data = response.json()
        
        if data.get("code") != 0:
            return {
                "success": False,
                "error": data.get("msg", "API返回错误")
            }
        
        # 解析代理列表
        ip_list = []
        if "data" in data:
            # 私密代理格式
            if "proxy_list" in data["data"]:
                for proxy in data["data"]["proxy_list"]:
                    ip_list.append({
                        "host": proxy.get("ip", ""),
                        "port": proxy.get("port", ""),
                        "username": proxy.get("username", ""),
                        "password": proxy.get("password", ""),
                        "full_address": f"http://{proxy.get('username', '')}:{proxy.get('password', '')}@{proxy.get('ip', '')}:{proxy.get('port', '')}"
                    })
            # 隧道IP格式
            elif "ip" in data["data"]:
                ip_list.append({
                    "ip": data["data"]["ip"],
                    "full_address": data["data"]["ip"]
                })
        
        return {
            "success": True,
            "data": {
                "ip_list": ip_list
            },
            "raw": data
        }
    
    except requests.exceptions.Timeout:
        return {"success": False, "error": "API调用超时"}
    except requests.exceptions.RequestException as e:
        return {"success": False, "error": f"API调用失败: {str(e)}"}
    except json.JSONDecodeError:
        return {"success": False, "error": "API返回数据格式异常"}


# 命令行入口
if __name__ == '__main__':
    import argparse
    
    parser = argparse.ArgumentParser(description='代理API调用工具')
    parser.add_argument('command', choices=['get', 'order', 'get_ip'], help='命令类型')
    parser.add_argument('--product', default='TPS', help='产品类型 (TPS/DPS/KPS/FPS/SFPS/TPS_PRO)')
    parser.add_argument('--num', type=int, default=1, help='提取数量（仅DPS）')
    args = parser.parse_args()
    
    if args.command == 'get':
        result = call_api(args.product)
        if result["success"]:
            print(f"[OK] 获取到 {len(result['data']['ip_list'])} 个代理IP")
            for ip in result['data']['ip_list'][:5]:
                print(f"  {ip['full_address']}")
        else:
            print(f"[错误] {result['error']}")
    
    elif args.command == 'order':
        result = get_order_info()
        if result["success"]:
            print("[订单信息]")
            info = result["order_info"]
            print(f"  订单ID: {info['order_id']}")
            print(f"  并发上限: {info['max_concurrency']}")
            print(f"  到期时间: {info['expire_time']}")
            print(f"  带宽: {info['bandwidth']}")
            print(f"  IP数量: {info['ip_count']}")
        else:
            print(f"[错误] {result['error']}")
    
    elif args.command == 'get_ip':
        result = get_proxy_ip(args.product, args.num)
        if result["success"]:
            ip_list = result["data"]["ip_list"]
            print(f"[OK] 获取到 {len(ip_list)} 个代理IP")
            for ip in ip_list:
                print(f"  {ip.get('full_address', ip)}")
        else:
            print(f"[错误] {result['error']}")

path: scripts/config_manager.py
content: #!/usr/bin/env python3
"""
配置管理模块
- 配置存储和读取
- 密钥验证
- 自动获取 secret_token
- 脱敏输出
"""

import os
import json
import re
import requests
from datetime import datetime
from pathlib import Path

# 配置文件路径
CONFIG_DIR = Path.home() / ".openclaw" / "skills" / "proxy-ip-manager"
CONFIG_FILE = CONFIG_DIR / "config.json"
STATS_DIR = CONFIG_DIR / "stats"

# 默认配置
DEFAULT_CONFIG = {
    "secret_id": "",
    "secret_key": "",
    "secret_token": "",
    "token_expire_time": None,
    "test_url": "https://httpbin.org/ip",
    "timeout": 5,
    "configured_at": None
}

# Token 获取接口
TOKEN_API_URL = "https://auth.kdlapi.com/api/get_secret_token"


def ensure_dirs():
    """确保目录存在"""
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    STATS_DIR.mkdir(parents=True, exist_ok=True)


def load_config():
    """加载配置"""
    ensure_dirs()
    if CONFIG_FILE.exists():
        with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    return DEFAULT_CONFIG.copy()


def save_config(config):
    """保存配置"""
    ensure_dirs()
    with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
        json.dump(config, f, ensure_ascii=False, indent=2)


def validate_credentials(secret_id, secret_key):
    """验证密钥格式"""
    if not secret_id:
        return False, "secret_id 不能为空"
    if not secret_key:
        return False, "secret_key 不能为空"
    
    # 基本格式检查
    if len(secret_id) < 10:
        return False, "secret_id 格式无效"
    if len(secret_key) < 10:
        return False, "secret_key 格式无效"
    
    return True, "密钥格式验证通过"


def get_secret_token(secret_id, secret_key, timeout=10):
    """
    获取 secret_token
    
    接口: POST https://auth.kdlapi.com/api/get_secret_token
    参数: secret_id, secret_key
    
    Returns:
        dict: {"success": bool, "secret_token": str, "expire_time": str}
    """
    try:
        data = {
            "secret_id": secret_id,
            "secret_key": secret_key
        }
        
        response = requests.post(
            TOKEN_API_URL,
            data=data,
            headers={"Content-Type": "application/x-www-form-urlencoded"},
            timeout=timeout
        )
        
        result = response.json()
        
        if result.get("code") != 0:
            return {
                "success": False,
                "error": f"API错误: {result.get('msg', '未知错误')}"
            }
        
        token_data = result.get("data", {})
        
        return {
            "success": True,
            "secret_token": token_data.get("secret_token", ""),
            "expire_time": token_data.get("expire_time", "")
        }
        
    except requests.exceptions.Timeout:
        return {"success": False, "error": "请求超时，请检查网络"}
    
    except requests.exceptions.RequestException as e:
        return {"success": False, "error": f"网络错误: {str(e)}"}
    
    except json.JSONDecodeError:
        return {"success": False, "error": "API返回数据格式异常"}


def mask_secret(secret):
    """脱敏密钥"""
    if not secret or len(secret) < 8:
        return "***"
    return secret[:4] + "***" + secret[-4:]


def set_config(secret_id, secret_key, test_url=None, timeout=None):
    """
    设置配置
    
    Args:
        secret_id: 快代理 SecretId
        secret_key: 快代理 SecretKey
        test_url: 测试URL（可选）
        timeout: 超时时间（可选）
    
    Returns:
        dict: {"success": bool, "message": str}
    """
    # 验证密钥格式
    valid, msg = validate_credentials(secret_id, secret_key)
    if not valid:
        return {"success": False, "error": msg}
    
    # 获取 secret_token
    print(f"[认证] 正在获取 secret_token...")
    token_result = get_secret_token(secret_id, secret_key)
    
    if not token_result["success"]:
        return {"success": False, "error": f"认证失败: {token_result['error']}"}
    
    secret_token = token_result["secret_token"]
    expire_time = token_result.get("expire_time", "")
    
    print(f"[认证] 成功获取 secret_token")
    
    # 保存配置
    config = load_config()
    config["secret_id"] = secret_id
    config["secret_key"] = secret_key
    config["secret_token"] = secret_token
    config["token_expire_time"] = expire_time
    config["configured_at"] = datetime.now().isoformat()
    
    if test_url:
        config["test_url"] = test_url
    if timeout:
        config["timeout"] = timeout
    
    save_config(config)
    
    return {
        "success": True,
        "message": "配置已保存",
        "masked_secret_id": mask_secret(secret_id),
        "masked_secret_key": mask_secret(secret_key)
    }


def refresh_token():
    """
    刷新 secret_token
    
    Returns:
        dict: {"success": bool, "secret_token": str}
    """
    config = load_config()
    
    if not config.get("secret_id") or not config.get("secret_key"):
        return {"success": False, "error": "未配置密钥，请先使用 set_config"}
    
    token_result = get_secret_token(config["secret_id"], config["secret_key"])
    
    if not token_result["success"]:
        return token_result
    
    config["secret_token"] = token_result["secret_token"]
    config["token_expire_time"] = token_result.get("expire_time", "")
    save_config(config)
    
    return {
        "success": True,
        "secret_token": token_result["secret_token"]
    }


def show_config():
    """显示配置（脱敏）"""
    config = load_config()
    
    if not config.get("secret_id"):
        return {
            "configured": False,
            "message": "请先配置API密钥（使用 set_config）"
        }
    
    return {
        "configured": True,
        "secret_id": mask_secret(config["secret_id"]),
        "secret_key": mask_secret(config["secret_key"]),
        "secret_token": mask_secret(config.get("secret_token", "")),
        "test_url": config["test_url"],
        "timeout": config["timeout"],
        "configured_at": config["configured_at"]
    }


def is_configured():
    """检查是否已配置"""
    config = load_config()
    return bool(config.get("secret_id") and config.get("secret_key"))


def get_credentials():
    """
    获取认证信息
    
    Returns:
        dict: {"secret_id": str, "signature": str} 或 {"error": str}
    """
    config = load_config()
    
    if not config.get("secret_id"):
        return {"error": "未配置密钥"}
    
    # 检查 token 是否存在
    if not config.get("secret_token"):
        # 尝试刷新 token
        refresh_result = refresh_token()
        if not refresh_result["success"]:
            return {"error": refresh_result["error"]}
        config = load_config()
    
    return {
        "secret_id": config["secret_id"],
        "signature": config["secret_token"]
    }


# 命令行入口
if __name__ == '__main__':
    import sys
    
    if sys.platform == 'win32':
        sys.stdout.reconfigure(encoding='utf-8')
    
    if len(sys.argv) < 2:
        print("用法: config_manager.py <command> [args]")
        print("命令: set <secret_id> <secret_key> | show | check | refresh")
        sys.exit(1)
    
    cmd = sys.argv[1]
    
    if cmd == "set":
        if len(sys.argv) < 4:
            print("错误: 请提供 secret_id 和 secret_key")
            print("示例: config_manager.py set example_id123 example_key456")
            sys.exit(1)
        result = set_config(sys.argv[2], sys.argv[3])
        if result["success"]:
            print(f"[OK] 配置已保存")
            print(f"     SecretId: {result['masked_secret_id']}")
            print(f"     SecretKey: {result['masked_secret_key']}")
            print(f"\n[安全提示] 密钥已保存在本地，请勿在群聊中分享")
        else:
            print(f"[错误] {result['error']}")
    
    elif cmd == "show":
        result = show_config()
        if result["configured"]:
            print("[配置信息]")
            print(f"  SecretId: {result['secret_id']}")
            print(f"  SecretKey: {result['secret_key']}")
            print(f"  [REDACTED_SECRET]'secret_token']}")
            print(f"  测试URL: {result['test_url']}")
            print(f"  超时: {result['timeout']}秒")
            print(f"  配置时间: {result['configured_at']}")
        else:
            print(f"[提示] {result['message']}")
    
    elif cmd == "check":
        if is_configured():
            print("[OK] 已配置")
        else:
            print("[提示] 未配置，请使用 set_config")
    
    elif cmd == "refresh":
        result = refresh_token()
        if result["success"]:
            print(f"[OK] Token 已刷新")
        else:
            print(f"[错误] {result['error']}")

path: scripts/expiry_checker.py
content: #!/usr/bin/env python3
"""
订单到期检查模块
- 通过 getorderexpiretime API 获取到期时间
- 计算剩余天数
- 多级提醒
"""

import os
import sys
import requests
from datetime import datetime, timedelta
from pathlib import Path

# 导入配置管理
try:
    from config_manager import load_config, is_configured, get_credentials
except ImportError:
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from config_manager import load_config, is_configured, get_credentials


def get_order_expire_time(retry_count=0):
    """
    获取订单到期时间
    
    接口: GET https://dev.kdlapi.com/api/getorderexpiretime
    参数: secret_id, signature (secret_token)
    
    Returns:
        dict: {"success": bool, "expire_time": str, "days_left": int}
    """
    if not is_configured():
        return {
            "success": False,
            "error": "请先配置API密钥（使用 set_config）"
        }
    
    # 获取认证信息
    creds = get_credentials()
    if "error" in creds:
        return {"success": False, "error": creds["error"]}
    
    config = load_config()
    max_retry = config.get("max_retry", 3)
    timeout = config.get("timeout", 10)
    
    secret_id = creds["secret_id"]
    signature = creds["signature"]
    
    # 调用API
    api_url = "https://dev.kdlapi.com/api/getorderexpiretime"
    params = {
        "secret_id": secret_id,
        "signature": signature
    }
    
    try:
        print(f"[API] 获取订单到期时间 (尝试 {retry_count + 1}/{max_retry})")
        
        response = requests.get(api_url, params=params, timeout=timeout)
        response.raise_for_status()
        
        result = response.json()
        
        if result.get("code") != 0:
            return {
                "success": False,
                "error": f"API错误: {result.get('msg', '未知错误')}"
            }
        
        expire_time = result.get("data", {}).get("expire_time", "")
        
        return {
            "success": True,
            "expire_time": expire_time,
            "raw": result
        }
        
    except requests.exceptions.Timeout:
        if retry_count < max_retry - 1:
            print(f"[重试] 请求超时...")
            import time
            time.sleep(2)
            return get_order_expire_time(retry_count + 1)
        return {"success": False, "error": "请求超时"}
    
    except requests.exceptions.RequestException as e:
        if retry_count < max_retry - 1:
            print(f"[重试] 网络错误: {e}")
            import time
            time.sleep(2)
            return get_order_expire_time(retry_count + 1)
        return {"success": False, "error": f"网络错误: {str(e)}"}


def parse_expire_time(expire_str):
    """解析到期时间字符串"""
    if not expire_str:
        return None
    
    formats = [
        '%Y-%m-%d %H:%M:%S',
        '%Y-%m-%dT%H:%M:%S',
        '%Y-%m-%d',
        '%Y/%m/%d %H:%M:%S',
    ]
    
    for fmt in formats:
        try:
            return datetime.strptime(expire_str, fmt)
        except ValueError:
            continue
    
    return None


def calculate_days_left(expire_time):
    """计算剩余天数"""
    if isinstance(expire_time, str):
        expire_time = parse_expire_time(expire_time)
    
    if not expire_time:
        return None, "无法解析到期时间"
    
    now = datetime.now()
    delta = expire_time - now
    days_left = delta.days
    hours_left = delta.seconds // 3600
    
    return days_left, hours_left, expire_time


def get_expiry_status(days_left):
    """获取到期状态"""
    if days_left is None:
        return "unknown", "无法确定"
    
    if days_left <= 0:
        return "expired", "已过期"
    elif days_left < 1:
        return "critical", "紧急（即将过期）"
    elif days_left < 3:
        return "warning", "警告（3天内过期）"
    elif days_left < 5:
        return "attention", "注意（5天内过期）"
    else:
        return "normal", "正常"


def check_expiry():
    """检查订单到期情况"""
    # 获取到期时间
    result = get_order_expire_time()
    
    if not result["success"]:
        return result
    
    expire_str = result["expire_time"]
    
    if not expire_str:
        return {
            "success": True,
            "status": "unknown",
            "message": "无法获取到期时间"
        }
    
    # 计算剩余时间
    calc_result = calculate_days_left(expire_str)
    days_left, hours_left, expire_datetime = calc_result
    
    if days_left is None:
        return {
            "success": True,
            "status": "unknown",
            "message": f"到期时间格式异常: {expire_str}"
        }
    
    status, status_text = get_expiry_status(days_left)
    
    # 构建响应
    response = {
        "success": True,
        "status": status,
        "status_text": status_text,
        "expire_time": expire_str,
        "days_left": days_left,
        "hours_left": hours_left,
        "renew_link": "https://www.kuaidaili.com/uc/order-list/"
    }
    
    # 状态处理
    if status == "expired":
        response["action"] = "停止使用"
        response["message"] = f"订单已过期 {abs(days_left)} 天，请立即续费"
    
    elif status == "critical":
        response["action"] = "紧急续费"
        response["message"] = f"订单将在 {hours_left} 小时内过期，请立即续费"
    
    elif status == "warning":
        response["action"] = "尽快续费"
        response["message"] = f"订单将在 {days_left} 天后过期（3天内）"
    
    elif status == "attention":
        response["action"] = "建议续费"
        response["message"] = f"订单将在 {days_left} 天后过期（5天内）"
    
    else:
        response["action"] = "正常使用"
        response["message"] = f"订单有效期充足，剩余 {days_left} 天"
    
    return response


# 命令行入口
if __name__ == '__main__':
    # 设置控制台编码
    if sys.platform == 'win32':
        sys.stdout.reconfigure(encoding='utf-8')
    
    result = check_expiry()
    
    if result["success"]:
        print(f"\n[到期检查结果]")
        print(f"  状态: {result['status_text']}")
        print(f"  到期时间: {result['expire_time']}")
        print(f"  剩余天数: {result['days_left']} 天")
        
        if result['hours_left'] and result['days_left'] < 1:
            print(f"  剩余时间: {result['hours_left']} 小时")
        
        print(f"  建议: {result['message']}")
        print(f"  续费链接: {result['renew_link']}")
        
        if result['status'] == 'expired':
            print(f"\n[警告] 订单已过期，请立即续费！")
    else:
        print(f"[错误] {result['error']}")

path: scripts/expiry_notify.py
content: #!/usr/bin/env python3
"""
订单到期提醒脚本
- 检查到期时间
- 根据剩余天数生成提醒
- 输出提醒信息（不发送通知）
"""

import os
import sys
import json
from datetime import datetime
from pathlib import Path

# 设置控制台编码
if sys.platform == 'win32':
    sys.stdout.reconfigure(encoding='utf-8')

# 导入其他模块
try:
    from expiry_checker import get_order_expire_time, calculate_days_left, get_expiry_status
    from config_manager import is_configured
except ImportError:
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from expiry_checker import get_order_expire_time, calculate_days_left, get_expiry_status
    from config_manager import is_configured


# 提醒阈值
REMIND_DAYS = [5, 3, 1]  # 5天、3天、1天提醒


def check_expiry_reminder():
    """
    检查到期时间并生成提醒
    
    Returns:
        dict: 包含提醒信息，不发送通知
    """
    
    if not is_configured():
        return {
            "need_notify": False,
            "error": "未配置代理API，请先使用 set_config 配置"
        }
    
    # 获取到期时间
    result = get_order_expire_time()
    
    if not result["success"]:
        return {
            "need_notify": False,
            "error": result["error"]
        }
    
    expire_str = result["expire_time"]
    
    if not expire_str:
        return {
            "need_notify": False,
            "error": "无法获取到期时间"
        }
    
    # 计算剩余天数
    calc_result = calculate_days_left(expire_str)
    days_left, hours_left, expire_datetime = calc_result
    
    if days_left is None:
        return {
            "need_notify": False,
            "error": "无法解析到期时间"
        }
    
    # 获取状态
    status, status_text = get_expiry_status(days_left)
    
    # 判断是否需要提醒
    need_notify = days_left in REMIND_DAYS or days_left <= 0
    
    # 生成通知内容
    output = {
        "need_notify": need_notify,
        "days_left": days_left,
        "expire_time": expire_str,
        "status": status,
        "status_text": status_text,
        "renew_link": "https://www.kuaidaili.com/uc/order-list/"
    }
    
    if need_notify:
        # 确定提醒级别
        if days_left <= 0:
            notify_level = "expired"
            emoji = "🚨"
            urgency = "紧急"
            message = f"快代理订单已过期 {abs(days_left)} 天，请立即续费！"
        elif days_left == 1:
            notify_level = "critical"
            emoji = "🚨"
            urgency = "紧急"
            message = f"快代理订单将在明天到期，请立即续费！"
        elif days_left == 3:
            notify_level = "warning"
            emoji = "⚠️"
            urgency = "警告"
            message = f"快代理订单将在3天后到期，请尽快续费。"
        else:  # days_left == 5
            notify_level = "attention"
            emoji = "📅"
            urgency = "提醒"
            message = f"快代理订单将在5天后到期，请安排续费。"
        
        output["notify_level"] = notify_level
        output["emoji"] = emoji
        output["urgency"] = urgency
        output["message"] = message
        
        # 格式化的通知文本
        output["notification_text"] = (
            f"{emoji}【快代理订单到期{urgency}】\n\n"
            f"{message}\n\n"
            f"到期时间：{expire_str}\n"
            f"剩余天数：{days_left} 天\n"
            f"续费链接：{output['renew_link']}"
        )
    else:
        output["notify_level"] = ""
        output["notification_text"] = (
            f"ℹ️ 快代理订单状态正常\n\n"
            f"到期时间：{expire_str}\n"
            f"剩余天数：{days_left} 天"
        )
    
    return output


# 命令行入口
if __name__ == '__main__':
    result = check_expiry_reminder()
    
    # 输出JSON结果
    print(json.dumps(result, ensure_ascii=False, indent=2))

path: scripts/health_checker.py
content: #!/usr/bin/env python3
"""
IP健康检测模块
- 代理可用性测试
- 响应延迟测量
- 质量评分计算
"""

import os
import sys
import time
import requests
from datetime import datetime
from pathlib import Path

# 导入其他模块
try:
    from api_client import call_api, record_stat, is_configured
    from config_manager import load_config, STATS_DIR
except ImportError:
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from api_client import call_api, record_stat, is_configured
    from config_manager import load_config, STATS_DIR


def test_single_proxy(proxy, test_url, timeout):
    """测试单个代理IP"""
    proxy_address = proxy.get("full_address", "")

    if not proxy_address:
        return {"success": False, "error": "代理地址无效"}

    proxies = {
        "http": proxy_address,
        "https": proxy_address
    }

    start_time = time.time()

    try:
        response = requests.get(test_url, proxies=proxies, timeout=timeout)
        latency = (time.time() - start_time) * 1000  # ms

        return {
            "success": True,
            "status_code": response.status_code,
            "latency_ms": round(latency, 2),
            "response": response.json() if 'json' in response.headers.get('content-type', '') else response.text[:200]
        }

    except requests.exceptions.Timeout:
        return {"success": False, "error": "timeout", "latency_ms": timeout * 1000}

    except requests.exceptions.ProxyError:
        return {"success": False, "error": "proxy_error"}

    except requests.exceptions.RequestException as e:
        return {"success": False, "error": str(e)}


def calculate_latency_score(latency_ms):
    """计算延迟评分"""
    latency_sec = latency_ms / 1000

    if latency_sec < 1:
        return 100
    elif latency_sec <= 3:
        return 80
    else:
        return 50


def calculate_quality_score(success_rate, avg_latency_ms):
    """计算IP质量评分"""
    # success_rate 权重 70%
    # latency_score 权重 30%

    latency_score = calculate_latency_score(avg_latency_ms)

    score = success_rate * 0.7 + latency_score * 0.3

    return round(score, 2)


def check_ip_health(sample_size=5):
    """检查IP健康状态"""
    if not is_configured():
        return {
            "success": False,
            "error": "请先配置代理API链接（使用 set_config）"
        }

    config = load_config()
    test_url = config.get("test_url", "https://httpbin.org/ip")
    timeout = config.get("timeout", 5)

    # 获取代理IP列表
    api_result = call_api()

    if not api_result["success"]:
        return api_result

    ip_list = api_result["data"]["ip_list"]

    if not ip_list:
        return {
            "success": False,
            "error": "API返回的IP列表为空"
        }

    # 测试样本
    test_ips = ip_list[:min(sample_size, len(ip_list))]

    print(f"[健康检测] 测试 {len(test_ips)} 个IP，目标: {test_url}")

    results = []
    success_count = 0
    fail_count = 0
    latencies = []

    for i, proxy in enumerate(test_ips):
        print(f"  [{i+1}/{len(test_ips)}] 测试 {proxy.get('host', 'unknown')}...")

        result = test_single_proxy(proxy, test_url, timeout)
        results.append({
            "proxy": proxy.get("host", ""),
            "port": proxy.get("port", ""),
            "result": result
        })

        if result["success"]:
            success_count += 1
            latencies.append(result["latency_ms"])
            print(f"    [OK] 响应 {result['latency_ms']}ms")
            record_stat(True, result["latency_ms"])
        else:
            fail_count += 1
            print(f"    [失败] {result.get('error', 'unknown')}")
            record_stat(False)

    # 计算指标
    success_rate = (success_count / len(test_ips)) * 100 if test_ips else 0
    avg_latency = sum(latencies) / len(latencies) if latencies else 0
    quality_score = calculate_quality_score(success_rate, avg_latency)

    return {
        "success": True,
        "summary": {
            "total_tested": len(test_ips),
            "success_count": success_count,
            "fail_count": fail_count,
            "success_rate": round(success_rate, 2),
            "avg_latency_ms": round(avg_latency, 2),
            "quality_score": quality_score
        },
        "details": results,
        "test_url": test_url,
        "check_time": datetime.now().isoformat()
    }


def get_health_status(quality_score):
    """根据评分获取健康状态"""
    if quality_score >= 80:
        return "excellent", "优秀"
    elif quality_score >= 60:
        return "good", "良好"
    elif quality_score >= 40:
        return "fair", "一般"
    else:
        return "poor", "较差"


# 命令行入口
if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser(description='IP健康检测')
    parser.add_argument('--sample', type=int, default=5, help='测试样本数量')
    args = parser.parse_args()

    result = check_ip_health(args.sample)

    if result["success"]:
        print(f"\n[健康检测报告]")
        s = result["summary"]
        print(f"  测试数量: {s['total_tested']}")
        print(f"  成功: {s['success_count']} | 失败: {s['fail_count']}")
        print(f"  成功率: {s['success_rate']}%")
        print(f"  平均延迟: {s['avg_latency_ms']}ms")
        print(f"  质量评分: {s['quality_score']}")

        status, status_text = get_health_status(s['quality_score'])
        print(f"  健康状态: {status_text}")

        # 输出详细结果
        print(f"\n[详细结果]")
        for d in result["details"]:
            status_icon = "[OK]" if d["result"]["success"] else "[失败]"
            print(f"  {status_icon} {d['proxy']}:{d['port']} - {d['result'].get('latency_ms', 'N/A')}ms")
    else:
        print(f"[错误] {result['error']}")

path: scripts/order_info.py
content: #!/usr/bin/env python3
"""
订单信息模块
- 通过 dev.kdlapi.com 获取完整订单信息
- 解析订单详情
- 生成代理连接信息
"""

import os
import sys
import requests
from datetime import datetime
from pathlib import Path

# 导入配置管理
try:
    from config_manager import load_config, is_configured, get_credentials, mask_secret
except ImportError:
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from config_manager import load_config, is_configured, get_credentials, mask_secret


def get_order_info_from_api(retry_count=0):
    """
    获取订单详细信息
    
    接口: GET https://dev.kdlapi.com/api/getorderinfo
    参数: secret_id, signature (secret_token)
    
    Returns:
        dict: 订单详细信息
    """
    if not is_configured():
        return {
            "success": False,
            "error": "请先配置API密钥（使用 set_config）"
        }
    
    # 获取认证信息
    creds = get_credentials()
    if "error" in creds:
        return {"success": False, "error": creds["error"]}
    
    config = load_config()
    max_retry = config.get("max_retry", 3)
    timeout = config.get("timeout", 10)
    
    secret_id = creds["secret_id"]
    signature = creds["signature"]
    
    # 调用订单信息API
    url = "https://dev.kdlapi.com/api/getorderinfo"
    params = {
        "secret_id": secret_id,
        "signature": signature
    }
    
    try:
        print(f"[API] 获取订单信息 (尝试 {retry_count + 1}/{max_retry})")
        
        response = requests.get(url, params=params, timeout=timeout)
        response.raise_for_status()
        
        result = response.json()
        
        if result.get("code") != 0:
            return {
                "success": False,
                "error": f"API错误: {result.get('msg', '未知错误')}",
                "code": result.get("code")
            }
        
        # 解析订单数据
        data = result.get("data", {})
        order_info = parse_order_data(data)
        
        return {
            "success": True,
            "order_info": order_info,
            "raw": data
        }
        
    except requests.exceptions.Timeout:
        if retry_count < max_retry - 1:
            print(f"[重试] 请求超时，等待后重试...")
            import time
            time.sleep(2)
            return get_order_info_from_api(retry_count + 1)
        return {"success": False, "error": "请求超时，请检查网络"}
    
    except requests.exceptions.RequestException as e:
        if retry_count < max_retry - 1:
            print(f"[重试] 网络错误: {e}")
            import time
            time.sleep(2)
            return get_order_info_from_api(retry_count + 1)
        return {"success": False, "error": f"网络错误: {str(e)}"}


def parse_order_data(data):
    """
    解析订单数据
    
    字段映射:
    - orderid → order_id
    - product → product_type
    - status → status
    - tunnel_req → max_concurrency
    - tunnel_bandwidth → bandwidth
    - tunnel_host → proxy_host
    - tunnel_port_http → proxy_port
    - tunnel_username → username
    - tunnel_password → password
    """
    order_info = {
        "order_id": data.get("orderid", "未知"),
        "pay_type": data.get("pay_type", "未知"),
        "product_type": data.get("product", "未知"),
        "status": data.get("status", "未知"),
        "expire_time": data.get("expire_time", ""),
        "is_auto_renew": data.get("is_auto_renew", False),
        "freeze_amount": data.get("freeze_amount", "0"),
        "unit_price": data.get("unit_price", "0"),
        
        # 代理连接信息
        "proxy_host": data.get("tunnel_host", ""),
        "proxy_port": data.get("tunnel_port_http", ""),
        "proxy_port_socks": data.get("tunnel_port_socks", ""),
        "username": data.get("tunnel_username", ""),
        "password": data.get("tunnel_password", ""),
        
        # 性能参数
        "max_concurrency": data.get("tunnel_req", 0),
        "bandwidth": data.get("tunnel_bandwidth", 0),
    }
    
    # 生成完整代理地址
    if order_info["username"] and order_info["password"]:
        order_info["proxy_address"] = f"[REDACTED_SECRET]'proxy_host']}:{order_info['proxy_port']}"
    else:
        order_info["proxy_address"] = f"http://{order_info['proxy_host']}:{order_info['proxy_port']}"
    
    return order_info


def format_order_report(order_info):
    """格式化订单报告"""
    lines = ["\n[订单信息]"]
    
    lines.append(f"  订单ID: {order_info['order_id']}")
    lines.append(f"  产品类型: {order_info['product_type']}")
    lines.append(f"  订单状态: {order_info['status']}")
    lines.append(f"  付费方式: {order_info['pay_type']}")
    
    # 根据付费方式显示不同信息
    if order_info['pay_type'] == 'POST_PAY':
        lines.append(f"  单价: ¥{order_info['unit_price']}/GB")
        if order_info['freeze_amount'] != "0":
            lines.append(f"  冻结金额: ¥{order_info['freeze_amount']}")
    elif order_info['expire_time']:
        lines.append(f"  到期时间: {order_info['expire_time']}")
        if order_info.get('is_auto_renew'):
            lines.append(f"  自动续费: 已开启")
    
    lines.append(f"\n[代理配置]")
    lines.append(f"  并发上限: {order_info['max_concurrency']}")
    lines.append(f"  带宽: {order_info['bandwidth']} Mbps")
    
    lines.append(f"\n[代理连接]")
    lines.append(f"  主机: {order_info['proxy_host']}")
    lines.append(f"  HTTP端口: {order_info['proxy_port']}")
    if order_info['proxy_port_socks']:
        lines.append(f"  SOCKS端口: {order_info['proxy_port_socks']}")
    
    # 脱敏显示代理地址
    if order_info.get('password'):
        masked_address = order_info['proxy_address'].replace(order_info['password'], '***')
        lines.append(f"  代理地址: {masked_address}")
    else:
        lines.append(f"  代理地址: {order_info['proxy_address']}")
    
    return "\n".join(lines)


# 命令行入口
if __name__ == '__main__':
    # 设置控制台编码
    if sys.platform == 'win32':
        sys.stdout.reconfigure(encoding='utf-8')
    
    result = get_order_info_from_api()
    
    if result["success"]:
        print(format_order_report(result["order_info"]))
    else:
        print(f"[错误] {result['error']}")

path: scripts/scheduler.py
content: #!/usr/bin/env python3
"""
智能并发调度模块
- 基于成功率动态调整并发
- 失败保护机制
- 推荐请求频率
"""

import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
import json

# 导入其他模块
try:
    from api_client import get_order_info, is_configured
    from health_checker import check_ip_health
    from stats_tracker import get_recent_stats
    from config_manager import load_config, STATS_DIR
except ImportError:
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from api_client import get_order_info, is_configured
    from health_checker import check_ip_health
    from stats_tracker import get_recent_stats
    from config_manager import load_config, STATS_DIR


# 连续失败计数器
FAIL_COUNTER_FILE = STATS_DIR / "fail_counter.json"


def load_fail_counter():
    """加载连续失败计数"""
    if FAIL_COUNTER_FILE.exists():
        with open(FAIL_COUNTER_FILE, 'r') as f:
            return json.load(f)
    return {"consecutive_fails": 0, "last_updated": None}


def save_fail_counter(counter):
    """保存失败计数"""
    STATS_DIR.mkdir(parents=True, exist_ok=True)
    with open(FAIL_COUNTER_FILE, 'w') as f:
        json.dump(counter, f)


def calculate_base_concurrency(max_concurrency):
    """计算基础并发（80%）"""
    return int(max_concurrency * 0.8)


def adjust_by_success_rate(base, success_rate):
    """根据成功率调整并发"""
    if success_rate >= 95:
        return base
    elif success_rate >= 80:
        return int(base * 0.7)
    elif success_rate >= 60:
        return int(base * 0.5)
    elif success_rate >= 50:
        return int(base * 0.3)
    else:
        return int(base * 0.2)  # 最低保护


def apply_fail_protection(current_concurrency, consecutive_fails, success_rate):
    """应用失败保护机制"""
    # 连续失败 > 10，并发降低50%
    if consecutive_fails > 10:
        return int(current_concurrency * 0.5), True

    # 成功率 < 50%，并发降至最低
    if success_rate < 50:
        return 1, True  # 最小并发

    return current_concurrency, False


def get_concurrency(force_health_check=False):
    """获取推荐并发配置"""
    if not is_configured():
        return {
            "success": False,
            "error": "请先配置代理API链接（使用 set_config）"
        }

    # 获取订单信息（并发上限）
    try:
        from order_info import get_order_info_from_api
        order_result = get_order_info_from_api()
        
        if not order_result["success"]:
            return order_result
        
        max_concurrency = order_result["order_info"].get("max_concurrency", 0)
    except:
        # 如果订单API失败，使用默认值
        max_concurrency = 5  # 默认并发

    if max_concurrency == 0:
        return {
            "success": False,
            "error": "订单并发上限为0，可能已过期"
        }

    # 获取成功率
    success_rate = None

    if force_health_check:
        # 强制健康检测
        health_result = check_ip_health(sample_size=3)
        if health_result["success"]:
            success_rate = health_result["summary"]["success_rate"]
    else:
        # 使用最近统计
        stats = get_recent_stats(days=1)
        if stats and stats.get("today"):
            success_rate = stats["today"].get("success_rate", 0)

    # 如果没有成功率数据，使用默认值
    if success_rate is None:
        success_rate = 85  # 默认假设良好

    # 计算并发
    base = calculate_base_concurrency(max_concurrency)
    adjusted = adjust_by_success_rate(base, success_rate)

    # 失败保护
    counter = load_fail_counter()
    final_concurrency, protection_active = apply_fail_protection(
        adjusted,
        counter.get("consecutive_fails", 0),
        success_rate
    )

    # 计算请求频率
    request_interval = calculate_request_interval(final_concurrency)

    return {
        "success": True,
        "recommendation": {
            "max_concurrency": max_concurrency,
            "recommended_concurrency": final_concurrency,
            "current_success_rate": success_rate,
            "base_concurrency": base,
            "protection_active": protection_active,
            "consecutive_fails": counter.get("consecutive_fails", 0)
        },
        "request_config": {
            "concurrent_requests": final_concurrency,
            "request_interval_ms": request_interval,
            "batch_size": min(final_concurrency, 10)
        },
        "advice": generate_advice(success_rate, final_concurrency, protection_active)
    }


def calculate_request_interval(concurrency):
    """计算请求间隔"""
    # 根据并发数计算推荐间隔
    if concurrency >= 50:
        return 100  # 高并发，短间隔
    elif concurrency >= 20:
        return 200
    elif concurrency >= 10:
        return 500
    else:
        return 1000  # 低并发，长间隔


def generate_advice(success_rate, concurrency, protection_active):
    """生成调度建议"""
    if protection_active:
        return "失败保护已激活，建议暂停或检查代理状态"

    if success_rate >= 95:
        return "状态优秀，可保持当前并发或适当提高"
    elif success_rate >= 80:
        return "状态良好，建议保持当前并发"
    elif success_rate >= 60:
        return "状态一般，建议降低并发或检查代理质量"
    else:
        return "状态较差，建议暂停并检查代理配置"


def record_request_result(success):
    """记录请求结果（用于失败计数）"""
    counter = load_fail_counter()

    if success:
        counter["consecutive_fails"] = 0
    else:
        counter["consecutive_fails"] += 1

    counter["last_updated"] = datetime.now().isoformat()
    save_fail_counter(counter)


# 命令行入口
if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser(description='智能并发调度')
    parser.add_argument('--check', action='store_true', help='强制健康检测')
    parser.add_argument('--record-success', action='store_true', help='记录成功')
    parser.add_argument('--record-fail', action='store_true', help='记录失败')
    args = parser.parse_args()

    if args.record_success:
        record_request_result(True)
        print("[OK] 记录成功请求")

    elif args.record_fail:
        record_request_result(False)
        print("[警告] 记录失败请求")

    else:
        result = get_concurrency(force_health_check=args.check)

        if result["success"]:
            print(f"\n[并发调度建议]")
            r = result["recommendation"]
            print(f"  并发上限: {r['max_concurrency']}")
            print(f"  基础并发: {r['base_concurrency']} (80%)")
            print(f"  推荐并发: {r['recommended_concurrency']}")
            print(f"  当前成功率: {r['current_success_rate']}%")

            if r['protection_active']:
                print(f"  [警告] 失败保护已激活")
                print(f"  连续失败: {r['consecutive_fails']}")

            c = result["request_config"]
            print(f"\n[请求配置]")
            print(f"  并发请求: {c['concurrent_requests']}")
            print(f"  请求间隔: {c['request_interval_ms']}ms")
            print(f"  批次大小: {c['batch_size']}")

            print(f"\n[建议] {result['advice']}")
        else:
            print(f"[错误] {result['error']}")

path: scripts/stats_tracker.py
content: #!/usr/bin/env python3
"""
使用统计模块
- 每日统计记录
- 7天趋势分析
- 稳定性评估
"""

import os
import sys
import json
from datetime import datetime, timedelta
from pathlib import Path

# 导入配置
try:
    from config_manager import STATS_DIR
except ImportError:
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from config_manager import STATS_DIR


def get_day_stats(date_str):
    """获取指定日期的统计"""
    stats_file = STATS_DIR / f"{date_str}.json"

    if stats_file.exists():
        with open(stats_file, 'r') as f:
            return json.load(f)
    return None


def get_recent_stats(days=7):
    """获取最近N天的统计"""
    stats = {}
    today = datetime.now()

    for i in range(days):
        date = today - timedelta(days=i)
        date_str = date.strftime('%Y-%m-%d')

        day_stats = get_day_stats(date_str)
        if day_stats:
            stats[date_str] = day_stats

    # 添加今日统计
    today_str = today.strftime('%Y-%m-%d')
    stats["today"] = get_day_stats(today_str)

    return stats


def calculate_success_rate(stats):
    """计算成功率"""
    if not stats:
        return 0

    total = stats.get("total_requests", 0)
    success = stats.get("success_count", 0)

    if total == 0:
        return 0

    return round((success / total) * 100, 2)


def calculate_avg_latency(stats):
    """计算平均延迟"""
    latencies = stats.get("latencies", [])

    if not latencies:
        return 0

    return round(sum(latencies) / len(latencies), 2)


def analyze_trend(stats_dict):
    """分析成功率趋势"""
    rates = []

    for date_str, day_stats in stats_dict.items():
        if date_str == "today" or not day_stats:
            continue
        rates.append(calculate_success_rate(day_stats))

    if len(rates) < 2:
        return "insufficient_data", "数据不足"

    # 计算波动
    avg_rate = sum(rates) / len(rates)
    variance = sum((r - avg_rate) ** 2 for r in rates) / len(rates)
    std_dev = variance ** 0.5

    # 评估稳定性
    if avg_rate >= 90 and std_dev < 5:
        return "stable", "稳定"
    elif std_dev <= 10:
        return "fluctuating", "波动"
    else:
        return "risk", "风险"


def generate_report():
    """生成统计报告"""
    stats = get_recent_stats(days=7)

    if not stats or all(v is None for v in stats.values()):
        return {
            "success": False,
            "error": "无统计数据，请先使用代理服务"
        }

    # 今日统计
    today_str = datetime.now().strftime('%Y-%m-%d')
    today_stats = stats.get("today")
    today_report = None

    if today_stats:
        today_report = {
            "date": today_str,
            "total_requests": today_stats.get("total_requests", 0),
            "success_count": today_stats.get("success_count", 0),
            "fail_count": today_stats.get("fail_count", 0),
            "success_rate": calculate_success_rate(today_stats),
            "avg_latency_ms": calculate_avg_latency(today_stats)
        }

    # 7天汇总
    total_requests = 0
    total_success = 0
    total_fail = 0
    all_latencies = []

    for date_str, day_stats in stats.items():
        if date_str == "today" or not day_stats:
            continue
        total_requests += day_stats.get("total_requests", 0)
        total_success += day_stats.get("success_count", 0)
        total_fail += day_stats.get("fail_count", 0)
        all_latencies.extend(day_stats.get("latencies", []))

    weekly_report = {
        "period": "最近7天",
        "total_requests": total_requests,
        "success_count": total_success,
        "fail_count": total_fail,
        "success_rate": round((total_success / total_requests * 100) if total_requests else 0, 2),
        "avg_latency_ms": round(sum(all_latencies) / len(all_latencies), 2) if all_latencies else 0,
        "days_with_data": len([v for v in stats.values() if v and v != stats.get("today")])
    }

    # 趋势分析
    trend_status, trend_text = analyze_trend(stats)

    return {
        "success": True,
        "today": today_report,
        "weekly": weekly_report,
        "trend": {
            "status": trend_status,
            "text": trend_text
        },
        "generated_at": datetime.now().isoformat()
    }


def show_stats():
    """显示统计"""
    report = generate_report()

    if not report["success"]:
        return report

    # 格式化输出
    lines = ["\n[使用统计报告]"]

    if report["today"]:
        t = report["today"]
        lines.append(f"\n今日统计 ({t['date']})")
        lines.append(f"  请求数: {t['total_requests']}")
        lines.append(f"  成功: {t['success_count']} | 失败: {t['fail_count']}")
        lines.append(f"  成功率: {t['success_rate']}%")
        lines.append(f"  平均延迟: {t['avg_latency_ms']}ms")

    w = report["weekly"]
    lines.append(f"\n7天汇总")
    lines.append(f"  总请求: {w['total_requests']}")
    lines.append(f"  成功: {w['success_count']} | 失败: {w['fail_count']}")
    lines.append(f"  成功率: {w['success_rate']}%")
    lines.append(f"  平均延迟: {w['avg_latency_ms']}ms")
    lines.append(f"  有效天数: {w['days_with_data']}")

    lines.append(f"\n稳定性: {report['trend']['text']}")

    return {
        "success": True,
        "report": report,
        "output": "\n".join(lines)
    }


# 命令行入口
if __name__ == '__main__':
    result = show_stats()

    if result["success"]:
        print(result["output"])
    else:
        print(f"[提示] {result['error']}")

path: references/api_response.md
content: # 快代理 API 响应字段说明

## 密钥令牌接口

**接口**：`POST https://auth.kdlapi.com/api/get_secret_token`

**Content-Type**：`application/x-www-form-urlencoded`

**参数**：
- `secret_id` - 用户密钥ID
- `secret_key` - 用户密钥Key

**请求示例**：
```
POST https://auth.kdlapi.com/api/get_secret_token
Content-Type: application/x-www-form-urlencoded

secret_id=example_secret_id_123&secret_key=example_secret_key_abc
```

**返回示例**：
```json
{
  "code": 0,
  "msg": "",
  "data": {
    "secret_token": "获得的令牌字符串",
    "expire_time": "YYYY-MM-DD HH:MM:SS"
  }
}
```

**说明**：
- `secret_token` 作为其他API的 `signature` 参数使用
- Token 有效期通常为1小时，过期需重新获取

---

## 获取代理IP接口

### 隧道代理Pro当前IP

**接口**：`GET https://tps.kdlapi.com/api/tpsprocurrentip`

**参数**：
- `secret_id` - 用户密钥ID
- `signature` - 签名（密钥令牌）

**请求示例**：
```
GET https://tps.kdlapi.com/api/tpsprocurrentip?secret_id=example_id&signature=xxx
```

**返回示例**：
```json
{
  "code": 0,
  "msg": "",
  "data": {
    "ip": "1.2.3.4"
  }
}
```

**⚠️ 错误码说明**：

| 错误码 | 说明 | 解决方案 |
|-------|------|---------|
| `-4` | 换IP周期不足30秒 | 此API仅支持**换IP周期 >= 30秒**的隧道订单，请在订单管理中确认换IP周期设置 |

---

### 隧道代理当前IP

**接口**：`GET https://tps.kdlapi.com/api/tpscurrentip`

**参数**：
- `secret_id` - 用户密钥ID
- `signature` - 签名（密钥令牌）

**请求示例**：
```
GET https://tps.kdlapi.com/api/tpscurrentip?secret_id=example_id&signature=xxx
```

**⚠️ 错误码说明**：

| 错误码 | 说明 | 解决方案 |
|-------|------|---------|
| `-4` | 换IP周期不足30秒 | 此API仅支持**换IP周期 >= 30秒**的隧道订单，请在订单管理中确认换IP周期设置 |

---

### 私密代理IP

**接口**：`GET https://dps.kdlapi.com/api/getdps`

**参数**：
- `secret_id` - 用户密钥ID
- `signature` - 签名（密钥令牌）
- `num` - 提取数量（可选，默认1）

**请求示例**：
```
GET https://dps.kdlapi.com/api/getdps?secret_id=example_id&num=5&signature=xxx
```

**返回示例**：
```json
{
  "code": 0,
  "msg": "",
  "data": {
    "proxy_list": [
      {"ip": "1.2.3.4", "port": 5678},
      {"ip": "5.6.7.8", "port": 9012}
    ]
  }
}
```

**⚠️ 错误码说明**：

| 错误码 | 说明 | 解决方案 |
|-------|------|---------|
| `1` | 今日提取余额已用尽 | 等待次日自动恢复，或充值增加配额 |
| `2` | 订单提取余额已用尽 | 订单配额耗尽，需续费或升级套餐 |
| `3` | 没有找到符合条件的代理 | 调整筛选条件，如地区、协议等 |
| `4` | 账号尚未通过实名认证 | 完成实名认证后再使用 |
| `-1` | 无效请求 | 检查请求格式是否正确 |
| `-2` | 订单无效（刚下单需等待） | 下单后1分钟内订单自动生效，请稍候 |
| `-3` | 参数错误 | 检查参数是否完整且格式正确 |
| `-4` | 提取失败: $err_msg | 查看具体错误信息处理 |
| `-5` | 此订单不能提取私密代理 | 确认订单产品类型是否为私密代理 |
| `-6` | 调用IP不在白名单内 | 在订单设置中添加调用IP到白名单 |
| `-51` | 订单1分钟内允许最多$ip_number个IP调用 | 控制调用IP数量，避免超限 |
| `-16` | 订单已退款 | 无法使用，需重新下单 |
| `-15` | 订单已过期 | 续费或重新下单 |
| `-14` | 订单被封禁 | 联系客服处理 |
| `-13` | 订单已过期 | 续费或重新下单 |
| `-12` | 订单无效 | 检查订单状态 |
| `-11` | 订单尚未支付 | 完成支付后再使用 |

---

### 独享代理IP

**接口**：`GET https://kps.kdlapi.com/api/getkps`

**参数**：
- `secret_id` - 用户密钥ID
- `signature` - 签名（密钥令牌）

**请求示例**：
```
GET https://kps.kdlapi.com/api/getkps?secret_id=example_id&signature=xxx
```

**⚠️ 错误码说明**：

| 错误码 | 说明 | 解决方案 |
|-------|------|---------|
| `2` | 订单已过期 | 续费或重新下单 |
| `3` | 没有找到符合条件的代理 | 调整筛选条件 |
| `4` | 账号尚未通过实名认证 | 完成实名认证后再使用 |
| `-1` | 无效请求 | 检查请求格式是否正确 |
| `-2` | 订单无效（刚下单需等待） | 下单后1分钟内订单自动生效，请稍候 |
| `-3` | 参数错误 | 检查参数是否完整且格式正确 |
| `-4` | 提取失败: $err_msg | 查看具体错误信息处理 |
| `-5` | 此订单不能提取独享代理 | 确认订单产品类型是否为独享代理 |
| `-51` | 订单1分钟内允许最多$ip_number个IP调用 | 控制调用IP数量，避免超限 |
| `-16` | 订单已退款 | 无法使用，需重新下单 |
| `-15/-13` | 订单已过期 | 续费或重新下单 |
| `-14` | 订单被封禁 | 联系客服处理 |
| `-12` | 订单无效 | 检查订单状态 |
| `-11` | 订单尚未支付 | 完成支付后再使用 |

---

### 海外动态代理隧道IP

**接口**：`GET https://fps.kdlapi.com/api/getfps`

**参数**：
- `secret_id` - 用户密钥ID
- `signature` - 签名（密钥令牌）

**请求示例**：
```
GET https://fps.kdlapi.com/api/getfps?secret_id=example_id&signature=xxx
```

**⚠️ 错误码说明**：

| 错误码 | 说明 | 解决方案 |
|-------|------|---------|
| `2` | 订单已过期 | 续费或重新下单 |
| `3` | 暂无可用代理 | 等待或联系客服 |
| `4` | 账号尚未通过实名认证 | 完成实名认证后再使用 |
| `-1` | 无效请求 | 检查请求格式是否正确 |
| `-2` | 订单无效（刚下单需等待） | 下单后1分钟内订单自动生效，请稍候 |
| `-3` | 参数错误 | 检查参数是否完整且格式正确 |
| `-4` | 提取失败: $err_msg | 查看具体错误信息处理 |
| `-5` | 此订单不能提取海外代理动态住宅 | 确认订单产品类型是否为海外动态代理 |
| `-51` | 订单1分钟内允许最多$ip_number个IP调用 | 控制调用IP数量，避免超限 |
| `-16` | 订单已退款 | 无法使用，需重新下单 |
| `-15/-13` | 订单已过期 | 续费或重新下单 |
| `-14` | 订单被封禁 | 联系客服处理 |
| `-12` | 订单无效 | 检查订单状态 |
| `-11` | 订单尚未支付 | 完成支付后再使用 |

---

### 海外静态住宅IP

**接口**：`GET https://dev.kdlapi.com/api/getsfps`

**参数**：
- `secret_id` - 用户密钥ID
- `signature` - 签名（密钥令牌）

**请求示例**：
```
GET https://dev.kdlapi.com/api/getsfps?secret_id=example_id&signature=xxx
```

**⚠️ 错误码说明**：

| 错误码 | 说明 | 解决方案 |
|-------|------|---------|
| `2` | 订单已过期 | 续费或重新下单 |
| `3` | 没有找到符合条件的代理 | 调整筛选条件 |
| `4` | 账号尚未通过实名认证 | 完成实名认证后再使用 |
| `-1` | 无效请求 | 检查请求格式是否正确 |
| `-2` | 订单无效（刚下单需等待） | 下单后1分钟内订单自动生效，请稍候 |
| `-3` | 参数错误 | 检查参数是否完整且格式正确 |
| `-4` | 提取失败: $err_msg | 查看具体错误信息处理 |
| `-5` | 此订单不能提取海外代理静态住宅 | 确认订单产品类型是否为海外静态代理 |
| `-51` | 订单1分钟内允许最多$ip_number个IP调用 | 控制调用IP数量，避免超限 |
| `-16` | 订单已退款 | 无法使用，需重新下单 |
| `-15/-13` | 订单已过期 | 续费或重新下单 |
| `-14` | 订单被封禁 | 联系客服处理 |
| `-12` | 订单无效 | 检查订单状态 |
| `-11` | 订单尚未支付 | 完成支付后再使用 |

---

## 订单信息接口

**接口**：`GET https://dev.kdlapi.com/api/getorderinfo`

**参数**：
- `secret_id` - 用户密钥ID
- `signature` - 签名（密钥令牌）

---

## 公共字段（所有产品类型）

| 字段 | 说明 | 取值 |
|------|------|------|
| `orderid` | 订单号 | |
| `pay_type` | 付费方式 | `PRE_PAY`: 包年包月(预付费)<br>`PRE_PAY_IP`: 按IP付费(预付费)<br>`POST_PAY`: 按量付费(后付费) |
| `product` | 产品类型 | 见下表 |
| `status` | 订单状态 | `VALID`: 有效<br>`WAIT_PAY`: 待支付<br>`OPENING`: 开通中<br>`EXPIRED`: 已过期<br>`OWING`: 欠费暂停<br>`CLOSED`: 已关闭<br>`FORBIDDEN`: 被封禁 |
| `expire_time` | 订单到期时间（仅包年包月订单返回） | 格式：`2021-08-18 12:01:43` |
| `is_auto_renew` | 是否开启自动续费（仅包年包月订单返回） | `true` / `false` |
| `auto_renew_time_type` | 自动续费时长（仅 `is_auto_renew=true` 时返回） | `DAY`: 按天<br>`WEEK`: 按周<br>`MONTH`: 按月<br>`YEAR`: 按年 |
| `freeze_amount` | 订单冻结金额（仅按量付费订单返回） | 单位：元，如 `36.28` |
| `unit_price` | 计费单价（仅按量付费订单返回） | 单位：元，如 `1.95` |

---

## 产品类型对照表

| product值 | 产品名称 | 特有字段 |
|-----------|----------|----------|
| `TPS` | 隧道代理 | tunnel_* 系列 |
| `DPS` | 私密代理 | proxy_username, proxy_password |
| `KPS` | 独享代理 | proxy_count, proxy_username, proxy_password |
| `FPS` | 海外动态代理 | fps_* 系列, transfer_area |
| `FPS_STATIC` | 海外静态代理 | proxy_count, proxy_username, proxy_password |

---

## 隧道代理 (TPS) 响应示例

```json
{
  "msg": "",
  "code": 0,
  "data": {
    "orderid": "123456789012345",
    "pay_type": "POST_PAY",
    "product": "TPS",
    "status": "VALID",
    "freeze_amount": "2.68",
    "unit_price": "2.68",
    "tunnel_password": "example_password",
    "tunnel_username": "t12345678901234",
    "tunnel_host": "tps123.kdlapi.com",
    "tunnel_port_http": "12345",
    "tunnel_port_socks": "20806",
    "tunnel_bandwidth": 10,
    "tunnel_req": 20
  }
}
```

### 隧道代理特有字段

| 字段 | 说明 |
|------|------|
| `tunnel_host` | 隧道host |
| `tunnel_port_http` | 隧道HTTP端口 |
| `tunnel_port_socks` | 隧道SOCKS端口 |
| `tunnel_username` | 隧道用户名 |
| `tunnel_password` | 隧道密码 |
| `tunnel_period` | 隧道换IP周期（秒） |
| `tunnel_bandwidth` | 隧道带宽（Mbps） |
| `tunnel_req` | 隧道并发上限 |

---

## 私密代理 (DPS) 响应示例

```json
{
  "msg": "",
  "code": 0,
  "data": {
    "orderid": "123456789012346",
    "pay_type": "PRE_PAY_IP",
    "product": "DPS",
    "status": "VALID",
    "expire_time": "2026-05-01 12:00:00",
    "proxy_username": "dps_user_example",
    "proxy_password": "dps_pass_example"
  }
}
```

### 私密代理特有字段

| 字段 | 说明 |
|------|------|
| `proxy_username` | 私密代理用户名 |
| `proxy_password` | 私密代理密码 |

---

## 独享代理 (KPS) 响应示例

```json
{
  "msg": "",
  "code": 0,
  "data": {
    "orderid": "123456789012347",
    "pay_type": "PRE_PAY",
    "product": "KPS",
    "status": "VALID",
    "expire_time": "2026-06-01 12:00:00",
    "proxy_count": 5,
    "proxy_username": "kps_user_example",
    "proxy_password": "kps_pass_example"
  }
}
```

### 独享代理特有字段

| 字段 | 说明 |
|------|------|
| `proxy_count` | 此订单下的独享代理数量 |
| `proxy_username` | 独享代理用户名 |
| `proxy_password` | 独享代理密码 |

---

## 海外动态代理 (FPS) 响应示例

```json
{
  "msg": "",
  "code": 0,
  "data": {
    "orderid": "123456789012348",
    "pay_type": "POST_PAY",
    "product": "FPS",
    "status": "VALID",
    "freeze_amount": "5.00",
    "unit_price": "5.00",
    "proxy_username": "fps_user_example",
    "proxy_password": "fps_pass_example",
    "transfer_area": "US",
    "fps_host": "fps123.kdlapi.com",
    "fps_port_http": "12400",
    "fps_port_socks": "12401"
  }
}
```

### 海外动态代理特有字段

| 字段 | 说明 |
|------|------|
| `proxy_username` | 海外动态代理用户名 |
| `proxy_password` | 海外动态代理密码 |
| `transfer_area` | 转发地区（如 US、JP、UK 等） |
| `fps_host` | 隧道host |
| `fps_port_http` | 隧道HTTP端口 |
| `fps_port_socks` | 隧道SOCKS端口 |

---

## 海外静态代理 (FPS_STATIC) 响应示例

```json
{
  "msg": "",
  "code": 0,
  "data": {
    "orderid": "123456789012349",
    "pay_type": "PRE_PAY",
    "product": "FPS_STATIC",
    "status": "VALID",
    "expire_time": "2026-12-01 12:00:00",
    "proxy_count": 10,
    "proxy_username": "fps_static_user_example",
    "proxy_password": "fps_static_pass_example"
  }
}
```

### 海外静态代理特有字段

| 字段 | 说明 |
|------|------|
| `proxy_count` | 此订单下的海外静态代理数量 |
| `proxy_username` | 海外静态代理用户名（包段订单返回此字段） |
| `proxy_password` | 海外静态代理密码（包段订单返回此字段） |

---

## 代理地址格式

### 隧道代理/海外动态代理
```
[REDACTED_SECRET]
```

示例：
```
http://t12345678901234:[REDACTED_SECRET]:12345
```

### 私密代理/独享代理/海外静态代理
需要通过API获取具体IP列表，认证方式为用户名密码。

---

## 快代理 API 类型

| API | 用途 | URL格式 |
|-----|------|---------|
| TPS隧道代理 | 获取隧道代理IP | `https://tps.kdlapi.com/api/gettps` |
| DPS私密代理 | 获取私密代理IP | `https://dps.kdlapi.com/api/getdps` |
| KPS独享代理 | 获取独享代理IP | `https://kps.kdlapi.com/api/getkps` |
| FPS海外动态 | 获取海外动态代理IP | `https://fps.kdlapi.com/api/getfps` |

## 官方文档

- 订单信息API：https://www.kuaidaili.com/doc/api/getorderinfo/
- 用户中心：https://www.kuaidaili.com/uc/overview/
- 订单管理：https://www.kuaidaili.com/uc/order-list/

path: references/scoring_rules.md
content: # 评分规则说明

## IP质量评分

### 评分公式

```
quality_score = success_rate * 0.7 + latency_score * 0.3
```

### 成功率权重（70%）

| 成功率 | 贡献分 |
|--------|--------|
| 100% | 70 |
| 95% | 66.5 |
| 80% | 56 |
| 60% | 42 |
| 50% | 35 |
| <50% | <35 |

### 延迟评分（30%）

| 响应时间 | 延迟评分 |
|---------|---------|
| < 1秒 | 100 |
| 1-3秒 | 80 |
| > 3秒 | 50 |

### 评分示例

| 成功率 | 平均延迟 | 延迟评分 | 最终评分 |
|--------|---------|---------|---------|
| 100% | 500ms | 100 | 100 |
| 95% | 1.5s | 80 | 90.5 |
| 80% | 2s | 80 | 80 |
| 60% | 3.5s | 50 | 57 |
| 50% | 4s | 50 | 50 |

## 健康状态分级

| 评分范围 | 状态 | 说明 |
|---------|------|------|
| 80-100 | 优秀 | 可放心使用 |
| 60-79 | 良好 | 正常使用 |
| 40-59 | 一般 | 建议监控 |
| 0-39 | 较差 | 建议检查 |

## 并发调整规则

### 基于成功率的调整

| success_rate | 并发倍数 |
|-------------|---------|
| ≥ 95% | base × 1.0 |
| 80-94% | base × 0.7 |
| 60-79% | base × 0.5 |
| 50-59% | base × 0.3 |
| < 50% | base × 0.2 |

### 失败保护

| 条件 | 动作 |
|------|------|
| 连续失败 > 10 | 并发降低50% |
| success_rate < 50% | 并发降至最低(1) |

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)