社区代码库

对接码分享

开发者贡献的多语言卡密验证对接示例

以下代码由开发者贡献,点击标题展开代码详情。 建议将代码喂给 AI(如 deepseek / ChatGPT / Claude 等),让 AI 帮你转换成你需要的语言、调试、适配你的具体项目。
📄 AutoJS(JavaScript)
Java 291 行
// ============================================================
// AutoJS 对接卡密通验证(完整签名+心跳)
// 基于按键精灵版逻辑移植
// ============================================================

// 配置区 ===================================================
var 用户名 = "你的用户名";           // 例如:qq345205377
var 应用名 = "你的应用名";           // 例如:a
var 签名密钥 = "你的签名密钥";       // 从后台「修改签名密钥」获取
var 服务器地址 = `https://www.keyt.cn/kami/${用户名}/check.php`;

// 全局变量 =================================================
var 当前卡密 = "";
var 当前机器码 = "";
var 心跳线程 = null;
var 连续失败次数 = 0;
var 剩余天数显示 = 0;
var 剩余分钟显示 = 0;

// ========== 工具函数 =======================================

/**
 * 获取设备机器码(使用 Android ID)
 */
function 获取机器码() {
    let androidId = device.getAndriodId();  // 注意拼写:AutoJS 中是 getAndroidId
    if (!androidId) {
        androidId = "UNKNOWN_DEVICE";
    }
    let mac = androidId + "MAC";
    console.log("[机器码] " + mac);
    return mac;
}

/**
 * MD5 加密
 */
function md5(data) {
    return java.security.MessageDigest.getInstance("MD5")
        .digest(new java.lang.String(data).getBytes())
        .map(function (v) {
            return ('0' + (v & 0xFF).toString(16)).slice(-2);
        }).join('');
}

/**
 * 获取当前时间戳(秒)
 */
function 获取时间戳() {
    return Math.floor(Date.now() / 1000);
}

/**
 * 验证服务器返回(签名 + 时间戳)
 */
function 验证响应(原始返回) {
    console.log("[验签] 原始返回: " + 原始返回);

    // 分离签名
    let signIndex = 原始返回.indexOf("|sign=");
    if (signIndex === -1) {
        console.log("[验签] ❌ 未找到签名标记");
        return null;
    }

    let body = 原始返回.substring(0, signIndex);
    let sign = 原始返回.substring(signIndex + 6);

    // 本地计算签名
    let 本地签名 = md5(body + 签名密钥);
    console.log("[验签] 本地签名: " + 本地签名);
    console.log("[验签] 服务器签名: " + sign);

    if (本地签名 !== sign) {
        console.log("[验签] ❌ 签名不匹配");
        return null;
    }
    console.log("[验签] ✅ 签名校验通过");

    // 提取时间戳
    let lastPipe = body.lastIndexOf("|");
    if (lastPipe === -1) {
        console.log("[验签] ❌ 未找到时间戳");
        return null;
    }

    let tsStr = body.substring(lastPipe + 1);
    let 服务器时间戳 = parseInt(tsStr);
    let 本地时间戳 = 获取时间戳();
    let 时间差 = Math.abs(本地时间戳 - 服务器时间戳);

    console.log("[验签] 服务器时间戳: " + 服务器时间戳);
    console.log("[验签] 本地时间戳: " + 本地时间戳);
    console.log("[验签] 时间差: " + 时间差 + " 秒");

    if (时间差 > 120) {
        console.log("[验签] ❌ 时间戳过期");
        return null;
    }
    console.log("[验签] ✅ 时间戳校验通过");

    let 业务内容 = body.substring(0, lastPipe);
    console.log("[验签] 业务内容: " + 业务内容);
    return 业务内容;
}

/**
 * 获取验证开关状态
 */
function 获取卡密开关() {
    for (let i = 0; i < 3; i++) {
        let url = `${服务器地址}?act=get_switch&app=${应用名}&t=${获取时间戳()}`;
        console.log(`[开关] 第${i + 1}次尝试: ${url}`);

        try {
            let r = http.get(url, {
                headers: {
                    'Cache-Control': 'no-cache'
                }
            });
            if (r.statusCode === 200) {
                let body = r.body.string();
                let biz = 验证响应(body);
                if (biz && biz.indexOf("CARD_ON") !== -1) {
                    console.log("[开关] ✅ 卡密验证已开启");
                    return "CARD_ON";
                } else if (biz && biz.indexOf("CARD_OFF") !== -1) {
                    console.log("[开关] ⚠️ 卡密验证已关闭");
                    return "CARD_OFF";
                }
            }
        } catch (e) {
            console.log("[开关] 请求异常: " + e.message);
        }
        sleep(500);
    }
    console.log("[开关] ⚠️ 开关获取失败,默认开启验证");
    return "CARD_ON";
}

/**
 * 验证卡密(核心)
 */
function 验证卡密(card, mac) {
    let url = `${服务器地址}?card=${card}&mac=${mac}&app=${应用名}&heart=1&t=${获取时间戳()}`;
    console.log("[验证] 请求URL: " + url);

    try {
        let r = http.get(url, {
            headers: {
                'Cache-Control': 'no-cache'
            }
        });
        if (r.statusCode === 200) {
            let body = r.body.string();
            console.log("[验证] 服务器返回: " + body);
            let biz = 验证响应(body);
            return biz;
        } else {
            console.log("[验证] HTTP错误: " + r.statusCode);
            return null;
        }
    } catch (e) {
        console.log("[验证] 请求异常: " + e.message);
        return null;
    }
}

/**
 * 解析并显示剩余时间
 */
function 解析剩余时间(biz) {
    if (!biz) return null;
    let parts = biz.split("|");
    // 格式: ok|valid|剩余天数|剩余分钟数
    // 或: ok|activate|剩余天数|剩余分钟数
    if (parts.length >= 3) {
        let 剩余天数 = parseInt(parts[2]);
        let 剩余分钟 = parts.length >= 4 ? parseInt(parts[3]) : 0;
        console.log(`[时间] 剩余天数: ${剩余天数} 天, 剩余分钟: ${剩余分钟} 分钟`);
        return { days: 剩余天数, minutes: 剩余分钟 };
    } else if (parts.length === 2 && parts[1] === "permanent") {
        console.log("[时间] 终身有效");
        return { days: 99999, minutes: 0 };
    }
    return null;
}

/**
 * 心跳线程(每50秒执行一次)
 */
function 心跳保活() {
    console.log("[心跳] 启动心跳线程");
    连续失败次数 = 0;
    while (true) {
        if (!当前卡密 || !当前机器码) {
            sleep(5000);
            continue;
        }
        let biz = 验证卡密(当前卡密, 当前机器码);
        if (biz && biz.startsWith("ok|")) {
            连续失败次数 = 0;
            console.log("[心跳] ✅ 心跳正常");
            let timeInfo = 解析剩余时间(biz);
            if (timeInfo) {
                toastLog(`剩余: ${timeInfo.days}天 ${timeInfo.minutes}分钟`);
            }
        } else {
            连续失败次数++;
            console.log(`[心跳] ❌ 心跳异常,连续失败: ${连续失败次数}/5`);
            if (连续失败次数 >= 5) {
                console.log("[心跳] 连续失败5次,退出脚本");
                engines.myEngine().forceStop();
                return;
            }
        }
        // 心跳间隔 50 秒(正式使用时)
        sleep(50000);
    }
}

// ========== 主流程 =========================================

function 主流程() {
    console.log("========================================");
    console.log("  卡密通 AutoJS 对接示例");
    console.log("========================================");

    // 1. 获取机器码
    当前机器码 = 获取机器码();
    if (!当前机器码 || 当前机器码 === "UNKNOWN_DEVICEMAC") {
        toast("获取机器码失败");
        return;
    }

    // 2. 获取验证开关
    let cardSwitch = 获取卡密开关();
    if (cardSwitch === "CARD_OFF") {
        toast("验证已关闭,直接进入");
        // 启动心跳(使用测试卡密)
        当前卡密 = "88888888";
        心跳线程 = threads.start(心跳保活);
        return;
    }

    // 3. 输入卡密
    let inputCard = dialogs.rawInput("请输入卡密", "");
    if (!inputCard || inputCard.trim() === "") {
        toast("卡密不能为空");
        return;
    }
    当前卡密 = inputCard.trim();

    // 4. 验证卡密
    toast("验证中...");
    let biz = 验证卡密(当前卡密, 当前机器码);

    if (!biz) {
        toast("验证失败:签名错误或网络异常");
        return;
    }

    if (biz.startsWith("ok|")) {
        let timeInfo = 解析剩余时间(biz);
        if (timeInfo) {
            let msg = `验证成功!\n剩余: ${timeInfo.days}天 ${timeInfo.minutes}分钟`;
            toast(msg);
            console.log(msg);
        } else {
            toast("验证成功(终身有效)");
        }

        // 5. 启动心跳保活
        心跳线程 = threads.start(心跳保活);

        // 6. 你的主逻辑(示例:倒计时显示)
        while (true) {
            console.log("用户代码运行中...");
            sleep(10000);
        }
    } else if (biz.startsWith("error|")) {
        let errCode = biz.split("|")[1];
        toast(`验证失败:${errCode}`);
        console.log(`[错误] ${errCode}`);
    } else {
        toast(`验证失败:${biz}`);
    }
}

