diff --git a/core/douyu/activity_client.py b/core/douyu/activity_client.py index 579b329..5f43ac9 100644 --- a/core/douyu/activity_client.py +++ b/core/douyu/activity_client.py @@ -42,7 +42,14 @@ class DouyuActivityClient: CREDIT_BALANCE_API = "https://www.douyu.com/japi/oms/web/peace/credit/balance" CREDIT_DIFF_API = "https://www.douyu.com/japi/oms/web/peace/credit/diff" PEACE_ITEM_API = "https://www.douyu.com/japi/oms/web/peace/item" + PEACE_TIME_API = "https://www.douyu.com/japi/oms/web/peace/time" PAY_QR_API = "https://www.douyu.com/japi/oms/web/pay/getQrCode" + ESPORTS_BIND_INFO_API = "https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo" + ESPORTS_BIND_ROLE_PARAM_API = "https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindRoleParam" + ESPORTS_CSRF_API = "https://www.douyu.com/wgapi/livenc/liveweb/csrfApi/getCsrfCookie" + ESPORTS_AGREEMENT_API = "https://www.douyu.com/wgapi/ordnc/activity/peacematch/agreement" + ESPORTS_ACT_BIND_API = "https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/actBind" + ESPORTS_USER_INFO_API = "https://www.douyu.com/wgapi/ordnc/activity/peacematch/getUserInfo" GOLD_QR_API = "https://cz.douyu.com/m/gold/getQrCode" GOLD_ACCOUNT_API = "https://cz.douyu.com/item/gold/account" EXCHANGE_BALANCE_API = "https://www.douyu.com/wjapi/nc/exchange/fim" @@ -238,6 +245,10 @@ class DouyuActivityClient: ) if payload.get("error") not in (0, "0", None): raise DouyuActivityError(payload.get("msg") or "查询绑定信息失败") + return self._parse_bind_info(payload, act_alias=act_alias, api_version="v2" if v2 else "v1") + + def _parse_bind_info(self, payload: dict[str, Any], *, act_alias: str, api_version: str) -> dict[str, Any]: + """统一解析不同活动的腾讯游戏绑定信息。""" data = payload.get("data") or {} game_account = data.get("gameAccount") or {} game_role = data.get("gameRole") or {} @@ -270,7 +281,7 @@ class DouyuActivityClient: can_change_role = self._first_present(tx_act, "canChangeRole", "canChangeBind") return { "act_alias": act_alias, - "api_version": "v2" if v2 else "v1", + "api_version": api_version, "bind_account": bind_account, "bind_role": bind_role, "bind_act": bind_act, @@ -289,6 +300,96 @@ class DouyuActivityClient: "raw": payload, } + def esports_bind_info(self, act_alias: str) -> dict[str, Any]: + """电竞手册唯一的角色状态查询,返回角色、绑定状态和换绑冷却。""" + payload = self._request_json( + "get", + self.ESPORTS_BIND_INFO_API, + "查询电竞手册绑定信息", + params={"actAlias": act_alias}, + headers={"Origin": "", "Referer": "https://www.douyu.com/pages/live-peace-pel/web/"}, + ) + if payload.get("error") not in (0, "0", None): + raise DouyuActivityError(payload.get("msg") or "查询电竞手册绑定信息失败") + return self._parse_bind_info(payload, act_alias=act_alias, api_version="esports") + + def get_esports_bind_qr(self, act_alias: str) -> dict[str, Any]: + """生成电竞手册的腾讯角色绑定二维码链接。""" + payload = self._request_json( + "get", + self.ESPORTS_BIND_ROLE_PARAM_API, + "获取电竞手册绑定二维码", + params={"actAlias": act_alias}, + headers={"Origin": "", "Referer": "https://www.douyu.com/pages/live-peace-pel/web/"}, + ) + if payload.get("error") not in (0, "0", None): + raise DouyuActivityError(payload.get("msg") or "获取电竞手册绑定二维码失败") + data = payload.get("data") or {} + # 电竞手册网页实际打开 data.url;gameRoleBindUrl 是兼容旧入口。 + bind_url = data.get("url") or data.get("gameRoleBindUrl") or "" + if not bind_url: + raise DouyuActivityError(payload.get("msg") or "电竞手册绑定二维码链接为空") + return { + "url": bind_url, + "bind_url": bind_url, + "role_param_url": data.get("url") or "", + "game_role_bind_url": data.get("gameRoleBindUrl") or "", + "act_alias": act_alias, + "raw": payload, + } + + def esports_ctn(self, *, referer: str) -> str: + """获取电竞手册页面当前 ctn,仅在 Cookie 缺失时走页面 CSRF 接口。""" + ctn = cookie_value(self.cookie, "acf_ccn") + if ctn: + return ctn + + response = self._request( + "get", + self.ESPORTS_CSRF_API, + source="获取电竞手册 ctn", + headers={"Origin": "", "Referer": referer}, + ) + ctn = cookie_value(self.cookie, "acf_ccn") + if not ctn: + raise DouyuActivityError(f"获取电竞手册 ctn 失败: {response.text[:200]}") + return ctn + + def confirm_esports_bind(self, act_alias: str, *, room_id: str = "9263298") -> dict[str, Any]: + """自动完成协议前置请求后,通过 actBind 确认电竞手册绑定。""" + referer = ( + "https://www.douyu.com/pages/live-peace-pel/web" + f"?roomId={room_id or '9263298'}&isAnchorSide=0" + ) + + # 页面成功流程固定为 agreement -> actBind;协议不作为独立业务状态暴露。 + # 两次 POST 都使用当前 Cookie 中的 acf_ccn,不额外主动刷新 CSRF。 + agreement_ctn = self.esports_ctn(referer=referer) + agreement = self._request_json( + "post", + self.ESPORTS_AGREEMENT_API, + "同意电竞手册协议", + files={"ctn": (None, agreement_ctn)}, + headers={"Referer": referer}, + ) + if agreement.get("error") not in (0, "0", None): + raise DouyuActivityError(agreement.get("msg") or "同意电竞手册协议失败") + + bind_ctn = self.esports_ctn(referer=referer) + bind = self._request_json( + "post", + self.ESPORTS_ACT_BIND_API, + "完成电竞手册绑定", + files={ + "actAlias": (None, act_alias), + "ctn": (None, bind_ctn), + }, + headers={"Referer": referer}, + ) + if bind.get("error") not in (0, "0", None): + raise DouyuActivityError(bind.get("msg") or "完成电竞手册绑定失败") + return {"agreement": agreement, "act_bind": bind} + def confirm_bind(self, act_alias: str) -> dict[str, Any]: """确认绑定当前扫码选择的游戏角色。""" token = self.csrf_token() @@ -337,6 +438,67 @@ class DouyuActivityClient: raise DouyuActivityError(payload.get("msg") or "生成精英宝典支付码失败") return {"pay_url": qr, "ctn": ctn, "act_alias": act_alias, "item": item_payload, "raw": payload} + def create_esports_qr(self, *, ctn: str, act_alias: str, amount: int, room_id: str) -> dict[str, Any]: + """生成电竞手册支付二维码。""" + self._set_cookie_value("acf_ccn", ctn) + time_payload = self._request_json( + "get", + self.PEACE_TIME_API, + "校验电竞手册开通时间", + headers={"Referer": "https://www.douyu.com/pages/live-peace-pel/web/"}, + ) + if time_payload.get("error") not in (0, "0"): + raise DouyuActivityError(time_payload.get("msg") or "电竞手册当前不可开通") + + payload = self._request_json( + "post", + self.PAY_QR_API, + "生成电竞手册支付码", + json={ + "bizType": "peacePel", + "biz": {"actAlias": act_alias, "amount": amount, "roomId": int(room_id or 0)}, + "ctn": ctn, + }, + headers={ + "Content-Type": "application/json;charset=UTF-8", + "Referer": "https://www.douyu.com/pages/live-peace-pel/web/", + }, + ) + data = payload.get("data") or {} + qr = data.get("qr") or "" + if not qr: + raise DouyuActivityError(payload.get("msg") or "生成电竞手册支付码失败") + return { + "pay_url": qr, + "pay_token": data.get("token") or "", + "pay_ttl": data.get("ttl"), + "ctn": ctn, + "act_alias": act_alias, + "time": time_payload, + "raw": payload, + } + + def esports_user_info(self, *, manual_id: str) -> dict[str, Any]: + """查询电竞手册的开通状态和积分。""" + payload = self._request_json( + "get", + self.ESPORTS_USER_INFO_API, + "查询电竞手册状态", + params={"manualID": manual_id}, + headers={"Origin": "", "Referer": "https://www.douyu.com/pages/live-peace-pel/web/"}, + ) + if payload.get("error") not in (0, "0", None): + raise DouyuActivityError(payload.get("msg") or "查询电竞手册状态失败") + user_info = (payload.get("data") or {}).get("userInfo") or {} + return { + "manual_id": manual_id, + "manual_type": user_info.get("manualType"), + "manual_score": user_info.get("manualScore"), + "expire_time": user_info.get("expireTime"), + "user_info": user_info, + "raw": payload, + } + def create_gold_qr(self, *, amount: int, pay_type: int = 1, product_id: str = "DYTV_Product_Gold_1") -> dict[str, Any]: """生成鱼翅充值支付二维码。""" payload = self._request_json( diff --git a/e语言/douyu/皮肤与充值/电竞手册活动.txt b/e语言/douyu/皮肤与充值/电竞手册活动.txt new file mode 100644 index 0000000..b7eb735 --- /dev/null +++ b/e语言/douyu/皮肤与充值/电竞手册活动.txt @@ -0,0 +1,2486 @@ +.版本 2 +.支持库 edroptarget +.支持库 iext +.支持库 EThread +.支持库 spec + +.程序集 窗口程序集_窗口1 +.程序集变量 集句柄, 整数型 +.程序集变量 工作中, 逻辑型 +.程序集变量 工作线程池EX, 鱼刺类_线程池Ex +.程序集变量 ww, 未闻花名花见皮肤 + +.子程序 _窗口1_创建完毕 + +ww.皮肤加载 (2, , , , 真) +' 网络版授权验证 (假, “f28ba622a3ef3abf0a419f5cd6b548396dbcd7bd0d25fc815b6256be1db23421d3a329637cdb54f61e492184d1602670dff739ec7bd2847d67b98f0bff373646”) +.如果真 (信息框 (“此程序仅用于学习、技术交流,请勿用于非法!如出现任何问题后果自负!” + #换行符 + “ ”, #确认取消钮, “免责声明!!!”, ) = #取消钮) + 结束 () +.如果真结束 + +全目录临时 = 取运行目录 () + “\XYZ\临时” +全目录状态 = 取运行目录 () + “\XYZ\数据状态” +全PIC = 取运行目录 () + “\XYZ\生成订单图片” +.如果真 (文件是否存在 (全目录临时) = 假) + 目录_创建 (全目录临时) +.如果真结束 +.如果真 (文件是否存在 (全目录状态) = 假) + 目录_创建 (全目录状态) +.如果真结束 +.如果真 (文件是否存在 (全PIC) = 假) + 目录_创建 (全PIC) +.如果真结束 + +拖放对象1.注册拖放控件 (超级列表框1.取窗口句柄 ()) +状态条1.置文本 (0, “双击列表指定数据行,查询账号详情....”) +数据写入列表 () +启动线程 (&商品列表, , ) +Gdip开始 () +配置_读取_易语言组件 (窗口1) + +.子程序 _超级列表框1_右键单击表项 + +弹出菜单 (菜单, , ) + +.子程序 _拖放对象1_得到文件 +.参数 接收到的文件路径, 文本型 +.局部变量 i, 文本型 +.局部变量 第一次分割, 文本型, , "0" +.局部变量 第二次分割, 文本型, , "3" +.局部变量 N, 整数型 +.局部变量 索引, 整数型 +.局部变量 计次1, 整数型 +.局部变量 计次2, 整数型 +.局部变量 临时数组, 文本型, , "0" +.局部变量 文本数组, 文本型, , "0" + +i = 到文本 (读入文件 (接收到的文件路径)) +文本数组 = 分割文本 (i, #换行符, ) +.计次循环首 (取数组成员数 (文本数组), 计次1) + 系统_处理事件 () + 索引 = 超级列表框1.插入表项 (, , , , , ) + 超级列表框1.置标题 (索引, 0, 到文本 (索引 + 1)) + 超级列表框1.置标题 (索引, 1, 文本数组 [计次1]) + 超级列表框1.置状态图片 (索引, 1) + 系统_处理事件 () +.计次循环尾 () +超级列表框1.允许重画 () ' 恢复自动重画 +超级列表框1.发送信息 (277, 7, 0) ' 显示最后一行 +清除数组 (文本数组) + +.子程序 _删除当前_被选择 +.局部变量 i, 整数型 +.局部变量 局_保存文本, 鱼刺类_输出文本 +.局部变量 局_保存目录, 文本型 + +超级列表框1.删除表项 (超级列表框1.现行选中项) +.计次循环首 (超级列表框1.取表项数 (), i) + 系统_处理事件 () + 超级列表框1.置标题 (i - 1, 0, 到文本 (i)) +.计次循环尾 () +.计次循环首 (超级列表框1.取表项数 (), i) + 系统_处理事件 () + 超级列表框1.置状态图片 (i - 1, 1) + 超级列表框1.置标题 (i - 1, 0, 到文本 (i)) +.计次循环尾 () + + +.子程序 _删除勾选_被选择 +.局部变量 表项数, 整数型 +.局部变量 m, 整数型 +.局部变量 i, 整数型 +.局部变量 局_保存目录, 文本型 +.局部变量 局_保存文本, 鱼刺类_输出文本 + +表项数 = 超级列表框1.取表项数 () +.计次循环首 (表项数, m) + 系统_处理事件 () + .如果真 (超级列表框1.取当前状态图片 (表项数 - m) = 1) + 超级列表框1.删除表项 (表项数 - m) + .如果真结束 + +.计次循环尾 () +.计次循环首 (超级列表框1.取表项数 (), i) + 系统_处理事件 () + 超级列表框1.置状态图片 (i - 1, 1) + 超级列表框1.置标题 (i - 1, 0, 到文本 (i)) +.计次循环尾 () + + +.子程序 _清空CK_被选择 +.局部变量 局_保存目录, 文本型 + +超级列表框1.全部删除 () +局_保存目录 = 取运行目录 () + “\斗鱼CK\” + 时间_格式化 (取现行时间 (), “MM.dd-”) + “斗鱼CK.txt” +删除文件 (局_保存目录) + +.子程序 _按钮_刷新商品_被单击 + +启动线程 (&商品列表, , ) + +.子程序 商品列表 +.局部变量 str, 文本型 +.局部变量 yyjson, json对象 +.局部变量 价格, 文本型 +.局部变量 N, 整数型 +.局部变量 commodityName, 文本型 +.局部变量 commodityId, 文本型 +.局部变量 列表NUM, 整数型 + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/activity/peacematch/storehome?manualID=1W0yGkOdDbAXb&rid=9263298”, , , , , “Host: www.douyu.com” + #换行符 + “Connection: keep-alive” + #换行符 + “sec-ch-ua: ” + #引号 + “Not_A Brand” + #引号 + “;v=” + #引号 + “8” + #引号 + “, ” + #引号 + “Chromium” + #引号 + “;v=” + #引号 + “120” + #引号 + “, ” + #引号 + “Google Chrome” + #引号 + “;v=” + #引号 + “120” + #引号 + “” + #换行符 + “Accept: application/json, text/plain, */*” + #换行符 + “sec-ch-ua-mobile: ?0” + #换行符 + “User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36” + #换行符 + “sec-ch-ua-platform: ” + #引号 + “Windows” + #引号 + “” + #换行符 + “Sec-Fetch-Site: same-origin” + #换行符 + “Sec-Fetch-Mode: cors” + #换行符 + “Sec-Fetch-Dest: empty” + #换行符 + “Referer: https://www.douyu.com/pages/live-peace-pel/web/shop”)) +yyjson.清空 () +.如果真 (yyjson.解析 (str) = 假) + 提示框 (“商品列表解析失败”, , , , 2000) + 返回 () +.如果真结束 + +组合框1.清空 () +状态条1.置文本 (0, “正在刷新列表商品....”) +.计次循环首 (yyjson.取成员数 (“data/list”), N) + commodityId = yyjson.取文本 (“data/list/” + 到文本 (N - 1) + “/commodityId”) + commodityName = yyjson.取文本 (“data/list/” + 到文本 (N - 1) + “/commodityName”) + 价格 = yyjson.取文本 (“data/list/” + 到文本 (N - 1) + “/score”) + 组合框1.插入项目 (N - 1, commodityName + “一” + 价格 + “一” + commodityId, ) + 系统_处理事件 () +.计次循环尾 () +组合框1.现行选中项 = 0 +状态条1.置文本 (0, “列表商品刷新完成”) + +.子程序 _获取绑定二维码_被选择 + +启动线程 (&取二维码, , ) + +.子程序 取二维码 +.局部变量 ctn1, 文本型 +.局部变量 返回CK, 文本型 +.局部变量 局ID, 文本型 +.局部变量 str2, 文本型 +.局部变量 局_CK, 文本型 +.局部变量 str, 文本型 +.局部变量 局_CCK, 文本型 +.局部变量 ctn2, 文本型 +.局部变量 manualID, 文本型 +.局部变量 actAlias, 文本型 +.局部变量 str3, 文本型 +.局部变量 yyjson, json对象 +.局部变量 绑定url, 文本型 +.局部变量 局_文本, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 绑定游戏昵称, 文本型 +.局部变量 局_斗鱼昵称, 文本型 +.局部变量 图片句柄, 整数型 + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_斗鱼昵称 = 局_F [1] +局_CCK = 局_F [3] + +str = 到文本 (网页_访问_对象 (“https://www.douyu.com/curl/csrfApi/getCsrfCookie?”, , , 局_CCK, 返回CK, #常量1)) +调试输出 (局_CCK, 返回CK) +' 调试输出 (str) +状态条1.置文本 (0, “生成1-> ” + str) +ctn1 = 文本_取出中间文本 (返回CK, “acf_ccn=”, “;”) +.如果 (ctn1 ≠ “”) + str2 = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/livenc/asubscribe/userSubStatus”, 1, “subscribe=cmn&ctn=” + ctn1, 返回CK, 局_CK, #常量1)) + 状态条1.置文本 (0, “生成二维码-> ” + str2) + ' 调试输出 (str2) + .如果 (寻找文本 (str2, “error” + #引号 + “:0”, , 假) ≠ -1) + ctn2 = 文本_取出中间文本 (局_CK, “acf_ccn=”, “;”) + ' 调试输出 (“ctn2---”, ctn2) + 全临界.进入 () + 超级列表框1.置标题 (超级列表框1.现行选中项, 5, ctn2) + 全临界.退出 () + + manualID = “mgJwBOaZ11ZDp” + actAlias = “20260226ZMGDG” + + str3 = 到文本 (网页_访问_对象 (“https://www.douyu.com/japi/carnivalApi/tencent/roleParam”, 1, “actAlias=” + actAlias + “&ctn=” + ctn2, 局_CCK, , #常量3)) + ' 调试输出 (str3) + yyjson.清空 () + yyjson.解析 (str3) + 绑定url = yyjson.取文本 (“data/url”) + ' 调试输出 (“绑定url-> ”, 绑定url) + .如果 (绑定url = “”) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “获取二维码失败”) + + .否则 + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “绑定二维码已生成”) + ' 图片框3.图片 = 文转图片E (局_斗鱼昵称) + 图片框1.图片 = 文本生成二维码图片 (绑定url, , , , , 10) + 状态条1.置文本 (1, “斗鱼名:” + 局_斗鱼昵称) + + 绑定游戏昵称 = 检测是否绑定游戏账号 (局_CCK) + 窗口_置信息3 (超级列表框1.现行选中项, 1, 绑定游戏昵称) + 检测账号 (局_CK, ctn2) + .如果结束 + + + .否则 + 窗口_置状态信息 (超级列表框1.现行选中项, 2, str2) + .如果结束 + +.否则 + 窗口_置状态信息 (超级列表框1.现行选中项, 2, 编码_usc2到ansi (str)) +.如果结束 + + +.子程序 检测账号, , , 通过兑换检测账号 +.参数 参_cook, 文本型 +.参数 参_ctn, 文本型 +.局部变量 局_文本, 文本型 +.局部变量 ctnEX, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_Cookie, 文本型 +.局部变量 str, 文本型 +.局部变量 局_xy, 文本型 +.局部变量 商品表, 文本型 +.局部变量 商品组, 文本型, , "0" +.局部变量 要兑换的商品名称, 文本型 +.局部变量 要兑换的商品价格, 文本型 +.局部变量 要兑换的商品ID, 文本型 +.局部变量 roleName, 文本型 +.局部变量 str2, 文本型 +.局部变量 局_bin, 字节集 +.局部变量 生成者, 文本型 +.局部变量 优化价格, 文本型 +.局部变量 areaName, 文本型 +.局部变量 platName, 文本型 +.局部变量 msg, 文本型 + +商品表 = 组合框1.取项目文本 (组合框1.现行选中项) +商品组 = 分割文本 (商品表, “一”, ) +.如果真 (取数组成员数 (商品组) < 3) + 状态条1.置文本 (0, “商品格式错误x”) + 返回 () +.如果真结束 + + +' 要兑换的商品名称 = 商品组 [1] +' 要兑换的商品价格 = 商品组 [2]nZALooPn0kZPW&ditchname=pass0&roomId=999 +要兑换的商品ID = 商品组 [3] +窗口_置状态信息 (超级列表框1.现行选中项, 2, “检测账号...”) +状态条1.置文本 (0, “正在检测账号-> ”) +' manualID=G4KA4Qnz4LDp7 +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/activity/peace/exchange”, 1, “manualID=1W0yGkOdDbAXb&rid=999&commodityID=” + 要兑换的商品ID + “&token%5Berror%5D%5Bcode%5D=-1&token%5Bdata%5D=&ctn=” + 参_ctn, 参_cook + “; acf_ccn=” + 参_ctn, , #常量7, 局_xy)) +' 调试输出 (取程序名称 (1), str) +状态条1.置文本 (0, “检测账号:” + str) +.如果真 (寻找文本 (str, “需要开通精英手册”, , 假) ≠ -1 或 寻找文本 (str, “服务器繁忙”, , 假) ≠ -1 或 寻找文本 (str, “不在时间范围内”, , 假) ≠ -1) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “账号有效”) + ' ’str2 = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/carnivalApi/tencent/bindInfo?actAlias=20220616CJSZU”, , , 参_cook, , #常量3)) + 返回 () +.如果真结束 + + +' msg = 文本_取出中间文本 (str, “msg” + #引号 + “:” + #引号, #引号) +.' 如果真 (msg = “”) + ' 窗口_置状态信息 (超级列表框1.现行选中项, 2, “账号异常”) + ' 返回 () +.如果真结束 + +窗口_置状态信息 (超级列表框1.现行选中项, 2, “账号异常:” + str) + +.子程序 检测是否绑定游戏账号, 文本型 +.参数 参_CK, 文本型 +.局部变量 str, 文本型 +.局部变量 roleName, 文本型 + +' https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo?actAlias=20260226ZMGDG +' https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo?gameId=cjm + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo?actAlias=20260226ZMGDG”, , , 参_CK, , #常量3)) + +状态条1.置文本 (0, “查绑游戏名-> ” + str) +' {"error":0,"msg":"success","data":{"bindAccount":1,"bindRole":1,"bindAct":1,"needBindRole":null,"txAct":{"txActId":2811,"gameId":"cjm","gameName":"和平精英","bindType":1},"gameAccount":{"bindType":2,"type":"","faceuri":"https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTI4cIOxb4f7vpSkqYCIIvY1EJTgkhS0ZhVzzJSl2LzDpUzVBJKiaDjvuzpdS6cadAvA6bnrYwZ7jlQ/132","nick":"zicoo"},"gameRole":{"roleName":"白菜被拱了呀","areaName":"微信","platName":"安卓(android)","partitionName":""},"isGranted":null,"gameAuthScene":null,"canChangeRole":0,"changeRoleWaitTime":2490528},"redirectUrl":null} + +roleName = 文本_取出中间文本 (str, “roleName” + #引号 + “:” + #引号, #引号) +.如果真 (roleName ≠ “”) + 返回 (roleName) +.如果真结束 + +返回 (“NULL”) + +.子程序 _确认绑定_被选择 + +启动线程 (&完成绑定, , ) + +.子程序 完成绑定 +.局部变量 str2, 文本型 +.局部变量 cvl_csrf_token, 文本型 +.局部变量 局_文本, 文本型 +.局部变量 str, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 返回CK, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 绑定游戏昵称, 文本型 +.局部变量 msg, 文本型 +.局部变量 ctnEX, 文本型 +.局部变量 返回游戏name, 文本型 +.局部变量 str_0, 文本型 +.局部变量 roleName, 文本型 +.局部变量 nick, 文本型 +.局部变量 areaName, 文本型 +.局部变量 platName, 文本型 + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +ctnEX = 超级列表框1.取标题 (超级列表框1.现行选中项, 5) + +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_Cookie = 局_F [3] +' https://www.douyu.com/japi/carnivalApi/v2/tencent/bindInfo?actAlias=20260120QYOOB +' 这个是新版的号得查询’str_0 = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/carnivalApi/v2/tencent/bindInfo?actAlias=20260120QYOOB”, , , 局_Cookie, , #常量3)) + +str_0 = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo?actAlias=20260226ZMGDG”, , , 局_Cookie, , #常量3)) + +roleName = 文本_取出中间文本 (str_0, “roleName” + #引号 + “:” + #引号, #引号) +nick = 文本_取出中间文本 (str_0, “nick” + #引号 + “:” + #引号, #引号) +areaName = 文本_取出中间文本 (str_0, “areaName” + #引号 + “:” + #引号, #引号) +platName = 文本_取出中间文本 (str_0, “platName” + #引号 + “:” + #引号, #引号) + +标签_状态.标题 = “昵称:” + nick + #换行符 + “游戏名称:” + roleName + #换行符 + “平台:” + areaName + #换行符 + “手机:” + platName +.如果真 (信息框 (“是否绑定:” + roleName, #确认取消钮, , ) = #取消钮) + 提示框 (“取消绑定:” + roleName, , , , 1000) + 返回 () +.如果真结束 + + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/carnival/nc/common/generateCsrf”, 1, “{}”, 局_Cookie, 返回CK, #常量3)) +cvl_csrf_token = 文本_取出中间文本 (返回CK, “cvl_csrf_token=”, “;”) +状态条1.置文本 (0, “取token-> ” + str) +' 调试输出 (cvl_csrf_token) +' 输出调试 (“cvl_csrf_token-”, cvl_csrf_token) +.如果真 (cvl_csrf_token = “”) + 窗口_置状态信息 (超级列表框1.现行选中项, 1, str) + 返回 () +.如果真结束 + +' 确认绑定 +str2 = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/carnivalApi/tencent/bind”, 1, “actAlias=20260226ZMGDG&csrfToken=” + cvl_csrf_token, 局_Cookie + “; cvl_csrf_token=” + cvl_csrf_token, , #常量3)) +状态条1.置文本 (0, “确认绑定-> ” + str2) +' 调试输出 (str2) +.如果真 (寻找文本 (str2, “error” + #引号 + “:0”, , 假) ≠ -1) + 窗口_置状态信息 (超级列表框1.现行选中项, 1, nick + “:绑定成功,生成二维码”) + 程序_延时 (500) + 窗口_置信息3 (超级列表框1.现行选中项, 1, roleName) + 程序_延时 (1000) + 电竞手册二维码30 (ctnEX, 局_Cookie) + 返回 () +.如果真结束 + +msg = 文本_取出中间文本 (str2, “msg” + #引号 + “:” + #引号, #引号) +窗口_置状态信息 (超级列表框1.现行选中项, 1, msg) +信息框 (msg, 0, , ) + +' 绑定成功 {"error":0,"msg":"success","data":null,"redirectUrl":null} +' {"error":1006,"msg":"CSRF校验不通过!","data":null} +' {"error":-200001,"msg":"当前账号处于换绑周期冷却期,不可换绑","data":{},"redirectUrl":null} +' 输出调试 (“str5确认绑定-”, str5) + +.子程序 电竞手册二维码30 +.参数 参_ctn, 文本型 +.参数 参_Cookie, 文本型 +.局部变量 str, 文本型 +.局部变量 str2, 文本型 +.局部变量 yyjson, json对象 +.局部变量 付款30链接, 文本型 + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/oms/web/peace/time”, , , 参_Cookie + “; ” + “acf_ccn=” + 参_ctn, , #常量4)) +状态条1.置文本 (0, “取acf_ccn-> ” + str) +.如果真 (寻找文本 (str, “error” + #引号 + “:0”, , 假) ≠ -1) + ' {"bizType":"peacePel","biz":{"actAlias":"20260226ZMGDG","amount":3000,"roomId":9263298},"ctn":"47493c6cf9c8461cd6d500d9853c45cf"} + ' {"bizType":"peacePel","biz":{"actAlias":"20260226ZMGDG","amount":3000,"roomId":9263298},"ctn":"47493c6cf9c8461cd6d500d9853c45cf"} + str2 = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/oms/web/pay/getQrCode”, 1, “{” + #引号 + “bizType” + #引号 + “:” + #引号 + “peacePel” + #引号 + “,” + #引号 + “biz” + #引号 + “:{” + #引号 + “actAlias” + #引号 + “:” + #引号 + “20260226ZMGDG” + #引号 + “,” + #引号 + “amount” + #引号 + “:3000,” + #引号 + “roomId” + #引号 + “:9263298},” + #引号 + “ctn” + #引号 + “:” + #引号 + 参_ctn + #引号 + “}”, 参_Cookie + “; ” + “acf_ccn=” + 参_ctn, , #常量4)) + 状态条1.置文本 (0, “开通电竞手册-> ” + str2) + yyjson.清空 () + yyjson.解析 (str2) + 付款30链接 = yyjson.取文本 (“data/qr”) + .如果真 (付款30链接 ≠ “”) + 图片框1.图片 = 文本生成二维码图片 (付款30链接, , , , , 10) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “电竞手册二维码已生成”) + 状态条1.置文本 (1, “开通电竞手册,30元支付宝付款码”) + ' 标签反馈.标题 = “开通宝典,30元支付宝付款码” + 返回 () + .如果真结束 + + 窗口_置状态信息 (超级列表框1.现行选中项, 2, str2) + +.如果真结束 + + +.子程序 _开通精英手册30_被选择 + +启动线程 (&查积分记录, , ) +程序_延时 (2000) +启动线程 (&开通30精英, , ) + +.子程序 开通30精英 +.局部变量 局_文本, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_Cookie, 文本型 +.局部变量 ctnEX, 文本型 +.局部变量 str, 文本型 +.局部变量 str2, 文本型 +.局部变量 yyjson, json对象 +.局部变量 付款30链接, 文本型 + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +ctnEX = 超级列表框1.取标题 (超级列表框1.现行选中项, 5) +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_Cookie = 局_F [3] + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/oms/web/peace/item”, 1, “{” + #引号 + “itemId” + #引号 + “:1,” + #引号 + “ctn” + #引号 + “:” + #引号 + ctnEX + #引号 + “}”, 局_Cookie + “; ” + “acf_ccn=” + ctnEX, , #常量4)) +状态条1.置文本 (0, “取acf_ccn-> ” + str) +.如果真 (寻找文本 (str, “error” + #引号 + “:0”, , 假) ≠ -1) + str2 = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/oms/web/pay/getQrCode”, 1, “{” + #引号 + “bizType” + #引号 + “:” + #引号 + “peace” + #引号 + “,” + #引号 + “biz” + #引号 + “:{” + #引号 + “actAlias” + #引号 + “:” + #引号 + “20260120QYOOB” + #引号 + “,” + #引号 + “amount” + #引号 + “:3000,” + #引号 + “roomId” + #引号 + “:9263298},” + #引号 + “ctn” + #引号 + “:” + #引号 + ctnEX + #引号 + “}”, 局_Cookie + “; ” + “acf_ccn=” + ctnEX, , #常量4)) + 状态条1.置文本 (0, “开通手册-> ” + str2) + yyjson.清空 () + yyjson.解析 (str2) + 付款30链接 = yyjson.取文本 (“data/qr”) + ' 输出调试 (“30元支付宝付款码-> ”, 付款30链接) + .如果真 (付款30链接 ≠ “”) + 图片框1.图片 = 文本生成二维码图片 (付款30链接, , , , , 10) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “宝典二维码已生成”) + 状态条1.置文本 (1, “开通宝典,30元支付宝付款码”) + 返回 () + .如果真结束 + + 窗口_置状态信息 (超级列表框1.现行选中项, 2, str2) + +.如果真结束 + + +.子程序 _充值鱼翅_被选择 +.局部变量 返回积分, 文本型 + +返回积分 = 查积分记录 () +.如果真 (返回积分 = “NULL”) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “未开通电竞手册”) + 返回 () +.如果真结束 + +.如果真 (到整数 (返回积分) < 10) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “未开通电竞手册”) + .如果真 (信息框 (“积分:” + 返回积分 + “,是否充值?”, #信息图标 + #确认钮 + #取消钮, , ) = #取消钮) + 返回 () + .如果真结束 + +.如果真结束 + +窗口_置状态信息 (超级列表框1.现行选中项, 2, “已开通积分:” + 返回积分) +程序_延时 (1000) +启动线程 (&鱼翅充值, , ) + +.子程序 鱼翅充值 +.局部变量 str, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 局_文本, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 鱼刺付款码, 文本型 +.局部变量 鱼刺token, 文本型 +.局部变量 局_bool, 逻辑型 + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “Cookie格式错误”) + 返回 () +.如果真结束 + +' pay_type=1 支付宝支付 pay_type=2 微信支付 + +局_Cookie = 局_F [3] +str = 到文本 (网页_访问_对象 (“https://cz.douyu.com/m/gold/getQrCode”, 1, “pay_type=1&product_id=DYTV_Product_Gold_1&number=” + 删首尾空 (编辑框_充值金额.内容) + “&toname=&source=dy_box”, 局_Cookie, , #常量2)) + +鱼刺付款码 = 编码_usc2到ansi (文本_取出中间文本 (str, “code_url” + #引号 + “:” + #引号, #引号)) +鱼刺token = 文本_取出中间文本 (str, “token” + #引号 + “:” + #引号, #引号) +状态条1.置文本 (0, “鱼翅码-> ” + str) +.如果真 (鱼刺付款码 ≠ “”) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “鱼翅充值码已生成”) + 图片框1.图片 = 文本生成二维码图片 (鱼刺付款码, , , , , 10) + 状态条1.置文本 (1, “鱼翅” + 编辑框_充值金额.内容 + “元,支付宝付款码”) + 程序_延时 (1000) + 检测鱼刺充值状态 (鱼刺token, 超级列表框1.现行选中项, 局_Cookie) + 程序_延时 (2000) + 直接送礼物 (局_Cookie, 超级列表框1.现行选中项) + 程序_延时 (3000) + .计次循环首 (3, ) + 查积分记录 () + .计次循环尾 () + 返回 () +.如果真结束 + +窗口_置状态信息 (超级列表框1.现行选中项, 2, str) + +.子程序 检测鱼刺充值状态 +.参数 参_token, 文本型, 可空 +.参数 参_索引, 整数型 +.参数 参_cook, 文本型 +.局部变量 str, 文本型 +.局部变量 局_余额, 整数型 +.局部变量 局_工作, 逻辑型 +.局部变量 an, 整数型 +.局部变量 msg, 文本型 +.局部变量 yyjson, json对象 + +' 检测充值鱼刺成功 '{"error":0,"data":{"state":2,"order_id":"7682614632081039361001","quantity":10,"nickname":"用户27335596"}} +局_工作 = 真 +.判断循环首 (局_工作 = 真) + _原子递增 (an) + str = 到文本 (网页_访问_对象 (“https://cz.douyu.com/item/gold/account”, , , 参_cook, , #常量2)) + ' '调试输出 (str) + 状态条1.置文本 (0, “[” + 到文本 (an) + “]检测鱼翅充值-> ” + str) + yyjson.清空 () + yyjson.解析 (str) + 局_余额 = yyjson.取整数 (“data/gold”) + ' 调试输出 (“局_余额”, 局_余额) + 窗口_置状态信息 (参_索引, 1, “[” + 到文本 (an) + “]检测鱼翅充值”) + .如果真 (局_余额 > 0) + 窗口_置状态信息 (参_索引, 1, “鱼刺余额:” + 到文本 (局_余额)) + 返回 () + .如果真结束 + + 程序_延时 (2000) +.判断循环尾 () + +.子程序 直接送礼物 +.参数 参_cook, 文本型 +.参数 参_索引, 整数型 +.局部变量 str, 文本型 +.局部变量 局_充值数, 整数型 + +局_充值数 = 到整数 (删首尾空 (编辑框_充值金额.内容)) + +' 正常返回'{"error":0,"msg":"success","data":{"priceType":2,"sb":0,"noble_gold":0,"ry":0,"noble_gold_black":0,"gid":22171,"gx":0,"num":1,"messages":["type@=dgb/rid@=9263298/gfid@=22171/gs@=0/uid@=460469086/bg@=1/nn@=用户470321034/ic@=avatar@Sdefault@S14/eid@=0/eic@=20051/level@=2/dw@=0/gfcnt@=1/hits@=1/bcnt@=1/bst@=6/ct@=0/el@=/cm@=0/bnn@=/bl@=0/brid@=0/hc@=/sahf@=0/fc@=0/bnid@=1/bnl@=1/receive_uid@=335920043/receive_nn@=若若跑的贼快/pfm@=3175/pma@=454872551/mss@=454872569/skinid@=1890/bcst@=1/bsfl@=1/"],"toast":""}} +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/gift/donate/mainsite/v3”, 1, “giftId=24767&giftCount=” + 到文本 (局_充值数) + “&roomId=9263298&bizExt=%7B%22isMainGift%22%3A1%7D&skinId=3850”, 参_cook, , #常量9)) +状态条1.置文本 (0, “送礼物-> ” + str) + +.如果真 (寻找文本 (str, “error” + #引号 + “:0”, , 假) ≠ -1) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “送礼物成功”) + 返回 () +.如果真结束 + +窗口_置状态信息 (参_索引, 2, str) + +.子程序 _赠送冠军烟花_被选择 + +启动线程 (&送礼物, , ) + +.子程序 送礼物 +.局部变量 str, 文本型 +.局部变量 局_文本, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_Cookie, 文本型 +.局部变量 局_充值数, 整数型 + +局_充值数 = 到整数 (删首尾空 (编辑框_充值金额.内容)) + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_Cookie = 局_F [3] + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/gift/donate/mainsite/v3”, 1, “giftId=24767&giftCount=” + 到文本 (局_充值数) + “&roomId=9263298&bizExt=%7B%22isMainGift%22%3A1%7D&skinId=3850”, 局_Cookie, , #常量9)) + +' 调试输出 (str) +状态条1.置文本 (0, “送礼物-> ” + str) +' 正常返回'{"error":0,"msg":"success","data":{"priceType":2,"sb":0,"noble_gold":0,"ry":0,"noble_gold_black":0,"gid":22171,"gx":0,"num":1,"messages":["type@=dgb/rid@=9263298/gfid@=22171/gs@=0/uid@=460469086/bg@=1/nn@=用户470321034/ic@=avatar@Sdefault@S14/eid@=0/eic@=20051/level@=2/dw@=0/gfcnt@=1/hits@=1/bcnt@=1/bst@=6/ct@=0/el@=/cm@=0/bnn@=/bl@=0/brid@=0/hc@=/sahf@=0/fc@=0/bnid@=1/bnl@=1/receive_uid@=335920043/receive_nn@=若若跑的贼快/pfm@=3175/pma@=454872551/mss@=454872569/skinid@=1890/bcst@=1/bsfl@=1/"],"toast":""}} +.如果真 (寻找文本 (str, “error” + #引号 + “:0”, , 假) ≠ -1) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “送礼物成功”) + 返回 () +.如果真结束 + +窗口_置状态信息 (超级列表框1.现行选中项, 2, str) + +.子程序 _查询手册积分_被选择 + +启动线程 (&查手册积分, , ) + +.子程序 查手册积分 +.局部变量 局_文本, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 ctnEX, 文本型 +.局部变量 str, 文本型 +.局部变量 userid, 文本型 +.局部变量 diff, 文本型 + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +ctnEX = 超级列表框1.取标题 (超级列表框1.现行选中项, 5) + +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “Cookie格式错误”) + 返回 () +.如果真结束 + +userid = 局_F [2] +局_Cookie = 局_F [3] + + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/oms/web/peace/credit/diff”, 1, “{” + #引号 + “userId” + #引号 + “:” + #引号 + userid + #引号 + “,” + #引号 + “ctn” + #引号 + “:” + #引号 + ctnEX + #引号 + “}”, 局_Cookie, , #常量5)) + +' '{"error":0,"msg":"success","data":{"diff":300} +状态条1.置文本 (0, “查积分-> ” + str) +diff = 文本_取出中间文本 (str, “diff” + #引号 + “:”, “}”) +.如果真 (diff ≠ “”) + 窗口_置状态信息 (超级列表框1.现行选中项, 5, “积分:” + diff) + 返回 () +.如果真结束 + +窗口_置状态信息 (超级列表框1.现行选中项, 5, str) + +.子程序 _一键查询积分_被选择 +.局部变量 p, 整数型 + +.计次循环首 (超级列表框1.取表项数 (), p) + .如果真 (超级列表框1.取当前状态图片 (p - 1) = 1) + 启动线程 (&查积分记录_线程, p - 1, ) + 程序_延时 (10) + .如果真结束 + +.计次循环尾 () + +.子程序 查积分记录_线程 +.参数 参_索引E, 整数型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_文本, 文本型 +.局部变量 uid, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 ctnEX, 文本型 +.局部变量 str, 文本型 +.局部变量 balance, 文本型 + +全临界.进入 () +局_文本 = 超级列表框1.取标题 (参_索引E, 1) +ctnEX = 超级列表框1.取标题 (参_索引E, 5) +全临界.退出 () + +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (参_索引E, 2, “Cookie格式错误”) + 返回 () +.如果真结束 + +uid = 局_F [2] +局_Cookie = 局_F [3] + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/activity/peacematch/score/balance”, , , 局_Cookie, , “Host: www.douyu.com” + #换行符 + “Connection: keep-alive” + #换行符 + “sec-ch-ua-platform: ” + #引号 + “Windows” + #引号 + “” + #换行符 + “User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36” + #换行符 + “Accept: application/json, text/plain, */*” + #换行符 + “sec-ch-ua: ” + #引号 + “Not(A:Brand” + #引号 + “;v=” + #引号 + “99” + #引号 + “, ” + #引号 + “Google Chrome” + #引号 + “;v=” + #引号 + “133” + #引号 + “, ” + #引号 + “Chromium” + #引号 + “;v=” + #引号 + “133” + #引号 + “” + #换行符 + “sec-ch-ua-mobile: ?0” + #换行符 + “Sec-Fetch-Site: same-origin” + #换行符 + “Sec-Fetch-Mode: cors” + #换行符 + “Sec-Fetch-Dest: empty” + #换行符 + “Referer: https://www.douyu.com/pages/live-peace-pel/web/point-detail?roomId=9263298&isAnchorSide=0”)) +balance = 文本_取出中间文本 (str, “balance” + #引号 + “:”, “,”) +状态条1.置文本 (0, “积分记录-> ” + str) + +.如果 (balance ≠ “”) + 窗口_置状态信息 (参_索引E, 2, “记录积分:” + balance) +.否则 + 窗口_置状态信息 (参_索引E, 2, “NULL”) +.如果结束 + +返回 () + +.子程序 查积分记录, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_文本, 文本型 +.局部变量 uid, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 ctnEX, 文本型 +.局部变量 str, 文本型 +.局部变量 balance, 文本型 + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +ctnEX = 超级列表框1.取标题 (超级列表框1.现行选中项, 5) + +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “Cookie格式错误”) + 返回 (“NULL”) +.如果真结束 + +uid = 局_F [2] +局_Cookie = 局_F [3] + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/oms/web/peace/credit/balance”, 1, “{” + #引号 + “uid” + #引号 + “:” + #引号 + uid + #引号 + “,” + #引号 + “ctn” + #引号 + “:” + #引号 + ctnEX + #引号 + “}”, 局_Cookie, , #常量6)) +balance = 文本_取出中间文本 (str, “balance” + #引号 + “:”, “,”) +状态条1.置文本 (0, “积分记录-> ” + str) + +.如果真 (balance ≠ “”) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “记录积分:” + balance) + 返回 (balance) +.如果真结束 + +窗口_置状态信息 (超级列表框1.现行选中项, 2, “NULL”) +返回 (“NULL”) + +.子程序 _兑换商品_被选择 + +启动线程 (&商品兑换, , ) + +.子程序 Getctn, 文本型 +.局部变量 局_文本, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_CCK, 文本型 +.局部变量 str, 文本型 +.局部变量 返回CK, 文本型 +.局部变量 ctn1, 文本型 +.局部变量 ctn2, 文本型 +.局部变量 局_CK, 文本型 +.局部变量 str2, 文本型 + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 返回 (“”) +.如果真结束 + +局_CCK = 局_F [3] + +str = 到文本 (网页_访问_对象 (“https://www.douyu.com/curl/csrfApi/getCsrfCookie?”, , , 局_CCK, 返回CK, #常量1)) +ctn1 = 文本_取出中间文本 (返回CK, “acf_ccn=”, “;”) +.如果真 (ctn1 ≠ “”) + str2 = 到文本 (网页_访问_对象 (“https://www.douyu.com/wgapi/livenc/asubscribe/userSubStatus”, 1, “subscribe=cmn&ctn=” + ctn1, 返回CK, 局_CK, #常量1)) + ' 调试输出 (局_CK) + .如果真 (寻找文本 (str, “error” + #引号 + “:0”, , 假) ≠ -1) + ctn2 = 文本_取出中间文本 (局_CK, “acf_ccn=”, “;”) + 返回 (ctn2) + .如果真结束 + + 返回 (“”) + +.如果真结束 + +返回 (“”) + +.子程序 Getcsrf_token, 文本型 +.参数 参_CK, 文本型 +.局部变量 局_文本, 文本型 +.局部变量 局_CCK, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 str, 文本型 +.局部变量 返回CK, 文本型 +.局部变量 csrf_token, 文本型 + +str = 到文本 (网页_访问_对象 (“https://www.douyu.com/japi/carnival/nc/common/generateCsrf”, 1, “{}”, 参_CK, 返回CK, #常量10)) +csrf_token = 文本_取出中间文本 (返回CK, “cvl_csrf_token=”, “;”) +' '调试输出 (返回CK, , , csrf_token) +返回 (csrf_token) + +.子程序 Getctn2, 文本型 +.参数 参_ck, 文本型 +.局部变量 局_文本, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_CCK, 文本型 +.局部变量 str, 文本型 +.局部变量 返回CK, 文本型 +.局部变量 ctn1, 文本型 +.局部变量 ctn2, 文本型 +.局部变量 局_CK, 文本型 +.局部变量 str2, 文本型 + +str = 到文本 (网页_访问_对象 (“https://www.douyu.com/curl/csrfNlApi/getCsrfCookie”, , , 参_ck, 返回CK, “Host: www.douyu.com” + #换行符 + “Connection: keep-alive” + #换行符 + “sec-ch-ua-platform: ” + #引号 + “Windows” + #引号 + “” + #换行符 + “User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36” + #换行符 + “Accept: application/json, text/plain, */*” + #换行符 + “sec-ch-ua: ” + #引号 + “Not(A:Brand” + #引号 + “;v=” + #引号 + “99” + #引号 + “, ” + #引号 + “Google Chrome” + #引号 + “;v=” + #引号 + “133” + #引号 + “, ” + #引号 + “Chromium” + #引号 + “;v=” + #引号 + “133” + #引号 + “” + #换行符 + “sec-ch-ua-mobile: ?0” + #换行符 + “Sec-Fetch-Site: same-origin” + #换行符 + “Sec-Fetch-Mode: cors” + #换行符 + “Sec-Fetch-Dest: empty” + #换行符 + “Referer: https://www.douyu.com/pages/ord-task-center?clientType=web&panelSource=1&rid=0”)) +ctn1 = _取单条Cookie (返回CK, “acf_ccn”, 真) +调试输出 (取程序名称 (1), ctn1) +返回 (ctn1) + +.子程序 商品兑换 +.局部变量 局_文本, 文本型 +.局部变量 ctnEX, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_Cookie, 文本型 +.局部变量 str, 文本型 +.局部变量 局_xy, 文本型 +.局部变量 商品表, 文本型 +.局部变量 商品组, 文本型, , "0" +.局部变量 要兑换的商品名称, 文本型 +.局部变量 要兑换的商品价格, 文本型 +.局部变量 要兑换的商品ID, 文本型 +.局部变量 roleName, 文本型 +.局部变量 str2, 文本型 +.局部变量 局_bin, 字节集 +.局部变量 生成者, 文本型 +.局部变量 优化价格, 文本型 +.局部变量 areaName, 文本型 +.局部变量 platName, 文本型 +.局部变量 COOK, 文本型 +.局部变量 局_F2, 文本型, , "0" +.局部变量 局_文本EX, 文本型 +.局部变量 局_用户名, 文本型 +.局部变量 csrf_token, 文本型 +.局部变量 body, 文本型 +.局部变量 randstr, 文本型 +.局部变量 sendxy, 文本型 +.局部变量 yyjson, json对象 +.局部变量 返回ck, 文本型 +.局部变量 acf_ccn, 文本型 + +' 局_文本EX = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +' 局_F2 = 分割文本 (局_文本EX, “----”, ) +.' 如果真 (取数组成员数 (局_F2) < 3) + ' 返回 () +.如果真结束 + +' 局_用户名 = 局_F2 [1] + +商品表 = 组合框1.取项目文本 (组合框1.现行选中项) +商品组 = 分割文本 (商品表, “一”, ) +.如果真 (取数组成员数 (商品组) < 3) + 状态条1.置文本 (0, “商品格式错误x”) + 返回 () +.如果真结束 + +要兑换的商品名称 = 商品组 [1] +要兑换的商品价格 = 商品组 [2] +要兑换的商品ID = 商品组 [3] + +状态条1.置文本 (0, “正在兑换商品-> ” + 要兑换的商品ID) + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (超级列表框1.现行选中项, 4, “Cookie格式错误”) + 状态条1.置文本 (0, “Cookie格式错误x ”) + 返回 () +.如果真结束 + +局_用户名 = 局_F [1] +局_Cookie = 局_F [3] + +acf_ccn = Getctn2 (局_Cookie) +csrf_token = Getcsrf_token (局_Cookie) +调试输出 (“acf_ccn---”, acf_ccn, , csrf_token) +COOK = 局_Cookie + “; cvl_csrf_token=” + csrf_token + “; acf_ccn=” + acf_ccn + + +' acf_ccn=9d396bf724108c36e71e34ba695adf58; acf_auth=4c35XAUqQT9Eew3DCKAvPZepvpY7BpbfJ7SsOaE%2FXKN8SAWGrJiM7Xqvxinz%2FyLCM9TzKW2JVmo781McjcCD%2F8f%2FxGgRGVe424OaeBJcQlOC7dEoA5YRkrk; acf_jwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJtZDUifQ.eyJ1aWQiOjUwMjU0NjEzOSwiY3QiOjAsInN1YiI6InN0IiwiYXVkIjpbImR5Iiwibm9uZSJdLCJiaXoiOjEsImx0a2lkIjo5MDQzNDY3OCwic3RrIjoiMTcwN2QyMmFjMWRmYWRhZSIsImV4cCI6MTc3NjQ4ODMwNSwiaWF0IjoxNzc1ODgzNTA1LCJrZXkiOiJkeS1qd3QtbWQ1In0.Nzk4NDAyMWY4NzgyZDA2YTQwZTIxODc3YzBkZGQwN2Q; cvl_csrf_token=21e65606ae0a47c38ce6fbb1e3d26106; acf_ccn=cvl_csrf_token=21e65606ae0a47c38ce6fbb1e3d26106; 9d396bf724108c36e71e34ba695adf58 +' 调试输出 (COOK) +置随机数种子 () +randstr = 文本_取随机字符 (16, 位或 (1, 2, 4)) +sendxy = 子文本替换 (#常量12, “[randstr]”, “qQizD2s4FNFzSTsG”, , , 真) + +body = 文本_替换 (#常量14, , , , “[randstr]”, randstr, “[manualID]”, “1W0yGkOdDbAXb”, “[商品ID]”, 要兑换的商品ID, “[token]”, acf_ccn) +' 调试输出 (body) +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/activity/peacematch/exchange”, 1, body, COOK, 返回ck, sendxy, 局_xy)) +' 调试输出 (“兑换商品-> ” + str) +状态条1.置文本 (0, “兑换商品-> ” + str) +.如果真 (寻找文本 (str, “error” + #引号 + “:0”, , 假) ≠ -1) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “兑换成功:” + 要兑换的商品名称) + .计次循环首 (3, ) + str2 = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo?actAlias=20260226ZMGDG”, , , 局_Cookie, , #常量3)) + .如果真 (str2 ≠ “”) + 跳出循环 () + .如果真结束 + + .计次循环尾 () + + + yyjson.清空 () + yyjson.解析 (str2) + roleName = yyjson.取文本 (“data/gameRole/roleName”) + .如果 (roleName ≠ “”) + 全线程临界.进入 () + 优化价格 = 取文本左边 (要兑换的商品价格, 取文本长度 (要兑换的商品价格) - 1) + 保存日志2 (时间_到文本 (取现行时间 (), 1) + “ ” + 局_用户名 + “----” + roleName + “----” + 要兑换的商品名称 + “----” + 优化价格) + 局_bin = 文转图片P (要兑换的商品名称 + “ ” + #换行符 + 时间_格式化 (取现行时间 (), “yyyy年MM月dd日”, “ hh:mm:ss”, 真) + #换行符 + “兑换账号:” + roleName + “(” + 局_用户名 + “)”) + 全线程临界.退出 () + 剪辑板_置图片 (局_bin) + 提示框 (“兑换成功,图片已复制”, , , , 1000) + + .否则 + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “兑换成功,获取昵称失败”) + + .如果结束 + + 返回 () + +.如果真结束 + + +窗口_置状态信息 (超级列表框1.现行选中项, 2, str) + +.子程序 查积分记录EX, 文本型, , 1W0yGkOdDbAXb +.参数 参_uid, 文本型 +.参数 参_CK, 文本型 +.参数 参_ctnEX, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_文本, 文本型 +.局部变量 str, 文本型 +.局部变量 balance, 文本型 +.局部变量 yyjson, json对象 + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/activity/peacematch/getUserInfo?manualID=1W0yGkOdDbAXb”, , , 参_CK, , #常量6)) +yyjson.清空 () +yyjson.解析 (str) +balance = yyjson.取文本 (“data/userInfo/manualScore”) +状态条1.置文本 (0, “积分记录-> ” + str) +返回 (balance) + +.子程序 _图片框1_被双击, 逻辑型 +.参数 横向位置, 整数型 +.参数 纵向位置, 整数型 +.参数 功能键状态, 整数型 + +.如果真 (图片框1.图片 = { }) + 提示框 (“请先生成后,再复制二维码”, , , , 1000) + 返回 () +.如果真结束 + +剪辑板_置图片 (图片框1.图片) +' 剪辑板_置图片 (图片_后台截图 (图片框4.取窗口句柄 ())) +提示框 (“二维码已复制”, , , , 1000) + +.子程序 _超级列表框1_被双击, 逻辑型 +.参数 横向位置, 整数型 +.参数 纵向位置, 整数型 +.参数 功能键状态, 整数型 + +启动线程 (&子程序标签更新, , ) + +.子程序 子程序标签更新 +.局部变量 局_文本, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_uid, 文本型 +.局部变量 nick, 文本型 +.局部变量 str, 文本型 +.局部变量 roleName, 文本型 +.局部变量 余额, 文本型 +.局部变量 areaName, 文本型 +.局部变量 platName, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 局_ctn, 文本型 +.局部变量 yyjson, json对象 + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +局_ctn = 超级列表框1.取标题 (超级列表框1.现行选中项, 5) +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 状态条1.置文本 (0, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_uid = 局_F [2] +局_Cookie = 局_F [3] +' 'https://www.douyu.com/japi/carnivalApi/tencent/bindInfo?actAlias=20260120QYOOB +' 这个回头调整 str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/japi/carnivalApi/v2/tencent/bindInfo?actAlias=cjm”, , , 局_Cookie, , #常量3)) + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo?actAlias=20260226ZMGDG”, , , 局_Cookie, , #常量3)) +调试输出 (取程序名称 (1), str) +状态条1.置文本 (0, “是否绑定-> ” + str) +' {"error":0,"msg":"success","data":{"bindAccount":1,"bindRole":1,"bindAct":1,"needBindRole":null,"txAct":{"txActId":2811,"gameId":"cjm","gameName":"和平精英","bindType":1},"gameAccount":{"bindType":2,"type":"","faceuri":"https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTI4cIOxb4f7vpSkqYCIIvY1EJTgkhS0ZhVzzJSl2LzDpUzVBJKiaDjvuzpdS6cadAvA6bnrYwZ7jlQ/132","nick":"zicoo"},"gameRole":{"roleName":"白菜被拱了呀","areaName":"微信","platName":"安卓(android)","partitionName":""},"i sGranted":null,"gameAuthScene":null,"canChangeRole":0,"changeRoleWaitTime":2490528},"redirectUrl":null} +yyjson.清空 () +yyjson.解析 (str) + +roleName = yyjson.取文本 (“data/gameRole/roleName”) +' 文本_取出中间文本 (str, “roleName” + #引号 + “:” + #引号, #引号) +nick = yyjson.取文本 (“data/gameAccount/nick”) +' 文本_取出中间文本 (str, “nick” + #引号 + “:” + #引号, #引号) +areaName = yyjson.取文本 (“data/gameRole/areaName”) +' 文本_取出中间文本 (str, “areaName” + #引号 + “:” + #引号, #引号) +platName = yyjson.取文本 (“data/gameRole/platName”) +' 文本_取出中间文本 (str, “platName” + #引号 + “:” + #引号, #引号) +余额 = 查积分记录EX (局_uid, 局_Cookie, 局_ctn) + +标签_昵称.标题 = “账号:” + 局_F [1] + “<->” + “余额:” + 余额 +标签_状态.标题 = “昵称:” + nick + #换行符 + “游戏名称:” + roleName + #换行符 + “平台:” + areaName + #换行符 + “设备:” + platName +超级列表框1.置标题 (超级列表框1.现行选中项, 2, roleName) + +.子程序 _标签_状态_被双击, 逻辑型 +.参数 横向位置, 整数型 +.参数 纵向位置, 整数型 +.参数 功能键状态, 整数型 + +剪辑板_置图片 (快照 (标签_状态.取窗口句柄 (), , )) +提示框 (“图片已复制”, , , , 1000) + +.子程序 _窗口1_可否被关闭, 逻辑型 + +.如果 (信息框 (“确定要退出程序吗?”, #信息图标 + #确认钮 + #取消钮, , ) = #确认钮) + 返回 (真) +.否则 + 返回 (假) +.如果结束 + + +.子程序 _窗口1_将被销毁 + +配置_保存_易语言组件 (窗口1) +Gdip结束 () +数据写出列表 () + +.子程序 _按钮_结算_被单击 + +启动线程 (&子程序结算, , ) + +.子程序 子程序结算 +.局部变量 str, 文本型 +.局部变量 局_1, 文本型, , "0" +.局部变量 局_F, 文本型, , "0" +.局部变量 N, 整数型 +.局部变量 局_NUM, 整数型 +.局部变量 局_日志路径, 文本型 +.局部变量 局_标识, 文本型, , "0" +.局部变量 局_文本1, 文本型 +.局部变量 局_文本2, 文本型 +.局部变量 局_返回余额, 整数型 + +局_日志路径 = 取运行目录 () + “\” + 时间_格式化 (取现行时间 (), “MM.dd-”) + “兑换日志.log” +str = 编码_Utf8到Ansi (读入文件 (局_日志路径)) +.如果真 (str = “”) + 提示框 (“生成日志后,再结算金额...”, , , , 1000) + 返回 () +.如果真结束 + +文本_取中间_批量 (str, “---------------------------------------------已结算----以上合计金额:”, #换行符, 局_标识) +.如果真 (取数组成员数 (局_标识) < 1) + ' 提示框 (“生成日志后,再结算金额”, , , , 2000) + 局_F = 分割文本 (str, #换行符, ) + .计次循环首 (取数组成员数 (局_F), N) + 局_返回余额 = 子程序处理余额 (局_F [N]) + _原子运算 (局_NUM, 局_返回余额) + 系统_处理事件 () + .计次循环尾 () + 保存日志2 (#换行符 + #换行符 + “---------------------------------------------已结算----以上合计金额:” + 到文本 (局_NUM) + #换行符 + #换行符) + 提示框 (“结算完成:” + 到文本 (局_NUM), , , , 2000) + 返回 () +.如果真结束 + + +.如果真 (局_标识 [取数组成员数 (局_标识)] ≠ “”) + 局_文本1 = “---------------------------------------------已结算----以上合计金额:” + 局_标识 [取数组成员数 (局_标识)] + 局_1 = 分割文本 (str, 局_文本1, ) + 局_文本2 = 局_1 [取数组成员数 (局_1)] + .如果真 (局_文本2 ≠ “”) + 局_F = 分割文本 (局_文本2, #换行符, ) + .计次循环首 (取数组成员数 (局_F), N) + 局_返回余额 = 子程序处理余额 (局_F [N]) + _原子运算 (局_NUM, 局_返回余额) + 系统_处理事件 () + .计次循环尾 () + .如果真 (局_NUM ≤ 0) + 提示框 (“未达到结算条件!”, , , , 2000) + 返回 () + .如果真结束 + + 保存日志2 (#换行符 + #换行符 + “---------------------------------------------已结算----以上合计金额:” + 到文本 (局_NUM) + #换行符 + #换行符) + 清除数组 (局_1) + 清除数组 (局_F) + 提示框 (“结算完成:” + 到文本 (局_NUM), , , , 2000) + 返回 () + .如果真结束 + + 提示框 (“未达到结算条件!”, , , , 2000) + +.如果真结束 + + +.子程序 子程序处理余额, 整数型 +.参数 参_数据, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 N, 整数型 + +局_F = 分割文本 (参_数据, “----”, ) +.如果真 (取数组成员数 (局_F) = 4) + 返回 (到整数 (局_F [4])) +.如果真结束 + +返回 (0) + +.子程序 _按钮_统计_被单击 +.局部变量 str, 文本型 +.局部变量 局_日志路径, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 P, 整数型 +.局部变量 局_返回余额, 整数型 +.局部变量 局_NUM, 整数型 + +局_日志路径 = 取运行目录 () + “\” + 时间_格式化 (取现行时间 (), “MM.dd-”) + “兑换日志.log” +str = 编码_Utf8到Ansi (读入文件 (局_日志路径)) +局_F = 分割文本 (str, #换行符, ) +.计次循环首 (取数组成员数 (局_F), P) + 局_返回余额 = 子程序处理余额 (局_F [P]) + _原子运算 (局_NUM, 局_返回余额) + 系统_处理事件 () +.计次循环尾 () +保存日志2 (#换行符 + #换行符 + “*************************************************今日统计总金额:” + 到文本 (局_NUM) + #换行符 + #换行符) +信息框 (时间_格式化 (取现行时间 (), “MM.dd-”) + “统计总金额:” + 到文本 (局_NUM), 0, , ) + + +.子程序 _读取Cookie_被选择 +.局部变量 局_保存目录, 文本型 +.局部变量 局_文本数据, 文本型 +.局部变量 局_文本数组, 文本型, , "0" +.局部变量 局_索引, 整数型 +.局部变量 P, 整数型 +.局部变量 N, 整数型 +.局部变量 局_3, 文本型, , "0" + +局_保存目录 = 取运行目录 () + “\斗鱼CK\” + 时间_格式化 (取现行时间 (), “MM.dd-”) + “斗鱼CK.txt” +.如果真 (文件是否存在 (局_保存目录) = 假) + 提示框 (“Cookie文件不存在!” + #换行符 + 局_保存目录, , , , 2000) + 返回 () +.如果真结束 + + +局_文本数据 = 到文本 (读入文件 (局_保存目录)) +局_文本数组 = 分割文本 (局_文本数据, #换行符, ) +.计次循环首 (取数组成员数 (局_文本数组), P) + 判断重复列表 (局_文本数组 [P]) + 系统_处理事件 () +.计次循环尾 () +.计次循环首 (超级列表框1.取表项数 (), N) + 系统_处理事件 () + 超级列表框1.置状态图片 (N - 1, 1) + 超级列表框1.置标题 (N - 1, 0, 到文本 (N)) +.计次循环尾 () + +.子程序 判断重复列表 +.参数 参_内容, 文本型 +.局部变量 局_文本, 文本型 +.局部变量 局_用户, 文本型 +.局部变量 _索引, 整数型 +.局部变量 局_索引2, 整数型 +.局部变量 P, 整数型 + +_索引 = 超级列表框1.查找表项 (参_内容, , , 1) +.如果 (_索引 = -1) + 局_索引2 = 超级列表框1.插入表项 (, , , , , ) + 超级列表框1.置标题 (局_索引2, 0, 到文本 (局_索引2 + 1)) + 超级列表框1.置标题 (局_索引2, 1, 参_内容) + 超级列表框1.置状态图片 (局_索引2, 1) + +.否则 + 超级列表框1.置标题 (_索引, 1, 参_内容) +.如果结束 + + +.子程序 _同步删除Cookie_被选择 +.局部变量 局_保存目录, 文本型 +.局部变量 局_保存文本, 鱼刺类_输出文本 +.局部变量 i, 整数型 +.局部变量 P, 整数型 + +局_保存目录 = 取运行目录 () + “\斗鱼CK\” + 时间_格式化 (取现行时间 (), “MM.dd-”) + “斗鱼CK.txt” +超级列表框1.删除表项 (超级列表框1.现行选中项) +删除文件 (局_保存目录) +程序_延时 (500) +局_保存目录 = 取运行目录 () + “\斗鱼CK\” +.计次循环首 (超级列表框1.取表项数 (), i) + 系统_处理事件 () + 局_保存文本.写文本行 (超级列表框1.取标题 (i - 1, 1), 时间_格式化 (取现行时间 (), “MM.dd-”) + “斗鱼CK”, 局_保存目录, “.txt”) +.计次循环尾 () +.计次循环首 (超级列表框1.取表项数 (), P) + 系统_处理事件 () + 超级列表框1.置状态图片 (P - 1, 1) + 超级列表框1.置标题 (P - 1, 0, 到文本 (P)) +.计次循环尾 () + +提示框 (“同步完成”, , , , 1000) + +.子程序 保存日志 +.参数 参_str, 文本型 +.局部变量 filename, 文本型 +.局部变量 保存到文件, 鱼刺类_输出文本 + +filename = 时间_格式化 (取现行时间 (), “MM.dd-”) + “流程日志” +保存到文件.写文本行 (参_str, filename, 取运行目录 () + “\”, “.log”) + +.子程序 保存日志2 +.参数 参_str, 文本型 +.局部变量 filename, 文本型 +.局部变量 保存到文件, 鱼刺类_输出文本 + +filename = 时间_格式化 (取现行时间 (), “MM.dd-”) + “兑换日志” +保存到文件.写文本行 (参_str, filename, 取运行目录 () + “\”, “.log”) +' 保存到文件.关闭所有文件占用 () + +.子程序 数据写入列表, , , APP +.局部变量 文件号, 整数型 +.局部变量 文件名, 文本型 +.局部变量 状态数组, 文本型, , "0" +.局部变量 文本数组, 文本型, , "0" +.局部变量 M, 整数型 +.局部变量 N, 整数型 +.局部变量 局_状态图片, 整数型 + +文件名 = 全目录临时 + “\临时ck.txt” +文件号 = 打开文件 (文件名, , ) +移到文件首 (文件号) +处理事件 () +.判断循环首 (取反 (是否在文件尾 (文件号, 真))) + 重定义数组 (状态数组, 假, 0) + 重定义数组 (文本数组, 假, 0) + 状态数组 = 分割文本 (读入一行 (文件号), “$”, ) + .如果真 (取数组成员数 (状态数组) ≥ 2) + .如果 (状态数组 [1] = “x”) + 局_状态图片 = 1 + .否则 + 局_状态图片 = 0 + .如果结束 + 文本数组 = 分割文本 (状态数组 [2], “|||”, ) + M = 超级列表框1.插入表项 (, , , , , ) + + 超级列表框1.置状态图片 (M, 局_状态图片) + .计次循环首 (取数组成员数 (文本数组), N) + 系统_处理事件 () + 超级列表框1.置标题 (M, N - 1, 文本数组 [N]) + .如果真 (N = 20) ' 填充满一页就手动重画 + 超级列表框1.禁止重画 () + .如果真结束 + .如果真 (N % 100 = 0) ' 每100次插入 换一次姿势 ``233333 + 超级列表框1.允许重画 () + 超级列表框1.发送信息 (277, 7, 0) ' 显示最后一行 + 超级列表框1.禁止重画 () + .如果真结束 + + .计次循环尾 () + + + + .如果真结束 + +.判断循环尾 () +关闭文件 (文件号) +超级列表框1.允许重画 () ' 恢复自动重画 +超级列表框1.发送信息 (277, 7, 0) ' 显示最后一行 + +.子程序 数据写出列表, , , APP +.局部变量 文件名, 文本型 +.局部变量 读入账号, 文本型 +.局部变量 n, 整数型 +.局部变量 临时文件名, 文本型 +.局部变量 文件号, 整数型 +.局部变量 文本, 文本型 +.局部变量 空, 字节集 +.局部变量 局_状态图片, 文本型 + +临时文件名 = 全目录临时 + “\临时ck.txt” +文件号 = 打开文件 (临时文件名, 4, ) +_处理事件 () +文本 = “” + +.计次循环首 (超级列表框1.取表项数 (), n) + 系统_处理事件 () + .判断开始 (超级列表框1.取当前状态图片 (n - 1) = 1) + 局_状态图片 = “x” + + .默认 + 局_状态图片 = “y” + .判断结束 + + 文本 = 局_状态图片 + “$” + 超级列表框1.取标题 (n - 1, 0) + “|||” + 超级列表框1.取标题 (n - 1, 1) + “|||” + 超级列表框1.取标题 (n - 1, 2) + “|||” + 超级列表框1.取标题 (n - 1, 3) + “|||” + 超级列表框1.取标题 (n - 1, 4) + “|||” + 超级列表框1.取标题 (n - 1, 5) + “|||” + 超级列表框1.取标题 (n - 1, 6) + + 写文本行 (文件号, 文本) +.计次循环尾 () +关闭文件 (文件号) + +.子程序 _标签_昵称_被双击, 逻辑型, , https://livelink.qq.com/act/a20200527midgroupage/pc/?sig=0d29d14f3e9bdb05f7f091340952054f&faceUrl=https%3A%2F%2Fapic.douyucdn.cn%2Fupload%2Favatar%2Fdefault%2F15_middle.jpg&code=wW6EhNWpLEVNHKEMfB9vaEWKPdowIikRqFJI0IKm%2BcxGa%2B8OEPhyF%2BZ58W80CrGigCGtLddIofBcnRkylmccKw%3D%3D&redirectUrl=null&t=1661433534&livePlatId=douyu&nickName=%E7%94%A8%E6%88%B702435853&actId=2811&gameIdList=cjm +.参数 横向位置, 整数型 +.参数 纵向位置, 整数型 +.参数 功能键状态, 整数型 +.局部变量 str, 文本型, , , 昵称:null <-> 余额:null + +str = 删首尾空 (文本_取出中间文本 (标签_昵称.标题, “账号:”, “<->”)) +置剪辑板文本 (str) +提示框 (str + “ 已复制”, , , , 1000) + +.子程序 _按钮_关闭_被单击, , , 测试 + +_扫码框.可视 = 假 +工作中 = 假 +关闭线程句柄 (集句柄) +强制结束线程 (集句柄) + +.子程序 _扫码登录_被选择 + +' 载入 (窗口_扫码, , 真) +_扫码框.可视 = 真 +标签显示.可视 = 假 +按钮_刷新.可视 = 假 +按钮_刷新.禁止 = 真 +启动线程 (&getscanEX, , 集句柄) + +.子程序 getscanEX, 逻辑型, , 获取扫码 +.局部变量 str, 文本型 +.局部变量 yyjson, json对象 +.局部变量 surl, 文本型 +.局部变量 code, 文本型 +.局部变量 bool, 逻辑型 +.局部变量 局_Code, 文本型 +.局部变量 局_msg, 文本型 +.局部变量 局_LoginUrl, 文本型 +.局部变量 CK, 文本型 + +图片框2.图片 = { } +网页_清除Cookie (“passport.douyu.com”) +标签_反馈.标题 = “正在二维码...” +网页_访问_对象 (“https://passport.douyu.com/index/login?passport_reg_callback=PASSPORT_REG_SUCCESS_CALLBACK&passport_login_callback=PASSPORT_LOGIN_SUCCESS_CALLBACK&passport_close_callback=PASSPORT_CLOSE_CALLBACK&passport_dp_callback=PASSPORT_DP_CALLBACK&type=login&client_id=1&state=https%3A%2F%2Fwww.douyu.com%2F&source=click_topnavi_login”, , , “dy_did=05444ffb59d181b4d194b59900011601; acf_did=05444ffb59d181b4d194b59900011601; Hm_lpvt_e99aee90ec1b2106afe7ec3b199020a7=” + 到文本 (时间_取现行时间戳1 (假)), , #常量8) +程序_延时 (1000) +str = 编码_usc2到ansi (到文本 (网页_访问_对象 (“https://passport.douyu.com/scan/generateCode”, 1, “client_id=1&isMultiAccount=0”, “dy_did=05444ffb59d181b4d194b59900011601; acf_did=05444ffb59d181b4d194b59900011601; Hm_lpvt_e99aee90ec1b2106afe7ec3b199020a7=” + 到文本 (时间_取现行时间戳1 (假)), , #扫码XY1))) + +' “dy_did=05444ffb59d181b4d194b59900011601; acf_did=05444ffb59d181b4d194b59900011601; Hm_lpvt_e99aee90ec1b2106afe7ec3b199020a7=” + 到文本 (时间_取现行时间戳1 (假)) + +' 调试输出 (str) +yyjson.清空 () +yyjson.解析 (str) +surl = yyjson.取文本 (“data/url”) +局_Code = yyjson.取文本 (“data/code”) +.如果真 (surl = “” 或 局_Code = “”) + 标签_反馈.标题 = “请关闭后重新生成二维码!” + 返回 (假) +.如果真结束 + +图片框2.图片 = 文本生成二维码图片 (surl, , , , , 20) +标签_反馈.标题 = “二维码已生成,请在手机上确认!” + +工作中 = 真 +.判断循环首 (工作中 = 真) + bool = scanauthEX (局_Code, 局_msg, 局_LoginUrl) + .如果真 (bool = 真) + 跳出循环 () + .如果真结束 + + .如果真 (寻找文本 (局_msg, “code不存在或者是已经过期”, , 假) ≠ -1) + 关闭线程句柄 (集句柄) + 强制结束线程 (集句柄) + 标签显示.可视 = 真 + 按钮_刷新.可视 = 真 + 按钮_刷新.禁止 = 假 + 返回 (假) + .如果真结束 + + 程序_延时 (1000) + 系统_处理事件 () +.判断循环尾 () +Login_douEX (局_LoginUrl) +返回 (真) + +.子程序 scanauthEX, 逻辑型, , 检测是否扫码 +.参数 参_code, 文本型 +.参数 返回msg, 文本型, 参考 +.参数 返回登录url, 文本型, 参考 +.局部变量 str, 文本型 +.局部变量 yyjson, json对象 +.局部变量 局_msg, 文本型 +.局部变量 局surl, 文本型 + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://passport.douyu.com/japi/scan/auth?time=” + 时间_取现行时间戳 () + “&code=” + 参_code, , , , , #扫码XY1)) +' 调试输出 (“参_code---”, 参_code) +' 调试输出 (str) +yyjson.清空 () +yyjson.解析 (str) +返回msg = yyjson.取文本 (“msg”) +返回登录url = yyjson.取文本 (“data/url”) +标签_反馈.标题 = “>>>>” + 返回msg +.如果真 (返回登录url ≠ “”) + 标签_反馈.标题 = “扫码成功,正在登录...” + 返回 (真) +.如果真结束 + +返回 (假) + +.子程序 Login_douEX, , , 登录斗鱼 +.参数 参_url, 文本型 +.局部变量 str, 文本型 +.局部变量 局_索引, 整数型 +.局部变量 局_COOK, 文本型 +.局部变量 acf_uid, 文本型 +.局部变量 acf_nickname, 文本型 +.局部变量 解析CK, 类_Cookie + +str = 编码_usc2到ansi (到文本 (网页_访问_对象 (参_url + “&callback=appClient_json_callback&_=” + 时间_取现行时间戳 (), , , , 局_COOK, #扫码登录))) +' 调试输出 (str) +' 调试输出 (“局_COOK---”, 局_COOK) +.如果真 (局_COOK ≠ “”) + 标签_反馈.标题 = “>>>扫码登录成功!” + acf_uid = 网页_取单条Cookie (局_COOK, “acf_uid”, 真) + + acf_nickname = 编码_URL解码 (网页_取单条Cookie (局_COOK, “acf_nickname”, 真), 真) + 局_索引 = 超级列表框1.插入表项 (, , , , , ) + 超级列表框1.置标题 (局_索引, 0, 到文本 (局_索引 + 1)) + 超级列表框1.置标题 (局_索引, 1, acf_nickname + “----” + acf_uid + “----” + 局_COOK) + 超级列表框1.置状态图片 (局_索引, 1) + 超级列表框1.保证显示 (局_索引) + 工作中 = 假 + _扫码框.可视 = 假 + +.如果真结束 + + +.子程序 _按钮_刷新_被单击 + +标签显示.可视 = 假 +按钮_刷新.可视 = 假 +按钮_刷新.禁止 = 真 +启动线程 (&getscanEX, , 集句柄) + +.子程序 _图片框2_被双击, 逻辑型 +.参数 横向位置, 整数型 +.参数 纵向位置, 整数型 +.参数 功能键状态, 整数型 + +剪辑板_置图片 (图片框2.图片) +提示框 (“图片已复制”, , , , 1000) + +.子程序 __扫码框_鼠标左键被按下, 逻辑型 +.参数 横向位置, 整数型 +.参数 纵向位置, 整数型 +.参数 功能键状态, 整数型 + +窗口1.发送信息 (161, 2, 0) + +.子程序 _粘贴Cookie_被选择 +.局部变量 文本数组, 文本型, , "0" +.局部变量 计次1, 整数型 +.局部变量 计次2, 整数型 +.局部变量 临时数组, 文本型, , "0" +.局部变量 索引, 整数型 +.局部变量 i, 文本型 +.局部变量 总数数组, 文本型, , "0" +.局部变量 内容, 文本型 +.局部变量 X, 整数型 +.局部变量 状态数组, 文本型, , "0" +.局部变量 N, 整数型 +.局部变量 M, 整数型 +.局部变量 判断数据, 文本型 + +' 下面的是粘贴根据“----“判断是否是一条或者两条数据 +i = 取剪辑板文本 () +.如果真 (i = “”) + 返回 () +.如果真结束 + +文本数组 = 分割文本 (i, #换行符, ) +.计次循环首 (取数组成员数 (文本数组), X) + .如果真 (文本数组 [X] ≠ “”) + M = 超级列表框1.插入表项 (, , , , , ) + 超级列表框1.置状态图片 (M, 1) + 超级列表框1.置标题 (M, 0, 到文本 (M + 1)) + 超级列表框1.置标题 (M, 1, 文本数组 [X]) + .如果真结束 + + 系统_处理事件 () +.计次循环尾 () +超级列表框1.允许重画 () ' 恢复自动重画 +超级列表框1.发送信息 (277, 7, 0) ' 显示最后一行 + +清除数组 (文本数组) +返回 () + +.子程序 _搜索游戏名_被选择 +.局部变量 索引EX, 整数型 +.局部变量 str, 文本型 + +输入框 (“查找包含游戏名的字符”, “查找游戏名”, , str, , ) +.如果真 (str = “”) + 返回 () +.如果真结束 + +索引EX = 超级列表框1.查找表项 (str, , 假, 2) +.如果真 (索引EX ≠ -1) + 超级列表框1.保证显示 (索引EX) + 超级列表框1.选择表项 (索引EX) +.如果真结束 + +返回 () + +.子程序 _搜索状态_被选择 +.局部变量 索引EX, 整数型 +.局部变量 str, 文本型 + +输入框 (“查找包含状态的字符”, “查找状态”, , str, , ) +.如果真 (str = “”) + 返回 () +.如果真结束 + +索引EX = 超级列表框1.查找表项 (str, , 假, 4) +.如果真 (索引EX ≠ -1) + 超级列表框1.保证显示 (索引EX) + 超级列表框1.选择表项 (索引EX) +.如果真结束 + +返回 () + + +.子程序 子程序检测账号EX +.参数 参_索引, 整数型 +.局部变量 局_文本, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_CCK, 文本型 +.局部变量 str, 文本型 +.局部变量 返回CK, 文本型 + +全检车临界.进入 () +局_文本 = 超级列表框1.取标题 (参_索引, 1) +全检车临界.退出 () + +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (参_索引, 2, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_CCK = 局_F [3] +str = 到文本 (网页_访问_对象 (“https://www.douyu.com/curl/csrfApi/getCsrfCookie?”, , , 局_CCK, , #常量1)) +' 调试输出 (str) +状态条1.置文本 (0, “生成1-> ” + 编码_usc2到ansi (str)) +.判断开始 (寻找文本 (str, “-99”, , 假) ≠ -1) + 窗口_置状态信息 (参_索引, 1, “账号未登录”) + 返回 () + +.判断 (寻找文本 (str, “无效”, , 假) ≠ -1) + 窗口_置状态信息 (参_索引, 1, “账号无效”) + 返回 () + +.默认 + 窗口_置状态信息 (参_索引, 1, “账号正常”) +.判断结束 + +返回 () + +.子程序 _一键检测账号_被选择 +.局部变量 p, 整数型 + +.计次循环首 (超级列表框1.取表项数 (), p) + .如果真 (超级列表框1.取当前状态图片 (p - 1) = 1) + 启动线程 (&子程序检测账号EX, p - 1, ) + 程序_延时 (10) + .如果真结束 + +.计次循环尾 () + + +.子程序 _全选_被选择 +.局部变量 P, 整数型 + +.计次循环首 (超级列表框1.取表项数 (), P) + 系统_处理事件 () + 超级列表框1.置状态图片 (P - 1, 1) +.计次循环尾 () + +.子程序 _反选_被选择 +.局部变量 P, 整数型 + +.计次循环首 (超级列表框1.取表项数 (), P) + 系统_处理事件 () + .如果 (超级列表框1.取当前状态图片 (P - 1) = 0) + 超级列表框1.置状态图片 (P - 1, 1) + + .否则 + 超级列表框1.置状态图片 (P - 1, 0) + .如果结束 + +.计次循环尾 () + + +.子程序 _一键获取游戏名_被选择 +.局部变量 p, 整数型 + +.计次循环首 (超级列表框1.取表项数 (), p) + .如果真 (超级列表框1.取当前状态图片 (p - 1) = 1) + 启动线程 (&子程序获取昵称EX, p - 1, ) + 程序_延时 (10) + .如果真结束 + +.计次循环尾 () + +.子程序 子程序获取昵称EX +.参数 索引EX, 整数型 +.局部变量 局_文本, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_uid, 文本型 +.局部变量 nick, 文本型 +.局部变量 str, 文本型 +.局部变量 roleName, 文本型 +.局部变量 余额, 文本型 +.局部变量 areaName, 文本型 +.局部变量 platName, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 局_ctn, 文本型 + +全临界.进入 () +局_文本 = 超级列表框1.取标题 (索引EX, 1) +局_ctn = 超级列表框1.取标题 (索引EX, 5) +全临界.退出 () +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 状态条1.置文本 (0, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_uid = 局_F [2] +局_Cookie = 局_F [3] + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo?actAlias=20260226ZMGDG”, , , 局_Cookie, , #常量3)) +' 调试输出 (str) +全临界.进入 () +状态条1.置文本 (0, “是否绑定-> ” + str) +roleName = 文本_取出中间文本 (str, “roleName” + #引号 + “:” + #引号, #引号) +超级列表框1.置标题 (索引EX, 2, roleName) +全临界.退出 () + +' {"error":0,"msg":"success","data":{"bindAccount":1,"bindRole":1,"bindAct":1,"needBindRole":null,"txAct":{"txActId":2811,"gameId":"cjm","gameName":"和平精英","bindType":1},"gameAccount":{"bindType":2,"type":"","faceuri":"https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTI4cIOxb4f7vpSkqYCIIvY1EJTgkhS0ZhVzzJSl2LzDpUzVBJKiaDjvuzpdS6cadAvA6bnrYwZ7jlQ/132","nick":"zicoo"},"gameRole":{"roleName":"白菜被拱了呀","areaName":"微信","platName":"安卓(android)","partitionName":""},"isGranted":null,"gameAuthScene":null,"canChangeRole":0,"changeRoleWaitTime":2490528},"redirectUrl":null} + + +' nick = 文本_取出中间文本 (str, “nick” + #引号 + “:” + #引号, #引号) +' areaName = 文本_取出中间文本 (str, “areaName” + #引号 + “:” + #引号, #引号) +' platName = 文本_取出中间文本 (str, “platName” + #引号 + “:” + #引号, #引号) + + +.子程序 _按钮_刷新商品1_被单击 + +启动线程 (&商品加列表, , ) + +.子程序 商品加列表 +.局部变量 局_商品名, 文本型 + +局_商品名 = 组合框1.取项目文本 (组合框1.现行选中项) +超级列表框1.置标题 (超级列表框1.现行选中项, 3, 局_商品名) + +.子程序 _按钮_启动_被单击 + +.判断开始 (按钮_启动.标题 = “批量兑换商品[列表勾选]”) + .如果真 (超级列表框1.取表项数 () ≤ 0) + 返回 () + .如果真结束 + + 按钮_启动.禁止 = 真 + .如果真 (线程_创建 (&工作线程) = 假) + 按钮_启动.禁止 = 假 + 返回 () + .如果真结束 + +.判断 (按钮_启动.标题 = “停止兑换商品”) + 按钮_启动.禁止 = 真 + 按钮_启动.标题 = “停 止 中...” + 全工作中 = 假 + 工作线程池EX.销毁 () + 返回 () +.默认 + +.判断结束 + + +.子程序 工作线程 +.局部变量 bool, 逻辑型 +.局部变量 int, 整数型 +.局部变量 任务数, 整数型 +.局部变量 局_文本txt, 文本型 +.局部变量 局_data, 文本型 +.局部变量 返回用户名, 文本型 +.局部变量 返回Cookie, 文本型 +.局部变量 返回商品名称, 文本型 +.局部变量 返回商品价格, 文本型 +.局部变量 返回商品ID, 文本型 + +全定时数 = 0 +按钮_启动.标题 = “兑换中...” +状态条1.置文本 (0, “线程启动中...”) +bool = 工作线程池EX.创建 (50, 50, , 真, , 1024 × 128) +.如果真 (bool = 假) + 状态条1.置文本 (0, “初始化工作线程池失败!”) + 按钮_启动.标题 = “批量兑换商品[列表勾选]” + 按钮_启动.禁止 = 假 + 返回 () +.如果真结束 + +全工作中 = 真 +按钮_启动.标题 = “停止兑换商品” +按钮_启动.禁止 = 假 + +int = 0 +.判断循环首 (全工作中 = 真) + 任务数 = 工作线程池EX.取_执行线程数 () + 工作线程池EX.取_队列任务数 () + 任务数 = 工作线程池EX.取_最大线程容量 () - 任务数 + .如果真 (任务数 > 0) + .如果真 (超级列表框1.取当前状态图片 (int) = 1) + 工作线程池EX.投递任务 (&线程子程序, int) + .如果真结束 + + int = int + 1 + + .如果真结束 + + .如果真 (int ≥ 超级列表框1.取表项数 ()) + 跳出循环 () + .如果真结束 + + _延迟 (1) +.判断循环尾 () +.循环判断首 () + 程序_延时 (300) +.循环判断尾 (工作线程池EX.取_是否完全空闲 () = 假) +状态条1.置文本 (0, “正在销毁工作线程池...”) +工作线程池EX.销毁 () +状态条1.置文本 (0, “销毁工作线程池成功!”) +按钮_启动.标题 = “批量兑换商品[列表勾选]” +按钮_启动.禁止 = 假 +状态条1.置文本 (0, “执行停止完毕!”) +.如果真 (选择框_定时抢购.选中 = 真) + 时钟1.时钟周期 = 0 + 选择框_定时抢购.选中 = 假 + 状态条1.置文本 (1, “定时操作完毕!”) +.如果真结束 + + +.子程序 处理Cookie, 逻辑型 +.参数 参_data, 文本型 +.参数 返回用户名, 文本型, 参考 +.参数 返回Cookie, 文本型, 参考 +.局部变量 F_F, 文本型, , "0" + +F_F = 分割文本 (参_data, “----”, ) +.如果真 (取数组成员数 (F_F) < 3) + 返回Cookie = “Cookie格式错误” + 返回 (假) +.如果真结束 + +返回用户名 = F_F [1] +返回Cookie = F_F [3] +返回 (真) + +.子程序 处理商品, 逻辑型 +.参数 参_data, 文本型 +.参数 返回商品名称, 文本型, 参考 +.参数 返回商品价格, 文本型, 参考 +.参数 返回商品ID, 文本型, 参考 +.局部变量 F_F, 文本型, , "0" + +F_F = 分割文本 (参_data, “一”, ) +.如果真 (取数组成员数 (F_F) < 3) + 返回商品ID = “商品格式错误” + 返回 (假) +.如果真结束 + +返回商品名称 = F_F [1] +返回商品价格 = F_F [2] +返回商品ID = F_F [3] +返回 (真) + +.子程序 子程序等待定时, , , 暂不使用 +.局部变量 P, 整数型 +.局部变量 N, 整数型 +.局部变量 局_兑换状态, 文本型 + +.计次循环首 (到整数 (删首尾空 (编辑框_定时秒.内容)), P) + 系统_处理事件 () + 状态条1.置文本 (0, “已设置定时:” + 编辑框_定时秒.内容 + “秒,开始计时/” + 到文本 (P)) + 程序_延时 (1000) +.计次循环尾 () +.计次循环首 (超级列表框1.取表项数 (), N) + 系统_处理事件 () + 全临界.进入 () + 局_兑换状态 = 超级列表框1.取标题 (N - 1, 4) + 全临界.退出 () + .如果真 (局_兑换状态 ≠ “兑换成功”) + 超级列表框1.置状态图片 (N - 1, 1) + .如果真结束 + +.计次循环尾 () + +.子程序 线程子程序 +.参数 参_索引, 整数型 +.局部变量 局_data, 文本型 +.局部变量 F_F, 文本型, , "0" +.局部变量 局_ctnEX, 文本型 +.局部变量 csrf_token, 文本型 +.局部变量 randstr, 文本型 +.局部变量 heardes, 文本型 +.局部变量 body, 文本型 +.局部变量 str, 文本型 +.局部变量 局_xy, 文本型 +.局部变量 roleName, 文本型 +.局部变量 str2, 文本型 +.局部变量 areaName, 文本型 +.局部变量 platName, 文本型 +.局部变量 优化价格, 文本型 +.局部变量 局_bin, 字节集 +.局部变量 局_文本txt, 文本型 +.局部变量 F_F2, 文本型, , "0" +.局部变量 ctnEX, 文本型 +.局部变量 COOK, 文本型 +.局部变量 yyjson, json对象 +.局部变量 error, 文本型 +.局部变量 msg, 文本型 +.局部变量 i, 整数型 +.局部变量 返回用户名, 文本型 +.局部变量 返回Cookie, 文本型 +.局部变量 返回商品名称, 文本型 +.局部变量 返回商品价格, 文本型 +.局部变量 返回商品ID, 文本型 +.局部变量 acf_ccn, 文本型 + +.如果真 (全工作中 = 假) + 返回 () +.如果真结束 + +全线程临界.进入 () +超级列表框1.置状态图片 (参_索引, 0) +局_data = 超级列表框1.取标题 (参_索引, 1) +局_文本txt = 超级列表框1.取标题 (参_索引, 3) +超级列表框1.保证显示 (参_索引) +全线程临界.退出 () +.如果真 (处理Cookie (局_data, 返回用户名, 返回Cookie) = 假) + 窗口_置状态信息 (参_索引, 1, 返回Cookie) + 返回 () +.如果真结束 + +.如果真 (处理商品 (局_文本txt, 返回商品名称, 返回商品价格, 返回商品ID) = 假) + 窗口_置状态信息 (参_索引, 1, 返回商品ID) + 返回 () +.如果真结束 + + +acf_ccn = Getctn2 (返回Cookie) +csrf_token = Getcsrf_token (返回Cookie) +COOK = 返回Cookie + “; cvl_csrf_token=” + csrf_token + “; acf_ccn=” + acf_ccn + +' randstr = 文本_取随机字符 (16, 位或 (1, 2, 4)) +heardes = 子文本替换 (#常量12, “[randstr]”, “qQizD2s4FNFzSTsG”, , , 真) + +body = 文本_替换 (#常量14, , , , “[randstr]”, “qQizD2s4FNFzSTsG”, “[manualID]”, “1W0yGkOdDbAXb”, “[商品ID]”, 返回商品ID, “[token]”, acf_ccn) +' 调试输出 (body) + + + +.判断循环首 (真) + .如果真 (全工作中 = 假) + 返回 () + .如果真结束 + + i = i + 1 + str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/activity/peacematch/exchange”, 1, body, COOK, , heardes)) + ' str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/activity/peacematch/exchange”, 1, body, COOK, , heardes)) + ' '调试输出 (str) + ' '状态条1.置文本 (0, “【” + 到文本 (i) + “】-> ” + str) + 窗口_置状态信息 (参_索引, 1, “【” + 到文本 (i) + “】-> ” + str) + .如果真 (寻找文本 (str, “无效”, , 假) ≠ -1 或 寻找文本 (str, “点击太快”, , 假) ≠ -1 或 str = “”) + csrf_token = 超级列表框1.取标题 (参_索引, 6) + ' csrf_token = Getcsrf_token222 (局_Cookie) + .如果真 (csrf_token ≠ “”) + COOK = 选择 (ctnEX ≠ “”, 返回Cookie + “; acf_ccn=” + ctnEX + “; cvl_csrf_token=” + csrf_token, 返回Cookie + “; cvl_csrf_token=” + csrf_token) + + randstr = 文本_取随机字符 (16, 位或 (1, 2, 4)) + heardes = 子文本替换 (#常量12, “[randstr]”, “qQizD2s4FNFzSTsG”, , , 真) + ' ’下次更新这里 替换#常量11得提交内容 里面得房间ID + body = 文本_替换 (#常量14, , , , “[randstr]”, “qQizD2s4FNFzSTsG”, “[manualID]”, “1W0yGkOdDbAXb”, “[商品ID]”, 返回商品ID, “[token]”, acf_ccn) + + .如果真结束 + + .如果真结束 + + .如果真 (寻找文本 (str, “error” + #引号 + “:0”, , 假) ≠ -1) + 跳出循环 () + .如果真结束 + + 系统_处理事件 () +.判断循环尾 () + +.如果 (寻找文本 (str, “error” + #引号 + “:0”, , 假) ≠ -1) + 窗口_置状态信息 (参_索引, 1, “兑换成功”) + .计次循环首 (2, ) + str2 = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo?actAlias=20260226ZMGDG”, , , 返回Cookie, , #常量3)) + .如果真 (str2 ≠ “”) + 跳出循环 () + .如果真结束 + + .计次循环尾 () + .如果真 (str2 ≠ “”) + yyjson.清空 () + yyjson.解析 (str2) + roleName = yyjson.取文本 (“data/gameRole/roleName”) + 全线程临界.进入 () + 优化价格 = 取文本左边 (返回商品价格, 取文本长度 (返回商品价格) - 1) + 保存日志2 (时间_到文本 (取现行时间 (), 1) + “ ” + 返回用户名 + “----” + roleName + “----” + 返回商品名称 + “----” + 优化价格) + 局_bin = 文转图片P (返回商品名称 + “ ” + #换行符 + 时间_格式化 (取现行时间 (), “yyyy年MM月dd日”, “ hh:mm:ss”, 真) + #换行符 + “兑换账号:” + roleName + “(” + 返回用户名 + “)”) + 写到文件_ (全PIC + “\” + roleName + “-” + 返回商品名称 + “.png”, 局_bin) + 全线程临界.退出 () + .如果真结束 + + +.否则 + 窗口_置状态信息 (参_索引, 1, str) + +.如果结束 + + +.子程序 Getcsrf_token222, 文本型 +.参数 参_Cookie, 文本型 +.局部变量 局_文本, 文本型 +.局部变量 局_CCK, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 str, 文本型 +.局部变量 返回CK, 文本型 +.局部变量 csrf_token, 文本型 + +str = 到文本 (网页_访问_对象 (“https://www.douyu.com/japi/carnival/nc/common/generateCsrf”, 1, , 参_Cookie, 返回CK, #常量10)) +csrf_token = 文本_取出中间文本 (返回CK, “cvl_csrf_token=”, “;”) +' '调试输出 (“csrf_token---”, csrf_token) +返回 (csrf_token) + +.子程序 _一键查询换绑时间_被选择 +.局部变量 p, 整数型 + +.计次循环首 (超级列表框1.取表项数 (), p) + 系统_处理事件 () + .如果真 (超级列表框1.取当前状态图片 (p - 1) = 1) + 启动线程 (&子程序查换绑时间, p - 1, ) + 程序_延时 (10) + .如果真结束 + +.计次循环尾 () + +.子程序 子程序查换绑时间 +.参数 索引E, 整数型 +.局部变量 局_文本, 文本型 +.局部变量 局_ctn, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_uid, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 str, 文本型 +.局部变量 yyjson, json对象 +.局部变量 changeRoleWaitTime, 整数型 +.局部变量 timetxt, 文本型 +.局部变量 局_txt, 文本型 + +全临界.进入 () +局_文本 = 超级列表框1.取标题 (索引E, 1) +局_ctn = 超级列表框1.取标题 (索引E, 5) +全临界.退出 () +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 状态条1.置文本 (0, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_uid = 局_F [2] +局_Cookie = 局_F [3] +' 'actAlias=20220616CJSZU +' actAlias=20260120QYOOB +' actAlias=20250213NQCYX + +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo?actAlias=20260226ZMGDG”, , , 局_Cookie, , #常量3)) +调试输出 (str) +yyjson.清空 () +yyjson.解析 (str) + +changeRoleWaitTime = yyjson.取整数 (“data/changeRoleWaitTime”) +.如果真 (到文本 (changeRoleWaitTime) = “”) + 窗口_置状态信息 (索引E, 2, “查询失败”) + 返回 () +.如果真结束 + + +timetxt = 时间_秒到时分秒格式 (changeRoleWaitTime) +窗口_置状态信息 (索引E, 2, “释放时间:” + timetxt) + + + +.子程序 _一键查询限兑商品_被选择 +.局部变量 p, 整数型 + +.计次循环首 (超级列表框1.取表项数 (), p) + 系统_处理事件 () + .如果真 (超级列表框1.取当前状态图片 (p - 1) = 1) + 启动线程 (&子程序查限兑商品, p - 1, ) + 程序_延时 (10) + .如果真结束 + +.计次循环尾 () + +.子程序 子程序查限兑商品 +.参数 索引E, 整数型 +.局部变量 局_文本, 文本型 +.局部变量 局_ctn, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_uid, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 str, 文本型 +.局部变量 yyjson, json对象 +.局部变量 changeRoleWaitTime, 整数型 +.局部变量 timetxt, 文本型 +.局部变量 zy数, 整数型 +.局部变量 statustxt, 文本型 +.局部变量 P, 整数型 +.局部变量 F_F, 文本型, , "0" +.局部变量 commodityName, 文本型 +.局部变量 排序E, 文本型 + +全临界.进入 () +局_文本 = 超级列表框1.取标题 (索引E, 1) +局_ctn = 超级列表框1.取标题 (索引E, 5) +全临界.退出 () +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 状态条1.置文本 (0, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_uid = 局_F [2] +局_Cookie = 局_F [3] + +' data.list[21].status = 0 +' manualID=G4KA4Qnz4LDp7 +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/activity/peace/storehome?manualID=G4KA4Qnz4LDp7&rid=9263298”, , , 局_Cookie, , #常量3)) +' 调试输出 (str) +yyjson.清空 () +yyjson.解析 (str) +zy数 = yyjson.取成员数 (“data/list”) +.计次循环首 (zy数, P) + 系统_处理事件 () + statustxt = yyjson.取文本 (“data/list/” + 到文本 (P - 1) + “/status”) + .如果真 (statustxt = “1”) + commodityName = yyjson.取文本 (“data/list/” + 到文本 (P - 1) + “/commodityName”) + 加入成员 (F_F, commodityName) + .如果真结束 + +.计次循环尾 () +.如果真 (取数组成员数 (F_F) < 1) + 窗口_置状态信息 (索引E, 2, “无限制商品”) + ' 状态条1.置文本 (0, “限兑-> 无限制商品”) + 返回 () +.如果真结束 + +排序E = 数组_合并 (F_F, “,”) +窗口_置状态信息 (索引E, 2, “限兑*” + 到文本 (取数组成员数 (F_F)) + “:” + 排序E) +' 状态条1.置文本 (0, “限兑*” + 到文本 (取数组成员数 (F_F)) + “:” + 排序E) +返回 () + +.子程序 _一键查询鱼刺余额_被选择 +.局部变量 p, 整数型 + +.计次循环首 (超级列表框1.取表项数 (), p) + .如果真 (超级列表框1.取当前状态图片 (p - 1) = 1) + 启动线程 (&子程序查询鱼刺, p - 1, ) + 程序_延时 (100) + .如果真结束 + +.计次循环尾 () + +.子程序 子程序查询鱼刺 +.参数 索引E +.局部变量 局_文本, 文本型 +.局部变量 局_ctn, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_uid, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 str, 文本型 +.局部变量 yyjson, json对象 +.局部变量 局_余额, 文本型 + +全临界.进入 () +局_文本 = 超级列表框1.取标题 (索引E, 1) +局_ctn = 超级列表框1.取标题 (索引E, 5) +全临界.退出 () +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 状态条1.置文本 (0, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_uid = 局_F [2] +局_Cookie = 局_F [3] +窗口_置状态信息 (索引E, 1, “查询鱼刺余额...”) +str = 到文本 (网页_访问_对象 (“https://cz.douyu.com/item/gold/account”, , , 局_Cookie, , #常量2)) +' 调试输出 (str) +yyjson.清空 () +yyjson.解析 (str) +局_余额 = yyjson.取文本 (“data/gold”) +窗口_置状态信息 (索引E, 1, “鱼刺余额:” + 到文本 (局_余额)) + + +.子程序 _时钟1_周期事件 +.局部变量 设置定时关闭, 文本型 +.局部变量 动态时间, 文本型 +.局部变量 设置定时开始, 文本型 + +设置定时开始 = 删首尾空 (编辑框_时.内容) + “:” + 删首尾空 (编辑框_分.内容) + “:” + 删首尾空 (编辑框_秒.内容) + “:” +动态时间 = 时间_格式化 (取现行时间 (), , “hh:mm:ss”, 真) +状态条1.置文本 (1, “[定时脚本]动态时间:” + 动态时间) +.如果真 (动态时间 = 设置定时开始) + _按钮_启动_被单击 () +.如果真结束 + + +.子程序 _选择框_定时抢购_被单击 + +.如果 (选择框_定时抢购.选中 = 真) + _全选_被选择 () + 时钟1.时钟周期 = 1000 + 状态条1.置文本 (1, “已开启定时抢购√”) + +.否则 + 时钟1.时钟周期 = 0 + 状态条1.置文本 (1, “已关闭定时抢购×”) + 工作中 = 假 + 工作线程池EX.销毁 () +.如果结束 + + +.子程序 _开通电竞手册30_被选择 +.局部变量 局_文本, 文本型 +.局部变量 ctnEX, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_Cookie, 文本型 + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +ctnEX = 超级列表框1.取标题 (超级列表框1.现行选中项, 5) +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_Cookie = 局_F [3] +电竞手册二维码30 (ctnEX, 局_Cookie) + + +.子程序 _一键查询兑换记录_被选择 +.局部变量 p, 整数型 + +.计次循环首 (超级列表框1.取表项数 (), p) + .如果真 (超级列表框1.取当前状态图片 (p - 1) = 1) + 启动线程 (&子程序查询兑换记录, p - 1, ) + 程序_延时 (100) + .如果真结束 + +.计次循环尾 () + + +.子程序 子程序查询兑换记录 +.参数 索引E +.局部变量 局_文本, 文本型 +.局部变量 局_ctn, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_uid, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 str, 文本型 +.局部变量 yyjson, json对象 +.局部变量 局_余额, 文本型 +.局部变量 zy数, 整数型 +.局部变量 P, 整数型 +.局部变量 F_F, 文本型, , "0" +.局部变量 commodityName, 文本型 +.局部变量 排序E, 文本型 + +全临界.进入 () +局_文本 = 超级列表框1.取标题 (索引E, 1) +局_ctn = 超级列表框1.取标题 (索引E, 5) +全临界.退出 () +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 状态条1.置文本 (0, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_uid = 局_F [2] +局_Cookie = 局_F [3] +窗口_置状态信息 (索引E, 1, “查询兑换记录...”) +' manualID=G4KA4Qnz4LDp7 +str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wgapi/ordnc/activity/peacematch/exchangeList?manualID=1W0yGkOdDbAXb&limit=15&offset=0”, , , 局_Cookie, , #常量3)) +调试输出 (str) +yyjson.清空 () +yyjson.解析 (str) +zy数 = yyjson.取成员数 (“data/list”) +.计次循环首 (zy数, P) + commodityName = yyjson.取文本 (“data/list/” + 到文本 (P - 1) + “/commodityName”) + 加入成员 (F_F, commodityName) +.计次循环尾 () +.如果真 (取数组成员数 (F_F) < 1) + 窗口_置状态信息 (索引E, 2, “无兑换记录”) + 返回 () +.如果真结束 + +排序E = 数组_合并 (F_F, “,”) +窗口_置状态信息 (索引E, 2, “记录*” + 到文本 (取数组成员数 (F_F)) + “:” + 排序E) + + + +.子程序 _充值鱼刺Api_被选择 +.局部变量 返回积分, 文本型 +.局部变量 局_充值账号, 文本型 +.局部变量 局_文本, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 局_Cookie, 文本型 +.局部变量 返回充值前余额, 文本型 +.局部变量 返回充值后余额, 文本型 +.局部变量 冲之前余额, 文本型 +.局部变量 充值后余额, 文本型 + +局_文本 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1) +局_充值账号 = 文本_取左边 (局_文本, “----”) +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “Cookie格式错误”) + 返回 () +.如果真结束 + +局_Cookie = 局_F [3] + +' 返回积分 = 查积分记录 () +.' 如果真 (返回积分 = “NULL”) + ' 窗口_置状态信息 (超级列表框1.现行选中项, 2, “未开通精英手册”) + ' 返回 () +.如果真结束 + +.' 如果真 (到整数 (返回积分) < 10) + ' 窗口_置状态信息 (超级列表框1.现行选中项, 2, “未开通精英手册”) + .' 如果真 (信息框 (“积分:” + 返回积分 + “,是否充值?”, #信息图标 + #确认钮 + #取消钮, , ) = #取消钮) + ' 返回 () + .如果真结束 + +.如果真结束 + + + +返回充值前余额 = 查询鱼刺余额 (局_Cookie) +冲之前余额 = 到文本 (到整数 (返回充值前余额) ÷ 100) +.如果真 (冲之前余额 = “”) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “查询余额失败”) + 返回 () +.如果真结束 + +.如果真 (信息框 (“充值数量:” + 编辑框_充值金额.内容 + “,是否充值?”, #信息图标 + #确认钮 + #取消钮, , ) = #取消钮) + 返回 () +.如果真结束 + + +窗口_置状态信息 (超级列表框1.现行选中项, 2, “充值前鱼刺:” + 冲之前余额 + “,正在充值...”) +程序_延时 (1000) +.如果真 (api充值 (超级列表框1.现行选中项, 局_充值账号) = 真) + .计次循环首 (10, ) + 返回充值后余额 = 查询鱼刺余额 (局_Cookie) + 充值后余额 = 到文本 (到整数 (返回充值后余额) ÷ 100) + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “充值前鱼刺:” + 冲之前余额 + “,等待充值后余额:” + 充值后余额) + .如果真 (充值后余额 ≠ 冲之前余额) + 跳出循环 () + .如果真结束 + 程序_延时 (1000) + .计次循环尾 () + 窗口_置状态信息 (超级列表框1.现行选中项, 2, “充值成功后鱼刺:” + 充值后余额) + 程序_延时 (1000) + 直接送礼物 (局_Cookie, 超级列表框1.现行选中项) + 程序_延时 (2000) + .计次循环首 (3, ) + 查积分记录 () + .计次循环尾 () +.如果真结束 + + +.子程序 api充值, 逻辑型 +.参数 参_索引, 整数型 +.参数 参_充值账号, 文本型 +.局部变量 timestamp, 文本型 +.局部变量 random, 文本型 +.局部变量 订单时间ordersn, 文本型 +.局部变量 secretKey, 文本型 +.局部变量 sign, 文本型 +.局部变量 body, 文本型 +.局部变量 str, 文本型 +.局部变量 yyjson, json对象 +.局部变量 ordersn, 文本型 +.局部变量 bool, 逻辑型 +.局部变量 i, 整数型 +.局部变量 局_充值数量, 文本型 +.局部变量 返回err0, 文本型 +.局部变量 dealer_id, 文本型 + +dealer_id = 删全部空 (编辑框_经销商ID.内容) +secretKey = 删全部空 (编辑框_API密钥.内容) +.如果真 (dealer_id = “”) + 窗口_置状态信息 (参_索引, 2, “请设置经销商ID~!”) + 返回 (假) +.如果真结束 + +.如果真 (secretKey = “”) + 窗口_置状态信息 (参_索引, 2, “请设置API密钥~!”) + 返回 (假) +.如果真结束 + +局_充值数量 = 删全部空 (编辑框_充值金额.内容) +.如果真 (局_充值数量 = “”) + 窗口_置状态信息 (参_索引, 2, “请设置充值数量~!”) + 返回 (假) +.如果真结束 + +timestamp = 时间_取现行时间戳 () +random = 文本_取随机字符 (13, 位或 (1, 2)) +订单时间ordersn = timestamp +sign = 校验_取md5_文本 (secretKey + “dealer_id=” + dealer_id + “&game_id=37¬ify_url=https://www.baidu.com&number=” + 局_充值数量 + “&ordersn=” + 订单时间ordersn + “&random=” + random + “&rid=” + 参_充值账号 + “&rules_id=317&server=默认区服×tamp=” + timestamp + secretKey, 真, 真) + +body = “dealer_id=” + dealer_id + “&game_id=37¬ify_url=https://www.baidu.com&number=” + 局_充值数量 + “&ordersn=” + 订单时间ordersn + “&random=” + random + “&rid=” + 参_充值账号 + “&rules_id=317&server=默认区服×tamp=” + timestamp + “&signature=” + sign + +str = 编码_Utf8到Ansi (网页_访问_对象 (“http://api.hapu.cc/order/createOrder”, 1, body, , , “User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36” + #换行符 + “Content-Type: application/x-www-form-urlencoded”)) +yyjson.清空 () +yyjson.解析 (str) +ordersn = yyjson.取文本 (“ordersn”) +.如果真 (ordersn ≠ “”) + 窗口_置状态信息 (参_索引, 2, “api下单成功”) + 返回 (真) + +.如果真结束 + +窗口_置状态信息 (参_索引, 2, “api下单失败:” + 编码_usc2到ansi (str)) +返回 (假) + +' {"status":200,"msg":"成功","ordersn":"1746864258996","fy_order_id":"20250510160420546837090","money":"0.980"} + +.子程序 查询充值结果, 逻辑型, , {"status":200,"msg":"成功","ordersn":"1746864258996","fy_order_id":"20250510160420546837090","money":"0.980"} +.参数 参_ordersn, 文本型 +.参数 返回err0, 文本型 +.局部变量 str, 文本型 +.局部变量 body, 文本型 +.局部变量 random, 文本型 +.局部变量 timestamp, 文本型 +.局部变量 secretKey, 文本型 +.局部变量 sign, 文本型 +.局部变量 yyjson, json对象 +.局部变量 msg, 文本型 + +timestamp = 时间_取现行时间戳 () +random = 文本_取随机字符 (13, 位或 (1, 2)) +secretKey = “403710daec035bbd94e30cefb337b6ccded16dbf” +sign = 校验_取md5_文本 (secretKey + “dealer_id=129873564&ordersn=” + 参_ordersn + “&random=” + random + “×tamp=” + timestamp + secretKey, 真, 真) + +body = “dealer_id=129873564&ordersn=” + 参_ordersn + “&random=” + random + “×tamp=” + timestamp + “&signature=” + sign +str = 编码_Utf8到Ansi (网页_访问_对象 (“http://api.hapu.cc/order/getorder”, 1, body, , , “User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36” + #换行符 + “Content-Type: application/x-www-form-urlencoded”)) +' 调试输出 (str) +yyjson.清空 () +yyjson.解析 (str) +msg = yyjson.取文本 (“msg”) +返回err0 = msg +.如果真 (msg = “成功”) + 返回 (真) +.如果真结束 + +返回 (假) + +.子程序 查询鱼刺余额, 文本型, , {"status":200,"msg":"成功","ordersn":"1746864258996","fy_order_id":"20250510160420546837090","money":"0.980"} +.参数 参_Cookie, 文本型 +.局部变量 str, 文本型 +.局部变量 yyjson, json对象 +.局部变量 count, 文本型 + +.计次循环首 (3, ) + str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/wjapi/nc/exchange/fim?appCode=YJTX”, , , 参_Cookie, , “Host: www.douyu.com” + #换行符 + “Connection: keep-alive” + #换行符 + “sec-ch-ua-platform: ” + #引号 + “Windows” + #引号 + “” + #换行符 + “User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36” + #换行符 + “Accept: application/json, text/plain, */*” + #换行符 + “Referer: https://www.douyu.com/member/walletcenter”)) + .如果真 (str ≠ “”) + 跳出循环 () + .如果真结束 + +.计次循环尾 () +调试输出 (str) +yyjson.清空 () +yyjson.解析 (str) +count = yyjson.取文本 (“data/count”) +返回 (count) + + +.子程序 _一键获取兑换csrftoken_被选择 +.局部变量 p, 整数型 + +.计次循环首 (超级列表框1.取表项数 (), p) + 系统_处理事件 () + .如果真 (超级列表框1.取当前状态图片 (p - 1) = 1) + 启动线程 (&Getcsrf_token3333, p - 1, ) + 程序_延时 (10) + .如果真结束 + +.计次循环尾 () + + +.子程序 Getcsrf_token3333 +.参数 参_索引, 整数型 +.局部变量 局_文本, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 局_F, 文本型, , "0" +.局部变量 str, 文本型 +.局部变量 返回CK, 文本型 +.局部变量 csrf_token, 文本型 + +' 超级列表框1.置状态图片 (参_索引, 0) +局_文本 = 超级列表框1.取标题 (参_索引, 1) +局_F = 分割文本 (局_文本, “----”, ) +.如果真 (取数组成员数 (局_F) < 3) + 全Fast_list.进入写锁 () + 超级列表框1.置标题 (参_索引, 4, “Cookie无效”) + 全Fast_list.退出写锁 () + 返回 () +.如果真结束 + + +局_Cookie = 局_F [3] +str = 到文本 (网页_访问_对象 (“https://www.douyu.com/japi/carnival/nc/common/generateCsrf”, 1, , 局_Cookie, 返回CK, #常量10)) + +csrf_token = 文本_取出中间文本 (返回CK, “cvl_csrf_token=”, “;”) +状态条1.置文本 (0, “#” + 到文本 (参_索引) + “=>” + csrf_token) +' 调试输出 (csrf_token) +.如果真 (csrf_token ≠ “”) + 全Fast_list.进入写锁 () + 超级列表框1.置标题 (参_索引, 4, “获取csrf_token成功”) + 超级列表框1.置标题 (参_索引, 6, csrf_token) + 全Fast_list.退出写锁 () + 返回 () +.如果真结束 + +全Fast_list.进入写锁 () +超级列表框1.置标题 (参_索引, 4, str) +全Fast_list.退出写锁 () + + +.子程序 __________________________________ + + + +.子程序 _取单条Cookie, 文本型, 公开, 从左往右获取 +.参数 Cookie, 文本型 +.参数 名称, 文本型 +.参数 不附带名称, 逻辑型, 可空 +.局部变量 t, 文本型 + +Cookie = 删首尾空 (Cookie) +.如果真 (取文本右边 (Cookie, 1) ≠ “;”) + Cookie = Cookie + “; ” +.如果真结束 +t = 单条Cookie (Cookie, 名称) +.判断开始 (不附带名称 或 t = “”) + 返回 (t) +.默认 + 返回 (名称 + “=” + t) +.判断结束 + + +.子程序 单条Cookie, 文本型 +.参数 原Cookies, 文本型 +.参数 单条Cookie名称, 文本型 +.局部变量 Cookies, 文本型, , "0" +.局部变量 i, 整数型 +.局部变量 Name, 文本型 + +Cookies = 分割文本 (原Cookies, “;”, ) +.计次循环首 (取数组成员数 (Cookies), i) + Name = 文本_取左边 (删首尾空 (Cookies [i]), “=”, , ) + .如果真 (到小写 (Name) = 到小写 (单条Cookie名称)) + .如果真 (寻找文本 (删首尾空 (Cookies [i]), “:”, , 假) ≠ -1) + 返回 (文本_取出中间文本 (删首尾空 (Cookies [i]), “=”, “:”, , )) + .如果真结束 + 返回 (文本_取右边 (删首尾空 (Cookies [i]), “=”, , )) + + .如果真结束 + +.计次循环尾 () +返回 (“”) + + diff --git a/web/backend/migrations/versions/20260728_0010_douyu_esports_handbook.py b/web/backend/migrations/versions/20260728_0010_douyu_esports_handbook.py new file mode 100644 index 0000000..37aa97b --- /dev/null +++ b/web/backend/migrations/versions/20260728_0010_douyu_esports_handbook.py @@ -0,0 +1,65 @@ +"""新增斗鱼电竞手册配置 + +Revision ID: 20260728_0010 +Revises: 20260725_0009 +Create Date: 2026-07-28 +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +revision: str = "20260728_0010" +down_revision: Union[str, None] = "20260725_0009" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def _columns(bind, table_name: str) -> set[str]: + if not sa.inspect(bind).has_table(table_name): + return set() + return {column["name"] for column in sa.inspect(bind).get_columns(table_name)} + + +def upgrade() -> None: + bind = op.get_bind() + config_columns = _columns(bind, "douyu_config") + for column in [ + sa.Column("esports_manual_id", sa.String(length=64), nullable=True), + sa.Column("esports_act_alias", sa.String(length=64), nullable=True), + sa.Column("esports_amount", sa.Integer(), nullable=True), + ]: + if column.name not in config_columns: + op.add_column("douyu_config", column) + + account_columns = _columns(bind, "accounts") + for column in [ + sa.Column("esports_points", sa.Integer(), nullable=True), + sa.Column("esports_game_name", sa.String(length=128), nullable=True), + sa.Column("esports_game_channel", sa.String(length=128), nullable=True), + sa.Column("esports_bind_status", sa.String(length=32), nullable=True), + sa.Column("esports_change_role_wait_time", sa.Integer(), nullable=True), + ]: + if column.name not in account_columns: + op.add_column("accounts", column) + + +def downgrade() -> None: + bind = op.get_bind() + account_columns = _columns(bind, "accounts") + for column_name in [ + "esports_change_role_wait_time", + "esports_bind_status", + "esports_game_channel", + "esports_game_name", + "esports_points", + ]: + if column_name in account_columns: + op.drop_column("accounts", column_name) + + config_columns = _columns(bind, "douyu_config") + for column_name in ["esports_amount", "esports_act_alias", "esports_manual_id"]: + if column_name in config_columns: + op.drop_column("douyu_config", column_name) diff --git a/web/backend/migrations/versions/20260728_0011_douyu_esports_account_fields.py b/web/backend/migrations/versions/20260728_0011_douyu_esports_account_fields.py new file mode 100644 index 0000000..dcf44a0 --- /dev/null +++ b/web/backend/migrations/versions/20260728_0011_douyu_esports_account_fields.py @@ -0,0 +1,51 @@ +"""补充电竞手册账号独立字段 + +Revision ID: 20260728_0011 +Revises: 20260728_0010 +Create Date: 2026-07-28 +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +revision: str = "20260728_0011" +down_revision: Union[str, None] = "20260728_0010" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def _columns(bind, table_name: str) -> set[str]: + if not sa.inspect(bind).has_table(table_name): + return set() + return {column["name"] for column in sa.inspect(bind).get_columns(table_name)} + + +def upgrade() -> None: + bind = op.get_bind() + columns = _columns(bind, "accounts") + for column in [ + sa.Column("esports_points", sa.Integer(), nullable=True), + sa.Column("esports_game_name", sa.String(length=128), nullable=True), + sa.Column("esports_game_channel", sa.String(length=128), nullable=True), + sa.Column("esports_bind_status", sa.String(length=32), nullable=True), + sa.Column("esports_change_role_wait_time", sa.Integer(), nullable=True), + ]: + if column.name not in columns: + op.add_column("accounts", column) + + +def downgrade() -> None: + bind = op.get_bind() + columns = _columns(bind, "accounts") + for column_name in [ + "esports_change_role_wait_time", + "esports_bind_status", + "esports_game_channel", + "esports_game_name", + "esports_points", + ]: + if column_name in columns: + op.drop_column("accounts", column_name) diff --git a/web/backend/models.py b/web/backend/models.py index 91ae99b..e87d29c 100644 --- a/web/backend/models.py +++ b/web/backend/models.py @@ -64,6 +64,11 @@ class Account(Base): exchange_balance = Column(Integer, nullable=True) bind_status = Column(String(32), default="") change_role_wait_time = Column(Integer, nullable=True) + esports_points = Column(Integer, nullable=True) + esports_game_name = Column(String(128), default="") + esports_game_channel = Column(String(128), default="") + esports_bind_status = Column(String(32), default="") + esports_change_role_wait_time = Column(Integer, nullable=True) created_at = Column(DateTime, default=_utcnow) updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow) @@ -119,6 +124,9 @@ class DouyuConfig(Base): legacy_act_alias = Column(String(64), default="cjm") room_id = Column(String(64), default="9263298") elite_amount = Column(Integer, default=3000) + esports_manual_id = Column(String(64), default="1W0yGkOdDbAXb") + esports_act_alias = Column(String(64), default="20260226ZMGDG") + esports_amount = Column(Integer, default=3000) gold_pay_type = Column(Integer, default=1) gift_id = Column(String(64), default="23643") skin_id = Column(String(64), default="2942") diff --git a/web/backend/routers/douyu.py b/web/backend/routers/douyu.py index fafa109..a5f8988 100644 --- a/web/backend/routers/douyu.py +++ b/web/backend/routers/douyu.py @@ -107,6 +107,11 @@ def _account_out(account: Account) -> DouyuTaskAccountOut: exchange_balance=account.exchange_balance, bind_status=account.bind_status or "", change_role_wait_time=account.change_role_wait_time, + esports_points=account.esports_points, + esports_game_name=account.esports_game_name or "", + esports_game_channel=account.esports_game_channel or "", + esports_bind_status=account.esports_bind_status or "", + esports_change_role_wait_time=account.esports_change_role_wait_time, assigned_to=account.assigned_to, assigned_username=account.assigned_user.username if account.assigned_user else None, ) @@ -140,6 +145,9 @@ def _config_out(config: DouyuConfig) -> DouyuConfigOut: legacy_act_alias=douyu_config_value("legacy_act_alias", config.legacy_act_alias), room_id=douyu_config_value("room_id", config.room_id), elite_amount=douyu_config_value("elite_amount", config.elite_amount), + esports_manual_id=douyu_config_value("esports_manual_id", config.esports_manual_id), + esports_act_alias=douyu_config_value("esports_act_alias", config.esports_act_alias), + esports_amount=douyu_config_value("esports_amount", config.esports_amount), gold_pay_type=douyu_config_value("gold_pay_type", config.gold_pay_type), gift_id=douyu_config_value("gift_id", config.gift_id), skin_id=douyu_config_value("skin_id", config.skin_id), @@ -170,6 +178,7 @@ def list_task_accounts( Account.nickname.ilike(pattern), Account.tag.ilike(pattern), Account.game_name.ilike(pattern), + Account.esports_game_name.ilike(pattern), )) rows = query.order_by(Account.id.desc()).limit(500).all() return [_account_out(account) for account in rows] diff --git a/web/backend/schemas.py b/web/backend/schemas.py index 2d649dd..71fa5b4 100644 --- a/web/backend/schemas.py +++ b/web/backend/schemas.py @@ -542,6 +542,9 @@ class DouyuConfigOut(BaseModel): legacy_act_alias: str = "cjm" room_id: str = "9263298" elite_amount: int = 3000 + esports_manual_id: str = "1W0yGkOdDbAXb" + esports_act_alias: str = "20260226ZMGDG" + esports_amount: int = 3000 gold_pay_type: int = 1 gift_id: str = "23643" skin_id: str = "2942" @@ -557,6 +560,9 @@ class DouyuConfigOut(BaseModel): "legacy_act_alias": self.legacy_act_alias, "room_id": self.room_id, "elite_amount": self.elite_amount, + "esports_manual_id": self.esports_manual_id, + "esports_act_alias": self.esports_act_alias, + "esports_amount": self.esports_amount, "gold_pay_type": self.gold_pay_type, "gift_id": self.gift_id, "skin_id": self.skin_id, @@ -572,6 +578,9 @@ class DouyuConfigUpdate(BaseModel): legacy_act_alias: Optional[str] = None room_id: Optional[str] = None elite_amount: Optional[int] = Field(None, ge=1) + esports_manual_id: Optional[str] = None + esports_act_alias: Optional[str] = None + esports_amount: Optional[int] = Field(None, ge=1) gold_pay_type: Optional[int] = Field(None, ge=1, le=9) gift_id: Optional[str] = None skin_id: Optional[str] = None @@ -657,6 +666,11 @@ class DouyuTaskAccountOut(BaseModel): exchange_balance: Optional[int] = None bind_status: str = "" change_role_wait_time: Optional[int] = None + esports_points: Optional[int] = None + esports_game_name: str = "" + esports_game_channel: str = "" + esports_bind_status: str = "" + esports_change_role_wait_time: Optional[int] = None assigned_to: Optional[int] = None assigned_username: Optional[str] = None diff --git a/web/backend/services/douyu_runner.py b/web/backend/services/douyu_runner.py index 39af10c..9870ce2 100644 --- a/web/backend/services/douyu_runner.py +++ b/web/backend/services/douyu_runner.py @@ -277,6 +277,53 @@ class DouyuBatchRunner: account.bind_status = status account.updated_at = datetime.now(timezone.utc) + def _apply_esports_bind_info_to_account(self, account: Account, bind_info: dict, status: str) -> None: + """将电竞手册角色状态写入专属字段,避免覆盖精英宝典数据。""" + role_name = str(bind_info.get("role_name") or "") + account.esports_game_name = role_name or account.esports_game_name + account.esports_game_channel = self._role_channel(bind_info) or account.esports_game_channel + account.esports_change_role_wait_time = self._to_int(bind_info.get("change_role_wait_time")) + account.esports_bind_status = status + account.updated_at = datetime.now(timezone.utc) + + def _esports_bind_state( + self, + client: DouyuActivityClient, + act_alias: str, + ) -> dict: + """查询电竞手册活动状态,接口同时返回当前角色和换绑冷却信息。""" + activity_info = client.esports_bind_info(act_alias) + activity_snapshot = self._bind_snapshot(activity_info) + esports_bound = activity_snapshot["is_bound_act"] + has_selected_role = bool(activity_snapshot["role_name"]) + tx_act = activity_info.get("tx_act") or {} + return { + **activity_snapshot, + "is_bound_role": has_selected_role, + "act_alias": act_alias, + "game_id": tx_act.get("gameId") or "", + "esports_bound": esports_bound, + "has_selected_role": has_selected_role, + "bind_ready_for_confirm": has_selected_role and not esports_bound, + "bind_confirmed": esports_bound, + "bind_phase": "confirmed" if esports_bound else ("role_ready" if has_selected_role else "waiting_role"), + "activity_bind_info": activity_info, + "activity_bind_snapshot": activity_snapshot, + "role_source": "activity", + } + + @staticmethod + def _esports_role_text(state: dict) -> str: + role_name = str(state.get("role_name") or "") + channel = " / ".join( + str(part) + for part in [state.get("plat_name"), state.get("area_name")] + if part + ) + if not role_name: + return "" + return f"{role_name}({channel})" if channel else role_name + def _mark_task( self, db: Session, @@ -415,6 +462,100 @@ class DouyuBatchRunner: result["points"] = last_points return False + def _refresh_esports_handbook( + self, + client: DouyuActivityClient, + account: Account, + *, + manual_id: str, + ) -> dict: + """刷新电竞手册开通状态并将积分写回账号。""" + result = client.esports_user_info(manual_id=manual_id) + manual_type = self._to_int(result.get("manual_type")) + manual_score = self._to_int(result.get("manual_score")) + account.esports_points = manual_score + account.updated_at = datetime.now(timezone.utc) + return { + "esports_manual_type": manual_type, + "esports_manual_score": manual_score, + "esports_expire_time": result.get("expire_time"), + "esports_user_info": result, + "esports_points": manual_score, + "points": manual_score, + } + + def _wait_esports_open_after_payment( + self, + db: Session, + task: DouyuTask, + account: Account, + client: DouyuActivityClient, + *, + manual_id: str, + result: dict, + baseline_manual_type: int | None, + baseline_manual_score: int | None, + ) -> bool: + """等待电竞手册支付到账,以 manualType=1 或积分变化作为成功条件。""" + deadline = time.monotonic() + DOUYU_PAYMENT_POLL_SECONDS + result["payment_polling"] = True + result["esports_manual_type_baseline"] = baseline_manual_type + result["esports_manual_score_baseline"] = baseline_manual_score + poll_count = 0 + last_manual_type = baseline_manual_type + last_manual_score = baseline_manual_score + + while not self._stop.is_set() and time.monotonic() <= deadline: + try: + handbook_result = self._refresh_esports_handbook( + client, + account, + manual_id=manual_id, + ) + db.commit() + poll_count += 1 + last_manual_type = handbook_result["esports_manual_type"] + last_manual_score = handbook_result["esports_manual_score"] + result.update(handbook_result) + result["payment_poll_count"] = poll_count + result["payment_polling"] = True + opened = ( + last_manual_type is not None + and last_manual_type >= 1 + ) or ( + baseline_manual_score is not None + and last_manual_score is not None + and last_manual_score > baseline_manual_score + ) + if opened: + result["payment_polling"] = False + result["esports_opened"] = True + return True + self._update_task_progress( + db, + task, + "running", + "电竞手册支付码已生成,等待开通到账" + f"(类型 {last_manual_type if last_manual_type is not None else '-'}," + f"积分 {last_manual_score if last_manual_score is not None else '-'})", + result, + ) + except Exception as exc: + poll_count += 1 + result["payment_poll_count"] = poll_count + result["payment_poll_error"] = str(exc) + self._update_task_progress(db, task, "running", f"等待电竞手册到账: {exc}", result) + if self._stop.wait(DOUYU_PAYMENT_POLL_INTERVAL): + break + + result["payment_polling"] = False + result["esports_opened"] = False + result["esports_manual_type"] = last_manual_type + result["esports_manual_score"] = last_manual_score + result["esports_points"] = last_manual_score + result["points"] = last_manual_score + return False + def _wait_gold_balance_after_payment( self, db: Session, @@ -1093,6 +1234,257 @@ class DouyuBatchRunner: result, ) + def _execute_prepare_esports_bind( + self, + db: Session, + task: DouyuTask, + account: Account, + cookie: str, + config: dict, + ): + """打开电竞手册绑定面板:查活动状态、当前角色和换绑冷却。""" + client = self._client(cookie) + act_alias = str(config.get("esports_act_alias") or "").strip() + if not act_alias: + self._mark_task(db, task, "failed", "请先配置电竞手册活动 actAlias") + return + + state = self._esports_bind_state(client, act_alias) + esports_bound = state["esports_bound"] + role_text = self._esports_role_text(state) + self._apply_esports_bind_info_to_account( + account, + state, + "esports_bound" if esports_bound else ("esports_bind_ready" if role_text else "game_not_bound"), + ) + # 二维码用于重新选择角色,不应因为已有角色或换绑冷却而隐藏。 + # 冷却是否允许最终由 actBind 返回结果决定。 + qr_result = client.get_esports_bind_qr(act_alias) + result = { + **state, + **qr_result, + "esports_bind_dialog": True, + "can_open_role_selector": True, + } + if esports_bound: + message = f"电竞手册已绑定: {role_text or '-'},可扫码换绑" + elif role_text: + message = f"当前角色: {role_text},可扫码切换角色或直接完成绑定" + else: + message = "请扫码选择游戏角色,完成后查询最新角色" + self._mark_task(db, task, "success", message, result) + + def _execute_get_esports_bind_qr( + self, + db: Session, + task: DouyuTask, + account: Account, + cookie: str, + config: dict, + ): + """生成电竞手册切换角色用的腾讯入口。""" + client = self._client(cookie) + act_alias = str(config.get("esports_act_alias") or "").strip() + if not act_alias: + self._mark_task(db, task, "failed", "请先配置电竞手册活动 actAlias") + return + + state = self._esports_bind_state(client, act_alias) + qr_result = client.get_esports_bind_qr(act_alias) + result = { + **state, + **qr_result, + "esports_bind_dialog": True, + "can_open_role_selector": True, + "bind_phase": "switching_role", + } + account.esports_bind_status = "esports_role_switching" + account.updated_at = datetime.now(timezone.utc) + self._mark_task( + db, + task, + "success", + "请在腾讯页面选择角色,返回后查询最新角色", + result, + ) + + def _execute_confirm_esports_bind( + self, + db: Session, + task: DouyuTask, + account: Account, + cookie: str, + config: dict, + ): + """通过 actBind 确认电竞手册绑定,并回读唯一状态接口确认结果。""" + client = self._client(cookie) + act_alias = str(config.get("esports_act_alias") or "").strip() + if not act_alias: + self._mark_task(db, task, "failed", "请先配置电竞手册活动 actAlias") + return + + account.esports_bind_status = "esports_bind_confirming" + account.updated_at = datetime.now(timezone.utc) + self._update_task_progress( + db, + task, + "running", + "正在确认电竞手册绑定", + { + "bind_phase": "confirming", + "bind_confirmed": False, + }, + ) + confirm_result = client.confirm_esports_bind( + act_alias, + room_id=str(config.get("room_id") or "9263298"), + ) + after_state = self._esports_bind_state(client, act_alias) + result = { + **after_state, + "confirm": confirm_result, + "bind_phase": "confirmed" if after_state["esports_bound"] else "confirm_failed", + } + if not after_state["esports_bound"]: + self._apply_esports_bind_info_to_account(account, after_state, "esports_bind_ready") + self._mark_task( + db, + task, + "failed", + f"电竞手册绑定未生效: {self._esports_role_text(after_state) or '-'}", + result, + ) + return + + self._apply_esports_bind_info_to_account(account, after_state, "esports_bound") + self._mark_task( + db, + task, + "success", + f"电竞手册绑定成功: {self._esports_role_text(after_state) or '-'}", + result, + ) + + def _execute_query_esports_game_name( + self, + db: Session, + task: DouyuTask, + account: Account, + cookie: str, + config: dict, + ): + """查询电竞手册活动返回的最新角色和换绑冷却状态。""" + client = self._client(cookie) + act_alias = str(config.get("esports_act_alias") or "").strip() + if not act_alias: + self._mark_task(db, task, "failed", "请先配置电竞手册活动 actAlias") + return + + state = self._esports_bind_state(client, act_alias) + role_name = state["role_name"] + is_bound = state["esports_bound"] + self._apply_esports_bind_info_to_account( + account, + state, + "esports_bound" if is_bound else ("esports_bind_ready" if role_name else "game_not_bound"), + ) + result = { + **state, + "esports_bind_dialog": True, + "can_open_role_selector": True, + } + if role_name: + message = f"最新角色: {self._esports_role_text(state)}" + else: + message = "未查询到游戏角色,请先切换角色" + self._mark_task(db, task, "success", message, result) + + def _execute_create_esports_qr( + self, + db: Session, + task: DouyuTask, + account: Account, + cookie: str, + config: dict, + ): + """生成电竞手册支付二维码,并通过活动状态确认开通到账。""" + client = self._client(cookie) + ctn = str(self._task_payload(task).get("ctn") or "") + if not ctn: + ctn = client.acf_ccn(refresh_subscribe=True) + act_alias = str(config.get("esports_act_alias") or "").strip() + manual_id = str(config.get("esports_manual_id") or "").strip() + if not act_alias or not manual_id: + self._mark_task(db, task, "failed", "请先配置电竞手册活动 actAlias 和 manualID") + return + + baseline_manual_type = None + baseline_manual_score = None + baseline_result: dict = {} + try: + baseline_result = self._refresh_esports_handbook(client, account, manual_id=manual_id) + baseline_manual_type = baseline_result["esports_manual_type"] + baseline_manual_score = baseline_result["esports_manual_score"] + db.commit() + except Exception as exc: + self._push_log("warning", f"生成电竞手册支付码前查询活动状态失败: {exc}") + + if baseline_manual_type is not None and baseline_manual_type >= 1: + account.esports_bind_status = "esports_opened" + account.updated_at = datetime.now(timezone.utc) + self._mark_task( + db, + task, + "success", + f"电竞手册已开通,积分: {baseline_manual_score if baseline_manual_score is not None else '-'}", + {**baseline_result, "esports_opened": True, "payment_polling": False}, + ) + return + + result = client.create_esports_qr( + ctn=ctn, + act_alias=act_alias, + amount=int(config["esports_amount"]), + room_id=str(config["room_id"]), + ) + result.update(baseline_result) + account.esports_bind_status = "esports_qr_created" + account.updated_at = datetime.now(timezone.utc) + self._update_task_progress(db, task, "running", "电竞手册支付码已生成,等待开通到账", result) + opened = self._wait_esports_open_after_payment( + db, + task, + account, + client, + manual_id=manual_id, + result=result, + baseline_manual_type=baseline_manual_type, + baseline_manual_score=baseline_manual_score, + ) + if self._stop.is_set(): + self._mark_task(db, task, "stopped", "任务已停止", result) + return + if opened: + account.esports_bind_status = "esports_opened" + account.updated_at = datetime.now(timezone.utc) + self._mark_task( + db, + task, + "success", + f"电竞手册已开通,积分: {account.esports_points if account.esports_points is not None else '-'}", + result, + ) + return + self._mark_task( + db, + task, + "failed", + "未检测到电竞手册开通到账" + f",类型: {result.get('esports_manual_type', '-')}," + f"积分: {result.get('esports_manual_score', '-')}", + result, + ) + def _execute_create_gold_qr(self, db: Session, task: DouyuTask, account: Account, cookie: str, config: dict): payload = self._task_payload(task) amount = int(payload.get("amount") or payload.get("gold_amount") or 1) @@ -1474,6 +1866,11 @@ class DouyuBatchRunner: "get_bind_qr": self._execute_get_bind_qr, "confirm_bind": self._execute_confirm_bind, "create_elite_qr": self._execute_create_elite_qr, + "prepare_esports_bind": self._execute_prepare_esports_bind, + "get_esports_bind_qr": self._execute_get_esports_bind_qr, + "query_esports_game_name": self._execute_query_esports_game_name, + "confirm_esports_bind": self._execute_confirm_esports_bind, + "create_esports_qr": self._execute_create_esports_qr, "create_gold_qr": self._execute_create_gold_qr, "donate_elite_gift": self._execute_donate_elite_gift, "query_points": self._execute_query_points, diff --git a/web/backend/services/douyu_service.py b/web/backend/services/douyu_service.py index 06c7466..6971894 100644 --- a/web/backend/services/douyu_service.py +++ b/web/backend/services/douyu_service.py @@ -17,6 +17,11 @@ SUPPORTED_DOUYU_TASK_TYPES = { "get_bind_qr": "获取绑定二维码", "confirm_bind": "确认绑定", "create_elite_qr": "开通精英宝典30", + "prepare_esports_bind": "绑定电竞手册角色", + "get_esports_bind_qr": "切换电竞角色", + "query_esports_game_name": "查询最新游戏角色", + "confirm_esports_bind": "确认电竞手册绑定", + "create_esports_qr": "开通电竞手册30", "create_gold_qr": "充值鱼翅", "donate_elite_gift": "赠送精英令", "query_points": "一键查询积分", @@ -39,6 +44,9 @@ DOUYU_CONFIG_DEFAULTS = { "legacy_act_alias": "cjm", "room_id": "9263298", "elite_amount": 3000, + "esports_manual_id": "1W0yGkOdDbAXb", + "esports_act_alias": "20260226ZMGDG", + "esports_amount": 3000, "gold_pay_type": 1, "gift_id": "23643", "skin_id": "2942", diff --git a/web/frontend/src/App.tsx b/web/frontend/src/App.tsx index ced6cb1..092fe12 100644 --- a/web/frontend/src/App.tsx +++ b/web/frontend/src/App.tsx @@ -72,7 +72,9 @@ function AppContent() { )} /> )} /> )} /> - )} /> + } /> + )} /> + )} /> )} /> )} /> )} /> diff --git a/web/frontend/src/api/types.ts b/web/frontend/src/api/types.ts index 695a02f..ddc7c8c 100644 --- a/web/frontend/src/api/types.ts +++ b/web/frontend/src/api/types.ts @@ -196,6 +196,11 @@ export interface DouyuTaskAccountItem { exchange_balance: number | null; bind_status: string; change_role_wait_time: number | null; + esports_points: number | null; + esports_game_name: string; + esports_game_channel: string; + esports_bind_status: string; + esports_change_role_wait_time: number | null; assigned_to: number | null; assigned_username: string | null; } @@ -208,6 +213,9 @@ export interface DouyuConfig { legacy_act_alias: string; room_id: string; elite_amount: number; + esports_manual_id: string; + esports_act_alias: string; + esports_amount: number; gold_pay_type: number; gift_id: string; skin_id: string; diff --git a/web/frontend/src/layouts/MainLayout.tsx b/web/frontend/src/layouts/MainLayout.tsx index be268fa..17537d0 100644 --- a/web/frontend/src/layouts/MainLayout.tsx +++ b/web/frontend/src/layouts/MainLayout.tsx @@ -6,6 +6,7 @@ import { CloudServerOutlined, TeamOutlined, ApiOutlined, KeyOutlined, MenuFoldOutlined, MenuUnfoldOutlined, SwapOutlined, SunOutlined, MoonOutlined, DesktopOutlined, GiftOutlined, ShoppingCartOutlined, ApartmentOutlined, MobileOutlined, + BookOutlined, TrophyOutlined, SafetyCertificateOutlined, } from '@ant-design/icons'; import { useNavigate, useLocation, Outlet } from 'react-router-dom'; @@ -71,7 +72,8 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) { douyuItems.push({ key: '/cookies', label: 'Cookie 管理', icon: }); } if (can('douyu:task')) { - douyuItems.push({ key: '/douyu/tasks', label: '任务操作台', icon: }); + douyuItems.push({ key: '/douyu/elite', label: '精英宝典', icon: }); + douyuItems.push({ key: '/douyu/esports', label: '电竞手册', icon: }); } // 虎牙 diff --git a/web/frontend/src/pages/DouyuTasksPage.tsx b/web/frontend/src/pages/DouyuTasksPage.tsx index 048c56c..f5bb656 100644 --- a/web/frontend/src/pages/DouyuTasksPage.tsx +++ b/web/frontend/src/pages/DouyuTasksPage.tsx @@ -24,7 +24,9 @@ import { message } from '../utils/antdMessage'; const { Text } = Typography; -const QUICK_ACTIONS = [ +type HandbookKind = 'elite' | 'esports'; + +const ELITE_QUICK_ACTIONS = [ { key: 'get_bind_qr', icon: }, { key: 'confirm_bind', icon: }, { key: 'create_elite_qr', icon: }, @@ -38,8 +40,37 @@ const QUICK_ACTIONS = [ { key: 'donate_elite_gift', icon: }, ]; -const BIND_ACTION_KEYS = ['get_bind_qr', 'confirm_bind', 'query_game_name', 'query_change_bind_time']; -const QUERY_ACTION_KEYS = ['query_points', 'query_limited_goods', 'query_gold_balance']; +const ESPORTS_QUICK_ACTIONS = [ + { key: 'prepare_esports_bind', icon: }, + { key: 'query_esports_game_name', icon: }, + { key: 'create_esports_qr', icon: }, +]; + +const ELITE_BIND_ACTION_KEYS = ['get_bind_qr', 'confirm_bind', 'query_game_name', 'query_change_bind_time']; +const ELITE_QUERY_ACTION_KEYS = ['query_points', 'query_limited_goods', 'query_gold_balance']; +const ELITE_TASK_TYPES = new Set([ + 'get_bind_qr', + 'confirm_bind', + 'create_elite_qr', + 'create_gold_qr', + 'donate_elite_gift', + 'query_points', + 'exchange_goods', + 'query_game_name', + 'query_change_bind_time', + 'query_limited_goods', + 'query_gold_balance', + 'refresh_goods', + 'query_exchange_records', + 'prefetch_csrf_token', +]); +const ESPORTS_TASK_TYPES = new Set([ + 'prepare_esports_bind', + 'get_esports_bind_qr', + 'query_esports_game_name', + 'confirm_esports_bind', + 'create_esports_qr', +]); const DOUYU_GOLD_AMOUNT_STORAGE_KEY = 'douyu_task_gold_amount'; const DOUYU_GIFT_COUNT_STORAGE_KEY = 'douyu_task_gift_count'; @@ -58,6 +89,14 @@ const BIND_STATUS_LABELS: Record = { game_queried: '已识别角色', game_not_bound: '未绑定', bind_confirmed: '已绑定', + esports_bind_qr_generated: '电竞待绑定', + esports_role_queried: '已查电竞角色', + esports_bind_ready: '电竞待确认', + esports_role_switching: '电竞换角色', + esports_bind_confirming: '电竞绑定中', + esports_bound: '电竞已绑定', + esports_qr_created: '电竞待开通', + esports_opened: '电竞已开通', change_time_queried: '已查换绑', }; @@ -93,17 +132,25 @@ function bindReadyForConfirm(task: DouyuTaskItem | null | undefined): boolean { function hasBindQrcode(task: DouyuTaskItem | null | undefined): boolean { if (!task || task.task_type !== 'get_bind_qr') return false; // 有二维码,或已有待确认角色(无需再扫码)都应可展示绑定面板 - return Boolean(resultText(task.result, 'url')) || bindReadyForConfirm(task); + return Boolean(resultText(task.result, 'url')) + || bindReadyForConfirm(task) + || resultFlag(task.result, 'esports_bound'); } function hasPaymentQrcode(task: DouyuTaskItem | null | undefined): boolean { - return Boolean(task && ['create_elite_qr', 'create_gold_qr'].includes(task.task_type) && resultText(task.result, 'pay_url')); + return Boolean( + task + && ['create_elite_qr', 'create_esports_qr', 'create_gold_qr'].includes(task.task_type) + && resultText(task.result, 'pay_url'), + ); } function paymentArrived(task: DouyuTaskItem | null | undefined): boolean { - if (!task || !['create_elite_qr', 'create_gold_qr'].includes(task.task_type)) return false; + if (!task || !['create_elite_qr', 'create_esports_qr', 'create_gold_qr'].includes(task.task_type)) return false; if (task.status !== 'success') return false; - return task.result?.gold_recharged === true || task.result?.elite_opened === true; + return task.result?.gold_recharged === true + || task.result?.elite_opened === true + || task.result?.esports_opened === true; } function bindPhaseText(phase: string, polling: boolean): string { @@ -149,7 +196,14 @@ function savedPositiveInteger(key: string, fallback = 1): number { return Number.isInteger(value) && value >= 1 ? value : fallback; } -export default function DouyuTasksPage() { +export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) { + const isEsportsHandbook = handbook === 'esports'; + const handbookTitle = isEsportsHandbook ? '电竞手册工作台' : '精英宝典工作台'; + const handbookDescription = isEsportsHandbook + ? '电竞手册角色绑定与开通任务' + : '精英宝典绑定、开通、积分与兑换任务'; + const activeTaskTypes = isEsportsHandbook ? ESPORTS_TASK_TYPES : ELITE_TASK_TYPES; + const quickActions = isEsportsHandbook ? ESPORTS_QUICK_ACTIONS : ELITE_QUICK_ACTIONS; const { can } = usePermissions(); const [accounts, setAccounts] = useState([]); const [selectedIds, setSelectedIds] = useState([]); @@ -174,10 +228,13 @@ export default function DouyuTasksPage() { // 绑定/支付弹窗以 task 为唯一数据源 const [qrTask, setQrTask] = useState(null); + const [esportsBindTask, setEsportsBindTask] = useState(null); const [payTask, setPayTask] = useState(null); const autoOpenedQrTaskIds = useRef>(new Set()); + const autoOpenedEsportsBindTaskIds = useRef>(new Set()); const autoOpenedPayTaskIds = useRef>(new Set()); const autoOpenQrReady = useRef(false); + const autoOpenEsportsBindReady = useRef(false); const autoOpenPayReady = useRef(false); const logs = useWebSocketLogs(); @@ -192,22 +249,41 @@ export default function DouyuTasksPage() { return map; }, [tasks]); + const latestEsportsStateTaskByAccount = useMemo(() => { + const map = new Map(); + for (const task of tasks) { + if (!['prepare_esports_bind', 'get_esports_bind_qr', 'query_esports_game_name', 'confirm_esports_bind'] + .includes(task.task_type) || !task.result) continue; + const previous = map.get(task.account_id); + if (!previous || task.id > previous.id) map.set(task.account_id, task); + } + return map; + }, [tasks]); + + const visibleTasks = useMemo( + () => tasks.filter((task) => activeTaskTypes.has(task.task_type)), + [activeTaskTypes, tasks], + ); + // 换绑时间:优先最近一次查询/拦截任务结果,回退账号落库字段 const latestChangeWaitByAccount = useMemo(() => { const map = new Map(); for (const task of tasks) { - if (!['query_change_bind_time', 'get_bind_qr'].includes(task.task_type)) continue; + const taskTypes = isEsportsHandbook + ? ['prepare_esports_bind', 'get_esports_bind_qr', 'query_esports_game_name', 'confirm_esports_bind'] + : ['query_change_bind_time', 'get_bind_qr']; + if (!taskTypes.includes(task.task_type)) continue; if (!['success', 'failed'].includes(task.status)) continue; const wait = resultNumber(task.result, 'change_role_wait_time'); // get_bind_qr 只有冷却拦截时才有 wait;无 wait 时跳过,避免覆盖 - if (task.task_type === 'get_bind_qr' && wait == null) continue; + if (!isEsportsHandbook && task.task_type === 'get_bind_qr' && wait == null) continue; const existing = map.get(task.account_id); if (existing && existing.taskId > task.id) continue; const text = resultText(task.result, 'change_role_wait_text') || formatWaitSeconds(wait); map.set(task.account_id, { wait, text, taskId: task.id }); } return map; - }, [tasks]); + }, [isEsportsHandbook, tasks]); const loadData = useCallback(async () => { setLoading(true); @@ -227,9 +303,13 @@ export default function DouyuTasksPage() { if (!autoOpenQrReady.current) { for (const task of taskResult.value) { if (hasBindQrcode(task)) autoOpenedQrTaskIds.current.add(task.id); + if (task.task_type === 'prepare_esports_bind' && task.result?.esports_bind_dialog === true) { + autoOpenedEsportsBindTaskIds.current.add(task.id); + } if (hasPaymentQrcode(task)) autoOpenedPayTaskIds.current.add(task.id); } autoOpenQrReady.current = true; + autoOpenEsportsBindReady.current = true; autoOpenPayReady.current = true; } } @@ -264,8 +344,8 @@ export default function DouyuTasksPage() { }, [giftCount]); const hasActiveTasks = useMemo( - () => tasks.some((task) => ['pending', 'running', 'planned'].includes(task.status)), - [tasks], + () => visibleTasks.some((task) => ['pending', 'running', 'planned'].includes(task.status)), + [visibleTasks], ); // 有活跃任务或 WS 连接时 3 秒刷 tasks;空闲 15 秒轻量刷新 @@ -277,33 +357,55 @@ export default function DouyuTasksPage() { const openQrTask = useCallback((task: DouyuTaskItem) => { autoOpenedQrTaskIds.current.add(task.id); + setEsportsBindTask(null); setPayTask(null); setQrTask(task); }, []); + const openEsportsBindTask = useCallback((task: DouyuTaskItem) => { + autoOpenedEsportsBindTaskIds.current.add(task.id); + setQrTask(null); + setPayTask(null); + setEsportsBindTask(task); + }, []); + const openPayTask = useCallback((task: DouyuTaskItem) => { autoOpenedPayTaskIds.current.add(task.id); setQrTask(null); + setEsportsBindTask(null); setPayTask(task); }, []); // 自动弹出最新绑定二维码(running 有 url 即可) useEffect(() => { if (!autoOpenQrReady.current || qrTask) return; - const nextQrTask = tasks + const nextQrTask = visibleTasks .filter((task) => hasBindQrcode(task) && !autoOpenedQrTaskIds.current.has(task.id)) .sort((a, b) => b.id - a.id)[0]; if (nextQrTask) openQrTask(nextQrTask); - }, [openQrTask, qrTask, tasks]); + }, [openQrTask, qrTask, visibleTasks]); + + // 电竞手册先查状态再打开独立绑定面板,不把切换角色二维码当作主流程。 + useEffect(() => { + if (!autoOpenEsportsBindReady.current || esportsBindTask) return; + const nextTask = visibleTasks + .filter((task) => ( + task.task_type === 'prepare_esports_bind' + && task.result?.esports_bind_dialog === true + && !autoOpenedEsportsBindTaskIds.current.has(task.id) + )) + .sort((a, b) => b.id - a.id)[0]; + if (nextTask) openEsportsBindTask(nextTask); + }, [esportsBindTask, openEsportsBindTask, visibleTasks]); // 自动弹出支付二维码 useEffect(() => { if (!autoOpenPayReady.current || payTask) return; - const nextPayTask = tasks + const nextPayTask = visibleTasks .filter((task) => hasPaymentQrcode(task) && !paymentArrived(task) && !autoOpenedPayTaskIds.current.has(task.id)) .sort((a, b) => b.id - a.id)[0]; if (nextPayTask) openPayTask(nextPayTask); - }, [openPayTask, payTask, tasks]); + }, [openPayTask, payTask, visibleTasks]); // 弹窗跟随最新 task 状态(后端 progress 写入后 UI 自动更新) useEffect(() => { @@ -312,6 +414,12 @@ export default function DouyuTasksPage() { if (latest && latest !== qrTask) setQrTask(latest); }, [qrTask, tasks]); + useEffect(() => { + if (!esportsBindTask) return; + const latest = tasks.find((task) => task.id === esportsBindTask.id); + if (latest && latest !== esportsBindTask) setEsportsBindTask(latest); + }, [esportsBindTask, tasks]); + useEffect(() => { if (!payTask) return; const latest = tasks.find((task) => task.id === payTask.id); @@ -325,14 +433,25 @@ export default function DouyuTasksPage() { const batchBusy = Boolean(runningBatchId || logs.connected || hasActiveTasks); - const startTask = async (taskType: string, accountIds?: number[]) => { + const startTask = async ( + taskType: string, + accountIds?: number[], + extraPayload: Record = {}, + ) => { const ids = accountIds || selectedIds; if (ids.length === 0) { message.warning('请先选择账号'); return; } // 绑定确认/查询允许在其他账号仍轮询时继续,避免卡在多账号批次里 - const allowDuringBatch = taskType === 'confirm_bind' || taskType === 'query_game_name'; + const allowDuringBatch = [ + 'confirm_bind', + 'confirm_esports_bind', + 'query_game_name', + 'query_esports_game_name', + 'prepare_esports_bind', + 'get_esports_bind_qr', + ].includes(taskType); if (batchBusy && !allowDuringBatch) { message.warning('当前已有批次在运行,请先停止或等待结束'); return; @@ -341,7 +460,7 @@ export default function DouyuTasksPage() { message.warning('请先选择兑换商品'); return; } - const payload: Record = {}; + const payload: Record = { ...extraPayload }; if (taskType === 'exchange_goods') payload.commodity_id = selectedGoodsId; if (taskType === 'create_gold_qr') payload.amount = goldAmount; if (taskType === 'donate_elite_gift') payload.gift_count = giftCount; @@ -460,12 +579,87 @@ export default function DouyuTasksPage() { void startTask(taskType, contextMenu.accountIds); }; + // ---- 电竞手册绑定面板 ---- + const esportsRelatedTasks = esportsBindTask + ? tasks + .filter((task) => ( + task.account_id === esportsBindTask.account_id + && task.id >= esportsBindTask.id + && ['prepare_esports_bind', 'get_esports_bind_qr', 'query_esports_game_name', 'confirm_esports_bind'] + .includes(task.task_type) + )) + .sort((a, b) => b.id - a.id) + : []; + const esportsLatestStateTask = esportsRelatedTasks.find((task) => task.result) || esportsBindTask; + const esportsLatestRoleTask = esportsRelatedTasks.find( + (task) => task.task_type === 'query_esports_game_name', + ) || null; + const esportsLatestQrTask = esportsRelatedTasks.find( + (task) => Boolean(resultText(task.result, 'url')), + ) || null; + const esportsConfirmRunning = esportsRelatedTasks.some( + (task) => task.task_type === 'confirm_esports_bind' && ['planned', 'pending', 'running'].includes(task.status), + ); + const esportsRoleQueryRunning = Boolean( + esportsLatestRoleTask && ['planned', 'pending', 'running'].includes(esportsLatestRoleTask.status), + ); + const esportsResult = esportsLatestStateTask?.result || esportsBindTask?.result || null; + const esportsBound = resultFlag(esportsResult, 'esports_bound') || resultFlag(esportsResult, 'bind_confirmed'); + const esportsRoleName = resultText(esportsResult, 'role_name'); + const esportsAreaName = resultText(esportsResult, 'area_name'); + const esportsPlatName = resultText(esportsResult, 'plat_name'); + const esportsRoleLine = [esportsPlatName, esportsAreaName].filter(Boolean).join(' / '); + const esportsCanChangeRole = Boolean(esportsBindTask && !esportsConfirmRunning); + const esportsChangeWait = resultNumber(esportsResult, 'change_role_wait_time'); + const esportsQrUrl = resultText(esportsLatestQrTask?.result, 'url'); + const esportsAccountName = esportsBindTask + ? ( + esportsBindTask.account_nickname + || esportsBindTask.account_username + || esportsBindTask.account_uid + || `#${esportsBindTask.account_id}` + ) + : ''; + const esportsStatusText = esportsBound + ? '电竞手册已绑定' + : esportsConfirmRunning + ? '正在完成电竞手册绑定' + : esportsRoleQueryRunning + ? '正在查询最新角色' + : esportsLatestStateTask?.message || '加载电竞手册绑定状态'; + const esportsCanConfirm = Boolean( + esportsBindTask + && !esportsBound + && esportsRoleName + && !esportsConfirmRunning, + ); + + const queryEsportsRole = () => { + if (!esportsBindTask || esportsRoleQueryRunning) return; + void startTask('query_esports_game_name', [esportsBindTask.account_id]); + }; + + const switchEsportsRole = () => { + if (!esportsBindTask || !esportsCanChangeRole) return; + void startTask('get_esports_bind_qr', [esportsBindTask.account_id]); + }; + + const confirmEsportsBindFromDialog = () => { + if (!esportsBindTask || !esportsCanConfirm) { + message.warning('请先确认当前角色'); + return; + } + void startTask('confirm_esports_bind', [esportsBindTask.account_id]); + }; + // ---- QR panel derived state ---- const qrResult = qrTask?.result || null; const qrUrl = resultText(qrResult, 'url'); const qrBindPhase = resultText(qrResult, 'bind_phase'); const qrAutoPolling = qrTask?.status === 'running' && qrResult?.bind_polling === true; - const qrQueryTask = qrTask ? latestQueryGameTaskByAccount.get(qrTask.account_id) || null : null; + const qrQueryTask = qrTask + ? latestQueryGameTaskByAccount.get(qrTask.account_id) || null + : null; const qrQueryResult = qrTask && qrQueryTask && qrQueryTask.id > qrTask.id ? qrQueryTask.result : null; const qrQueryRunning = Boolean(qrQueryTask && qrTask && qrQueryTask.id > qrTask.id && qrQueryTask.status === 'running'); const qrQueryRoleName = resultText(qrQueryResult, 'role_name'); @@ -485,14 +679,29 @@ export default function DouyuTasksPage() { const qrRoleName = resultText(qrRoleSourceResult, 'role_name'); const qrAreaName = resultText(qrRoleSourceResult, 'area_name'); const qrPlatName = resultText(qrRoleSourceResult, 'plat_name'); - const qrCurrentRoleName = resultText(qrResult, 'current_role_name') - || (resultFlag(qrResult, 'is_bound_act') ? resultText(qrResult, 'role_name') : '') + const qrCurrentRoleName = qrQueryRoleName + || resultText(qrResult, 'current_role_name') + || ( + resultFlag(qrResult, 'is_bound_act') + ? resultText(qrResult, 'role_name') + : '' + ) || (qrQueryIsBoundAct ? qrQueryRoleName : ''); - const qrCurrentAreaName = resultText(qrResult, 'current_area_name') - || (resultFlag(qrResult, 'is_bound_act') ? resultText(qrResult, 'area_name') : '') + const qrCurrentAreaName = resultText(qrQueryResult, 'area_name') + || resultText(qrResult, 'current_area_name') + || ( + resultFlag(qrResult, 'is_bound_act') + ? resultText(qrResult, 'area_name') + : '' + ) || (qrQueryIsBoundAct ? resultText(qrQueryResult, 'area_name') : ''); - const qrCurrentPlatName = resultText(qrResult, 'current_plat_name') - || (resultFlag(qrResult, 'is_bound_act') ? resultText(qrResult, 'plat_name') : '') + const qrCurrentPlatName = resultText(qrQueryResult, 'plat_name') + || resultText(qrResult, 'current_plat_name') + || ( + resultFlag(qrResult, 'is_bound_act') + ? resultText(qrResult, 'plat_name') + : '' + ) || (qrQueryIsBoundAct ? resultText(qrQueryResult, 'plat_name') : ''); const qrRoleLine = qrBindReady ? [qrPlatName, qrAreaName, qrRoleName].filter(Boolean).join(' - ') @@ -542,13 +751,18 @@ export default function DouyuTasksPage() { { title: '游戏名', dataIndex: 'game_name', width: 130, render: (_, record) => { - const queryResult = latestQueryGameTaskByAccount.get(record.id)?.result || null; - const roleName = resultText(queryResult, 'role_name') || record.game_name; + const queryResult = ( + isEsportsHandbook + ? latestEsportsStateTaskByAccount.get(record.id) + : latestQueryGameTaskByAccount.get(record.id) + )?.result || null; + const roleName = resultText(queryResult, 'role_name') + || (isEsportsHandbook ? record.esports_game_name : record.game_name); const channel = ( [resultText(queryResult, 'area_name'), resultText(queryResult, 'plat_name')] .filter(Boolean) .join(' / ') - || record.game_channel + || (isEsportsHandbook ? record.esports_game_channel : record.game_channel) ); return roleName ? ( @@ -559,18 +773,22 @@ export default function DouyuTasksPage() { }, }, { - title: '积分', dataIndex: 'points', width: 70, + title: isEsportsHandbook ? '电竞积分' : '积分', + dataIndex: isEsportsHandbook ? 'esports_points' : 'points', + width: 80, render: (v) => v ?? -, }, - { + ...(isEsportsHandbook ? [] : [{ title: '鱼翅', dataIndex: 'gold_balance', width: 70, - render: (v) => v ?? -, - }, + render: (v: number | null) => v ?? -, + }]), { title: '换绑时间', dataIndex: 'change_role_wait_time', width: 130, render: (_, record) => { const fromTask = latestChangeWaitByAccount.get(record.id); - const wait = fromTask?.wait ?? record.change_role_wait_time; + const wait = fromTask?.wait ?? ( + isEsportsHandbook ? record.esports_change_role_wait_time : record.change_role_wait_time + ); const text = fromTask?.text || formatWaitSeconds(wait); if (wait == null && !text) { return 未查; @@ -591,16 +809,21 @@ export default function DouyuTasksPage() { return {text || '-'}; }, sorter: (a, b) => { - const aw = latestChangeWaitByAccount.get(a.id)?.wait ?? a.change_role_wait_time ?? -1; - const bw = latestChangeWaitByAccount.get(b.id)?.wait ?? b.change_role_wait_time ?? -1; + const aw = latestChangeWaitByAccount.get(a.id)?.wait + ?? (isEsportsHandbook ? a.esports_change_role_wait_time : a.change_role_wait_time) ?? -1; + const bw = latestChangeWaitByAccount.get(b.id)?.wait + ?? (isEsportsHandbook ? b.esports_change_role_wait_time : b.change_role_wait_time) ?? -1; return aw - bw; }, }, { title: '状态', dataIndex: 'bind_status', width: 100, - render: (v) => (v - ? {BIND_STATUS_LABELS[v] || v} - : -), + render: (_, record) => { + const value = isEsportsHandbook ? record.esports_bind_status : record.bind_status; + return value + ? {BIND_STATUS_LABELS[value] || value} + : -; + }, }, ]; @@ -618,6 +841,22 @@ export default function DouyuTasksPage() { { title: '结果', dataIndex: 'message', ellipsis: true, render: (_, r) => { + if (['prepare_esports_bind', 'get_esports_bind_qr'].includes(r.task_type)) { + const roleName = resultText(r.result, 'role_name'); + const esportsBound = resultFlag(r.result, 'esports_bound') || resultFlag(r.result, 'bind_confirmed'); + const bindUrl = resultText(r.result, 'url'); + return ( + + {r.message || '-'} + {roleName ? {roleName} : null} + {bindUrl || r.result?.esports_bind_dialog === true ? ( + + ) : null} + + ); + } if (r.task_type === 'get_bind_qr') { const roleName = resultText(r.result, 'role_name'); const phase = resultText(r.result, 'bind_phase'); @@ -639,7 +878,7 @@ export default function DouyuTasksPage() { ); } - if (r.task_type === 'query_game_name') { + if (['query_game_name', 'query_esports_game_name'].includes(r.task_type)) { const roleName = resultText(r.result, 'role_name'); const areaName = resultText(r.result, 'area_name'); const platName = resultText(r.result, 'plat_name'); @@ -663,6 +902,16 @@ export default function DouyuTasksPage() { ); } + if (r.task_type === 'confirm_esports_bind') { + const roleName = resultText(r.result, 'role_name'); + const confirmed = resultFlag(r.result, 'bind_confirmed'); + return ( + + {r.message || '-'} + {roleName ? {roleName} : null} + + ); + } const url = taskPayUrl(r); return ( @@ -696,7 +945,8 @@ export default function DouyuTasksPage() { (a.nickname || '').toLowerCase().includes(kw) || (a.username || '').toLowerCase().includes(kw) || (a.uid || '').toLowerCase().includes(kw) - || (a.game_name || '').toLowerCase().includes(kw), + || (a.game_name || '').toLowerCase().includes(kw) + || (a.esports_game_name || '').toLowerCase().includes(kw), ); }, [accounts, accountSearch]); @@ -710,7 +960,7 @@ export default function DouyuTasksPage() { ? (payTask.account_nickname || payTask.account_username || payTask.account_uid || `#${payTask.account_id}`) : ''; const selectedText = selectedIds.length > 0 ? `已选 ${selectedIds.length} 个账号` : '请先勾选账号'; - const actionByKey = useMemo(() => new Map(QUICK_ACTIONS.map((item) => [item.key, item])), []); + const actionByKey = useMemo(() => new Map(quickActions.map((item) => [item.key, item])), [quickActions]); const actionDisabled = (taskType: string) => { if (batchBusy || selectedIds.length === 0) return true; if (taskType === 'exchange_goods') return !selectedGoodsId; @@ -757,8 +1007,8 @@ export default function DouyuTasksPage() { {/* Header */}
-