// 启动脚本
主流程();
📄 C# (.NET)
易语言 375 行
using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace KamiVerification
{
    class Program
    {
        // ==================== 配置区 ====================
        private static readonly string UserName = "你的用户名";       // 例如:qq345205377
        private static readonly string AppName = "你的应用名";       // 例如:a
        private static readonly string SignKey = "你的签名密钥";     // 从后台「修改签名密钥」获取
        private static readonly string BaseUrl = $"https://www.keyt.cn/kami/{UserName}/check.php";

        private const int TimestampMaxDiff = 120;
        private const int HeartbeatInterval = 50000; // 毫秒,50秒

        // ==================== 全局变量 ====================
        private static string currentCard = "";
        private static string currentMac = "";
        private static int failCount = 0;
        private static bool isRunning = true;

        static async Task Main(string[] args)
        {
            Console.WriteLine("========================================");
            Console.WriteLine("  卡密通 C# 对接示例");
            Console.WriteLine("========================================");

            // 1. 获取机器码
            currentMac = GetMachineCode();
            if (string.IsNullOrEmpty(currentMac))
            {
                Console.WriteLine("[错误] 获取机器码失败");
                return;
            }
            Console.WriteLine($"[机器码] {currentMac}");

            // 2. 获取验证开关
            string cardSwitch = await GetCardSwitchAsync();
            if (cardSwitch == "CARD_OFF")
            {
                Console.WriteLine("[提示] 验证已关闭,直接进入");
                currentCard = "88888888";
                _ = Task.Run(() => HeartbeatLoop());
                // 用户主逻辑
                UserMainLogic();
                return;
            }

            // 3. 输入卡密
            Console.Write("请输入卡密: ");
            currentCard = Console.ReadLine()?.Trim();
            if (string.IsNullOrEmpty(currentCard))
            {
                Console.WriteLine("[错误] 卡密不能为空");
                return;
            }

            // 4. 验证卡密
            Console.WriteLine("[验证] 验证中...");
            string biz = await VerifyCardAsync(currentCard, currentMac);

            if (string.IsNullOrEmpty(biz))
            {
                Console.WriteLine("[错误] 验证失败:签名错误或网络异常");
                return;
            }

            if (biz.StartsWith("ok|"))
            {
                // 解析剩余时间
                var timeInfo = ParseRemainingTime(biz);
                if (timeInfo != null)
                {
                    Console.WriteLine($"[成功] 验证通过!剩余: {timeInfo.Days}天 {timeInfo.Minutes}分钟");
                }
                else
                {
                    Console.WriteLine("[成功] 验证通过(终身有效)");
                }

                // 5. 启动心跳
                _ = Task.Run(() => HeartbeatLoop());

                // 6. 用户主逻辑
                UserMainLogic();
            }
            else if (biz.StartsWith("error|"))
            {
                string errCode = biz.Split('|')[1];
                string errMsg = TransMsg(errCode);
                Console.WriteLine($"[失败] {errMsg}");
            }
            else
            {
                Console.WriteLine($"[失败] {biz}");
            }
        }

        // ==================== 工具函数 ====================

        /// <summary>
        /// 获取机器码(使用 MAC 地址)
        /// </summary>
        private static string GetMachineCode()
        {
            try
            {
                var networkInterfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();
                foreach (var ni in networkInterfaces)
                {
                    if (ni.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up &&
                        ni.NetworkInterfaceType != System.Net.NetworkInformation.NetworkInterfaceType.Loopback)
                    {
                        var mac = ni.GetPhysicalAddress()?.ToString();
                        if (!string.IsNullOrEmpty(mac) && mac != "000000000000")
                        {
                            return mac + "MAC";
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[机器码异常] {ex.Message}");
            }
            return "获取失败";
        }

        /// <summary>
        /// MD5 加密
        /// </summary>
        private static string Md5(string input)
        {
            using var md5 = MD5.Create();
            var bytes = Encoding.UTF8.GetBytes(input);
            var hash = md5.ComputeHash(bytes);
            return BitConverter.ToString(hash).Replace("-", "").ToLower();
        }

        /// <summary>
        /// 验证服务器返回(签名 + 时间戳)
        /// </summary>
        private static string VerifyResponse(string raw)
        {
            // 分离签名
            int signIndex = raw.IndexOf("|sign=", StringComparison.Ordinal);
            if (signIndex == -1)
            {
                Console.WriteLine("[验签] ❌ 未找到签名标记");
                return null;
            }

            string body = raw.Substring(0, signIndex);
            string sign = raw.Substring(signIndex + 6);

            // 本地计算签名
            string localSign = Md5(body + SignKey);
            Console.WriteLine($"[验签] 本地签名: {localSign}");
            Console.WriteLine($"[验签] 服务器签名: {sign}");

            if (localSign != sign)
            {
                Console.WriteLine("[验签] ❌ 签名不匹配");
                return null;
            }
            Console.WriteLine("[验签] ✅ 签名校验通过");

            // 提取时间戳
            int lastPipe = body.LastIndexOf('|');
            if (lastPipe == -1)
            {
                Console.WriteLine("[验签] ❌ 未找到时间戳");
                return null;
            }

            string tsStr = body.Substring(lastPipe + 1);
            if (!long.TryParse(tsStr, out long ts))
            {
                Console.WriteLine("[验签] ❌ 时间戳无效");
                return null;
            }

            long nowTs = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            long diff = Math.Abs(nowTs - ts);

            Console.WriteLine($"[验签] 服务器时间戳: {ts}");
            Console.WriteLine($"[验签] 本地时间戳: {nowTs}");
            Console.WriteLine($"[验签] 时间差: {diff} 秒 (最大允许: {TimestampMaxDiff} 秒)");

            if (diff > TimestampMaxDiff)
            {
                Console.WriteLine("[验签] ❌ 时间戳过期");
                return null;
            }
            Console.WriteLine("[验签] ✅ 时间戳校验通过");

            string biz = body.Substring(0, lastPipe);
            Console.WriteLine($"[验签] 业务内容: {biz}");
            return biz;
        }

        /// <summary>
        /// 获取卡密验证开关
        /// </summary>
        private static async Task<string> GetCardSwitchAsync()
        {
            using var http = new HttpClient();
            http.Timeout = TimeSpan.FromSeconds(10);

            for (int i = 0; i < 3; i++)
            {
                long ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
                string url = $"{BaseUrl}?act=get_switch&app={AppName}&t={ts}";
                Console.WriteLine($"[开关] 第{i + 1}次尝试: {url}");

                try
                {
                    var response = await http.GetAsync(url);
                    string body = await response.Content.ReadAsStringAsync();
                    string biz = VerifyResponse(body);
                    if (!string.IsNullOrEmpty(biz))
                    {
                        if (biz.Contains("CARD_ON"))
                        {
                            Console.WriteLine("[开关] ✅ 卡密验证已开启");
                            return "CARD_ON";
                        }
                        else if (biz.Contains("CARD_OFF"))
                        {
                            Console.WriteLine("[开关] ⚠️ 卡密验证已关闭");
                            return "CARD_OFF";
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"[开关] 请求异常: {ex.Message}");
                }
                await Task.Delay(500);
            }

            Console.WriteLine("[开关] ⚠️ 开关获取失败,默认开启验证");
            return "CARD_ON";
        }

        /// <summary>
        /// 验证卡密
        /// </summary>
        private static async Task<string> VerifyCardAsync(string card, string mac)
        {
            using var http = new HttpClient();
            long ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            string url = $"{BaseUrl}?card={card}&mac={mac}&app={AppName}&heart=1&t={ts}";
            Console.WriteLine($"[验证] 请求URL: {url}");

            try
            {
                var response = await http.GetAsync(url);
                string body = await response.Content.ReadAsStringAsync();
                Console.WriteLine($"[验证] 服务器返回: {body}");
                return VerifyResponse(body);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[验证] 请求异常: {ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// 解析剩余时间
        /// </summary>
        private static (int Days, int Minutes)? ParseRemainingTime(string biz)
        {
            var parts = biz.Split('|');
            // 格式: ok|valid|剩余天数|剩余分钟数
            // 或: ok|activate|剩余天数|剩余分钟数
            if (parts.Length >= 3)
            {
                if (int.TryParse(parts[2], out int days))
                {
                    int minutes = parts.Length >= 4 && int.TryParse(parts[3], out int mins) ? mins : 0;
                    return (days, minutes);
                }
            }
            else if (parts.Length == 2 && parts[1] == "permanent")
            {
                Console.WriteLine("[时间] 终身有效");
                return null;
            }
            return null;
        }

        /// <summary>
        /// 错误码转中文
        /// </summary>
        private static string TransMsg(string code)
        {
            return code switch
            {
                "activate" => "激活成功",
                "valid" => "验证通过",
                "permanent" => "终身有效",
                "expired" => "卡密已过期",
                "banned" => "卡密已被禁用",
                "device_mismatch" => "设备不匹配",
                "online_limit_reached" => "在线设备数已满",
                "invalid_card" => "卡密无效",
                _ => code
            };
        }

        /// <summary>
        /// 心跳保活(每50秒)
        /// </summary>
        private static async Task HeartbeatLoop()
        {
            Console.WriteLine("[心跳] 启动心跳线程");
            failCount = 0;

            while (isRunning)
            {
                if (string.IsNullOrEmpty(currentCard) || string.IsNullOrEmpty(currentMac))
                {
                    await Task.Delay(2000);
                    continue;
                }

                string biz = await VerifyCardAsync(currentCard, currentMac);
                if (!string.IsNullOrEmpty(biz) && biz.StartsWith("ok|"))
                {
                    failCount = 0;
                    Console.WriteLine("[心跳] ✅ 心跳正常");
                    var timeInfo = ParseRemainingTime(biz);
                    if (timeInfo != null)
                    {
                        Console.WriteLine($"[心跳] 剩余: {timeInfo.Value.Days}天 {timeInfo.Value.Minutes}分钟");
                    }
                }
                else
                {
                    failCount++;
                    Console.WriteLine($"[心跳] ❌ 心跳异常,连续失败: {failCount}/5");
                    if (failCount >= 5)
                    {
                        Console.WriteLine("[心跳] 连续失败5次,退出程序");
                        isRunning = false;
                        Environment.Exit(0);
                        return;
                    }
                }

                await Task.Delay(HeartbeatInterval);
            }
        }

        /// <summary>
        /// 用户主线程(占位,用户自行实现)
        /// </summary>
        private static void UserMainLogic()
        {
            Console.WriteLine("[用户代码] 用户代码运行中...");
            while (isRunning)
            {
                // 你的软件主逻辑
                Thread.Sleep(1000);
            }
        }
    }
}
📄 C++(基于 libcurl)
4 行
百度网盘下载链接:
https://pan.baidu.com/s/1klUGYq42maotMQ-zEhjxDw 
(复制到浏览器打开即可,如果网盘失效,请访问下面官网下载)
📄 EasyClick (JavaScript) QQ3956391022贡献
按键精灵 297 行
// ============================================================
// EasyClick 对接卡密通验证
// ============================================================

// 配置区 ===================================================
var 用户名 = "";           // 输入自己注册的用户名
var 应用名 = "";           // 创建的应用名字
var 签名密钥 = "";       // 从后台「修改签名密钥」获取
var 服务器地址 = `https://www.keyt.cn/kami/${用户名}/check.php`;

// 全局变量 =================================================
var 当前卡密 = "NQXGYZLYXZ";
var 当前机器码 = "";
var 心跳线程 = null;
var 连续失败次数 = 0;
var tid
// ========== 工具函数 =======================================

/**
 * 获取设备机器码(使用 Android ID)
 */
function 获取机器码() {
    let androidId = device.getAndroidId();  // 注意拼写:AutoJS 中是 getAndroidId
    if (!androidId) {
        androidId = "UNKNOWN_DEVICE";
    }
    let mac = androidId + "MAC";
    console.log("[机器码] " + mac);
    return mac;
}

/**
 * MD5 加密
 */
function md5(data) {
    return java.security.MessageDigest.getInstance("MD5")
        .digest(new java.lang.String(data).getBytes())
        .map(function (v) {
            return ('0' + (v & 0xFF).toString(16)).slice(-2);
        }).join('');
}

/**
 * 获取当前时间戳(秒)
 */
function 获取时间戳() {
    return Math.floor(Date.now() / 1000);
}

/**
 * 验证服务器返回(签名 + 时间戳)
 */
function 验证响应(原始返回) {
    console.log("[验签] 原始返回: " + 原始返回);

    // 分离签名
    let signIndex = 原始返回.indexOf("|sign=");
    if (signIndex === -1) {
        console.log("[验签] ❌ 未找到签名标记");
        return null;
    }

    let body = 原始返回.substring(0, signIndex);
    let sign = 原始返回.substring(signIndex + 6);

    // 本地计算签名
    let 本地签名 = md5(body + 签名密钥);
    console.log("[验签] 本地签名: " + 本地签名);
    console.log("[验签] 服务器签名: " + sign);

    if (本地签名 !== sign) {
        console.log("[验签] ❌ 签名不匹配");
        return null;
    }
    console.log("[验签] ✅ 签名校验通过");

    // 提取时间戳
    let lastPipe = body.lastIndexOf("|");
    if (lastPipe === -1) {
        console.log("[验签] ❌ 未找到时间戳");
        return null;
    }

    let tsStr = body.substring(lastPipe + 1);
    let 服务器时间戳 = parseInt(tsStr);
    let 本地时间戳 = 获取时间戳();
    let 时间差 = Math.abs(本地时间戳 - 服务器时间戳);

    console.log("[验签] 服务器时间戳: " + 服务器时间戳);
    console.log("[验签] 本地时间戳: " + 本地时间戳);
    console.log("[验签] 时间差: " + 时间差 + " 秒");

    if (时间差 > 120) {
        console.log("[验签] ❌ 时间戳过期");
        return null;
    }
    console.log("[验签] ✅ 时间戳校验通过");

    let 业务内容 = body.substring(0, lastPipe);
    console.log("[验签] 业务内容: " + 业务内容);
    return 业务内容;
}

/**
 * 获取验证开关状态
 */
function 获取卡密开关() {
    for (let i = 0; i < 3; i++) {
        let url = `${服务器地址}?act=get_switch&app=${应用名}&t=${获取时间戳()}`;
        console.log(`[开关] 第${i + 1}次尝试: ${url}`);
        try {
            let r = http.httpGet(url, null, 20 * 1000,{
                headers: {
                    'Cache-Control': 'no-cache'
                }
            });
            logi(r)
            if (r && r.indexOf("CARD_ON") !== -1) {
                logi("[开关] ✅ 卡密验证已开启");
                return "CARD_ON";
            } else if (r && r.indexOf("CARD_OFF") !== -1) {
                logi("[开关] ⚠️ 卡密验证已关闭");
                return "CARD_OFF";
            }
        } catch (e) {
            console.log("[开关] 请求异常: " + e.message);
        }
        sleep(500);
    }
    console.log("[开关] ⚠️ 开关获取失败,默认开启验证");
    return "CARD_ON";
}

/**
 * 验证卡密(核心)
 */
function 验证卡密(card, mac) {
    let url = `${服务器地址}?card=${card}&mac=${mac}&app=${应用名}&heart=1&t=${获取时间戳()}`;
    console.log("[验证] 请求URL: " + url);
    try {
        let r = http.httpGet(url, null, 20 * 1000,{
            headers: {
                'Cache-Control': 'no-cache'
            }
        });
        logi(r)
        if (r) {
            let biz = 验证响应(r);
            return biz;
        } else {
            logi("[验证] 请求失败");
            return null;
        }
    } catch (e) {
        console.log("[验证] 请求异常: " + e.message);
        return null;
    }
}

/**
 * 解析并显示剩余时间
 */
function 解析剩余时间(biz) {
    if (!biz) return null;
    let parts = biz.split("|");
    // 格式: ok|valid|剩余天数|剩余分钟数
    // 或: ok|activate|剩余天数|剩余分钟数
    if (parts.length >= 3) {
        let 剩余天数 = parseInt(parts[2]);
        let 剩余分钟 = parts.length >= 4 ? parseInt(parts[3]) : 0;
        console.log(`[时间] 剩余天数: ${剩余天数} 天, 剩余分钟: ${剩余分钟} 分钟`);
        return { days: 剩余天数, minutes: 剩余分钟 };
    } else if (parts.length === 2 && parts[1] === "permanent") {
        console.log("[时间] 终身有效");
        return { days: 99999, minutes: 0 };
    }
    return null;
}

/**
 * 心跳线程(每50秒执行一次)
 */
function 心跳保活() {
    console.log("[心跳] 启动心跳线程");
    连续失败次数 = 0;
    while (true) {
        if (!当前卡密 || !当前机器码) {
            sleep(5000);
            continue;
        }
        let biz = 验证卡密(当前卡密, 当前机器码);
        if (biz && biz.startsWith("ok|")) {
            连续失败次数 = 0;
            console.log("[心跳] ✅ 心跳正常");
            let timeInfo = 解析剩余时间(biz);
            if (timeInfo) {
                logi(`剩余: ${timeInfo.days}天 ${timeInfo.minutes}分钟`);
            }
        } else {
            连续失败次数++;
            console.log(`[心跳] ❌ 心跳异常,连续失败: ${连续失败次数}/5`);
            if (连续失败次数 >= 5) {
                console.log("[心跳] 连续失败5次,退出脚本")
                exit()
                return;
            }
        }
        // 心跳间隔 50 秒(正式使用时)
        sleep(50000);
    }
}

//弹窗显示
function POP(title, msg, cancelText, okText) {
    var p = {
        "title": title,
        "msg": msg,
        "cancelable": false,
        "cancelText": cancelText,
        "okText": okText
    };
    ui.alert(p,
        function (dialog, v) {
            dialog.doDismiss();
            return true;
        },
        function (dialog, v) {
            dialog.doDismiss();
            return true;
        },
        function () {
            return true;
        });
}

// ========== 主流程 =========================================

function 主流程() {
    console.log("========================================");
    console.log("  卡密通 EasyClick 对接示例");
    console.log("========================================");
    当前机器码 = 获取机器码();
    if (!当前机器码 || 当前机器码 === "UNKNOWN_DEVICEMAC") {
        toast("获取机器码失败");
        return;
    }
    // 2. 获取验证开关
    let cardSwitch = 获取卡密开关();
    if (cardSwitch === "CARD_OFF") {
        toast("验证已关闭,直接进入");
        // 启动心跳(使用测试卡密)
        当前卡密 = "";
        tid = thread.execAsync(function () {
            while (true) 心跳线程 = 心跳保活()
        });
        return;
    }
    if (!当前卡密 || 当前卡密.trim() === "") {
        toast("卡密不能为空");
        return;
    }

    toast("验证中...");
    let biz = 验证卡密(当前卡密, 当前机器码);
    if (!biz) {
        toast("验证失败:签名错误或网络异常");
        return;
    }
    if (biz.startsWith("ok|")) {
        let timeInfo = 解析剩余时间(biz);
        if (timeInfo) {
            let msg = `验证成功!\n剩余: ${timeInfo.days}天 ${timeInfo.minutes}分钟`;
            toast(msg);
            console.log(msg);
        } else {
            toast("验证成功(终身有效)");
        }
        tid = thread.execAsync(function () {
            while (true) 心跳线程 = 心跳保活()
        });
        while (true) {
            console.log("用户代码运行中...");
            sleep(10000);
        }
    } else if (biz.startsWith("error|")) {
        let errCode = biz.split("|")[1];
        POP("验证失败", "卡密不正确:" + errCode, "取消", "确定")
    } else {
        toast(`验证失败:${biz}`);
    }
}

// 启动脚本
主流程();


📄 Java
Java 603 行
package mobaiclient.utils;

import javax.net.ssl.HttpsURLConnection;
import javax.swing.*;
import java.awt.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

/**
 * 卡密验证系统 - 单文件完整版
 * 
 * 功能:
 * 1. 自动获取机器码(MAC地址/系统信息/UUID)
 * 2. 向卡密通服务器验证卡密
 * 3. 图形化验证窗口(Swing)
 * 4. 验证成功后自动关闭窗口
 * 
 * 使用方法:
 * KeyAuthSystem.showVerificationWindow();
 * 
 * 或直接验证:
 * boolean result = KeyAuthSystem.verifyCard("你的卡密");
 */
public class KeyAuthSystem {

    // ============================================================
    //  配置区 - 请修改为你的实际信息
    // ============================================================
    
    /** 卡密通后台的用户名 */
    private static final String USER_NAME = "your_username";
    
    /** 卡密通后台的应用名 */
    private static final String APP_NAME = "your_appname";
    
    /** 卡密通后台的签名密钥 */
    private static final String SIGN_KEY = "your_sign_key";
    
    /** API 基础地址(自动拼接) */
    private static final String BASE_URL = "https://www.keyt.cn/kami/" + USER_NAME + "/check.php";
    
    /** 时间戳最大允许偏差(秒) */
    private static final int TIMESTAMP_MAX_DIFF = 120;

    // ============================================================
    //  状态变量
    // ============================================================
    
    /** 缓存的机器码 */
    private static String cachedMachineCode = null;
    
    /** 是否已验证通过 */
    private static boolean isVerified = false;
    
    /** 验证窗口是否已显示 */
    private static boolean hasShownWindow = false;

    // ============================================================
    //  核心验证方法
    // ============================================================

    /**
     * 获取当前时间戳(秒)
     */
    private static long getTimestamp() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * MD5 加密
     * @param input 输入字符串
     * @return MD5 哈希值(32位小写十六进制)
     */
    private static String md5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(input.getBytes());
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b & 0xff));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 获取机器码(唯一设备标识)
     * 
     * 尝试顺序:
     * 1. MAC 地址(最可靠)
     * 2. 系统属性组合(备用)
     * 3. 随机 UUID(最终备选)
     * 
     * @return 16位机器码
     */
    public static String getMachineCode() {
        // 如果已缓存,直接返回
        if (cachedMachineCode != null) {
            return cachedMachineCode;
        }

        // ============================================================
        //  方法1:尝试获取 MAC 地址
        // ============================================================
        try {
            java.net.NetworkInterface networkInterface = 
                java.net.NetworkInterface.getNetworkInterfaces().nextElement();
            byte[] mac = networkInterface.getHardwareAddress();
            
            if (mac != null) {
                StringBuilder sb = new StringBuilder();
                for (byte b : mac) {
                    sb.append(String.format("%02X", b));
                }
                String macStr = sb.toString();
                
                // 排除无效的 MAC 地址
                if (!macStr.equals("000000000000") && !macStr.isEmpty()) {
                    cachedMachineCode = macStr + "MAC";
                    System.out.println("[机器码] 使用MAC地址: " + cachedMachineCode);
                    return cachedMachineCode;
                }
            }
        } catch (Exception e) {
            System.err.println("[机器码-MAC方式] 失败: " + e.getMessage());
        }

        // ============================================================
        //  方法2:备用方案 - 基于系统属性生成唯一ID
        // ============================================================
        try {
            // 组合多个系统属性,生成唯一标识
            String raw = System.getProperty("os.name")
                    + System.getProperty("user.name")
                    + System.getProperty("java.version")
                    + System.getProperty("user.home");
            
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(raw.getBytes());
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            
            // 取前16位作为机器码
            String fallbackCode = sb.toString().substring(0, 16).toUpperCase();
            System.out.println("[机器码] 使用备用机器码: " + fallbackCode);
            cachedMachineCode = fallbackCode;
            return cachedMachineCode;
        } catch (Exception e) {
            System.err.println("[机器码-备选] 生成失败: " + e.getMessage());
        }

        // ============================================================
        //  方法3:终极备选 - 生成随机UUID
        // ============================================================
        String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 16).toUpperCase();
        System.out.println("[机器码] 使用随机UUID作为机器码: " + uuid);
        cachedMachineCode = uuid;
        return cachedMachineCode;
    }

    /**
     * HTTP GET 请求
     * @param urlString 请求地址
     * @return 服务器响应字符串
     */
    private static String httpGet(String urlString) {
        HttpsURLConnection conn = null;
        try {
            URL url = new URL(urlString);
            conn = (HttpsURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(10000);
            conn.setReadTimeout(10000);
            conn.setRequestProperty("Cache-Control", "no-cache");

            int responseCode = conn.getResponseCode();
            
            if (responseCode == 200) {
                BufferedReader reader = new BufferedReader(
                    new InputStreamReader(conn.getInputStream())
                );
                StringBuilder response = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
                reader.close();
                return response.toString();
            } else {
                System.out.println("[HTTP] 响应码: " + responseCode);
            }
        } catch (Exception e) {
            System.err.println("[HTTP] 请求异常: " + e.getMessage());
        } finally {
            if (conn != null) conn.disconnect();
        }
        return "";
    }

    /**
     * 验证服务器返回(签名校验 + 时间戳校验)
     * 
     * 服务器返回格式:业务数据|sign=MD5(业务数据 + 签名密钥)
     * 
     * @param raw 原始返回字符串
     * @return 业务数据(验证通过),null(验证失败)
     */
    private static String verifyResponse(String raw) {
        System.out.println("[验签] 原始返回: " + raw);

        // 1. 提取签名
        int signIndex = raw.indexOf("|sign=");
        if (signIndex == -1) {
            System.out.println("[验签] ❌ 未找到签名标记");
            return null;
        }

        String body = raw.substring(0, signIndex);        // 业务数据
        String sign = raw.substring(signIndex + 6);       // 签名

        // 2. 本地计算签名
        String localSign = md5(body + SIGN_KEY);
        System.out.println("[验签] 本地签名: " + localSign);
        System.out.println("[验签] 服务器签名: " + sign);

        // 3. 比对签名
        if (!localSign.equals(sign)) {
            System.out.println("[验签] ❌ 签名不匹配");
            return null;
        }
        System.out.println("[验签] ✅ 签名校验通过");

        // 4. 提取时间戳
        int lastPipe = body.lastIndexOf('|');
        if (lastPipe == -1) {
            System.out.println("[验签] ❌ 未找到时间戳");
            return null;
        }

        String tsStr = body.substring(lastPipe + 1);
        long serverTs = Long.parseLong(tsStr);
        long localTs = getTimestamp();
        long diff = Math.abs(localTs - serverTs);

        System.out.println("[验签] 服务器时间戳: " + serverTs);
        System.out.println("[验签] 本地时间戳: " + localTs);
        System.out.println("[验签] 时间差: " + diff + " 秒");

        // 5. 验证时间戳(防重放攻击)
        if (diff > TIMESTAMP_MAX_DIFF) {
            System.out.println("[验签] ❌ 时间戳过期");
            return null;
        }
        System.out.println("[验签] ✅ 时间戳校验通过");

        // 6. 返回业务数据
        String biz = body.substring(0, lastPipe);
        System.out.println("[验签] 业务内容: " + biz);
        return biz;
    }

    /**
     * 验证卡密(核心方法)
     * 
     * @param card 用户输入的卡密
     * @return true=验证成功,false=验证失败
     */
    public static boolean verifyCard(String card) {
        String machineCode = getMachineCode();
        System.out.println("[验证] 机器码: " + machineCode);

        long ts = getTimestamp();
        String url = BASE_URL + "?card=" + card + "&mac=" + machineCode + 
                     "&app=" + APP_NAME + "&heart=1&t=" + ts;
        System.out.println("[验证] 请求URL: " + url);
        System.out.println("[验证] 使用卡密: " + card);

        String response = httpGet(url);
        if (response.isEmpty()) {
            System.out.println("[验证] ❌ 网络错误或返回为空");
            return false;
        }

        System.out.println("[验证] 服务器返回: " + response);
        String biz = verifyResponse(response);

        if (biz == null) {
            System.out.println("[验证] ❌ 签名验证失败");
            return false;
        }

        // 解析业务数据
        if (biz.startsWith("ok|")) {
            isVerified = true;
            System.out.println("[验证] ✅ 验证通过!");
            parseRemainingTime(biz);
            return true;
        } else if (biz.startsWith("error|")) {
            String errCode = biz.split("\\|")[1];
            System.out.println("[验证] ❌ " + transMsg(errCode));
            return false;
        }

        System.out.println("[验证] ❌ 未知返回: " + biz);
        return false;
    }

    /**
     * 解析剩余时间(从业务数据中提取)
     */
    private static void parseRemainingTime(String biz) {
        String[] parts = biz.split("\\|");
        if (parts.length >= 3) {
            int days = Integer.parseInt(parts[2]);
            int minutes = parts.length >= 4 ? Integer.parseInt(parts[3]) : 0;
            System.out.println("[时间] 剩余: " + days + "天 " + minutes + "分钟");
        } else if (parts.length >= 2 && "permanent".equals(parts[1])) {
            System.out.println("[时间] 终身有效");
        }
    }

    /**
     * 错误码转中文
     */
    private static String transMsg(String code) {
        switch (code) {
            case "activate":      return "激活成功";
            case "valid":         return "验证通过";
            case "permanent":     return "终身有效";
            case "expired":       return "卡密已过期";
            case "banned":        return "卡密已被禁用";
            case "device_mismatch": return "设备不匹配";
            case "online_limit_reached": return "在线设备数已满";
            case "invalid_card":  return "卡密无效";
            default:              return code;
        }
    }

    /**
     * 获取当前验证状态
     */
    public static boolean isVerified() {
        return isVerified;
    }

    /**
     * 重置验证状态
     */
    public static void reset() {
        isVerified = false;
        hasShownWindow = false;
    }

    // ============================================================
    //  图形验证窗口(Swing)
    // ============================================================

    /**
     * 显示卡密验证窗口(图形界面)
     * 
     * 窗口包含:
     * - 标题:OpenZen 卡密验证
     * - 机器码显示
     * - 卡密输入框
     * - 验证按钮
     * - 状态提示
     */
    public static void showVerificationWindow() {
        // 如果已经验证通过,不重复显示
        if (isVerified) {
            System.out.println("[验证窗口] 已验证通过,跳过");
            return;
        }

        // 如果窗口已显示,不重复打开
        if (hasShownWindow) {
            System.out.println("[验证窗口] 窗口已显示");
            return;
        }

        hasShownWindow = true;

        // 在事件线程中创建窗口
        SwingUtilities.invokeLater(() -> {
            createAndShowGUI();
        });
    }

    /**
     * 创建并显示 Swing 验证窗口
     */
    private static void createAndShowGUI() {
        // 主窗口
        JFrame frame = new JFrame("卡密验证");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(420, 280);
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);

        // 主面板
        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BorderLayout(10, 10));
        mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

        // ============================================================
        //  标题
        // ============================================================
        JLabel titleLabel = new JLabel("OpenZen 客户端验证", SwingConstants.CENTER);
        titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 18));
        titleLabel.setForeground(new Color(0, 100, 200));
        mainPanel.add(titleLabel, BorderLayout.NORTH);

        // ============================================================
        //  中间内容面板
        // ============================================================
        JPanel centerPanel = new JPanel();
        centerPanel.setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(5, 5, 5, 5);
        gbc.fill = GridBagConstraints.HORIZONTAL;

        // 机器码显示
        String machineCode = getMachineCode();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.gridwidth = 2;
        JLabel machineLabel = new JLabel("机器码: " + machineCode);
        machineLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        machineLabel.setForeground(Color.GRAY);
        centerPanel.add(machineLabel, gbc);

        // 卡密输入框
        gbc.gridy = 1;
        gbc.gridwidth = 1;
        JLabel keyLabel = new JLabel("卡密:");
        keyLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        centerPanel.add(keyLabel, gbc);

        gbc.gridx = 1;
        JTextField keyField = new JTextField(20);
        keyField.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        keyField.setHorizontalAlignment(JTextField.CENTER);
        centerPanel.add(keyField, gbc);

        // 状态标签
        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.gridwidth = 2;
        JLabel statusLabel = new JLabel("请输入卡密", SwingConstants.CENTER);
        statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
        statusLabel.setForeground(Color.GRAY);
        centerPanel.add(statusLabel, gbc);

        mainPanel.add(centerPanel, BorderLayout.CENTER);

        // ============================================================
        //  底部按钮面板
        // ============================================================
        JPanel bottomPanel = new JPanel();
        bottomPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 5));

        JButton verifyButton = new JButton("验证");
        verifyButton.setFont(new Font("微软雅黑", Font.BOLD, 14));
        verifyButton.setPreferredSize(new Dimension(120, 35));
        verifyButton.setBackground(new Color(0, 150, 80));
        verifyButton.setForeground(Color.WHITE);
        verifyButton.setFocusPainted(false);

        JButton exitButton = new JButton("退出");
        exitButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        exitButton.setPreferredSize(new Dimension(80, 35));
        exitButton.setFocusPainted(false);

        bottomPanel.add(verifyButton);
        bottomPanel.add(exitButton);
        mainPanel.add(bottomPanel, BorderLayout.SOUTH);

        frame.add(mainPanel);
        frame.setVisible(true);

        // ============================================================
        //  事件监听
        // ============================================================

        // 验证按钮点击事件
        verifyButton.addActionListener(e -> {
            String card = keyField.getText().trim();
            if (card.isEmpty()) {
                statusLabel.setText("❌ 请输入卡密");
                statusLabel.setForeground(Color.RED);
                return;
            }

            // 禁用按钮,防止重复点击
            verifyButton.setEnabled(false);
            verifyButton.setText("验证中...");
            statusLabel.setText("⏳ 正在验证...");
            statusLabel.setForeground(new Color(255, 165, 0));

            // 在独立线程中执行网络请求
            new Thread(() -> {
                boolean success = verifyCard(card);

                // 回到主线程更新 UI
                SwingUtilities.invokeLater(() -> {
                    verifyButton.setEnabled(true);
                    verifyButton.setText("验证");

                    if (success) {
                        statusLabel.setText("✅ 验证成功!");
                        statusLabel.setForeground(new Color(0, 180, 0));
                        
                        // 延迟关闭窗口
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException ignored) {}
                        
                        frame.dispose();
                        System.out.println("[验证窗口] ✅ 验证通过,窗口已关闭");
                    } else {
                        statusLabel.setText("❌ 卡密无效,请重新输入");
                        statusLabel.setForeground(Color.RED);
                        keyField.setText("");
                        keyField.requestFocus();
                    }
                });
            }).start();
        });

        // 回车键触发验证
        keyField.addActionListener(e -> verifyButton.doClick());

        // 退出按钮
        exitButton.addActionListener(e -> {
            frame.dispose();
            System.out.println("[验证窗口] 用户主动退出");
        });

        // 窗口关闭事件
        frame.addWindowListener(new java.awt.event.WindowAdapter() {
            @Override
            public void windowClosing(java.awt.event.WindowEvent e) {
                System.out.println("[验证窗口] 窗口被关闭");
            }
        });

        // 默认聚焦到输入框
        keyField.requestFocus();

        System.out.println("[验证窗口] 窗口已显示");
    }

    // ============================================================
    //  使用示例(主方法)
    // ============================================================

    /**
     * 命令行测试入口
     * 
     * 运行方式:
     * 1. 直接运行 main 方法(显示图形窗口)
     * 2. 或使用命令行参数:java KeyAuthSystem "卡密"
     */
    public static void main(String[] args) {
        System.out.println("========================================");
        System.out.println("  卡密验证系统 v1.0");
        System.out.println("========================================");
        
        // 检查命令行参数
        if (args.length > 0) {
            // 命令行模式:直接验证
            String card = args[0];
            System.out.println("命令行模式,验证卡密: " + card);
            boolean result = verifyCard(card);
            System.out.println("验证结果: " + (result ? "✅ 成功" : "❌ 失败"));
            System.exit(result ? 0 : 1);
        } else {
            // 图形界面模式
            System.out.println("启动图形验证窗口...");
            showVerificationWindow();
            
            // 等待窗口关闭(保持程序运行)
            while (!isVerified && hasShownWindow) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    break;
                }
            }
            
            System.out.println("程序结束,验证状态: " + (isVerified ? "✅ 通过" : "❌ 未通过"));
        }
    }
}
📄 Kotlin
286 行
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.security.MessageDigest
import kotlin.concurrent.thread

object KamiVerification {
    
    // ==================== 配置区 ====================
    private const val USER_NAME = "你的用户名"       // 例如:qq345205377
    private const val APP_NAME = "你的应用名"        // 例如:a
    private const val SIGN_KEY = "你的签名密钥"      // 从后台获取
    private const val BASE_URL = "https://www.keyt.cn/kami/$USER_NAME/check.php"
    
    private const val TIMESTAMP_MAX_DIFF = 120
    private const val HEARTBEAT_INTERVAL_MS = 50000L  // 50秒
    
    // ==================== 全局变量 ====================
    @Volatile
    private var currentCard = ""
    @Volatile
    private var currentMac = ""
    @Volatile
    private var failCount = 0
    @Volatile
    private var isRunning = true
    
    // ==================== 工具函数 ====================
    
    private fun getTimestamp(): Long = System.currentTimeMillis() / 1000
    
    private fun md5(input: String): String {
        val digest = MessageDigest.getInstance("MD5").digest(input.toByteArray())
        return digest.joinToString("") { "%02x".format(it) }
    }
    
    private fun getMachineCode(): String {
        return try {
            val networkInterface = java.net.NetworkInterface.getNetworkInterfaces().nextElement()
            val mac = networkInterface.hardwareAddress
            if (mac != null) {
                val macStr = mac.joinToString("") { "%02X".format(it) }
                if (macStr != "000000000000") "${macStr}MAC" else "获取失败"
            } else "获取失败"
        } catch (e: Exception) {
            println("[机器码异常] ${e.message}")
            "获取失败"
        }
    }
    
    private fun httpGet(urlString: String): String {
        return try {
            val url = URL(urlString)
            val conn = url.openConnection() as HttpURLConnection
            conn.requestMethod = "GET"
            conn.connectTimeout = 10000
            conn.readTimeout = 10000
            conn.setRequestProperty("Cache-Control", "no-cache")
            
            if (conn.responseCode == 200) {
                BufferedReader(InputStreamReader(conn.inputStream)).use { reader ->
                    reader.readText()
                }
            } else ""
        } catch (e: Exception) {
            println("[HTTP] 请求异常: ${e.message}")
            ""
        }
    }
    
    private fun verifyResponse(raw: String): String? {
        val signIndex = raw.indexOf("|sign=")
        if (signIndex == -1) {
            println("[验签] ❌ 未找到签名标记")
            return null
        }
        
        val body = raw.substring(0, signIndex)
        val sign = raw.substring(signIndex + 6)
        
        val localSign = md5(body + SIGN_KEY)
        println("[验签] 本地签名: $localSign")
        println("[验签] 服务器签名: $sign")
        
        if (localSign != sign) {
            println("[验签] ❌ 签名不匹配")
            return null
        }
        println("[验签] ✅ 签名校验通过")
        
        val lastPipe = body.lastIndexOf('|')
        if (lastPipe == -1) {
            println("[验签] ❌ 未找到时间戳")
            return null
        }
        
        val tsStr = body.substring(lastPipe + 1)
        val ts = tsStr.toLong()
        val nowTs = getTimestamp()
        val diff = Math.abs(nowTs - ts)
        
        println("[验签] 服务器时间戳: $ts")
        println("[验签] 本地时间戳: $nowTs")
        println("[验签] 时间差: ${diff} 秒 (最大允许: $TIMESTAMP_MAX_DIFF 秒)")
        
        if (diff > TIMESTAMP_MAX_DIFF) {
            println("[验签] ❌ 时间戳过期")
            return null
        }
        println("[验签] ✅ 时间戳校验通过")
        
        val biz = body.substring(0, lastPipe)
        println("[验签] 业务内容: $biz")
        return biz
    }
    
    private fun getCardSwitch(): String {
        repeat(3) { i ->
            val ts = getTimestamp()
            val url = "$BASE_URL?act=get_switch&app=$APP_NAME&t=$ts"
            println("[开关] 第 ${i + 1} 次尝试: $url")
            
            val response = httpGet(url)
            if (response.isNotEmpty()) {
                val biz = verifyResponse(response)
                if (biz != null) {
                    when {
                        biz.contains("CARD_ON") -> {
                            println("[开关] ✅ 卡密验证已开启")
                            return "CARD_ON"
                        }
                        biz.contains("CARD_OFF") -> {
                            println("[开关] ⚠️ 卡密验证已关闭")
                            return "CARD_OFF"
                        }
                    }
                }
            }
            Thread.sleep(500)
        }
        println("[开关] ⚠️ 开关获取失败,默认开启验证")
        return "CARD_ON"
    }
    
    private fun verifyCard(card: String, mac: String): String? {
        val ts = getTimestamp()
        val url = "$BASE_URL?card=$card&mac=$mac&app=$APP_NAME&heart=1&t=$ts"
        println("[验证] 请求URL: $url")
        println("[验证] 当前应用: $APP_NAME")
        println("[验证] 使用卡密: $card")
        
        val response = httpGet(url)
        if (response.isEmpty()) {
            println("[验证] 网络错误")
            return null
        }
        
        println("[验证] 服务器返回: $response")
        return verifyResponse(response)
    }
    
    private fun parseRemainingTime(biz: String) {
        val parts = biz.split("|")
        when {
            parts.size >= 3 -> {
                val days = parts[2].toInt()
                val minutes = if (parts.size >= 4) parts[3].toInt() else 0
                println("[时间] 剩余天数: $days 天, 剩余分钟: $minutes 分钟")
                
                if (minutes > 0) {
                    val hours = minutes / 60
                    val mins = minutes % 60
                    println("[时间] 详细: ${days}天${hours}小时${mins}分钟")
                }
            }
            parts.size >= 2 && parts[1] == "permanent" -> println("[时间] 终身有效")
        }
    }
    
    private fun transMsg(code: String): String = when (code) {
        "activate" -> "激活成功"
        "valid" -> "验证通过"
        "permanent" -> "终身有效"
        "expired" -> "卡密已过期"
        "banned" -> "卡密已被禁用"
        "device_mismatch" -> "设备不匹配"
        "online_limit_reached" -> "在线设备数已满"
        "invalid_card" -> "卡密无效"
        else -> code
    }
    
    private fun heartbeatLoop() {
        println("[心跳] 启动心跳线程")
        failCount = 0
        
        while (isRunning) {
            if (currentCard.isEmpty() || currentMac.isEmpty()) {
                Thread.sleep(2000)
                continue
            }
            
            val biz = verifyCard(currentCard, currentMac)
            if (biz != null && biz.startsWith("ok|")) {
                failCount = 0
                println("[心跳] ✅ 心跳正常")
                parseRemainingTime(biz)
            } else {
                failCount++
                println("[心跳] ❌ 心跳异常,连续失败: $failCount/5")
                if (failCount >= 5) {
                    println("[心跳] 连续失败5次,退出程序")
                    isRunning = false
                    System.exit(0)
                }
            }
            Thread.sleep(HEARTBEAT_INTERVAL_MS)
        }
    }
    
    private fun userMainThread() {
        while (isRunning) {
            println("[用户代码] 用户代码运行中... 当前时间: ${getTimestamp()}")
            Thread.sleep(5000)
        }
    }
    
    // ==================== 主函数 ====================
    
    @JvmStatic
    fun main(args: Array<String>) {
        println("========================================")
        println("  卡密通 Kotlin 对接示例")
        println("========================================")
        
        // 1. 获取机器码
        currentMac = getMachineCode()
        if (currentMac == "获取失败") {
            println("[错误] 获取机器码失败")
            return
        }
        println("[机器码] $currentMac")
        
        // 2. 获取验证开关
        val cardSwitch = getCardSwitch()
        if (cardSwitch == "CARD_OFF") {
            println("[提示] 验证已关闭,直接进入")
            currentCard = "88888888"
            thread { heartbeatLoop() }
            userMainThread()
            return
        }
        
        // 3. 输入卡密
        print("请输入卡密: ")
        currentCard = readlnOrNull()?.trim() ?: ""
        
        if (currentCard.isEmpty()) {
            println("[错误] 卡密不能为空")
            return
        }
        
        // 4. 验证卡密
        println("[验证] 验证中...")
        val biz = verifyCard(currentCard, currentMac)
        
        if (biz == null) {
            println("[错误] 验证失败:签名错误或网络异常")
            return
        }
        
        when {
            biz.startsWith("ok|") -> {
                println("[成功] 验证通过!")
                parseRemainingTime(biz)
                thread { heartbeatLoop() }
                userMainThread()
            }
            biz.startsWith("error|") -> {
                val errCode = biz.split("|")[1]
                println("[失败] ${transMsg(errCode)}")
            }
            else -> println("[失败] $biz")
        }
    }
}
📄 Objective-C语言对接码
易语言 438 行
//
//  KamiManager.m
//  卡密验证系统(基于新签名机制)
//  使用方法:调用 [KamiManager start] 即可
//

#import <UIKit/UIKit.h>
#import <CommonCrypto/CommonDigest.h>

// ========== 卡密服务器配置(请根据实际修改)==========
#define KAMI_BASE_URL      @"https://www.keyt.cn/kami/你的用户名/check.php"
#define KAMI_APP_NAME      @"你的应用名"
#define KAMI_SIGN_KEY      @"你的签名密钥"        // 从后台「修改签名密钥」获取

// ========== 时间配置 ==========
#define TIMESTAMP_MAX_DIFF 120       // 时间戳最大容差(秒)
#define HEARTBEAT_INTERVAL 50        // 心跳间隔(秒)

// ========== 持久化 Key ==========
static NSString * const kSavedCardKey    = @"com.kami.saved.card";
static NSString * const kDeviceIDKey     = @"com.kami.device.id";
static NSString * const kInvalidCardsKey = @"com.kami.invalid.cards";

// ========== 全局状态 ==========
static NSString *g_deviceID = nil;
static NSString *g_currentCard = nil;
static dispatch_source_t g_heartbeatTimer = nil;
static BOOL g_isVerified = NO;
static BOOL g_isRunning = YES;
static NSInteger g_failCount = 0;

@interface KamiManager : NSObject
@property (nonatomic, weak) UIAlertController *currentAlert;
+ (void)start;
+ (void)stop;
@end