斗鱼任务操作台

- 绑定、宝典、鱼翅、积分、兑换和余额任务 +

{handbookTitle}

+ {handbookDescription}
@@ -822,15 +1072,15 @@ export default function DouyuTasksPage() { size="small" title="任务记录" extra={ - 成功 {tasks.filter((t) => t.status === 'success').length} - 失败 {tasks.filter((t) => ['failed', 'error'].includes(t.status)).length} + 成功 {visibleTasks.filter((t) => t.status === 'success').length} + 失败 {visibleTasks.filter((t) => ['failed', 'error'].includes(t.status)).length} } style={{ flex: '0 0 300px', minHeight: 260, display: 'flex', flexDirection: 'column', overflow: 'hidden' }} styles={{ body: { flex: 1, minHeight: 0, padding: 8, overflow: 'hidden', display: 'flex', flexDirection: 'column' } }} > {selectedText}} styles={{ body: { padding: 8 } }} > @@ -859,92 +1109,116 @@ export default function DouyuTasksPage() { /> -
-
- - 绑定与角色 -
-
- {BIND_ACTION_KEYS.map((key) => renderActionButton(key))} -
-
- -
-
- - 查询 -
-
- {QUERY_ACTION_KEYS.map((key) => renderActionButton(key))} -
-
- -
-
- - 兑换 -
- - ({ value: item.commodity_id, label: goodsLabel(item) }))} + placeholder="选择兑换商品" + showSearch + optionFilterProp="label" + style={{ width: '100%' }} + /> +
+ {renderActionButton('refresh_goods')} + {renderActionButton('exchange_goods', 'primary')} +
+
+
+ +
+
+ + 开通、充值与送礼 +
+ + {renderActionButton('create_elite_qr', 'primary')} + + setGoldAmount(v || 1)} + style={{ width: '100%' }} + /> + + + + setGiftCount(v || 1)} + style={{ width: '100%' }} + /> + + + +
+ + )} 勾选账号后可批量执行;右键账号行可快速执行单账号动作。 @@ -965,7 +1239,7 @@ export default function DouyuTasksPage() { style={{ width: 200, boxShadow: '0 4px 12px rgba(0,0,0,0.12)' }} > - {QUICK_ACTIONS.map((item) => ( + {quickActions.map((item) => ( + + + + + )} + + + {/* 精英宝典绑定二维码弹窗。 */}