@implementation KamiManager

#pragma mark - 启动入口

+ (void)start {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        KamiManager *manager = [[KamiManager alloc] init];
        [manager prepareDeviceID];
        [manager attemptAutoLogin];
    });
}

+ (void)stop {
    g_isRunning = NO;
    if (g_heartbeatTimer) {
        dispatch_source_cancel(g_heartbeatTimer);
        g_heartbeatTimer = nil;
    }
}

#pragma mark - 设备ID

- (void)prepareDeviceID {
    g_deviceID = [self getDeviceID];
    NSLog(@"[卡密] 设备ID: %@", g_deviceID);
}

- (NSString *)getDeviceID {
    NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
    if (idfv.length > 0) return idfv;
    
    NSString *saved = [[NSUserDefaults standardUserDefaults] stringForKey:kDeviceIDKey];
    if (saved.length > 0) return saved;
    
    NSString *newUUID = [[NSUUID UUID] UUIDString];
    [[NSUserDefaults standardUserDefaults] setObject:newUUID forKey:kDeviceIDKey];
    [[NSUserDefaults standardUserDefaults] synchronize];
    return newUUID;
}

#pragma mark - MD5 签名

- (NSString *)md5:(NSString *)input {
    const char *cStr = [input UTF8String];
    unsigned char digest[CC_MD5_DIGEST_LENGTH];
    CC_MD5(cStr, (CC_LONG)strlen(cStr), digest);
    
    NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
        [output appendFormat:@"%02x", digest[i]];
    }
    return output;
}

#pragma mark - 时间戳

- (NSTimeInterval)currentTimestamp {
    return [[NSDate date] timeIntervalSince1970];
}

#pragma mark - 验证响应(签名 + 时间戳)

- (NSString *)verifyResponse:(NSString *)rawResponse {
    if (!rawResponse || rawResponse.length == 0) {
        NSLog(@"[验签] ❌ 返回数据为空");
        return nil;
    }
    
    NSRange signRange = [rawResponse rangeOfString:@"|sign="];
    if (signRange.location == NSNotFound) {
        NSLog(@"[验签] ❌ 未找到签名标记");
        return nil;
    }
    
    NSString *body = [rawResponse substringToIndex:signRange.location];
    NSString *sign = [rawResponse substringFromIndex:signRange.location + 6];
    
    NSString *localSign = [self md5:[body stringByAppendingString:KAMI_SIGN_KEY]];
    NSLog(@"[验签] 本地签名: %@", localSign);
    NSLog(@"[验签] 服务器签名: %@", sign);
    
    if (![localSign isEqualToString:sign]) {
        NSLog(@"[验签] ❌ 签名不匹配");
        return nil;
    }
    NSLog(@"[验签] ✅ 签名校验通过");
    
    NSRange lastPipe = [body rangeOfString:@"|" options:NSBackwardsSearch];
    if (lastPipe.location == NSNotFound) {
        NSLog(@"[验签] ❌ 未找到时间戳");
        return nil;
    }
    
    NSString *tsStr = [body substringFromIndex:lastPipe.location + 1];
    NSTimeInterval serverTs = [tsStr doubleValue];
    NSTimeInterval localTs = [self currentTimestamp];
    NSTimeInterval diff = fabs(localTs - serverTs);
    
    NSLog(@"[验签] 服务器时间戳: %.0f", serverTs);
    NSLog(@"[验签] 本地时间戳: %.0f", localTs);
    NSLog(@"[验签] 时间差: %.0f 秒 (最大允许: %d 秒)", diff, TIMESTAMP_MAX_DIFF);
    
    if (diff > TIMESTAMP_MAX_DIFF) {
        NSLog(@"[验签] ❌ 时间戳过期");
        return nil;
    }
    NSLog(@"[验签] ✅ 时间戳校验通过");
    
    NSString *biz = [body substringToIndex:lastPipe.location];
    NSLog(@"[验签] 业务内容: %@", biz);
    return biz;
}

#pragma mark - HTTP 请求

- (void)sendRequestWithURL:(NSString *)urlString
                completion:(void(^)(NSString *response, NSError *error))completion {
    NSURL *url = [NSURL URLWithString:urlString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"GET";
    request.timeoutInterval = 10.0;
    [request setValue:@"no-cache" forHTTPHeaderField:@"Cache-Control"];
    
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request
                                                                 completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            if (completion) completion(nil, error);
            return;
        }
        NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        if (completion) completion(responseString, nil);
    }];
    [task resume];
}

#pragma mark - 验证卡密

- (void)verifyCard:(NSString *)card
          deviceID:(NSString *)deviceID
        completion:(void(^)(NSString *biz, NSError *error))completion {
    NSTimeInterval ts = [self currentTimestamp];
    NSString *url = [NSString stringWithFormat:@"%@?card=%@&mac=%@&app=%@&heart=1&t=%.0f",
                     KAMI_BASE_URL, card, deviceID, KAMI_APP_NAME, ts];
    NSLog(@"[验证] URL: %@", url);
    
    [self sendRequestWithURL:url completion:^(NSString *response, NSError *error) {
        if (error) {
            if (completion) completion(nil, error);
            return;
        }
        NSLog(@"[验证] 返回: %@", response);
        NSString *biz = [self verifyResponse:response];
        if (completion) completion(biz, nil);
    }];
}

#pragma mark - 解析剩余时间

- (NSString *)parseRemainingTime:(NSString *)biz {
    NSArray *parts = [biz componentsSeparatedByString:@"|"];
    if (parts.count >= 3) {
        NSInteger days = [parts[2] integerValue];
        NSInteger minutes = (parts.count >= 4) ? [parts[3] integerValue] : 0;
        if (minutes > 0) {
            NSInteger hours = minutes / 60;
            NSInteger mins = minutes % 60;
            return [NSString stringWithFormat:@"%ld天%ld小时%ld分钟", (long)days, (long)hours, (long)mins];
        }
        return [NSString stringWithFormat:@"%ld天", (long)days];
    }
    return @"";
}

#pragma mark - 错误码转中文

- (NSString *)transMsg:(NSString *)code {
    NSDictionary *map = @{
        @"expired": @"卡密已过期",
        @"banned": @"卡密已被禁用",
        @"device_mismatch": @"设备不匹配",
        @"invalid_card": @"卡密无效"
    };
    return map[code] ?: code;
}

#pragma mark - 黑名单

- (BOOL)isCardInvalid:(NSString *)card {
    NSArray *invalid = [[NSUserDefaults standardUserDefaults] arrayForKey:kInvalidCardsKey];
    return [invalid containsObject:card];
}

- (void)addCardToInvalidList:(NSString *)card {
    if (card.length == 0) return;
    NSMutableArray *invalid = [[[NSUserDefaults standardUserDefaults] arrayForKey:kInvalidCardsKey] mutableCopy];
    if (!invalid) invalid = [NSMutableArray array];
    if (![invalid containsObject:card]) {
        [invalid addObject:card];
        [[NSUserDefaults standardUserDefaults] setObject:invalid forKey:kInvalidCardsKey];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
}

- (void)removeCardFromInvalidList:(NSString *)card {
    if (card.length == 0) return;
    NSMutableArray *invalid = [[[NSUserDefaults standardUserDefaults] arrayForKey:kInvalidCardsKey] mutableCopy];
    if (invalid && [invalid containsObject:card]) {
        [invalid removeObject:card];
        [[NSUserDefaults standardUserDefaults] setObject:invalid forKey:kInvalidCardsKey];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
}

#pragma mark - 自动登录

- (void)attemptAutoLogin {
    NSString *saved = [[NSUserDefaults standardUserDefaults] stringForKey:kSavedCardKey];
    if (saved.length > 0 && ![self isCardInvalid:saved]) {
        g_currentCard = saved;
        [self performSilentVerification];
    } else {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self showCardInputAlert];
        });
    }
}

- (void)performSilentVerification {
    __weak typeof(self) weakSelf = self;
    [self verifyCard:g_currentCard deviceID:g_deviceID completion:^(NSString *biz, NSError *error) {
        if (error || !biz) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf showMessageAndExit:@"网络异常"];
            });
            return;
        }
        if ([biz hasPrefix:@"ok|"]) {
            [weakSelf removeCardFromInvalidList:g_currentCard];
            g_isVerified = YES;
            NSLog(@"[卡密] 自动登录成功");
            [weakSelf startHeartbeat];
            [weakSelf startUserMainThread];
        } else {
            [weakSelf addCardToInvalidList:g_currentCard];
            [[NSUserDefaults standardUserDefaults] removeObjectForKey:kSavedCardKey];
            NSString *errMsg = biz;
            if ([biz hasPrefix:@"error|"]) {
                NSArray *parts = [biz componentsSeparatedByString:@"|"];
                if (parts.count >= 2) errMsg = [weakSelf transMsg:parts[1]];
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf showMessageAndExit:errMsg];
            });
        }
    }];
}

- (void)performFirstVerification:(NSString *)card {
    __weak typeof(self) weakSelf = self;
    [self verifyCard:card deviceID:g_deviceID completion:^(NSString *biz, NSError *error) {
        if (error || !biz) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf showMessageAndExit:@"网络异常"];
            });
            return;
        }
        if ([biz hasPrefix:@"ok|"]) {
            [weakSelf removeCardFromInvalidList:card];
            [[NSUserDefaults standardUserDefaults] setObject:card forKey:kSavedCardKey];
            g_currentCard = card;
            g_isVerified = YES;
            NSString *timeStr = [weakSelf parseRemainingTime:biz];
            [weakSelf showSuccessWithMessage:[NSString stringWithFormat:@"验证成功\n剩余:%@", timeStr]];
            [weakSelf startHeartbeat];
            [weakSelf startUserMainThread];
        } else if ([biz hasPrefix:@"error|"]) {
            [weakSelf addCardToInvalidList:card];
            NSArray *parts = [biz componentsSeparatedByString:@"|"];
            NSString *errMsg = (parts.count >= 2) ? [weakSelf transMsg:parts[1]] : biz;
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf showMessageAndExit:errMsg];
            });
        }
    }];
}

#pragma mark - 心跳

- (void)startHeartbeat {
    if (g_heartbeatTimer) {
        dispatch_source_cancel(g_heartbeatTimer);
        g_heartbeatTimer = nil;
    }
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    g_heartbeatTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(g_heartbeatTimer,
                              dispatch_time(DISPATCH_TIME_NOW, HEARTBEAT_INTERVAL * NSEC_PER_SEC),
                              HEARTBEAT_INTERVAL * NSEC_PER_SEC,
                              1 * NSEC_PER_SEC);
    
    __weak typeof(self) weakSelf = self;
    dispatch_source_set_event_handler(g_heartbeatTimer, ^{
        if (!g_isRunning || !g_currentCard) return;
        [weakSelf verifyCard:g_currentCard deviceID:g_deviceID completion:^(NSString *biz, NSError *error) {
            if (error || !biz) {
                g_failCount++;
                if (g_failCount >= 5) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [weakSelf showMessageAndExit:@"心跳失败"];
                    });
                }
                return;
            }
            if ([biz hasPrefix:@"ok|"]) {
                g_failCount = 0;
                NSLog(@"[心跳] 正常");
            } else {
                g_failCount++;
                if (g_failCount >= 5) {
                    [weakSelf addCardToInvalidList:g_currentCard];
                    [[NSUserDefaults standardUserDefaults] removeObjectForKey:kSavedCardKey];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [weakSelf showMessageAndExit:@"卡密已失效"];
                    });
                }
            }
        }];
    });
    dispatch_resume(g_heartbeatTimer);
}

#pragma mark - 用户主线程(占位)

- (void)startUserMainThread {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (g_isRunning && g_isVerified) {
            // ========== 你的代码写这里 ==========
            // ==================================
            [NSThread sleepForTimeInterval:1.0];
        }
    });
}

#pragma mark - UI

- (void)showCardInputAlert {
    dispatch_async(dispatch_get_main_queue(), ^{
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"卡密验证"
                                                                       message:@"请输入卡密"
                                                                preferredStyle:UIAlertControllerStyleAlert];
        [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
            textField.placeholder = @"卡密";
        }];
        
        __weak typeof(self) weakSelf = self;
        [alert addAction:[UIAlertAction actionWithTitle:@"验证" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
            NSString *input = [alert.textFields.firstObject.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
            if (input.length == 0) exit(0);
            [weakSelf performFirstVerification:input];
        }]];
        [alert addAction:[UIAlertAction actionWithTitle:@"退出" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
            exit(0);
        }]];
        
        UIViewController *root = [UIApplication sharedApplication].windows.firstObject.rootViewController;
        [root presentViewController:alert animated:YES completion:nil];
        self.currentAlert = alert;
    });
}

- (void)showSuccessWithMessage:(NSString *)message {
    dispatch_async(dispatch_get_main_queue(), ^{
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"成功"
                                                                       message:message
                                                                preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
        UIViewController *root = [UIApplication sharedApplication].windows.firstObject.rootViewController;
        [root presentViewController:alert animated:YES completion:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            [alert dismissViewControllerAnimated:YES completion:nil];
        });
    });
}

- (void)showMessageAndExit:(NSString *)message {
    dispatch_async(dispatch_get_main_queue(), ^{
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示"
                                                                       message:message
                                                                preferredStyle:UIAlertControllerStyleAlert];
        UIViewController *root = [UIApplication sharedApplication].windows.firstObject.rootViewController;
        [root presentViewController:alert animated:YES completion:^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
                exit(0);
            });
        }];
    });
}

@end
📄 Python验证模块
Python 918 行
# ============================================================
# 🔧 配置区(开发者修改这里)
# ============================================================

class Config:
    # ============================================================
    # 必填配置(由服务端提供)
    # ============================================================
    
    # 你的用户名(服务端分配)
    USER_ID = "qq345205377"
    
    # 应用名(和服务器一致)
    APP_NAME = "a"
    
    # 签名密钥(和服务器一致)
    SIGN_KEY = "keyt@2026"
    
    
    # ============================================================
    # 可选配置
    # ============================================================
    
    # 时间容差(秒),防止重放攻击
    TIMESTAMP_MAX_DIFF = 120
    
    # 是否启用卡密缓存(记住卡密)
    ENABLE_CACHE = True
    
    # 调试模式(True=打印日志,False=静默)
    DEBUG_MODE = False
    
    # 窗口标题
    GUI_TITLE = "软件验证"
    
    # 窗口大小
    GUI_WIDTH = 420
    GUI_HEIGHT = 520
    
    # 请求超时时间(秒)
    REQUEST_TIMEOUT = 20
    
    # 请求失败最大重试次数
    MAX_RETRIES = 3
    
    # 验证冷却时间(秒)
    VERIFY_COOLDOWN = 50
    
    # ============================================================
    # 心跳配置
    # ============================================================
    
    # 心跳间隔(秒)【低于50可能封号】
    HEARTBEAT_INTERVAL = 55
    
    # 首次心跳延迟(秒)
    HEARTBEAT_INITIAL_DELAY = 55
    
    # 心跳最大失败次数(超过则退出)
    MAX_HEARTBEAT_FAILS = 3


# ============================================================
# 📦 核心代码(以下不需要修改,直接使用)
# ============================================================

import tkinter as tk
from tkinter import ttk, messagebox
import urllib.request
import urllib.parse
import hashlib
import datetime
import time
import ssl
import gzip
import uuid
import random
import os
import json
import sys
import threading


# ==================== 浏览器头 ====================
BROWSER_HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Cache-Control": "no-cache",
}

_g_last_xsign2 = ""


# ==================== 日志 ====================
def _log(msg):
    if Config.DEBUG_MODE:
        print(f"[{datetime.datetime.now().strftime('%H:%M:%S')}] {msg}")


# ==================== 获取UTC时间戳(兼容Python 3.13+) ====================
def get_utc_timestamp():
    try:
        return int(datetime.datetime.now(datetime.timezone.utc).timestamp())
    except AttributeError:
        return int((datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1)).total_seconds())


# ==================== 网络请求 ====================
def _urlopen_browser_style(url, timeout=None, max_retries=None):
    global _g_last_xsign2
    
    timeout = timeout or Config.REQUEST_TIMEOUT
    max_retries = max_retries or Config.MAX_RETRIES
    
    _log(f"📤 请求: {url[:100]}...")
    
    for attempt in range(max_retries):
        try:
            req = urllib.request.Request(url)
            for key, value in BROWSER_HEADERS.items():
                req.add_header(key, value)
            time.sleep(random.uniform(0.1, 0.5))
            context = ssl._create_unverified_context()
            with urllib.request.urlopen(req, timeout=timeout, context=context) as resp:
                data = resp.read()
                xsign2 = resp.headers.get('X-Sign2', '')
                if xsign2:
                    _g_last_xsign2 = xsign2
                if 'gzip' in resp.headers.get('Content-Encoding', ''):
                    data = gzip.decompress(data)
                _log(f"📥 响应: {len(data)} bytes")
                return data
        except Exception as e:
            _log(f"⚠️ 请求失败 (尝试 {attempt+1}/{max_retries}): {e}")
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)
    return None


def get_last_xsign2():
    return _g_last_xsign2


# ==================== 机器码 ====================
def get_machine_code():
    try:
        mac = uuid.UUID(int=uuid.getnode()).hex[-12:].upper()
        return mac + "MAC"
    except:
        return "FFFFFFFFFFFFMAC"


# ==================== MD5签名验证(核心安全机制) ====================
def verify_response(raw_body, xsign2_header="", sign_key=None):
    """
    MD5签名验证
    
    服务器返回格式: 数据|sign=MD5(数据 + 密钥)
    
    验证流程:
        1. 从返回中提取 body 和 sign
        2. 本地计算: local_sign = MD5(body + SIGN_KEY)
        3. 比对 local_sign == sign
        4. 验证时间戳(防重放攻击)
    """
    sign_key = sign_key or Config.SIGN_KEY
    
    # 1. 提取签名
    pos = raw_body.find("|sign=")
    if pos == -1:
        _log("❌ 签名验证失败: 返回数据缺少签名")
        return ""
    
    body = raw_body[:pos]
    sign = raw_body[pos + 6:]
    
    # 2. 本地计算MD5
    sign_str = body + sign_key
    local_sign = hashlib.md5(sign_str.encode()).hexdigest().lower()
    
    _log(f"🔑 本地签名: {local_sign}")
    _log(f"🔑 服务器签名: {sign}")
    
    # 3. 验证签名
    sign_valid = False
    if xsign2_header and local_sign == xsign2_header.lower():
        sign_valid = True
        _log("✅ X-Sign2 验证通过")
    elif local_sign == sign.lower():
        sign_valid = True
        _log("✅ MD5签名验证通过")
    
    if not sign_valid:
        _log("❌ 签名验证失败: 签名不匹配")
        return ""
    
    # 4. 验证时间戳(防重放攻击)
    last_pipe = body.rfind("|")
    if last_pipe == -1:
        _log("❌ 时间戳验证失败: 缺少时间戳")
        return ""
    
    ts_str = body[last_pipe + 1:]
    if not ts_str.isdigit():
        _log("❌ 时间戳验证失败: 格式错误")
        return ""
    
    server_ts = int(ts_str)
    local_ts = get_utc_timestamp()
    diff = abs(local_ts - server_ts)
    
    if diff > Config.TIMESTAMP_MAX_DIFF:
        _log(f"❌ 时间戳验证失败: 相差 {diff} 秒 (最大允许 {Config.TIMESTAMP_MAX_DIFF} 秒)")
        return ""
    
    _log(f"✅ 时间戳验证通过 (相差 {diff} 秒)")
    
    return body[:last_pipe]


# ==================== 解析剩余时间 ====================
def parse_remaining_time(resp):
    if not resp or "|" not in resp:
        return None
    
    parts = resp.split("|")
    
    if "bypass" in resp:
        return "验证已关闭"
    
    if len(parts) >= 2 and parts[1] == "permanent":
        return "永久有效"
    
    if len(parts) >= 3 and parts[2] == "permanent":
        return "永久有效"
    
    total_mins = None
    if len(parts) >= 4 and parts[3].isdigit():
        total_mins = int(parts[3])
    elif len(parts) >= 2 and parts[1].isdigit():
        days = int(parts[1])
        total_mins = days * 1440
    
    if total_mins is not None:
        days = total_mins // 1440
        hours = (total_mins % 1440) // 60
        mins = total_mins % 60
        if days > 0:
            result = f"剩余 {days} 天"
            if hours > 0:
                result += f" {hours} 小时"
            if mins > 0:
                result += f" {mins} 分钟"
            return result
        elif hours > 0:
            result = f"剩余 {hours} 小时"
            if mins > 0:
                result += f" {mins} 分钟"
            return result
        else:
            return f"剩余 {mins} 分钟"
    
    return None


def translate_error(code):
    msg_map = {
        "already_online": "该卡密已在线",
        "online_limit_reached": "在线设备数已满",
        "invalid_card": "卡密无效",
        "expired": "卡密已过期",
        "banned": "卡密已被禁用",
        "device_mismatch": "设备不匹配",
        "missing_params": "参数不完整",
        "too_frequent": "验证过于频繁,请50秒后再试",
        "system_locked": "系统已被管理员锁死",
    }
    return msg_map.get(code, code)


# ==================== 缓存 ====================
def _get_cache_path():
    try:
        cache_dir = os.path.join(os.environ.get('APPDATA', os.path.expanduser('~')), 'KamiCache')
        os.makedirs(cache_dir, exist_ok=True)
        return os.path.join(cache_dir, f"{Config.APP_NAME}.cache")
    except:
        return None


def save_cache(card):
    if not Config.ENABLE_CACHE:
        return
    cache_path = _get_cache_path()
    if not cache_path:
        return
    try:
        data = f"{card}|{Config.USER_ID}|{Config.APP_NAME}"
        key = hashlib.md5(Config.SIGN_KEY.encode()).digest()
        encrypted = bytearray()
        for i, ch in enumerate(data):
            encrypted.append(ord(ch) ^ key[i % len(key)])
        with open(cache_path, "wb") as f:
            f.write(bytes(encrypted))
        _log(f"卡密已缓存: {card[:4]}****")
    except Exception as e:
        _log(f"缓存保存失败: {e}")


def load_cache():
    if not Config.ENABLE_CACHE:
        return None
    cache_path = _get_cache_path()
    if not cache_path or not os.path.exists(cache_path):
        return None
    try:
        with open(cache_path, "rb") as f:
            encrypted = f.read()
        key = hashlib.md5(Config.SIGN_KEY.encode()).digest()
        decrypted = bytearray()
        for i, b in enumerate(encrypted):
            decrypted.append(b ^ key[i % len(key)])
        parts = bytes(decrypted).decode("utf-8").split("|")
        if len(parts) >= 3 and len(parts[0]) >= 6:
            if parts[1] == Config.USER_ID and parts[2] == Config.APP_NAME:
                _log(f"从缓存加载卡密")
                return parts[0]
    except Exception as e:
        _log(f"缓存加载失败: {e}")
    return None


# ==================== 核心验证函数 ====================
def verify_card(card):
    """
    验证卡密
    
    返回:
        {
            'success': bool,
            'message': str,
            'remaining': str or None,
            'raw': str or None,
            'card': str
        }
    """
    global _g_last_xsign2
    _g_last_xsign2 = ""
    
    _log(f"🔍 验证卡密: {card[:4]}****")
    
    mac = get_machine_code()
    t = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    url = f"https://www.keyt.cn/kami/{Config.USER_ID}/check.php"
    params = {
        "card": card,
        "mac": mac,
        "app": Config.APP_NAME,
        "heart": "1",
        "t": t,
        "_t": int(time.time())
    }
    full_url = url + "?" + urllib.parse.urlencode(params)
    
    for retry in range(Config.MAX_RETRIES):
        try:
            data = _urlopen_browser_style(full_url)
            if data:
                raw_body = data.decode("utf-8")
                _log(f"📥 原始返回: {raw_body}")
                
                # ===== MD5签名验证 =====
                xsign2 = get_last_xsign2()
                biz = verify_response(raw_body, xsign2)
                
                if not biz:
                    return {
                        'success': False,
                        'message': '签名校验失败',
                        'remaining': None,
                        'raw': raw_body,
                        'card': card
                    }
                
                # ===== 解析业务数据 =====
                if "system_locked" in biz:
                    return {
                        'success': False,
                        'message': '系统已被管理员锁死',
                        'remaining': None,
                        'raw': raw_body,
                        'card': card
                    }
                
                if biz.startswith("ok|") or "bypass" in biz:
                    remaining = parse_remaining_time(biz)
                    return {
                        'success': True,
                        'message': '验证成功',
                        'remaining': remaining,
                        'raw': raw_body,
                        'card': card
                    }
                
                if "error|" in biz:
                    error_code = biz.split("|")[1] if len(biz.split("|")) > 1 else ""
                    return {
                        'success': False,
                        'message': translate_error(error_code),
                        'remaining': None,
                        'raw': raw_body,
                        'card': card
                    }
                
                return {
                    'success': False,
                    'message': biz,
                    'remaining': None,
                    'raw': raw_body,
                    'card': card
                }
                
        except Exception as e:
            _log(f"⚠️ 验证异常: {e}")
            if retry < Config.MAX_RETRIES - 1:
                time.sleep(1)
    
    return {
        'success': False,
        'message': '验证超时或服务器无响应',
        'remaining': None,
        'raw': None,
        'card': card
    }


# ==================== 心跳 ====================
def start_heartbeat(card, on_fail=None):
    """
    启动心跳验证(每分钟验证一次)
    
    参数:
        card: 卡密
        on_fail: 心跳失败回调 function(reason)
    """
    def heartbeat_loop():
        # 首次延迟
        time.sleep(Config.HEARTBEAT_INITIAL_DELAY)
        
        fail_count = 0
        _log(f"💓 心跳已启动 (间隔: {Config.HEARTBEAT_INTERVAL}s, 最大失败: {Config.MAX_HEARTBEAT_FAILS}次)")
        
        while True:
            time.sleep(Config.HEARTBEAT_INTERVAL)
            
            try:
                _log(f"💓 心跳验证...")
                result = verify_card(card)
                
                if result['success']:
                    fail_count = 0
                    _log(f"✅ 心跳成功: {result.get('remaining', 'OK')}")
                else:
                    fail_count += 1
                    _log(f"❌ 心跳失败 ({fail_count}/{Config.MAX_HEARTBEAT_FAILS}): {result['message']}")
                    
                    if fail_count >= Config.MAX_HEARTBEAT_FAILS:
                        _log(f"💀 心跳连续失败 {Config.MAX_HEARTBEAT_FAILS} 次,退出程序")
                        if on_fail:
                            on_fail(result['message'])
                        # 强制退出
                        os._exit(0)
                        
            except Exception as e:
                fail_count += 1
                _log(f"⚠️ 心跳异常 ({fail_count}/{Config.MAX_HEARTBEAT_FAILS}): {e}")
                
                if fail_count >= Config.MAX_HEARTBEAT_FAILS:
                    _log(f"💀 心跳连续失败 {Config.MAX_HEARTBEAT_FAILS} 次,退出程序")
                    if on_fail:
                        on_fail(str(e))
                    os._exit(0)
    
    # 启动心跳线程(守护线程)
    thread = threading.Thread(target=heartbeat_loop, daemon=True)
    thread.start()
    return thread


# ==================== 辅助函数 ====================

def get_notice():
    """获取系统公告"""
    try:
        url = f"https://www.keyt.cn/kami/{Config.USER_ID}/check.php?act=get_notice&app={Config.APP_NAME}&_t={int(time.time())}"
        data = _urlopen_browser_style(url, timeout=15)
        if data:
            return data.decode("utf-8", errors="ignore")[:500]
    except Exception as e:
        _log(f"获取公告失败: {e}")
    return "暂无公告"


def get_version():
    """获取最新版本号"""
    try:
        url = f"https://www.keyt.cn/kami/{Config.USER_ID}/check.php?act=get_version&app={Config.APP_NAME}&_t={int(time.time())}"
        data = _urlopen_browser_style(url, timeout=15)
        if data:
            v = data.decode("utf-8").strip()
            return v if v and v != "0" else ""
    except Exception as e:
        _log(f"获取版本失败: {e}")
    return ""


def check_status():
    """检查系统开关状态"""
    global _g_last_xsign2
    _g_last_xsign2 = ""
    
    t = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    url = f"https://www.keyt.cn/kami/{Config.USER_ID}/check.php?act=get_switch&app={Config.APP_NAME}&t={t}&_t={int(time.time())}"
    
    try:
        data = _urlopen_browser_style(url, timeout=15)
        if data:
            raw_body = data.decode("utf-8")
            xsign2 = get_last_xsign2()
            biz = verify_response(raw_body, xsign2)
            if biz:
                if "system_locked" in biz:
                    return "LOCKED"
                if "CARD_ON" in biz:
                    return True
                elif "CARD_OFF" in biz:
                    return False
    except Exception as e:
        _log(f"检查状态失败: {e}")
    return True


# ==================== 验证窗口 ====================

class AuthWindow:
    def __init__(self, on_success=None, on_fail=None, on_heartbeat_fail=None):
        self.on_success = on_success
        self.on_fail = on_fail
        self.on_heartbeat_fail = on_heartbeat_fail
        self.result = None
        self._last_verify_time = None
        self.heartbeat_thread = None
        
        self.root = tk.Tk()
        self.root.title(Config.GUI_TITLE)
        self.root.geometry(f"{Config.GUI_WIDTH}x{Config.GUI_HEIGHT}")
        self.root.resizable(False, False)
        self.root.configure(bg="#ffffff")
        
        ws = self.root.winfo_screenwidth()
        hs = self.root.winfo_screenheight()
        self.root.geometry(f"{Config.GUI_WIDTH}x{Config.GUI_HEIGHT}+{(ws-Config.GUI_WIDTH)//2}+{(hs-Config.GUI_HEIGHT)//2}")
        
        self.root.attributes('-topmost', True)
        self.root.focus_force()
        
        # 状态
        self.notice = "加载中..."
        self.version = ""
        self.remaining_time = None
        self.verified_card = None
        self.cached_card = load_cache()
        self.verifying = False
        self.verified = False
        self.switch_off = False
        self.heartbeat_running = False
        
        # 构建UI
        self._build_ui()
        
        # 如果有缓存卡密,填入输入框
        if self.cached_card:
            self.card_entry.insert(0, self.cached_card)
            self.remember_var.set(True)
        
        # 异步加载数据
        threading.Thread(target=self._load_data, daemon=True).start()
        
        self.root.protocol("WM_DELETE_WINDOW", self._on_close)
    
    def _build_ui(self):
        # 标题
        tk.Label(self.root, text="🔐 软件验证", 
                 font=("微软雅黑", 20, "bold"), 
                 fg="#1a73e8", bg="#ffffff").pack(pady=(25, 5))
        
        # 安全标识
        security_frame = tk.Frame(self.root, bg="#e8f5e9", bd=0)
        security_frame.pack(fill="x", padx=30, pady=(0, 10))
        tk.Label(security_frame, text="🛡️  安全加密连接  ·  MD5签名校验",
                 font=("微软雅黑", 9, "bold"), fg="#2e7d32", bg="#e8f5e9").pack(pady=(8, 8))
        
        # 公告
        notice_frame = tk.Frame(self.root, bg="#f0f7ff", bd=1, relief="solid")
        notice_frame.pack(fill="x", padx=30, pady=(0, 8))
        tk.Label(notice_frame, text="📢 系统公告", 
                 font=("微软雅黑", 10, "bold"), fg="#1565c0", bg="#f0f7ff").pack(anchor="w", padx=12, pady=(8, 2))
        self.notice_label = tk.Label(notice_frame, text="加载中...",
                                      font=("微软雅黑", 9), fg="#555", bg="#f0f7ff",
                                      wraplength=340, justify="left")
        self.notice_label.pack(anchor="w", padx=12, pady=(2, 8))
        
        # 版本
        self.version_label = tk.Label(self.root, text="", 
                                       font=("微软雅黑", 9), fg="#888", bg="#ffffff")
        self.version_label.pack(pady=(0, 3))
        
        # 剩余时间
        self.time_frame = tk.Frame(self.root, bg="#e8f5e9", bd=0)
        self.time_frame.pack(fill="x", padx=30, pady=(3, 10))
        self.time_label = tk.Label(self.time_frame, text="",
                                    font=("微软雅黑", 10, "bold"), fg="#0d904f", bg="#e8f5e9")
        self.time_label.pack(pady=(6, 6))
        # 默认隐藏
        self.time_frame.pack_forget()
        
        # 分隔线
        tk.Frame(self.root, height=1, bg="#ddd").pack(fill="x", padx=30, pady=5)
        
        # 卡密输入
        tk.Label(self.root, text="请输入卡密", 
                 font=("微软雅黑", 12, "bold"), fg="#333", bg="#ffffff").pack(pady=(15, 5))
        
        self.card_entry = tk.Entry(self.root, width=28, font=("微软雅黑", 13), 
                                    justify="center", bd=2, relief="solid")
        self.card_entry.pack(pady=(0, 5))
        self.card_entry.focus()
        
        # 记住卡密
        self.remember_var = tk.BooleanVar(value=bool(self.cached_card))
        tk.Checkbutton(self.root, text="记住卡密", variable=self.remember_var,
                       font=("微软雅黑", 9), fg="#666", bg="#ffffff",
                       activebackground="#ffffff").pack(pady=(2, 10))
        
        # 验证按钮
        self.verify_btn = tk.Button(self.root, text="🔑  验证卡密",
                                     command=self._do_verify,
                                     bg="#1a73e8", fg="white",
                                     font=("微软雅黑", 13, "bold"),
                                     width=16, height=2, bd=0, cursor="hand2")
        self.verify_btn.pack(pady=(5, 5))
        
        # 状态
        self.status_label = tk.Label(self.root, text="", 
                                      font=("微软雅黑", 9), fg="#999", bg="#ffffff")
        self.status_label.pack(pady=(8, 0))
        
        # 绑定回车
        self.root.bind("<Return>", lambda e: self._do_verify())
    
    def _load_data(self):
        """异步加载数据"""
        try:
            # 获取公告
            notice = get_notice()
            self.notice = notice if notice else "暂无公告"
            
            # 获取版本
            version = get_version()
            self.version = version if version else ""
            
            # 检查开关状态
            status = check_status()
            if status == "LOCKED":
                self.root.after(0, lambda: self._show_error("系统已被管理员锁死"))
                return
            self.switch_off = (status == False)
            
            # 如果有缓存卡密,后台验证获取剩余时间
            if self.cached_card and not self.switch_off:
                self.root.after(0, lambda: self.status_label.config(text="正在验证缓存...", fg="blue"))
                result = verify_card(self.cached_card)
                if result['success']:
                    self.verified = True
                    self.verified_card = self.cached_card
                    self.remaining_time = result['remaining']
                    _log(f"✅ 缓存卡密验证成功: {result['remaining']}")
                    # 保存卡密
                    save_cache(self.cached_card)
                else:
                    _log(f"❌ 缓存卡密验证失败: {result['message']}")
                    self.cached_card = None
                    self.verified = False
            
            self.root.after(0, self._update_ui)
            
        except Exception as e:
            _log(f"加载数据失败: {e}")
    
    def _update_ui(self):
        try:
            # 更新公告
            self.notice_label.config(text=self.notice)
            
            # 更新版本
            if self.version:
                self.version_label.config(text=f"📌 当前版本:{self.version}")
            
            # 如果已验证成功,显示剩余时间
            if self.verified and self.remaining_time:
                self.time_label.config(text=f"⏱️  {self.remaining_time}")
                self.time_frame.pack(fill="x", padx=30, pady=(3, 10))
                self.status_label.config(text="✅ 已验证,点击进入", fg="green")
                self.verify_btn.config(text="🚀  进入程序", bg="#0d904f")
            else:
                self.time_frame.pack_forget()
                if self.cached_card and not self.verified:
                    self.status_label.config(text="⚠️ 缓存已失效,请重新输入卡密", fg="orange")
            
            # 如果是开关模式(无需验证)
            if self.switch_off:
                self.verify_btn.config(text="🚀  直接使用", bg="#0d904f", command=self._run_direct)
                
        except Exception as e:
            _log(f"更新UI失败: {e}")
    
    def _show_error(self, msg):
        for w in self.root.winfo_children():
            w.destroy()
        tk.Label(self.root, text=msg, font=("微软雅黑", 14), fg="red", bg="#ffffff").pack(expand=True)
        self.root.after(3000, self.root.destroy)
    
    def _do_verify(self):
        """点击验证/进入按钮"""
        if self.verifying:
            return
        
        # 如果已经验证成功,直接进入程序
        if self.verified and self.remaining_time:
            self._enter_program()
            return
        
        # 如果是开关模式(无需验证)
        if self.switch_off:
            self._run_direct()
            return
        
        card = self.card_entry.get().strip()
        if not card:
            self.status_label.config(text="请输入卡密", fg="red")
            return
        
        # 验证冷却
        if self._last_verify_time:
            elapsed = time.time() - self._last_verify_time
            if elapsed < Config.VERIFY_COOLDOWN:
                remaining = int(Config.VERIFY_COOLDOWN - elapsed)
                self.status_label.config(text=f"验证过于频繁,请 {remaining} 秒后再试", fg="orange")
                return
        
        self._last_verify_time = time.time()
        self.verifying = True
        self.status_label.config(text="验证中...", fg="blue")
        self.verify_btn.config(state="disabled", text="验证中...")
        self.root.update()
        
        def do():
            result = verify_card(card)
            self.root.after(0, lambda: self._on_verify_result(result))
        
        threading.Thread(target=do, daemon=True).start()
    
    def _on_verify_result(self, result):
        self.verifying = False
        self.verify_btn.config(state="normal", text="🔑  验证卡密")
        
        if result['success']:
            self.verified = True
            self.verified_card = result['card']
            self.remaining_time = result['remaining']
            self.result = result
            
            # 保存卡密
            if self.remember_var.get():
                save_cache(result['card'])
            
            self.status_label.config(text=f"✅ {result['message']}", fg="green")
            
            # 显示剩余时间
            if result['remaining']:
                self.time_label.config(text=f"⏱️  {result['remaining']}")
                self.time_frame.pack(fill="x", padx=30, pady=(3, 10))
            
            # 弹出验证成功消息
            messagebox.showinfo("验证成功", result['remaining'] or "验证通过!", parent=self.root)
            
            # 按钮变为"进入程序"
            self.verify_btn.config(text="🚀  进入程序", bg="#0d904f")
            
            # 直接进入程序
            self._enter_program()
            
        else:
            self.status_label.config(text=f"❌ {result['message']}", fg="red")
            if self.on_fail:
                self.on_fail(result)
    
    def _enter_program(self):
        """验证成功,进入程序(启动心跳)"""
        _log("✅ 验证成功,进入程序")
        
        # 保存卡密
        if self.remember_var.get() and self.verified_card:
            save_cache(self.verified_card)
        
        # 启动心跳(每分钟验证一次)
        if self.verified_card and not self.switch_off:
            def on_heartbeat_fail(reason):
                _log(f"💀 心跳失败,退出程序: {reason}")
                if self.on_heartbeat_fail:
                    self.on_heartbeat_fail(reason)
                # 关闭窗口
                self.root.after(0, self.root.destroy)
            
            self.heartbeat_thread = start_heartbeat(self.verified_card, on_heartbeat_fail)
            self.heartbeat_running = True
            _log("💓 心跳已启动")
        
        # 关闭窗口
        self.root.destroy()
        
        # 调用成功回调
        if self.on_success:
            self.on_success({
                'success': True,
                'card': self.verified_card,
                'remaining': self.remaining_time,
                'message': '验证成功'
            })
    
    def _run_direct(self):
        """直接使用(开关模式)"""
        messagebox.showinfo("提示", "系统已关闭验证,直接使用", parent=self.root)
        self.root.destroy()
        if self.on_success:
            self.on_success({'success': True, 'message': '直接使用'})
    
    def _on_close(self):
        """关闭窗口"""
        if self.verified or self.switch_off:
            self.root.destroy()
        else:
            if messagebox.askokcancel("退出", "确定要退出吗?未验证通过将无法使用程序。", parent=self.root):
                self.root.destroy()
                sys.exit(0)
    
    def run(self):
        self.root.mainloop()
        return self.result


# ============================================================
# 🚀 启动函数
# ============================================================

def start_auth(on_success=None, on_fail=None, on_heartbeat_fail=None):
    """
    启动验证窗口
    
    参数:
        on_success: 验证成功回调 function(result)
        on_fail: 验证失败回调 function(result)
        on_heartbeat_fail: 心跳失败回调 function(reason)
    """
    app = AuthWindow(on_success, on_fail, on_heartbeat_fail)
    return app.run()


# ============================================================
# 📝 使用示例
# ============================================================

if __name__ == "__main__":
    
    # 开启调试模式,看签名验证日志
    Config.DEBUG_MODE = True
    
    def on_success(result):
        print(f"\n{'='*50}")
        print(f"✅ 验证成功!")
        print(f"   卡密: {result.get('card', 'N/A')}")
        print(f"   剩余: {result.get('remaining', 'N/A')}")
        print(f"💓 心跳已启动 (间隔: {Config.HEARTBEAT_INTERVAL}秒)")
        print(f"{'='*50}\n")
        
        # ============================================================
        # ⚠️ 你的主程序写在这里!
        # 下面用 while True 模拟主程序运行,心跳在后台持续验证
        # ============================================================
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            print("\n程序退出")
            sys.exit(0)
    
    def on_fail(result):
        print(f"❌ 验证失败: {result['message']}")
    
    def on_heartbeat_fail(reason):
        print(f"💀 心跳失败: {reason}")
        print("程序将退出")
        sys.exit(0)
    
    start_auth(on_success, on_fail, on_heartbeat_fail)
📄 懒人精灵 (繁華若夢提供)
359 行
-- ================================================================
-- 卡密验证系统 懒人精灵版(完整签名+心跳)
-- 基于按键精灵版逻辑移植
-- 说明:本脚本由卡密通(keyt.cn)生成,请勿修改核心逻辑
-- ================================================================

-- ================== 基本配置 ==================
local server_url = "https://www.keyt.cn/kami/你的用户名/check.php"  -- 替换为实际地址
local APP_NAME = "你的应用名"              -- 应用名称(如 a)
local SIGN_KEY = "你的签名密钥"            -- 签名密钥(从后台获取)
local TIMESTAMP_MAX_DIFF = 120            -- 时间戳容差(秒)

local card_path = "/sdcard/save_card.txt"  -- 卡密缓存路径

-- ================== MD5 签名计算 ==================
function Md5(str)
    return crypto.md5(str)
end

-- ================== 签名校验 + 时间戳验证 ==================
function VerifyResponse(raw)
    if raw == nil or raw == "" then
        print("[验签] ❌ 返回数据为空")
        return nil
    end
    
    -- 分离签名
    local signPos = string.find(raw, "|sign=")
    if not signPos then
        print("[验签] ❌ 未找到签名标记")
        return nil
    end
    
    local body = string.sub(raw, 1, signPos - 1)
    local sign = string.sub(raw, signPos + 6)
    
    -- MD5 签名校验
    local localSign = Md5(body .. SIGN_KEY)
    print("[验签] 本地签名: " .. localSign)
    print("[验签] 服务器签名: " .. sign)
    
    if localSign ~= sign then
        print("[验签] ❌ 签名不匹配")
        return nil
    end
    print("[验签] ✅ 签名校验通过")
    
    -- 提取时间戳(最后一个 | 之后的内容)
    local lastPipe = nil
    for i = string.len(body), 1, -1 do
        if string.sub(body, i, i) == "|" then
            lastPipe = i
            break
        end
    end
    if not lastPipe then
        print("[验签] ❌ 找不到时间戳")
        return nil
    end
    
    local tsStr = string.sub(body, lastPipe + 1)
    local ts = tonumber(tsStr)
    if not ts then
        print("[验签] ❌ 时间戳格式错误")
        return nil
    end
    
    -- 校验时间差(120秒内)
    local nowTs = os.time()
    local diff = math.abs(nowTs - ts)
    print("[验签] 服务器时间戳: " .. ts)
    print("[验签] 本地时间戳: " .. nowTs)
    print("[验签] 时间差: " .. diff .. " 秒 (最大允许: " .. TIMESTAMP_MAX_DIFF .. " 秒)")
    
    if diff > TIMESTAMP_MAX_DIFF then
        print("[验签] ❌ 时间戳过期")
        return nil
    end
    print("[验签] ✅ 时间戳校验通过")
    
    -- 返回纯业务内容(去掉时间戳)
    local biz = string.sub(body, 1, lastPipe - 1)
    print("[验签] 业务内容: " .. biz)
    return biz
end

-- ================== HTTP 请求 ==================
function HttpGet(url)
    print("[HTTP] 请求: " .. url)
    local ret, code = httpGet(url, 10)  -- 10秒超时
    if code == 200 then
        print("[HTTP] 响应: " .. ret)
        return ret
    else
        print("[HTTP] ❌ 请求失败,状态码: " .. tostring(code))
        return nil
    end
end

-- ================== 获取机器码 ==================
function GetMachineID()
    local id = getDeviceId()
    if id and id ~= "" then
        print("[机器码] " .. id)
        return id .. "MAC"
    end
    print("[机器码] ❌ 获取失败")
    return "获取失败"
end

-- ================== 获取验证开关 ==================
function GetCardSwitch()
    for i = 1, 3 do
        local ts = os.time()
        local url = server_url .. "?act=get_switch&app=" .. APP_NAME .. "&t=" .. ts
        print("[开关] 第 " .. i .. " 次尝试")
        
        local ret = HttpGet(url)
        if ret then
            local biz = VerifyResponse(ret)
            if biz then
                if string.find(biz, "CARD_ON") then
                    print("[开关] ✅ 卡密验证已开启")
                    return "CARD_ON"
                elseif string.find(biz, "CARD_OFF") then
                    print("[开关] ⚠️ 卡密验证已关闭")
                    return "CARD_OFF"
                end
            end
        end
        sleep(500)
    end
    print("[开关] ⚠️ 开关获取失败,默认开启验证")
    return "CARD_ON"
end

-- ================== 验证卡密 ==================
function CheckCard(card, mac)
    local ts = os.time()
    local url = server_url .. "?card=" .. card .. "&mac=" .. mac .. "&app=" .. APP_NAME .. "&heart=1&t=" .. ts
    print("[验证] 卡密: " .. card)
    
    local ret = HttpGet(url)
    if not ret then
        return nil
    end
    
    local biz = VerifyResponse(ret)
    if not biz then
        return nil
    end
    return biz
end

-- ================== 解析剩余时间 ==================
function ParseRemainingTime(biz)
    if not biz then return "" end
    
    local parts = {}
    for part in string.gmatch(biz, "[^|]+") do
        table.insert(parts, part)
    end
    
    if #parts >= 3 then
        local days = tonumber(parts[3])
        local minutes = (#parts >= 4) and tonumber(parts[4]) or 0
        print("[时间] 剩余天数: " .. days .. " 天, 剩余分钟: " .. minutes .. " 分钟")
        
        if minutes > 0 then
            local hours = math.floor(minutes / 60)
            local mins = minutes % 60
            return days .. "天" .. hours .. "小时" .. mins .. "分钟"
        else
            return days .. "天"
        end
    elseif #parts >= 2 and parts[2] == "permanent" then
        print("[时间] 终身有效")
        return "终身有效"
    end
    
    return ""
end

-- ================== 错误码转中文 ==================
function TransMsg(code)
    local map = {
        activate = "激活成功",
        valid = "验证通过",
        permanent = "终身有效",
        expired = "卡密已过期",
        banned = "卡密已被禁用",
        device_mismatch = "设备不匹配",
        online_limit_reached = "在线设备数已满",
        invalid_card = "卡密无效",
        missing_params = "参数不完整"
    }
    return map[code] or code
end

-- ================== 获取公告 ==================
function GetNotice()
    local url = server_url .. "?act=get_notice&app=" .. APP_NAME
    local ret = HttpGet(url)
    if ret and ret ~= "" then
        return ret
    end
    return "暂无公告"
end

-- ================== 文件读写 ==================
function ReadSavedCard()
    if fileExist(card_path) then
        local f = io.open(card_path, "r")
        if f then
            local card = f:read("*l")
            f:close()
            return card or ""
        end
    end
    return ""
end

function SaveCard(card)
    local f = io.open(card_path, "w")
    if f then
        f:write(card)
        f:close()
        print("[缓存] 卡密已保存")
    end
end

function DelInvalidCard()
    if fileExist(card_path) then
        os.remove(card_path)
        print("[缓存] 失效卡密已删除")
    end
end

-- ===================== 主流程 =====================
print("========================================")
print("  卡密通 懒人精灵 对接示例")
print("========================================")

-- 1. 获取机器码
local mac = GetMachineID()
if mac == "获取失败" then
    print("[错误] 获取机器码失败,脚本退出")
    exitScript()
end

-- 2. 获取公告
local notice = GetNotice()
print("[公告] " .. notice)

-- 3. 获取验证开关
local card_switch = GetCardSwitch()
if card_switch ~= "CARD_ON" and card_switch ~= "CARD_OFF" then
    card_switch = "CARD_ON"
    print("[开关] 开关获取失败,默认开启验证")
end

local card, isSuccess

if card_switch == "CARD_OFF" then
    card = "88888888"
    print("[提示] 卡密验证已关闭,直接启动")
    isSuccess = true
else
    -- 尝试使用缓存的卡密
    card = ReadSavedCard()
    isSuccess = false
    
    if card ~= "" then
        print("[缓存] 检测到已保存卡密: " .. card)
        local result = CheckCard(card, mac)
        if result and string.sub(result, 1, 3) == "ok|" then
            isSuccess = true
            print("[缓存] ✅ 卡密有效,直接启动")
            local remaining = ParseRemainingTime(result)
            if remaining ~= "" then
                print("[缓存] 剩余时间: " .. remaining)
            end
        else
            DelInvalidCard()
            card = ""
            print("[缓存] ❌ 卡密已失效,请重新输入")
        end
    end
    
    -- 手动输入卡密
    while not isSuccess do
        -- 懒人精灵输入框(根据实际 API 调整)
        local inputResult = inputDialog("请输入卡密", "卡密验证", "")
        if inputResult == "" or inputResult == nil then
            print("[错误] 卡密不能为空,脚本退出")
            exitScript()
        end
        card = inputResult
        
        print("[验证] 验证中...")
        local result = CheckCard(card, mac)
        if result and string.sub(result, 1, 3) == "ok|" then
            SaveCard(card)
            isSuccess = true
            print("[成功] ✅ 卡密验证成功")
            local remaining = ParseRemainingTime(result)
            if remaining ~= "" then
                print("[成功] 剩余时间: " .. remaining)
            end
        elseif result and string.sub(result, 1, 6) == "error|" then
            local errCode = string.match(result, "error|([^|]+)")
            if errCode then
                print("[失败] ❌ " .. TransMsg(errCode))
            else
                print("[失败] ❌ " .. result)
            end
        else
            print("[失败] ❌ 验证失败,请重新输入")
        end
    end
end

-- ================== 心跳保活 + 用户代码 ==================
if isSuccess then
    print("========================================")
    print("[启动] 辅助启动成功!")
    print("========================================")
    
    local fail_count = 0
    local heartbeat_interval = 55  -- 心跳间隔(秒),建议 50-59
    
    while true do
        -- 心跳验证
        local heartResult = CheckCard(card, mac)
        if heartResult and string.sub(heartResult, 1, 3) == "ok|" then
            fail_count = 0
            print("[心跳] ✅ 心跳正常")
        else
            fail_count = fail_count + 1
            print("[心跳] ❌ 心跳异常,连续失败: " .. fail_count .. "/5")
            if fail_count >= 5 then
                print("[心跳] 连续失败5次,脚本退出")
                exitScript()
            end
        end
        
        -- ========== ↓ 你的脚本代码写在这里 ↓ ==========
        -- 示例:打印日志
        print("[用户代码] 正在运行... 时间: " .. os.date("%Y-%m-%d %H:%M:%S"))
        
        -- 你的业务逻辑
        -- 
        -- 
        -- 
        -- ========== ↑ 你的脚本代码写在这里 ↑ ==========
        
        sleep(heartbeat_interval * 1000)  -- 心跳间隔(毫秒)
    end
end
📄 按键精灵PC自定义界面(上仙优化版本QQ 1802563034)
按键精灵 3 行
www.keyt.cn/kami/duijiema/按键精灵PC自定义按钮对接案例(上仙优化版本).Q
复制上面链接到浏览器打开可直接下载源文件! 
📄 易语言对接例子QQ1412932000提供
按键精灵 472 行
百度网盘下载链接:
https://pan.baidu.com/s/1klUGYq42maotMQ-zEhjxDw 
(复制到浏览器打开即可,如果网盘失效,请访问下面官网下载)
.版本 2
.支持库 spec

.程序集 窗口程序集_启动窗口
.程序集变量 全局_到期时间, 日期时间型

.子程序 检测破解环境
.局部变量 窗口句柄, 整数型

.如果真 (IsDebuggerPresent () = 真)
    信息框 (“0x000000001,访问内存冲突!”, 0, “安全警告”, )
    结束 ()
.如果真结束

' 检测 Cheat Engine 多版本
窗口句柄 = FindWindowA (“”, “Cheat Engine 7.4”)
.如果真 (窗口句柄 = 0)
    窗口句柄 = FindWindowA (“”, “Cheat Engine 7.5”)
.如果真结束
.如果真 (窗口句柄 = 0)
    窗口句柄 = FindWindowA (“”, “Cheat Engine”)
.如果真结束
.如果真 (窗口句柄 ≠ 0)
    信息框 (“检测到CE修改工具,软件即将退出!”, 0, “安全警告”, )
    结束 ()
.如果真结束

' 检测抓包工具
窗口句柄 = FindWindowA (“”, “Fiddler”)
.如果真 (窗口句柄 ≠ 0)
    信息框 (“检测到Fiddler抓包工具,软件即将退出!”, 0, “安全警告”, )
    结束 ()
.如果真结束

窗口句柄 = FindWindowA (“”, “Charles”)
.如果真 (窗口句柄 ≠ 0)
    信息框 (“检测到Charles抓包工具,软件即将退出!”, 0, “安全警告”, )
    结束 ()
.如果真结束

窗口句柄 = FindWindowA (“Qt5QWindowIcon”, “Wireshark”)
.如果真 (窗口句柄 ≠ 0)
    信息框 (“检测到Wireshark抓包工具,软件即将退出!”, 0, “安全警告”, )
    结束 ()
.如果真结束

' 新增检测x64dbg、OD调试器
窗口句柄 = FindWindowA (“”, “x64dbg”)
.如果真 (窗口句柄 ≠ 0)
    信息框 (“检测到x64dbg调试器,软件即将退出!”, 0, “安全警告”, )
    结束 ()
.如果真结束
窗口句柄 = FindWindowA (“”, “OllyDbg”)
.如果真 (窗口句柄 ≠ 0)
    信息框 (“检测到OllyDbg调试器,软件即将退出!”, 0, “安全警告”, )
    结束 ()
.如果真结束

' ====================新增:获取当前WiFi无线网卡MAC地址(优先WiFi,无WiFi取有线)====================

.子程序 取本机优先MAC, 文本型, 公开, 优先WiFi无线网卡MAC,无WiFi则返回有线网卡MAC,失败返回空
.局部变量 cmd结果, 文本型
.局部变量 行数组, 文本型, , "0"
.局部变量 i, 整数型
.局部变量 mac, 文本型
.局部变量 wifiMac, 文本型

返回 (删首尾空 (系统_取MAC地址 ()))

.子程序 _按钮1_被单击, , , (登录成功开启心跳)
.局部变量 应用名称, 文本型
.局部变量 账号名称, 文本型
.局部变量 卡密, 文本型
.局部变量 提交文本, 文本型
.局部变量 请求链接, 文本型
.局部变量 服务器返回, 文本型
.局部变量 明文body, 文本型
.局部变量 服务器MD5, 文本型
.局部变量 本地MD5, 文本型
.局部变量 分割数组, 文本型, , "0"
.局部变量 状态码, 文本型
.局部变量 剩余天数, 文本型
.局部变量 机器码, 文本型
.局部变量 到期时间戳, 整数型
.局部变量 到期北京时间, 日期时间型
.局部变量 配置文件, 文本型

' 基础配置
应用名称 = “”  ' 填自己的软件ID
账号名称 = “”  ' 自己的后台登录账号
卡密 = 编辑框1.内容
' 调用新函数:优先WiFi网卡MAC作为机器码
机器码 = 取本机优先MAC ()

标签_验证信息.标题 = “正在验证中,请稍候...”
标签_验证信息.文本颜色 = #黑色
标签_获取时间.标题 = “”

' 判空检查
.如果真 (删首尾空 (卡密) = “”)
    标签_验证信息.标题 = “提示:请输入卡密!”
    标签_验证信息.文本颜色 = #红色
    返回 ()
.如果真结束

.如果真 (机器码 = “” 或 机器码 = “获取失败”)
    标签_验证信息.标题 = “提示:机器码获取失败,请检查网络或硬件!”
    标签_验证信息.文本颜色 = #红色
    返回 ()
.如果真结束

配置文件 = 取运行目录 () + “\kmt.ini”
' 记住卡密写入配置(删除重复冗余判断)
.如果真 (选择框_记住卡密.选中 = 真)
    写配置项 (配置文件, “用户设置”, “记住卡密”, “真”)
    写配置项 (配置文件, “用户设置”, “卡密”, 卡密)
    写配置项 (配置文件, “用户设置”, “记住卡密”, “假”)
    写配置项 (配置文件, “用户设置”, “卡密”, )
.如果真结束

' 拼接请求链接
提交文本 = 卡密 + “&mac=” + 机器码 + “&app=” + 应用名称 + “&heart=1&t=” + 到文本 (时间_取现行时间戳 ())
请求链接 = “https://www.keyt.cn/kami/” + 账号名称 + “/check.php?card=” + 提交文本

' 规范网页访问S调用
服务器返回 = 网页_访问S (请求链接, 0)
调试输出 (“服务器返回: ” + 服务器返回)

.如果真 (服务器返回 = “” 或 服务器返回 = 字符 (0))
    标签_验证信息.标题 = “错误:网络请求失败,请检查网络连接!”
    标签_验证信息.文本颜色 = #红色
    返回 ()
.如果真结束

.如果真 (取文本长度 (服务器返回) ≤ 38)
    标签_验证信息.标题 = “错误:服务器返回数据过短,格式异常!”
    标签_验证信息.文本颜色 = #红色
    返回 ()
.如果真结束

明文body = 文本_删右边 (服务器返回, 38)
服务器MD5 = 取文本右边 (服务器返回, 32)

' MD5签名校验
本地MD5 = 到小写 (校验_取md5 (到字节集 (明文body + “填自己的签名密钥”)))
调试输出 (“本地MD5: ” + 本地MD5 + “ | 服务器MD5: ” + 服务器MD5)

.如果真 (本地MD5 ≠ 服务器MD5)
    标签_验证信息.标题 = “安全警告:MD5效验失败,数据可能被篡改!”
    标签_验证信息.文本颜色 = #红色
    返回 ()
.如果真结束

' 解析明文结果
分割数组 = 分割文本 (明文body, “|”, )
.如果真 (取数组成员数 (分割数组) < 3)
    标签_验证信息.标题 = “错误:数据解析失败!”
    标签_验证信息.文本颜色 = #灰色
    返回 ()
.如果真结束

状态码 = 分割数组 [1]
剩余天数 = 分割数组 [3]

.判断开始 (状态码 = “ok”)
    标签_验证信息.标题 = “验证登录成功!剩余天数: ” + 剩余天数 + “天”
    标签_验证信息.文本颜色 = #绿色
    .如果真 (取数组成员数 (分割数组) ≥ 5)
        到期时间戳 = 到整数 (分割数组 [5])
    .如果真结束
    到期北京时间 = 增减时间 (到时间 (“1970-01-01 08:00:00”), #秒, 到期时间戳)
    标签_获取时间.标题 = “到期北京时间:” + 到文本 (到期北京时间)
    全局_到期时间 = 到期北京时间
    写配置项 (配置文件, “用户设置”, “到期时间”, 到文本 (到期北京时间))
    信息框 (“验证成功,点击确定进入软件!”, 0, , )
    时钟_心跳.时钟周期 = 55000
    载入 (窗口1, _启动窗口, 真)
    _启动窗口.销毁 ()

.判断 (状态码 = “error” 或 状态码 = “fail”)
    标签_验证信息.标题 = “登陆失败或卡密错误: ” + 分割数组 [2]
    标签_验证信息.文本颜色 = #红色
    标签_获取时间.标题 = “”

.默认
    标签_验证信息.标题 = “未知状态:” + 状态码
    标签_验证信息.文本颜色 = #红色
    标签_获取时间.标题 = “”
.判断结束


.子程序 __启动窗口_创建完毕
.局部变量 配置文件, 文本型
.局部变量 记住状态, 文本型
.局部变量 本机MAC, 文本型

' 程序启动先执行环境检测
检测破解环境 ()

' 获取WiFi优先MAC显示到标签
本机MAC = 取本机优先MAC ()
标签_机器码.标题 = 删首尾空 (本机MAC)
.如果真 (标签_机器码.标题 = “” 或 标签_机器码.标题 = “0” 或 标签_机器码.标题 = “-1”)
    标签_机器码.标题 = “获取失败”
.如果真结束

标签_验证信息.标题 = “请输入验证卡密”
标签_验证信息.文本颜色 = #黑色
标签_获取时间.标题 = “”

配置文件 = 取运行目录 () + “\kmt.ini”
记住状态 = 读配置项 (配置文件, “用户设置”, “记住卡密”, “假”)
.如果真 (记住状态 = “真”)
    选择框_记住卡密.选中 = 真
    编辑框1.内容 = 读配置项 (配置文件, “用户设置”, “卡密”, )
.如果真结束

获取公告 ()

.子程序 _按钮2_被单击, , , (解绑成功后停止心跳)
.局部变量 账号名称, 文本型
.局部变量 卡密, 文本型
.局部变量 机器码, 文本型
.局部变量 请求链接, 文本型
.局部变量 服务器返回, 文本型
.局部变量 错误明文, 文本型
.局部变量 错误数组, 文本型, , "0"
.局部变量 应用名称, 文本型
.局部变量 错误信息, 文本型

账号名称 = “”  ' 自己软件ID
应用名称 = “”  ' 自己的登录账号
卡密 = 删首尾空 (编辑框1.内容)
' 解绑同样使用WiFi优先机器码
机器码 = 取本机优先MAC ()

.如果真 (机器码 = “” 或 机器码 = “获取失败”)
    信息框 (“提示:机器码获取异常,无法执行解绑!”, 0, “提示”, )
    返回 ()
.如果真结束

.如果真 (卡密 = “”)
    信息框 (“提示:请输入需要解绑的卡密!”, 0, “提示”, )
    返回 ()
.如果真结束

请求链接 = “https://www.keyt.cn/kami/” + 账号名称 + “/check.php?act=self_unbind&card=” + 卡密 + “&mac=” + 机器码 + “&app=” + 应用名称
标签_验证信息.标题 = “正在解绑中,请稍候...”
标签_验证信息.文本颜色 = #黑色

服务器返回 = 网页_访问S (请求链接, 0)
调试输出 (“解绑服务器返回原始: ” + 服务器返回)

.如果真 (服务器返回 = “” 或 服务器返回 = 字符 (0))
    信息框 (“网络请求失败,请检查网络连接!”, 0, “错误”, )
    标签_验证信息.标题 = “解绑失败:网络错误”
    标签_验证信息.文本颜色 = #红色
    返回 ()
.如果真结束

服务器返回 = 编码_Utf8到Ansi (到字节集 (服务器返回))
调试输出 (“解绑服务器返回解码: ” + 服务器返回)

.判断开始 (寻找文本 (服务器返回, “ok”, , 假) = 1)
    时钟_心跳.时钟周期 = 0
    信息框 (“解绑成功!请重新点击登录验证。”, 0, “成功”, )
    标签_验证信息.标题 = “解绑成功,请重新登录”
    标签_验证信息.文本颜色 = #绿色
    标签_获取时间.标题 = “”
    返回 ()

.判断 (寻找文本 (服务器返回, “error”, , 假) > 0 或 寻找文本 (服务器返回, “fail”, , 假) > 0)
    错误明文 = 服务器返回
    .如果真 (寻找文本 (错误明文, “|sign=”, , 假) > 0)
        错误明文 = 取文本左边 (错误明文, 寻找文本 (错误明文, “|sign=”, , 假) - 1)
    .如果真结束
    错误数组 = 分割文本 (错误明文, “|”, )
    .如果真 (取数组成员数 (错误数组) ≥ 2)
        错误信息 = 错误数组 [2]
    .如果真结束
    .如果真 (错误信息 = “” 或 错误信息 = “error” 或 错误信息 = “fail”)
        错误信息 = “未知错误(原始返回: ” + 服务器返回 + “)”
    .如果真结束
    信息框 (“解绑失败:” + 错误信息, 0, “失败”, )
    标签_验证信息.标题 = “解绑失败: ” + 错误信息
    标签_验证信息.文本颜色 = #红色
    返回 ()

.默认
    信息框 (“解绑接口返回异常:” + 服务器返回, 0, “提示”, )
    标签_验证信息.标题 = “解绑异常”
    标签_验证信息.文本颜色 = #红色
.判断结束


.子程序 获取公告
.局部变量 账号名称, 文本型
.局部变量 请求链接, 文本型
.局部变量 服务器返回, 文本型
.局部变量 应用名称, 文本型
.局部变量 公告明文, 文本型
.局部变量 版本号, 文本型
.局部变量 当前版本, 文本型
.局部变量 更新链接, 文本型

当前版本 = “”  ' 软件版本号
更新链接 = “”
账号名称 = “”  ' 自己登录账号
应用名称 = “”  ' 软件ID

' 获取公告
请求链接 = “https://www.keyt.cn/kami/” + 账号名称 + “/check.php?act=get_notice&app=” + 应用名称
服务器返回 = 网页_访问S (请求链接, 0)
.如果真 (服务器返回 = “” 或 服务器返回 = 字符 (0))
    返回 ()
.如果真结束
服务器返回 = 编码_Utf8到Ansi (到字节集 (服务器返回))
.如果真 (寻找文本 (服务器返回, “error”, , 假) = 1 或 寻找文本 (服务器返回, “fail”, , 假) = 1)
    调试输出 (“获取公告失败,服务器返回: ” + 服务器返回)
    返回 ()
.如果真结束
.如果真 (寻找文本 (服务器返回, “|sign=”, , 假) > 0)
    公告明文 = 取文本左边 (服务器返回, 寻找文本 (服务器返回, “|sign=”, , 假) - 1)
.如果真结束
.如果真 (公告明文 = “”)
    公告明文 = 服务器返回
.如果真结束
公告明文 = 子文本替换 (公告明文, “\n”, #换行符, , , 真)
公告明文 = 子文本替换 (公告明文, “<br>”, #换行符, , , 真)
公告明文 = 子文本替换 (公告明文, “<br/>”, #换行符, , , 真)

' 获取最新版本号
请求链接 = “https://www.keyt.cn/kami/” + 账号名称 + “/check.php?act=get_version&app=” + 应用名称
服务器返回 = 网页_访问S (请求链接, 0)
.如果真 (服务器返回 = “” 或 服务器返回 = 字符 (0))
    标签_版本.标题 = “当前版本:” + 当前版本 + “(获取最新版本失败)”
    返回 ()
.如果真结束
服务器返回 = 编码_Utf8到Ansi (到字节集 (服务器返回))
.如果真 (寻找文本 (服务器返回, “error”, , 假) = 1 或 寻找文本 (服务器返回, “fail”, , 假) = 1)
    标签_版本.标题 = “当前版本:” + 当前版本 + “(获取最新版本失败)”
    调试输出 (“获取版本失败,服务器返回: ” + 服务器返回)
    返回 ()
.如果真结束
.如果真 (寻找文本 (服务器返回, “|sign=”, , 假) > 0)
    版本号 = 取文本左边 (服务器返回, 寻找文本 (服务器返回, “|sign=”, , 假) - 1)
.如果真结束
.如果真 (版本号 = “”)
    版本号 = 服务器返回
.如果真结束
版本号 = 删首尾空 (版本号)

.如果真 (版本号 ≠ 当前版本)
    标签_版本.标题 = “当前版本:” + 当前版本 + “(发现新版本:” + 版本号 + “)”
    ' 修复版本更新弹窗,固定参数36,判断返回值6(点击是),删除末尾多余逗号
    .如果真 (信息框 (“发现新版本:” + 版本号 + #换行符 + “是否立即前往下载更新?”, 36, “更新提示”, ) = 6)
        运行 (“explorer.exe ” + 更新链接, 假, 1)
    .如果真结束

.如果真结束

.如果真 (版本号 = “”)
    标签_版本.标题 = “当前版本:” + 当前版本
.如果真结束


.子程序 _时钟_心跳_周期事件
.局部变量 应用名称, 文本型
.局部变量 卡密, 文本型
.局部变量 机器码, 文本型
.局部变量 提交文本, 文本型
.局部变量 请求链接, 文本型
.局部变量 服务器返回, 文本型
.局部变量 明文body, 文本型
.局部变量 服务器MD5, 文本型
.局部变量 本地MD5, 文本型
.局部变量 分割数组, 文本型, , "0"
.局部变量 状态码, 文本型
.局部变量 错误原因, 文本型
.局部变量 账号名称, 文本型

' 随机概率执行环境检测
.如果真 (取随机数 (1, 5) = 1)
    检测破解环境 ()
.如果真结束

账号名称 = “hjm848878147”
应用名称 = “yunding2”
卡密 = 删首尾空 (编辑框1.内容)
' 心跳同步使用WiFi优先MAC
机器码 = 取本机优先MAC ()

.如果真 (卡密 = “” 或 机器码 = “” 或 机器码 = “获取失败”)
    时钟_心跳.时钟周期 = 0
    返回 ()
.如果真结束

提交文本 = 卡密 + “&mac=” + 机器码 + “&app=” + 应用名称 + “&heart=1&t=” + 到文本 (时间_取现行时间戳 ())
请求链接 = “https://www.keyt.cn/kami/” + 账号名称 + “/check.php?card=” + 提交文本
服务器返回 = 网页_访问S (请求链接, 0)

.如果真 (服务器返回 = “” 或 服务器返回 = 字符 (0))
    调试输出 (“心跳验证:网络请求失败,等待下一次重试”)
    返回 ()
.如果真结束

.如果真 (取文本长度 (服务器返回) ≤ 38)
    调试输出 (“心跳验证:返回数据过短”)
    返回 ()
.如果真结束

明文body = 文本_删右边 (服务器返回, 38)
服务器MD5 = 取文本右边 (服务器返回, 32)
本地MD5 = 到小写 (校验_取md5 (到字节集 (明文body + “自己的签名密钥”)))

.如果真 (本地MD5 ≠ 服务器MD5)
    调试输出 (“心跳验证:MD5校验失败”)
    返回 ()
.如果真结束

分割数组 = 分割文本 (明文body, “|”, )
.如果真 (取数组成员数 (分割数组) < 3)
    返回 ()
.如果真结束

状态码 = 分割数组 [1]
.判断开始 (状态码 = “ok”)

.判断 (状态码 = “error” 或 状态码 = “fail”)
    时钟_心跳.时钟周期 = 0
    错误原因 = 分割数组 [2]
    .如果真 (错误原因 = “”)
        错误原因 = “未知原因”
    .如果真结束
    信息框 (“心跳验证失败,软件即将退出!” + #换行符 + “原因:” + 错误原因, 0, “验证失效”, )
    结束 ()
.默认
    时钟_心跳.时钟周期 = 0
    信息框 (“心跳验证返回未知状态,软件即将退出!”, 0, “验证失效”, )
    结束 ()
.判断结束


.子程序 _窗口1_将被销毁

' 主窗口关闭停止心跳并彻底退出
时钟_心跳.时钟周期 = 0
结束 ()

.子程序 __启动窗口_将被销毁

' 启动窗口关闭强制结束进程
时钟_心跳.时钟周期 = 0
结束 ()


.子程序 取网卡信息



.子程序 运行取结果



.子程序 运行_取程序输出



📄 节点精灵 Lua 脚本代码
313 行
-- ============================================================
-- 节点精灵 对接卡密通验证(完整签名+心跳)
-- 基于按键精灵版逻辑移植
-- 使用方法:导入节点精灵,修改配置区参数
-- ============================================================

-- ==================== 配置区(请修改) ====================
local USER_NAME = "你的用户名"           -- 例如:qq345205377
local APP_NAME = "你的应用名"            -- 例如:a
local SIGN_KEY = "你的签名密钥"          -- 从后台「修改签名密钥」获取
local BASE_URL = "https://www.keyt.cn/kami/" .. USER_NAME .. "/check.php"

local TIMESTAMP_MAX_DIFF = 120
local HEARTBEAT_INTERVAL = 50  -- 50秒

-- ==================== 全局变量 ====================
local currentCard = ""
local currentMac = ""
local failCount = 0
local isRunning = true
local heartbeatThread = nil

-- ==================== 工具函数 ====================

-- 获取当前时间戳(秒)
function getTimestamp()
    return os.time()
end

-- MD5 加密(节点精灵自带)
function md5(str)
    return crypto.md5(str)
end

-- 获取机器码(使用设备ID)
function getMachineCode()
    local deviceId = device.getDeviceId()
    if deviceId and deviceId ~= "" then
        return deviceId .. "MAC"
    end
    -- 备选:使用 Android ID
    local androidId = device.getAndroidId()
    if androidId and androidId ~= "" then
        return androidId .. "MAC"
    end
    return "获取失败"
end

-- HTTP GET 请求
function httpGet(url)
    local headers = {
        ["Cache-Control"] = "no-cache"
    }
    local code, body = http.get(url, headers)
    if code == 200 then
        return body
    else
        print("[HTTP] 请求失败,状态码: " .. tostring(code))
        return nil
    end
end

-- 验证服务器返回(签名 + 时间戳)
function verifyResponse(raw)
    if not raw then
        print("[验签] ❌ 返回数据为空")
        return nil
    end
    
    -- 分离签名
    local signIndex = string.find(raw, "|sign=")
    if not signIndex then
        print("[验签] ❌ 未找到签名标记")
        return nil
    end
    
    local body = string.sub(raw, 1, signIndex - 1)
    local sign = string.sub(raw, signIndex + 6)
    
    -- 本地计算签名
    local localSign = md5(body .. SIGN_KEY)
    print("[验签] 本地签名: " .. localSign)
    print("[验签] 服务器签名: " .. sign)
    
    if localSign ~= sign then
        print("[验签] ❌ 签名不匹配")
        return nil
    end
    print("[验签] ✅ 签名校验通过")
    
    -- 提取时间戳
    local lastPipe = string.find(body, "|[^|]*$")
    if not lastPipe then
        print("[验签] ❌ 未找到时间戳")
        return nil
    end
    
    local tsStr = string.sub(body, lastPipe + 1)
    local ts = tonumber(tsStr)
    local nowTs = getTimestamp()
    local diff = math.abs(nowTs - ts)
    
    print("[验签] 服务器时间戳: " .. ts)
    print("[验签] 本地时间戳: " .. nowTs)
    print("[验签] 时间差: " .. diff .. " 秒 (最大允许: " .. TIMESTAMP_MAX_DIFF .. " 秒)")
    
    if diff > TIMESTAMP_MAX_DIFF then
        print("[验签] ❌ 时间戳过期")
        return nil
    end
    print("[验签] ✅ 时间戳校验通过")
    
    local biz = string.sub(body, 1, lastPipe - 1)
    print("[验签] 业务内容: " .. biz)
    return biz
end

-- 获取卡密验证开关
function getCardSwitch()
    for i = 1, 3 do
        local ts = getTimestamp()
        local url = BASE_URL .. "?act=get_switch&app=" .. APP_NAME .. "&t=" .. ts
        print("[开关] 第 " .. i .. " 次尝试: " .. url)
        
        local response = httpGet(url)
        if response then
            local biz = verifyResponse(response)
            if biz then
                if string.find(biz, "CARD_ON") then
                    print("[开关] ✅ 卡密验证已开启")
                    return "CARD_ON"
                elseif string.find(biz, "CARD_OFF") then
                    print("[开关] ⚠️ 卡密验证已关闭")
                    return "CARD_OFF"
                end
            end
        end
        sleep(500)
    end
    print("[开关] ⚠️ 开关获取失败,默认开启验证")
    return "CARD_ON"
end

-- 验证卡密
function verifyCard(card, mac)
    local ts = getTimestamp()
    local url = BASE_URL .. "?card=" .. card .. "&mac=" .. mac .. "&app=" .. APP_NAME .. "&heart=1&t=" .. ts
    print("[验证] 请求URL: " .. url)
    print("[验证] 当前应用: " .. APP_NAME)
    print("[验证] 使用卡密: " .. card)
    
    local response = httpGet(url)
    if not response then
        print("[验证] 网络错误")
        return nil
    end
    
    print("[验证] 服务器返回: " .. response)
    return verifyResponse(response)
end

-- 解析剩余时间
function parseRemainingTime(biz)
    local parts = {}
    for part in string.gmatch(biz, "[^|]+") do
        table.insert(parts, part)
    end
    
    if #parts >= 3 then
        local days = tonumber(parts[3])
        local minutes = (#parts >= 4) and tonumber(parts[4]) or 0
        print("[时间] 剩余天数: " .. days .. " 天, 剩余分钟: " .. minutes .. " 分钟")
        
        if minutes > 0 then
            local hours = math.floor(minutes / 60)
            local mins = minutes % 60
            print("[时间] 详细: " .. days .. "天" .. hours .. "小时" .. mins .. "分钟")
            return days .. "天" .. hours .. "小时" .. mins .. "分钟"
        else
            return days .. "天"
        end
    elseif #parts >= 2 and parts[2] == "permanent" then
        print("[时间] 终身有效")
        return "终身有效"
    end
    return "未知"
end

-- 错误码转中文
function transMsg(code)
    local map = {
        activate = "激活成功",
        valid = "验证通过",
        permanent = "终身有效",
        expired = "卡密已过期",
        banned = "卡密已被禁用",
        device_mismatch = "设备不匹配",
        online_limit_reached = "在线设备数已满",
        invalid_card = "卡密无效"
    }
    return map[code] or code
end

-- 心跳线程函数
function heartbeatLoop()
    print("[心跳] 启动心跳线程")
    failCount = 0
    
    while isRunning do
        if currentCard == "" or currentMac == "" then
            sleep(2000)
            goto continue
        end
        
        local biz = verifyCard(currentCard, currentMac)
        if biz and string.sub(biz, 1, 3) == "ok|" then
            failCount = 0
            print("[心跳] ✅ 心跳正常")
            parseRemainingTime(biz)
        else
            failCount = failCount + 1
            print("[心跳] ❌ 心跳异常,连续失败: " .. failCount .. "/5")
            if failCount >= 5 then
                print("[心跳] 连续失败5次,退出程序")
                isRunning = false
                luaExit()
            end
        end
        
        ::continue::
        sleep(HEARTBEAT_INTERVAL * 1000)
    end
end

-- 用户主线程(占位,用户自行实现)
function userMainLoop()
    local count = 0
    print("[用户代码] 用户代码运行中...")
    while isRunning do
        count = count + 1
        if count % 10 == 0 then
            print("[用户代码] 用户代码运行中... 当前时间: " .. os.date("%Y-%m-%d %H:%M:%S"))
        end
        sleep(1000)
    end
end

-- ==================== 主函数 ====================

function main()
    print("========================================")
    print("  卡密通 节点精灵 对接示例")
    print("========================================")
    
    -- 1. 获取机器码
    currentMac = getMachineCode()
    if currentMac == "获取失败" then
        print("[错误] 获取机器码失败")
        return
    end
    print("[机器码] " .. currentMac)
    
    -- 2. 获取验证开关
    local cardSwitch = getCardSwitch()
    if cardSwitch == "CARD_OFF" then
        print("[提示] 验证已关闭,直接进入")
        currentCard = "88888888"
        heartbeatThread = thread.start(heartbeatLoop)
        userMainLoop()
        return
    end
    
    -- 3. 输入卡密
    print("请输入卡密: ")
    currentCard = io.read()
    if not currentCard or currentCard == "" then
        print("[错误] 卡密不能为空")
        return
    end
    currentCard = string.gsub(currentCard, "%s+", "")
    
    -- 4. 验证卡密
    print("[验证] 验证中...")
    local biz = verifyCard(currentCard, currentMac)
    
    if not biz then
        print("[错误] 验证失败:签名错误或网络异常")
        return
    end
    
    if string.sub(biz, 1, 3) == "ok|" then
        local remaining = parseRemainingTime(biz)
        print("[成功] 验证通过!剩余时间: " .. remaining)
        
        -- 5. 启动心跳线程
        heartbeatThread = thread.start(heartbeatLoop)
        
        -- 6. 用户主逻辑
        userMainLoop()
    elseif string.sub(biz, 1, 6) == "error|" then
        local errCode = string.match(biz, "error|([^|]+)")
        if errCode then
            print("[失败] " .. transMsg(errCode))
        else
            print("[失败] " .. biz)
        end
    else
        print("[失败] " .. biz)
    end
end

-- 启动脚本
main()
📄 触动精灵
326 行
-- ============================================================
-- 触动精灵 对接卡密通验证(完整签名+心跳)
-- 基于按键精灵版逻辑移植
-- 使用方法:修改配置区参数,导入触动精灵运行
-- ============================================================

-- ==================== 配置区(请修改) ====================
local USER_NAME = "你的用户名"           -- 例如:qq345205377
local APP_NAME = "你的应用名"            -- 例如:a
local SIGN_KEY = "你的签名密钥"          -- 从后台「修改签名密钥」获取
local BASE_URL = "https://www.keyt.cn/kami/" .. USER_NAME .. "/check.php"

local TIMESTAMP_MAX_DIFF = 120   -- 时间戳最大容差(秒)
local HEARTBEAT_INTERVAL = 50    -- 心跳间隔(秒)

-- ==================== 全局变量 ====================
local currentCard = ""
local currentMac = ""
local failCount = 0
local isRunning = true
local heartbeatThread = nil

-- ==================== 工具函数 ====================

-- 获取当前时间戳(秒)
function getTimestamp()
    return os.time()
end

-- MD5 加密(触动精灵使用 nLog 或 Crypto)
function md5(str)
    -- 触动精灵可以使用 nLog 或 require "crypto"
    local crypto = require("crypto")
    return crypto.digest("MD5", str)
end

-- 获取机器码(触动精灵使用 getDeviceID)
function getMachineCode()
    local deviceId = getDeviceID()
    if deviceId and deviceId ~= "" then
        return deviceId .. "MAC"
    end
    -- 备选:使用 getDeviceSerial
    local serial = getDeviceSerial()
    if serial and serial ~= "" then
        return serial .. "MAC"
    end
    return "获取失败"
end

-- HTTP GET 请求(触动精灵使用 http.get)
function httpGet(url)
    local headers = {
        ["Cache-Control"] = "no-cache"
    }
    local code, body = http.get(url, headers)
    if code == 200 then
        return body
    else
        nLog("[HTTP] 请求失败,状态码: " .. tostring(code))
        return nil
    end
end

-- 验证服务器返回(签名 + 时间戳)
function verifyResponse(raw)
    if not raw then
        nLog("[验签] ❌ 返回数据为空")
        return nil
    end
    
    -- 分离签名
    local signIndex = string.find(raw, "|sign=")
    if not signIndex then
        nLog("[验签] ❌ 未找到签名标记")
        return nil
    end
    
    local body = string.sub(raw, 1, signIndex - 1)
    local sign = string.sub(raw, signIndex + 6)
    
    -- 本地计算签名
    local localSign = md5(body .. SIGN_KEY)
    nLog("[验签] 本地签名: " .. localSign)
    nLog("[验签] 服务器签名: " .. sign)
    
    if localSign ~= sign then
        nLog("[验签] ❌ 签名不匹配")
        return nil
    end
    nLog("[验签] ✅ 签名校验通过")
    
    -- 提取时间戳
    local lastPipe = string.find(body, "|[^|]*$")
    if not lastPipe then
        nLog("[验签] ❌ 未找到时间戳")
        return nil
    end
    
    local tsStr = string.sub(body, lastPipe + 1)
    local ts = tonumber(tsStr)
    local nowTs = getTimestamp()
    local diff = math.abs(nowTs - ts)
    
    nLog("[验签] 服务器时间戳: " .. ts)
    nLog("[验签] 本地时间戳: " .. nowTs)
    nLog("[验签] 时间差: " .. diff .. " 秒 (最大允许: " .. TIMESTAMP_MAX_DIFF .. " 秒)")
    
    if diff > TIMESTAMP_MAX_DIFF then
        nLog("[验签] ❌ 时间戳过期")
        return nil
    end
    nLog("[验签] ✅ 时间戳校验通过")
    
    local biz = string.sub(body, 1, lastPipe - 1)
    nLog("[验签] 业务内容: " .. biz)
    return biz
end

-- 获取卡密验证开关
function getCardSwitch()
    for i = 1, 3 do
        local ts = getTimestamp()
        local url = BASE_URL .. "?act=get_switch&app=" .. APP_NAME .. "&t=" .. ts
        nLog("[开关] 第 " .. i .. " 次尝试: " .. url)
        
        local response = httpGet(url)
        if response then
            local biz = verifyResponse(response)
            if biz then
                if string.find(biz, "CARD_ON") then
                    nLog("[开关] ✅ 卡密验证已开启")
                    return "CARD_ON"
                elseif string.find(biz, "CARD_OFF") then
                    nLog("[开关] ⚠️ 卡密验证已关闭")
                    return "CARD_OFF"
                end
            end
        end
        mSleep(500)
    end
    nLog("[开关] ⚠️ 开关获取失败,默认开启验证")
    return "CARD_ON"
end

-- 验证卡密
function verifyCard(card, mac)
    local ts = getTimestamp()
    local url = BASE_URL .. "?card=" .. card .. "&mac=" .. mac .. "&app=" .. APP_NAME .. "&heart=1&t=" .. ts
    nLog("[验证] 请求URL: " .. url)
    nLog("[验证] 当前应用: " .. APP_NAME)
    nLog("[验证] 使用卡密: " .. card)
    
    local response = httpGet(url)
    if not response then
        nLog("[验证] 网络错误")
        return nil
    end
    
    nLog("[验证] 服务器返回: " .. response)
    return verifyResponse(response)
end

-- 解析剩余时间
function parseRemainingTime(biz)
    local parts = {}
    for part in string.gmatch(biz, "[^|]+") do
        table.insert(parts, part)
    end
    
    if #parts >= 3 then
        local days = tonumber(parts[3])
        local minutes = (#parts >= 4) and tonumber(parts[4]) or 0
        nLog("[时间] 剩余天数: " .. days .. " 天, 剩余分钟: " .. minutes .. " 分钟")
        
        if minutes > 0 then
            local hours = math.floor(minutes / 60)
            local mins = minutes % 60
            nLog("[时间] 详细: " .. days .. "天" .. hours .. "小时" .. mins .. "分钟")
            return days .. "天" .. hours .. "小时" .. mins .. "分钟"
        else
            return days .. "天"
        end
    elseif #parts >= 2 and parts[2] == "permanent" then
        nLog("[时间] 终身有效")
        return "终身有效"
    end
    return "未知"
end

-- 错误码转中文
function transMsg(code)
    local map = {
        activate = "激活成功",
        valid = "验证通过",
        permanent = "终身有效",
        expired = "卡密已过期",
        banned = "卡密已被禁用",
        device_mismatch = "设备不匹配",
        online_limit_reached = "在线设备数已满",
        invalid_card = "卡密无效",
        missing_params = "参数不完整",
        already_online = "该卡密已在线"
    }
    return map[code] or code
end

-- 心跳线程函数
function heartbeatLoop()
    nLog("[心跳] 启动心跳线程")
    failCount = 0
    
    while isRunning do
        if currentCard == "" or currentMac == "" then
            mSleep(2000)
            goto continue
        end
        
        local biz = verifyCard(currentCard, currentMac)
        if biz and string.sub(biz, 1, 3) == "ok|" then
            failCount = 0
            nLog("[心跳] ✅ 心跳正常")
            parseRemainingTime(biz)
        else
            failCount = failCount + 1
            nLog("[心跳] ❌ 心跳异常,连续失败: " .. failCount .. "/5")
            if failCount >= 5 then
                nLog("[心跳] 连续失败5次,退出脚本")
                isRunning = false
                luaExit()
            end
        end
        
        ::continue::
        mSleep(HEARTBEAT_INTERVAL * 1000)
    end
end

-- 用户主线程(占位,用户自行实现)
function userMainLoop()
    local count = 0
    nLog("[用户代码] 用户代码运行中...")
    while isRunning do
        count = count + 1
        if count % 10 == 0 then
            nLog("[用户代码] 用户代码运行中... 当前时间: " .. os.date("%Y-%m-%d %H:%M:%S"))
        end
        mSleep(1000)
    end
end

-- ==================== 主函数 ====================

function main()
    nLog("========================================")
    nLog("  卡密通 触动精灵 对接示例")
    nLog("========================================")
    
    -- 1. 获取机器码
    currentMac = getMachineCode()
    if currentMac == "获取失败" then
        nLog("[错误] 获取机器码失败")
        dialog("获取机器码失败", 0)
        return
    end
    nLog("[机器码] " .. currentMac)
    
    -- 2. 获取验证开关
    local cardSwitch = getCardSwitch()
    if cardSwitch == "CARD_OFF" then
        nLog("[提示] 卡密验证已关闭,直接进入")
        dialog("卡密验证已关闭,直接进入", 0)
        currentCard = "88888888"
        heartbeatThread = thread.create(heartbeatLoop)
        thread.resume(heartbeatThread)
        userMainLoop()
        return
    end
    
    -- 3. 输入卡密(使用触动精灵对话框)
    local inputResult = dialog("请输入卡密:", "卡密验证", "", 1)
    if inputResult == "" or inputResult == nil then
        nLog("[错误] 卡密不能为空")
        return
    end
    currentCard = inputResult
    
    -- 4. 验证卡密
    nLog("[验证] 验证中...")
    local biz = verifyCard(currentCard, currentMac)
    
    if not biz then
        nLog("[错误] 验证失败:签名错误或网络异常")
        dialog("验证失败:网络异常或签名错误", 0)
        return
    end
    
    if string.sub(biz, 1, 3) == "ok|" then
        local remaining = parseRemainingTime(biz)
        nLog("[成功] 验证通过!剩余时间: " .. remaining)
        dialog("验证成功!\n剩余时间:" .. remaining, 0)
        
        -- 5. 启动心跳线程
        heartbeatThread = thread.create(heartbeatLoop)
        thread.resume(heartbeatThread)
        
        -- 6. 用户主逻辑
        userMainLoop()
    elseif string.sub(biz, 1, 6) == "error|" then
        local errCode = string.match(biz, "error|([^|]+)")
        if errCode then
            local errMsg = transMsg(errCode)
            nLog("[失败] " .. errMsg)
            dialog("验证失败:" .. errMsg, 0)
        else
            nLog("[失败] " .. biz)
            dialog("验证失败:" .. biz, 0)
        end
    else
        nLog("[失败] " .. biz)
        dialog("验证失败:" .. biz, 0)
    end
end

-- 启动脚本
main()