diff --git a/core/douyu/__init__.py b/core/douyu/__init__.py index 5194f20..48cb635 100644 --- a/core/douyu/__init__.py +++ b/core/douyu/__init__.py @@ -1,6 +1,16 @@ """斗鱼登录模块""" -from .login import DouyuLogin +from .login import DouyuLogin, CredentialError +from .login_api import LoginAPIStrategy +from .login_api_wgapi import WgapiLoginAPI +from .login_api_iframe import IframeLoginAPI from .email_verifier import EmailVerifier -__all__ = ["DouyuLogin", "EmailVerifier"] +__all__ = [ + "DouyuLogin", + "CredentialError", + "LoginAPIStrategy", + "WgapiLoginAPI", + "IframeLoginAPI", + "EmailVerifier", +] diff --git a/core/douyu/login.py b/core/douyu/login.py index 28c8280..83a383b 100644 --- a/core/douyu/login.py +++ b/core/douyu/login.py @@ -10,8 +10,9 @@ from urllib.parse import urlsplit, urlunsplit from loguru import logger from .cookie_enricher import CookieEnricher -from .crypto import encrypt_password, encrypt_nickname_or_phone from .email_verifier import EmailLoginError, EmailVerifier +from .login_api import LoginAPIStrategy +from .login_api_wgapi import WgapiLoginAPI from .proxy_fetcher import ProxyFetcher from core.geetest.v3_slide.solver import ( @@ -27,6 +28,11 @@ from core.geetest.common.network import ( _geetest_semaphore = threading.Semaphore(2) +class CredentialError(ValueError): + """账号或密码错误,不应重试。""" + pass + + class AccountLike(Protocol): """DouyuLogin 所需的最小账号接口,ORM 对象或 SimpleNamespace 均可满足。""" username: str @@ -78,6 +84,7 @@ class DouyuLogin: max_total_time: float = 0, proxy_fetcher: Optional[ProxyFetcher] = None, stop_event: Optional[threading.Event] = None, + api_strategy: Optional[LoginAPIStrategy] = None, ): self.account = account self.proxy = proxy @@ -86,6 +93,7 @@ class DouyuLogin: self.max_total_time = max_total_time # 0=不限制单账号登录总时长 self.stop_event = stop_event self.session = requests.Session() + self.api = api_strategy or WgapiLoginAPI() # 默认使用 wgapi 接口 self.proxy_fetcher = proxy_fetcher or ( ProxyFetcher(api_url=proxy_api_url) if proxy_api_url else None @@ -310,6 +318,9 @@ class DouyuLogin: except InterruptedError as e: logger.warning(f"登录任务已停止: {e}") return LoginResult(success=False, message=str(e)) + except CredentialError as e: + logger.error(f"登录失败(凭据错误,不再重试): {e}") + return LoginResult(success=False, message=str(e), code="credential_error") except EmailLoginError as e: logger.error(f"登录失败(邮箱登录失败,不再重试): {e}") return LoginResult(success=False, message=str(e), code="email_login_failed") @@ -361,27 +372,15 @@ class DouyuLogin: Returns: (gt, challenge, code_token, cookies) """ - # 加密用户名和密码 - encrypted_username = encrypt_nickname_or_phone(self.account.username) - encrypted_password = encrypt_password(self.account.password) - - data = { - 'type': '1', - 'nicknameOrPhoneEncrypt': encrypted_username, - 'password': encrypted_password, - 'biz_type': '1', - 'room_id': '0', - 'redirect_url': self.LOGIN_REFERER, - 't': str(int(time.time() * 1000)), - 'client_id': '1', - 'did': '', - 'lang': '', - 'isMultiAccount': '0', - } + data = self.api.build_first_login_data( + self.account.username, + self.account.password, + self.LOGIN_REFERER, + ) payload = self._request_json( 'post', - self.LOGIN_API, + self.api.first_login_url, '第一次登录接口', data=data, ) @@ -390,15 +389,13 @@ class DouyuLogin: if payload.get('error') != 81: error_msg = payload.get('msg', '未知错误') + # 密码错误等凭据问题不应重试 + if any(kw in error_msg for kw in ['密码错误', '账号或密码', '账号或者密码', '账号不存在']): + raise CredentialError(f"第一次登录失败: {error_msg}") raise ValueError(f"第一次登录失败: {error_msg}") # 提取极验参数 - geetest_data = payload.get('data', {}).get('geetest', {}) - code_data = geetest_data.get('code_data', {}) - code_token = geetest_data.get('code_token', '') - - gt = code_data.get('gt', '') - challenge = code_data.get('challenge', '') + gt, challenge, code_token = self.api.extract_geetest_params(payload) if not gt or not challenge: raise ValueError("获取极验参数失败") @@ -517,39 +514,18 @@ class DouyuLogin: Returns: remote_code: 用于邮箱验证的code """ - encrypted_username = encrypt_nickname_or_phone(self.account.username) - encrypted_password = encrypt_password(self.account.password) - - data = { - 'type': '1', - 'nicknameOrPhoneEncrypt': encrypted_username, - 'password': encrypted_password, - 'room_id': '0', - 'code_type': '1', - 'code_token': code_token, - 'gt_version': 'v3', - 'geetest_challenge': challenge, - 'geetest_validate': validate, - 'geetest_seccode': seccode, - 'code_data[geetest_challenge]': challenge, - 'code_data[geetest_validate]': validate, - 'code_data[geetest_seccode]': seccode, - 'code_data[gt_version]': 'v3', - 'code_data[code]': '', - 'redirect_url': self.LOGIN_REFERER, - 't': str(int(time.time() * 1000)), - 'client_id': '1', - 'did': '', - 'lang': '', - 'isMultiAccount': '0', - 'biz_type': '1', - } + data = self.api.build_second_login_data( + self.account.username, + self.account.password, + gt, challenge, validate, seccode, code_token, + self.LOGIN_REFERER, + ) logger.debug(f"第二次登录参数: challenge={challenge[:20]}..., validate={validate[:20]}...") payload = self._request_json( 'post', - self.LOGIN_API, + self.api.login_url, '第二次登录接口', data=data, ) @@ -558,10 +534,13 @@ class DouyuLogin: if payload.get('error') != 130014: error_msg = payload.get('msg', '未知错误') + # 密码错误等凭据问题不应重试 + if any(kw in error_msg for kw in ['密码错误', '账号或密码', '账号或者密码', '账号不存在']): + raise CredentialError(f"第二次登录失败: {error_msg}") raise ValueError(f"第二次登录失败: {error_msg}") # 提取remote_code - remote_code = payload.get('data', {}).get('remoteLogin', {}).get('code', '') + remote_code = self.api.extract_remote_code(payload) if not remote_code: raise ValueError("获取remote_code失败") @@ -572,14 +551,11 @@ class DouyuLogin: def _send_email_verify(self, remote_code: str) -> None: """发送邮箱验证""" - data = { - 'code': remote_code, - 'client_id': '1', - } + data = self.api.build_send_email_data(remote_code) payload = self._request_json( 'post', - self.SEND_EMAIL_API, + self.api.send_email_url, '发送验证邮件接口', data=data, ) @@ -612,18 +588,11 @@ class DouyuLogin: Returns: login_url: 登录回调URL """ - data = { - 'verify_type': '2', - 'captcha': verify_code, - 'isMultiAccount': '0', - 'code': remote_code, - 'client_id': '1', - 'redirect_url': '//www.douyu.com/api/passport/login', - } + data = self.api.build_verify_data(remote_code, verify_code) payload = self._request_json( 'post', - self.VERIFY_API, + self.api.verify_url, '提交验证码接口', data=data, ) @@ -631,7 +600,7 @@ class DouyuLogin: if payload.get('error') != 0: raise ValueError(f"提交验证码失败: {payload.get('msg')}") - login_url = payload.get('data', {}).get('url', '') + login_url = self.api.extract_login_url(payload) if not login_url: raise ValueError("获取登录URL失败") diff --git a/core/douyu/login_api.py b/core/douyu/login_api.py new file mode 100644 index 0000000..7e14957 --- /dev/null +++ b/core/douyu/login_api.py @@ -0,0 +1,115 @@ +"""登录接口策略基类""" + +from abc import ABC, abstractmethod +from typing import Tuple + + +class LoginAPIStrategy(ABC): + """登录接口策略基类 + + 定义斗鱼登录各步骤的接口地址和参数构建方式。 + 子类需实现 wgapi 和 iframe 两套接口的差异部分。 + """ + + @property + @abstractmethod + def name(self) -> str: + """策略名称,如 'wgapi'、'iframe'""" + ... + + @property + @abstractmethod + def first_login_url(self) -> str: + """第一次登录接口 URL(获取极验参数)""" + ... + + @property + @abstractmethod + def login_url(self) -> str: + """第二次登录接口 URL(带极验验证)""" + ... + + @property + @abstractmethod + def send_email_url(self) -> str: + """发送邮箱验证 URL""" + ... + + @property + @abstractmethod + def verify_url(self) -> str: + """提交验证码 URL""" + ... + + @abstractmethod + def encrypt_username(self, username: str) -> str: + """加密用户名(各接口加密方式不同)""" + ... + + @abstractmethod + def build_first_login_data(self, username: str, password: str, referer: str) -> dict: + """构建第一次登录请求参数(获取极验参数)""" + ... + + @abstractmethod + def build_second_login_data( + self, + username: str, + password: str, + gt: str, + challenge: str, + validate: str, + seccode: str, + code_token: str, + referer: str, + ) -> dict: + """构建第二次登录请求参数(带极验验证)""" + ... + + @abstractmethod + def build_send_email_data(self, code: str) -> dict: + """构建发送邮箱验证参数""" + ... + + @abstractmethod + def build_verify_data(self, code: str, captcha: str) -> dict: + """构建提交验证码参数""" + ... + + def extract_geetest_params(self, payload: dict) -> Tuple[str, str, str]: + """从第一次登录响应提取极验参数 + + Args: + payload: 登录接口响应 JSON + + Returns: + (gt, challenge, code_token) + """ + geetest_data = payload.get('data', {}).get('geetest', {}) + code_data = geetest_data.get('code_data', {}) + gt = code_data.get('gt', '') + challenge = code_data.get('challenge', '') + code_token = geetest_data.get('code_token', '') + return gt, challenge, code_token + + def extract_remote_code(self, payload: dict) -> str: + """从第二次登录响应提取 remote_code + + Args: + payload: 登录接口响应 JSON + + Returns: + remote_code 字符串 + """ + return payload.get('data', {}).get('remoteLogin', {}).get('code', '') + + def extract_login_url(self, payload: dict) -> str: + """从验证码响应提取登录 URL + + Args: + payload: 提交验证码接口响应 JSON + + Returns: + 登录回调 URL + """ + return payload.get('data', {}).get('url', '') diff --git a/core/douyu/login_api_iframe.py b/core/douyu/login_api_iframe.py new file mode 100644 index 0000000..0988e48 --- /dev/null +++ b/core/douyu/login_api_iframe.py @@ -0,0 +1,117 @@ +"""iframe 版登录接口策略""" + +import time +import urllib.parse + +from .crypto import encrypt_nickname_or_phone, encrypt_password +from .login_api import LoginAPIStrategy + + +class IframeLoginAPI(LoginAPIStrategy): + """iframe 版接口策略 + + 使用旧版 iframe 接口,参数嵌套结构,用户名 RC4 加密后 URL 编码。 + 第一次登录使用 wgapi 接口获取极验参数,第二次登录使用 iframe 接口。 + """ + + # e 语言中硬编码的 did 值 + DEFAULT_DID = "6ab33184203a7d50d5f9a8fa00021701" + + @property + def name(self) -> str: + return "iframe" + + @property + def first_login_url(self) -> str: + """第一次登录用 wgapi 接口获取极验参数""" + return "https://passport.douyu.com/wgapi/member/passport/login" + + @property + def login_url(self) -> str: + """第二次登录用 iframe 接口""" + return "https://passport.douyu.com/iframe/loginNew" + + @property + def send_email_url(self) -> str: + return "https://passport.douyu.com/iframe/sendEmail" + + @property + def verify_url(self) -> str: + return "https://passport.douyu.com/iframe/remoteLogin" + + def encrypt_username(self, username: str) -> str: + """RC4 加密后 URL 编码""" + encrypted = encrypt_nickname_or_phone(username) + return urllib.parse.quote(encrypted, safe='') + + def build_first_login_data(self, username: str, password: str, referer: str) -> dict: + """构建第一次登录参数(获取极验参数) + + 注:第一次登录使用 wgapi 接口,所以用户名用 base64 编码(而非 URL 编码)。 + """ + # 第一次登录用 wgapi 接口,需要 base64 编码的用户名 + encrypted_username = encrypt_nickname_or_phone(username) + return { + 'type': '1', + 'nicknameOrPhoneEncrypt': encrypted_username, + 'password': encrypt_password(password), + 'biz_type': '1', + 'room_id': '0', + 'redirect_url': referer, + 't': str(int(time.time() * 1000)), + 'client_id': '1', + 'did': '', + 'lang': '', + 'isMultiAccount': '0', + } + + def build_second_login_data( + self, + username: str, + password: str, + gt: str, + challenge: str, + validate: str, + seccode: str, + code_token: str, + referer: str, + ) -> dict: + """构建第二次登录参数(带极验验证) + + 使用 iframe 接口的嵌套参数结构。 + 注意:iframe 接口使用 username 参数(直接用户名URL编码),而非 nicknameOrPhoneEncrypt。 + """ + # seccode 格式: validate|jordan + seccode_value = validate + '%7Cjordan' + + return { + 'username': username, # iframe 接口用 username,直接用户名 + 'password': encrypt_password(password), + 'room_id': '0', + 'code_verify_data[code_type]': '1', + 'code_verify_data[code_token]': code_token, + 'code_verify_data[code_data][geetest_challenge]': challenge, + 'code_verify_data[code_data][geetest_validate]': validate, + 'code_verify_data[code_data][geetest_seccode]': seccode_value, + 'code_verify_data[code_data][gt_version]': 'v3', + 'redirect_url': referer, + 't': str(int(time.time() * 1000)), + 'client_id': '1', + 'did': '', + 'lang': '', + 'isMultiAccount': '0', + } + + def build_send_email_data(self, code: str) -> dict: + """构建发送邮箱验证参数""" + return {'cacheId': code} + + def build_verify_data(self, code: str, captcha: str) -> dict: + """构建提交验证码参数""" + return { + 'veify_type': '2', # e 语言原文如此,斗鱼接口拼写 + 'phoneCaptcha': captcha, + 'client_id': '1', + 'isMultiAccount': '0', + 'cacheId': code, + } diff --git a/core/douyu/login_api_wgapi.py b/core/douyu/login_api_wgapi.py new file mode 100644 index 0000000..dd8e928 --- /dev/null +++ b/core/douyu/login_api_wgapi.py @@ -0,0 +1,108 @@ +"""wgapi 版登录接口策略""" + +import time + +from .crypto import encrypt_nickname_or_phone, encrypt_password +from .login_api import LoginAPIStrategy + + +class WgapiLoginAPI(LoginAPIStrategy): + """wgapi 版接口策略 + + 使用新版 wgapi 接口,参数扁平化,用户名 RC4 加密后 base64 编码。 + """ + + @property + def name(self) -> str: + return "wgapi" + + @property + def first_login_url(self) -> str: + return "https://passport.douyu.com/wgapi/member/passport/login" + + @property + def login_url(self) -> str: + return "https://passport.douyu.com/wgapi/member/passport/login" + + @property + def send_email_url(self) -> str: + return "https://passport.douyu.com/wgapi/member/passport/remotelogin/sendemail" + + @property + def verify_url(self) -> str: + return "https://passport.douyu.com/wgapi/member/passport/remotelogin/verify" + + def encrypt_username(self, username: str) -> str: + """RC4 加密后 base64 编码""" + return encrypt_nickname_or_phone(username) + + def build_first_login_data(self, username: str, password: str, referer: str) -> dict: + """构建第一次登录参数""" + return { + 'type': '1', + 'nicknameOrPhoneEncrypt': self.encrypt_username(username), + 'password': encrypt_password(password), + 'biz_type': '1', + 'room_id': '0', + 'redirect_url': referer, + 't': str(int(time.time() * 1000)), + 'client_id': '1', + 'did': '', + 'lang': '', + 'isMultiAccount': '0', + } + + def build_second_login_data( + self, + username: str, + password: str, + gt: str, + challenge: str, + validate: str, + seccode: str, + code_token: str, + referer: str, + ) -> dict: + """构建第二次登录参数(带极验验证)""" + return { + 'type': '1', + 'nicknameOrPhoneEncrypt': self.encrypt_username(username), + 'password': encrypt_password(password), + 'room_id': '0', + 'code_type': '1', + 'code_token': code_token, + 'gt_version': 'v3', + 'geetest_challenge': challenge, + 'geetest_validate': validate, + 'geetest_seccode': seccode, + 'code_data[geetest_challenge]': challenge, + 'code_data[geetest_validate]': validate, + 'code_data[geetest_seccode]': seccode, + 'code_data[gt_version]': 'v3', + 'code_data[code]': '', + 'redirect_url': referer, + 't': str(int(time.time() * 1000)), + 'client_id': '1', + 'did': '', + 'lang': '', + 'isMultiAccount': '0', + 'biz_type': '1', + } + + def build_send_email_data(self, code: str) -> dict: + """构建发送邮箱验证参数""" + return { + 'code': code, + 'client_id': '1', + } + + def build_verify_data(self, code: str, captcha: str) -> dict: + """构建提交验证码参数""" + return { + 'verify_type': '2', + 'captcha': captcha, + 'isMultiAccount': '0', + 'code': code, + 'client_id': '1', + 'redirect_url': '//www.douyu.com/api/passport/login', + } diff --git a/e 语言登录.txt b/e 语言登录.txt new file mode 100644 index 0000000..bc8a5b9 --- /dev/null +++ b/e 语言登录.txt @@ -0,0 +1,736 @@ +.版本 2 +.支持库 commobj +.支持库 iext +.支持库 EThread +.支持库 edroptarget + +.程序集 窗口程序集_窗口1 +.程序集变量 工作线程池EX, 鱼刺类_线程池Ex +.程序集变量 运行状态, 运行状态信息, , , 状态信息 +.程序集变量 许可证, 整数型 +.程序集变量 全局游戏分类成功, 整数型 +.程序集变量 超级文本, 文本型 +.程序集变量 最大数量, 整数型 +.程序集变量 超时值, 整数型 +.程序集变量 导出存放文件名, 文本型 +.程序集变量 集_成功数, 整数型 +.程序集变量 线程操作, 鱼刺类_线程操作 +.程序集变量 集_临界, 鱼刺类_临界许可 + +.子程序 _标签_运行状态信息_反馈事件, 整数型 +.参数 参数一, 整数型 +.参数 参数二, 整数型 +.局部变量 text, 文本型 +.局部变量 log, 文本型 +.局部变量 Color, 整数型 + +' 小伙伴们可能遇到过 频繁操作窗口组件 会照那个组件成有几率一闪一闪的 +' 投递消息给UI线程 让消息循环 去队列处理窗口组件操作 即可解决此问题 +.如果真 (内部_取队列日志 (log, Color) = 真) + ' 画板_运行日志.文本颜色 = Color + ' 画板_运行日志.滚动写行 (log) +.如果真结束 + + +.子程序 _列表框数据_右键单击表项 + +弹出菜单 (菜单1, , ) + +.子程序 _拖放对象1_得到文件 +.参数 接收到的文件路径, 文本型 +.局部变量 快速, 快速文本对象 +.局部变量 保存文本, 鱼刺类_输出文本 +.局部变量 文本, 文本型, , "0" +.局部变量 重复数, 整数型 +.局部变量 i, 整数型 +.局部变量 临时目录, 文本型 +.局部变量 临时文件名, 文本型 +.局部变量 j_str, 文本型 +.局部变量 去重后文件名, 文本型 +.局部变量 去重后目录完整路径, 文本型 + +状态条.置文本 (5, “文件已拖入,正在导入列表...”) +载入小小小号 (接收到的文件路径) + +.子程序 _列表数据去重复_被选择, , 公开 + +启动线程 (&去重复, 1, ) + +.子程序 去重复 +.参数 _, 整数型 +.局部变量 重复数, 整数型 +.局部变量 总耗时, 整数型 +.局部变量 去重耗时, 整数型 + +超级列表框_去重复 (列表框数据, 1, 真, 重复数, 总耗时, 去重耗时) +输出运行日志 (“数据对比重复数:” + 到文本 (重复数) + “|去重耗时:” + 到文本 (去重耗时), 2) + +.子程序 超级列表框_去重复 +.参数 参_超级列表框, 超级列表框, 参考 +.参数 参考列, 整数型 +.参数 重新编号, 逻辑型, 可空, 为真时重新编号 即使没有重复 +.参数 重复数, 整数型, 参考 可空 +.参数 总耗时, 整数型, 参考 可空 +.参数 去重耗时, 整数型, 参考 +.局部变量 哈希, QEHashTable +.局部变量 开始时间, 整数型 +.局部变量 新表项, 快速字节集对象 +.局部变量 原表项, 快速字节集对象 +.局部变量 原表项数, 整数型 +.局部变量 原表项字节集数组, 字节集, , "0" +.局部变量 计次, 整数型 +.局部变量 内部字节集, 字节集 +.局部变量 内部字节集数组, 字节集, , "0" +.局部变量 内内部字节集, 字节集 +.局部变量 计数, 整数型 +.局部变量 计次2, 整数型 + + +开始时间 = 取启动时间 () ' 计时开始 + +' 原表项.置可用内存 (, ) +原表项.置字节集 (参_超级列表框.表项) ' 用快速字节集对象的话 数据量大的时候分割快一点 但是超过2G要置可用内存 +原表项字节集数组 = 原表项.分割字节集 ({ 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0 }, ) ' 创建个超级列表框 加点表项 调试输出一下就知道字节集构成了 + +哈希.重置 (取数组成员数 (原表项字节集数组) + 1) ' 加1是自我安慰 可以无视... + +.计次循环首 (取数组成员数 (原表项字节集数组) - 1, 计次) ' 数组第一个是空字节集 + 内部字节集 = 原表项字节集数组 [计次 + 1] + 内部字节集数组 = 分割字节集 (内部字节集, { 0, 0, 1, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0 }, ) ' 数组最后一个多了{0,0} 但是并没有影响什么 + .如果真 (取数组成员数 (内部字节集数组) > 参考列) ' 默认将没有参考列或者为空的表项也当作重复项删除了 可以改 + 内内部字节集 = 内部字节集数组 [参考列 + 1] + .如果真 (内内部字节集 ≠ { }) + .如果真 (哈希.查找 (内内部字节集, ) = 假) + 哈希.添加 (内内部字节集, { 0 }) + .如果 (重新编号) + 计数 = 计数 + 1 + 新表项.添加 ({ 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0 } + 到字节集 (到文本 (计数))) + .计次循环首 (取数组成员数 (内部字节集数组) - 1, 计次2) + 新表项.添加 ({ 0, 0, 1, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0 } + 内部字节集数组 [计次2 + 1]) + .计次循环尾 () + .否则 + 新表项.添加 ({ 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0 } + 内部字节集) + .如果结束 + + 到循环尾 () + .如果真结束 + + .如果真结束 + + .如果真结束 + 重复数 = 重复数 + 1 +.计次循环尾 () + +去重耗时 = 取启动时间 () - 开始时间 ' 去重时间 + +.如果真 (原表项.取字节集 (, ) ≠ 新表项.取字节集 (, )) + 参_超级列表框.可视 = 假 ' 可视和重画会严重影响时间 + 参_超级列表框.禁止重画 () + 参_超级列表框.表项 = 新表项.取字节集 (, ) + 参_超级列表框.可视 = 真 + 参_超级列表框.允许重画 () + 参_超级列表框.保证显示 (参_超级列表框.取表项数 () - 1) +.如果真结束 + +总耗时 = 取启动时间 () - 开始时间 + + +.子程序 去文本数组重复, , , By 帝皇法老 +.参数 文本数组, 文本型, 参考 数组 +.局部变量 X, 整数型 +.局部变量 键, 变体型 +.局部变量 空变体型变量, 变体型 +.局部变量 哈希表, 对象 +.局部变量 N, 整数型 + +X = 1 +哈希表.创建 (“scripting.Dictionary”, ) +.循环判断首 () + 键.赋值 (文本数组 [X], ) + + .如果 (哈希表.逻辑方法 (“Exists”, 键)) + 删除成员 (文本数组, X, ) + .否则 + 哈希表.方法 (“Add”, 键, 空变体型变量) + 置入代码 ({ 255, 69, 252 }) + + .如果结束 + +.循环判断尾 (X ≤ 取数组成员数 (文本数组)) + + +.子程序 去重复操作 +.参数 num, 整数型 +.参数 文件所在路径, 文本型 +.局部变量 快速, 快速文本对象 +.局部变量 文本数组数据, 文本型, , "0" +.局部变量 原数组量, 整数型 +.局部变量 新数组量, 整数型 +.局部变量 index, 整数型 +.局部变量 临时数组, 文本型, , "0" +.局部变量 计次2, 整数型 +.局部变量 计次1, 整数型 +.局部变量 N, 整数型 + +输出运行日志 (“正在导入数据。。。。”, 1) + + +快速.从文件读 (文件所在路径) +文本数组数据 = 快速.分割文本 (#换行符, ) +快速.清除文本 () +原数组量 = 取数组成员数 (文本数组数据) + +去文本数组重复 (文本数组数据) + +新数组量 = 取数组成员数 (文本数组数据) +输出运行日志 (“去重复前:” + 到文本 (原数组量) + “>>>检测重复:” + 到文本 (原数组量 - 新数组量) + “>>>去重复后:” + 到文本 (新数组量), 1) +输出运行日志 (“正在载入到列表数据。。。。”, 1) + +列表框数据.全部删除 () +.计次循环首 (取数组成员数 (文本数组数据), N) + 临时数组 = 分割文本 (文本数组数据 [N], “----”, ) + .如果真 (取数组成员数 (临时数组) ≥ 3) + index = 列表框数据.插入表项 (, , , , , ) + 列表框数据.置状态图片 (index, 1) + 列表框数据.置标题 (index, 0, 到文本 (index + 1)) + 列表框数据.置标题 (index, 1, 临时数组 [1]) + 列表框数据.置标题 (index, 2, 临时数组 [2]) + 列表框数据.置标题 (index, 3, 临时数组 [3]) + 列表框数据.置标题 (index, 3, 临时数组 [3]) + .如果真结束 + + _处理事件 () +.计次循环尾 () +清除数组 (临时数组) +清除数组 (文本数组数据) +列表框数据.保证显示 (index) +输出运行日志 (“导入数据到列表完成。。。。”, 1) + +.子程序 __启动窗口_创建完毕 +.局部变量 返回值, 文本型 +.局部变量 str, 文本型 +.局部变量 s广告, 文本型 +.局部变量 a, 文本型 +.局部变量 b, 文本型 +.局部变量 c, 文本型 + +未闻花名_皮肤加载 (13, , , , , 真) +.如果真 (信息框 (“此程序仅用于学习、技术交流,请勿用于非法!如出现任何问题后果自负!”, #确认取消钮, “免责声明!!!”, ) = #取消钮) + 结束 () +.如果真结束 + +.如果真 (文件是否存在 (取运行目录 () + “\RSCProject.dll”) = 假) + 写到文件 (取运行目录 () + “\RSCProject.dll”, #RSCProjectDLL) +.如果真结束 +.如果真 (文件是否存在 (取运行目录 () + “\RSOpenCV.dll”) = 假) + 写到文件 (取运行目录 () + “\RSOpenCV.dll”, #RSOpenCVDLL) +.如果真结束 +.如果真 (文件是否存在 (取运行目录 () + “\XfreeJY.dll”) = 假) + 写到文件 (取运行目录 () + “\XfreeJY.dll”, #XfreeJYDLL) +.如果真结束 + +jyInit () +' 'jiRel (2) +SetProcessAffinityMask (-1, 1) +全目录Test = 取运行目录 () + “\Dy_登录\” +全目录临时 = 全目录Test + “临时” +全目录状态 = 全目录Test + “数据状态” +全目录导出 = 全目录Test + “导出记录” +.如果真 (文件是否存在 (全目录Test) = 假) + 目录_创建 (全目录Test) +.如果真结束 +.如果真 (文件是否存在 (全目录临时) = 假) + 目录_创建 (全目录临时) +.如果真结束 +.如果真 (文件是否存在 (全目录状态) = 假) + 目录_创建 (全目录状态) +.如果真结束 +.如果真 (文件是否存在 (全目录导出) = 假) + 目录_创建 (全目录导出) +.如果真结束 + +配置_读取_易语言组件 (_启动窗口) +拖放对象1.注册拖放控件 (列表框数据.取窗口句柄 ()) +启动线程 (&账号写入列表, , ) + +.子程序 __启动窗口_可否被关闭, 逻辑型 + +_启动窗口.总在最前 = 假 +.如果 (信息框 (“程序正在工作,确定要退出?”, #信息图标 + #确认钮 + #取消钮, , ) = #确认钮) + 返回 (真) +.否则 + 返回 (假) +.如果结束 + + +.子程序 __启动窗口_将被销毁 + +工作中 = 假 +配置_保存_易语言组件 (_启动窗口) +账号写出列表 () +结束 () + +.子程序 _时钟_更新运行状态_周期事件 +.局部变量 str, 文本型 +.局部变量 int, 整数型 +.局部变量 更新配置, 整数型 + +.如果真 (工作中 = 真) + 更新配置 = 更新配置 + 300 + .如果真 (更新配置 ≥ 30000) ' 30秒更新一次配置 + 更新配置 = 0 + .如果真结束 + .' 如果真 (配置_更新 () = 假) + ' 状态条.置文本 (1, “运行参数设置异常!”) + .如果真结束 + + int = 到整数 (编辑框_工作线程.内容) ' 重置最大线程数 + .如果真 (int > 0 且 int < 50) ' 限制最大线程数 + 工作线程池EX.置_最大线程数 (int) + .如果真结束 + +.如果真结束 + + +.子程序 _粘贴数据_被选择 + +T粘贴数据 (列表框数据) + +.子程序 _文本导入_被选择 + +通用对话框1.初始目录 = 取运行目录 () ' 3=系统桌面 +通用对话框1.标题 = “请选择欲导入的文本” +.如果真 (通用对话框1.打开 () = 真) + 超级文本 = 通用对话框1.文件名 + 启动线程 (&超级导入, , ) +.如果真结束 + + +.子程序 超级导入 +.局部变量 time, 整数型 + +超级列表数据_导入文本 (列表框数据, 超级文本, , “----”, 3, 假, time) + +提示框 (“导入完成,耗时:” + 到文本 (time ÷ 1000) + “秒”, 0, , , 1000) + +.子程序 _全选_被选择 + +T全选 (列表框数据) + +.子程序 _反选_被选择 + +T反选 (列表框数据) + +.子程序 _全不选_被选择 + +T全不选 (列表框数据) + +.子程序 _删除当前_被选择 + +T删除当前行 (列表框数据) + +.子程序 _删除勾选_被选择 + +T删除勾选 (列表框数据) + +.子程序 _清空全部_被选择 + +列表框数据.全部删除 () + +.子程序 _导出勾选账号数据_被选择 + +输入框 (“默认保存在默认在同程序路径下的导出数据目录下,仅输入文件名即可”, “导出文件”, , 导出存放文件名, , ) +.如果真 (导出存放文件名 = “”) + 信息框 (“请输入文件名!”, 0, , ) + 返回 () +.如果真结束 + +启动线程 (&保存勾选, , ) + +.子程序 保存勾选 +.局部变量 导出到文件, 鱼刺类_输出文本 +.局部变量 j_str, 文本型 +.局部变量 开始时间, 整数型 +.局部变量 耗时, 整数型 +.局部变量 N, 整数型 + +开始时间 = 取启动时间 () +输出运行日志 (“正在导出数据。。。。”, 1) +.计次循环首 (列表框数据.取表项数 (), N) + .如果真 (列表框数据.取当前状态图片 (N - 1) = 1) + j_str = 列表框数据.取标题 (N - 1, 1) + “----” + 列表框数据.取标题 (N - 1, 2) + “----” + 列表框数据.取标题 (N - 1, 3) + “----” + 列表框数据.取标题 (N - 1, 4) + 导出到文件.写文本行 (j_str, 导出存放文件名, 全目录导出, “.txt”) + + .如果真结束 + +.计次循环尾 () +导出到文件.关闭所有文件占用 () +耗时 = 取启动时间 () - 开始时间 + +输出运行日志 (“导出数据完成,耗时:” + 到文本 (耗时), 1) +提示框 (“导出完成”, , , , 2000) + + + +.子程序 _按钮_启动_被单击 + +.判断开始 (按钮_启动.标题 = “启 动”) + 配置_保存_易语言组件 (_启动窗口) + 按钮_启动.禁止 = 真 + .如果真 (线程操作.创建 (&工作线程) = 假) + 按钮_启动.禁止 = 假 + 返回 () + .如果真结束 + + +.判断 (按钮_启动.标题 = “停 止”) + 按钮_启动.禁止 = 真 + 按钮_启动.标题 = “停 止 中...” + 状态条.置文本 (1, “程 序 停 止 中...”) + 工作中 = 假 + 工作线程池EX.销毁 () +.默认 + +.判断结束 + + +.子程序 工作线程 +.局部变量 bool, 逻辑型 +.局部变量 是否创建, 逻辑型 +.局部变量 N, 整数型 +.局部变量 int, 整数型 +.局部变量 局_任务数量, 整数型 +.局部变量 列表数量, 整数型 +.局部变量 user, 文本型 +.局部变量 pass, 文本型 +.局部变量 data, 文本型 +.局部变量 任务数, 整数型 + +线程操作.置CPU亲和性 () +按钮_启动.标题 = “启 动 中...” +状态条.置文本 (1, “程序启动中...”) + +bool = 工作线程池EX.创建 (到整数 (删首尾空 (编辑框_工作线程.内容)), , , 真, , 1024 × 512) +.如果真 (bool = 假) + 状态条.置文本 (1, “初始化工作线程池失败!”) + 按钮_启动.标题 = “启 动” + 按钮_启动.禁止 = 假 + 返回 () +.如果真结束 + +工作中 = 真 +按钮_启动.标题 = “停 止” +按钮_启动.禁止 = 假 +int = 0 +.判断循环首 (工作中 = 真) + 任务数 = 工作线程池EX.取_执行线程数 () + 工作线程池EX.取_队列任务数 () + 任务数 = 工作线程池EX.取_最大线程容量 () - 任务数 + .如果 (任务数 > 0) + .如果真 (列表框数据.取当前状态图片 (int) = 1) + 工作线程池EX.投递任务 (&任务子程序, int) + 程序_延时 (到整数 (删首尾空 (编辑框_时间间隔.内容))) + .如果真结束 + + int = int + 1 + + .否则 + 程序_延时 (20) + .如果结束 + + .如果真 (int ≥ 列表框数据.取表项数 ()) + 跳出循环 () + .如果真结束 + +.判断循环尾 () +.循环判断首 () + 程序_延时 (300) +.循环判断尾 (工作线程池EX.取_是否完全空闲 () = 假) +按钮_启动.禁止 = 真 +工作中 = 假 +状态条.置文本 (1, “正在销毁工作线程池....”) +程序_延时 (300) +工作线程池EX.销毁 () +状态条.置文本 (1, “销毁工作线程池成功!”) +程序_延时 (300) +按钮_启动.标题 = “启 动” +按钮_启动.禁止 = 假 +状态条.置文本 (1, “执行停止完毕!”) + +.子程序 GetIP, 文本型 +.局部变量 str, 文本型 +.局部变量 bool, 逻辑型 + +.循环判断首 () + str = 编码_Utf8到Ansi (网页_访问_对象 (删首尾空 (编辑框_代理IP.内容))) + 状态条.置文本 (1, “正在获取代理:” + str) + bool = 是否IP格式 (str) + 程序_延时 (300) +.循环判断尾 (取反 (bool)) +返回 (str) + +.子程序 是否IP格式, 逻辑型 +.参数 IP, 文本型 +.局部变量 正则, 正则表达式类 + +正则.创建 (“([1-9]\d{0,2}\.\d{1,3}\.\d{1,3}\.\d{1,3}):([1-9]\d{1,4})”, IP, , , , ) +返回 (正则.取匹配数量 () > 0) + +.子程序 任务子程序 +.参数 索引EX, 整数型 +.局部变量 httpR, WinHttpR +.局部变量 str, 文本型 +.局部变量 登录状态, 文本型 +.局部变量 zyjson, zyJsonValue +.局部变量 zyjson2, zyJsonValue +.局部变量 zyjson3, zyJsonValue +.局部变量 zyjson4, zyJsonValue +.局部变量 code_token, 文本型 +.局部变量 challenge, 文本型 +.局部变量 str3, 文本型 +.局部变量 局_短信, 文本型 +.局部变量 Lurl, 文本型 +.局部变量 局pass, 文本型 +.局部变量 COOK, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 str4, 文本型 +.局部变量 P, 整数型 +.局部变量 acf_nickname, 文本型 +.局部变量 acf_uid, 文本型 +.局部变量 返回CK, 文本型 +.局部变量 geetest_seccode, 文本型 +.局部变量 局_CookieNEW, 文本型 +.局部变量 返回URL, 文本型 +.局部变量 boolEX, 逻辑型 +.局部变量 areaCode, 文本型 +.局部变量 Sdata, 文本型 +.局部变量 F_F, 文本型, , "0" +.局部变量 dy_user, 文本型 +.局部变量 dy_pass, 文本型 +.局部变量 mail_user, 文本型 +.局部变量 mail_pass, 文本型 +.局部变量 P1, 整数型 +.局部变量 str2e, 文本型 +.局部变量 局返回CK, 文本型 +.局部变量 E, 整数型 +.局部变量 返回msg, 文本型 +.局部变量 局_mail_user, 文本型 +.局部变量 局_mail_pass, 文本型 +.局部变量 run_Cookie, 文本型 +.局部变量 K, 整数型 +.局部变量 DYL, 逻辑型 +.局部变量 数循环, 整数型 +.局部变量 bs1, 逻辑型 +.局部变量 bs2, 逻辑型 +.局部变量 M, 整数型 +.局部变量 runerr1, 文本型 +.局部变量 返回登录, 文本型 +.局部变量 sild3, 逻辑型 +.局部变量 challenge3, 文本型 +.局部变量 validate3, 文本型 +.局部变量 token3, 文本型 +.局部变量 bool3, 逻辑型 +.局部变量 CK, 文本型 +.局部变量 did, 文本型 +.局部变量 p2, 整数型 +.局部变量 p3, 整数型 +.局部变量 seccode2, 文本型 +.局部变量 sendstr, 文本型 +.局部变量 P5, 整数型 +.局部变量 局_代理IP, 文本型 +.局部变量 返回code, 文本型 +.局部变量 Cookie_new, 文本型 +.局部变量 data, 文本型 +.局部变量 acf_auth, 文本型 +.局部变量 acf_jwt_token, 文本型 +.局部变量 acf_stk, 文本型 +.局部变量 acf_ltkid, 文本型 +.局部变量 局_token, 文本型 + +全临界.进入 () +列表框数据.置状态图片 (索引EX, 0) +Sdata = 列表框数据.取标题 (索引EX, 1) +列表框数据.保证显示 (索引EX) +全临界.退出 () + +F_F = 分割文本 (Sdata, “|”, ) +.如果真 (取数组成员数 (F_F) < 4) + 状态条.置文本 (1, “数据格式错误”) + 窗口_置表项状态 (索引EX, 0, “数据格式错误”) + 返回 () +.如果真结束 + +dy_user = F_F [1] +dy_pass = F_F [2] +mail_user = F_F [3] +mail_pass = F_F [4] + +.计次循环首 (20, K) + 状态条.置文本 (1, “正在识别滑块...”) + 窗口_置表项状态 (索引EX, 0, “正在识别滑块...”) + sild3 = slide2 (challenge3, validate3, token3) + .如果真 (sild3 = 真) + 窗口_置表项状态 (索引EX, 0, “滑块成功,正在登录[” + 到文本 (K) + “]”) + 局_代理IP = GetIP () + DYL = 登录斗鱼2 (局_代理IP, dy_user, dy_pass, challenge3, validate3, token3, 返回code, 局返回CK) + ' '调试输出 (局返回CK) + .如果真 (DYL = 真) + 跳出循环 () + .如果真结束 + + .如果真 (局返回CK = “账号或者密码错误”) + 状态条.置文本 (1, “账号或者密码错误”) + 窗口_置表项状态 (索引EX, 0, “账号或者密码错误”) + CK入库 (Sdata, “账号或者密码错误”, 全目录状态) + + 返回 () + .如果真结束 + .如果真 (局返回CK = “登录异常”) + 状态条.置文本 (1, “账号登录异常”) + 窗口_置表项状态 (索引EX, 0, “账号登录异常”) + CK入库 (Sdata, “账号登录异常”, 全目录状态) + 返回 () + .如果真结束 + .如果真 (局返回CK = “账号不存在”) + 状态条.置文本 (1, “账号不存在”) + 窗口_置表项状态 (索引EX, 0, “账号不存在”) + CK入库 (Sdata, “账号不存在”, 全目录状态) + 返回 () + .如果真结束 + .如果真 (局返回CK = “邮箱密码错误”) + 状态条.置文本 (1, “邮箱密码错误”) + 窗口_置表项状态 (索引EX, 0, “邮箱密码错误”) + CK入库 (Sdata, “邮箱密码错误”, 全目录状态) + 返回 () + .如果真结束 + .如果真 (局返回CK = “账号登录异常”) + 状态条.置文本 (1, “账号登录异常”) + 窗口_置表项状态 (索引EX, 0, “账号登录异常”) + CK入库 (Sdata, “账号登录异常”, 全目录状态) + 返回 () + .如果真结束 + + + + .如果真结束 + +.计次循环尾 () +.如果真 (DYL = 假) + 状态条.置文本 (1, “登录验证失败”) + 窗口_置表项状态 (索引EX, 0, “登录验证失败”) + 返回 () +.如果真结束 + +.如果真 (邮箱发送验证 (返回code, 局返回CK) = 假) + 状态条.置文本 (1, “邮件发送失败”) + 窗口_置表项状态 (索引EX, 0, “邮件发送失败”) + 返回 () +.如果真结束 + +局_代理IP = GetIP () +.计次循环首 (5, ) + 窗口_置表项状态 (索引EX, 0, “正在获取邮件...”) + .如果真 (取最新邮件E2X (mail_user, mail_pass, 返回msg) = 真) + 局_代理IP = GetIP () + 窗口_置表项状态 (索引EX, 0, “正在提交邮件...”) + .如果真 (提交邮件码验证 (返回code, 返回msg, 局返回CK, Lurl) = 真) + 局_代理IP = GetIP () + 窗口_置表项状态 (索引EX, 0, “正在更新Cookie...”) + bool3 = 登录取Cookie (局_代理IP, 局返回CK, Lurl, run_Cookie) + .如果真 (bool3 = 真) + 窗口_置表项状态 (索引EX, 0, “登录成功”) + 跳出循环 () + + .如果真结束 + + .如果真结束 + + .如果真结束 + +.计次循环尾 () +.如果真 (取反 (bool3)) + 窗口_置表项状态 (索引EX, 0, “登录失败”) + 返回 () +.如果真结束 + + +acf_auth = _取单条Cookie (run_Cookie, “acf_auth”, 假) +acf_jwt_token = _取单条Cookie (run_Cookie, “acf_jwt_token”, 假) +acf_uid = _取单条Cookie (run_Cookie, “acf_uid”, 假) +acf_stk = _取单条Cookie (run_Cookie, “acf_stk”, 假) +acf_ltkid = _取单条Cookie (run_Cookie, “acf_ltkid”, 假) + +Cookie_new = dy_user + “----” + dy_pass + “----” + acf_auth + “; ” + acf_jwt_token + “; ” + acf_uid + “; ” + acf_stk + “; ” + acf_ltkid +' 调试输出 (Cookie_new) +' 用户3557899963----a778899----acf_auth=bb65yPiagitvgsakjF1SBchOePmprnWsAPKGHGuSqv%2FldVFH2xr9En5GRhZG7uLWAsGWrwlZjQs9tPufAuVpuq9FREP%2BHMf%2FBJQidxP6ZrBn%2FdmdHpdHz5Q; acf_jwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJtZDUifQ.eyJ1aWQiOjUwMjU0NjEzOSwiY3QiOjAsInN1YiI6InN0IiwiYXVkIjpbImR5Iiwibm9uZSJdLCJiaXoiOjEsImx0a2lkIjo5MDQzNDY4MCwic3RrIjoiOWE1ZGNiZTliYWQwMTRkMiIsImV4cCI6MTc3Njc3ODI1OCwiaWF0IjoxNzc2MTczNDU4LCJrZXkiOiJkeS1qd3QtbWQ1In0.ZTg5NmU5ZGRjODgwNjYzNDU1NjI1NWRkOGFjOTM0NDY +窗口_置表项状态 (索引EX, 0, Cookie_new) + +CK入库 (Cookie_new, “斗鱼Cookie”, 全目录状态) + + +.如果真 (组合框_功能.现行选中项 = 1) + .如果 (查询SM (run_Cookie) = 真) + CK入库 (Sdata, “已认证”, 全目录状态) + 窗口_置表项状态 (索引EX, 0, “已认证”) + .否则 + CK入库 (Sdata, “未认证”, 全目录状态) + 窗口_置表项状态 (索引EX, 0, “未认证XX”) + .如果结束 + +.如果真结束 + + + +.子程序 _勾选自定义回调状态_被选择 + +载入 (窗口1, _启动窗口, 真) + +.子程序 _勾选动态量_被选择 +.局部变量 n, 整数型 +.局部变量 索引, 整数型 +.局部变量 存放, 文本型 +.局部变量 是否存在, 逻辑型 + +输入框 (“勾选自定义动态量”, “自定义勾选”, , 存放, , ) +.计次循环首 (列表框数据.取表项数 (), n) + .判断开始 (寻找文本 (列表框数据.取标题 (n - 1, 5), 存放, , 假) ≠ -1) + 列表框数据.置状态图片 (n - 1, 1) + + .默认 + 列表框数据.置状态图片 (n - 1, 0) + .判断结束 + +.计次循环尾 () + +.子程序 _勾选列表范围1N_被选择 +.局部变量 K, 整数型 +.局部变量 T, 整数型 +.局部变量 A, 文本型 + +' N = 到整数 (编辑框_跳到.内容) - 1 ' 为什么要-1 因为表项索引是从0开始的 比如第10行 表项就是9 +' M = 到整数 (编辑框1.内容) +输入框 (“比如:1-10(代表从第1列勾选到第999列),注意:只能是连续范围!”, “勾选选用指定范围”, “1-99”, A, 1, ) +.如果真 (A ≠ “”) + .计次循环首 (列表框数据.取表项数 (), T) + 列表框数据.置状态图片 (T - 1, 0) + .计次循环尾 () + .计次循环首 (到整数 (文本_取右边 (A, “-”)), K) + .如果真 (K ≥ 到整数 (文本_取左边 (A, “-”))) + 列表框数据.置状态图片 (K - 1, 1) + .如果真结束 + + .计次循环尾 () + 信息框 (“共选择:” + 到文本 (K) + “条数据”, 0, , ) +.如果真结束 + + + +.子程序 _列表框数据_被双击, 逻辑型 +.参数 横向位置, 整数型 +.参数 纵向位置, 整数型 +.参数 功能键状态, 整数型 +.局部变量 str, 文本型 + +str = 列表框数据.取标题 (列表框数据.现行选中项, 2) +置剪辑板文本 (str) +提示框 (“已复制:” + #换行符 + str) + diff --git a/e 语言登录2.txt b/e 语言登录2.txt new file mode 100644 index 0000000..724fb2f --- /dev/null +++ b/e 语言登录2.txt @@ -0,0 +1,578 @@ +.版本 2 +.支持库 spec + +.程序集 斗鱼登录EX + +.子程序 登录加密, 文本型, 公开 +.参数 参_str, 文本型 +.局部变量 value, 文本型 +.局部变量 v8, , , , + +??? () ' +value = ??? () ' + +返回 (value) + +.子程序 slide1, 逻辑型, , 注册滑块 +.参数 geetest_challenge, 文本型, 参考 +.参数 geetest_validate, 文本型, 参考 +.参数 code_token, 文本型, 参考 +.局部变量 httpR, WinHttpR +.局部变量 局_代理IP, 文本型 +.局部变量 retult, 文本型 +.局部变量 zyjson, zyJsonValue +.局部变量 type, 文本型 + +.计次循环首 (10, ) + .如果真 (工作中 = 假) + 返回 (假) + .如果真结束 + + 局_代理IP = GetIP () + retult = diaoyong (局_代理IP) + ' 调试输出 (“注册滑块--> ”, retult) + zyjson.解析 (retult) + geetest_challenge = zyjson.取文本 (“geetest_challenge”) + geetest_validate = zyjson.取文本 (“geetest_validate”) + code_token = zyjson.取文本 (“token”) + ' type = zyjson.取文本 (“type”) + .如果真 (geetest_challenge ≠ “” 且 geetest_validate ≠ “” 且 code_token ≠ “”) + 返回 (真) + .如果真结束 + +.计次循环尾 () + +返回 (假) + +.子程序 slide2, 逻辑型, , 登录滑块 +.参数 返回challenge, 文本型, 参考 +.参数 返回validate, 文本型, 参考 +.参数 返回token, 文本型, 参考 +.局部变量 httpR, WinHttpR +.局部变量 局_代理IP, 文本型 +.局部变量 retult, 文本型 +.局部变量 zyjson, zyJsonValue +.局部变量 type, 文本型 + +局_代理IP = GetIP () +.计次循环首 (20, ) + .' 如果真 (工作中 = 假) + ' 返回 (假) + .如果真结束 + + retult = diaoyong1 (局_代理IP) + ' 调试输出 (“slide2->”, retult, , 局_代理IP) + ' httpR.Open (“POST”, “http://127.0.0.1:5000/solve”) + ' httpR.SetRequestHeaders (“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” + #换行符 + “Content-Type: application/json”) + ' httpR.Send (“{” + #引号 + “gt” + #引号 + “:” + #引号 + “0484f8c2410a5a988d9e94292a34c0c4” + #引号 + “,” + #引号 + “challenge” + #引号 + “:” + #引号 + “5f9502ee96a67e7e41185d636f5d3a5e” + #引号 + “,” + #引号 + “proxy” + #引号 + “:” + #引号 + “127.0.0.1:1080” + #引号 + “}”) + + ' retult = 编码_usc2到ansi (httpR.GetResponseTextU2A ()) + ' 调试输出 (取程序名称 (1), retult) + ' zyjson.清空 () + ' zyjson.解析 (retult) + ' 参_challenge = “5f9502ee96a67e7e41185d636f5d3a5e” + ' 参_validate = zyjson.取文本 (“validate”) + ' 参_token = “cc7ac52b5e9c47f8b3c9919f7718eb5f” + ' ' type = zyjson.取文本 (“type”) + zyjson.清空 () + zyjson.解析 (retult) + 返回challenge = zyjson.取文本 (“geetest_challenge”) + 返回validate = zyjson.取文本 (“geetest_validate”) + 返回token = zyjson.取文本 (“token”) + + .如果 (返回challenge ≠ “” 且 返回validate ≠ “” 且 返回token ≠ “”) + 返回 (真) + + .否则 + 局_代理IP = GetIP () + 程序_延时 (1000) + .如果结束 + +.计次循环尾 () + +返回 (假) + +.子程序 slide3, 逻辑型, , 绑邮箱点选 +.参数 参_Cookie, 文本型 +.参数 参_challenge, 文本型, 参考 +.参数 参_validate, 文本型, 参考 +.参数 参_seccode, 文本型, 参考 +.局部变量 retult, 文本型 +.局部变量 局_代理IP, 文本型 +.局部变量 zyjson, zyJsonValue +.局部变量 geetest_challenge, 文本型 +.局部变量 geetest_validate, 文本型 +.局部变量 code_token, 文本型 +.局部变量 局_type, 文本型 +.局部变量 gp, 整数型 + +局_代理IP = GetIP () +.计次循环首 (10, ) + .如果真 (工作中 = 假) + 返回 (假) + .如果真结束 + + gp = diaoyong2 (参_Cookie + “ Client_ip: ” + 局_代理IP + “ X-forwarded-for: ” + 局_代理IP, “”) + ' retult = diaoyong2 (参_Cookie, 局_代理IP) + retult = 指针到文本 (gp) + ' ‘调试输出 (“绑邮箱点选---”, retult) + zyjson.清空 () + zyjson.解析 (retult) + 参_challenge = zyjson.取文本 (“geetest_challenge”) + 参_validate = zyjson.取文本 (“geetest_validate”) + 参_seccode = zyjson.取文本 (“geetest_seccode”) + ' 局_type = zyjson.取文本 (“type”) + ' '这里的type 要为word才能通过 + jiRel (gp) + .如果 (参_challenge ≠ “” 且 参_validate ≠ “” 且 参_seccode ≠ “”) + 返回 (真) + + .否则 + 局_代理IP = GetIP () + .如果结束 + +.计次循环尾 () +返回 (假) + +.子程序 初始化CK, 文本型 +.参数 参_ip, 文本型 +.局部变量 httpR, WinHttpR +.局部变量 str, 文本型 +.局部变量 did, 文本型 + +httpR.SetProxy (参_ip) +httpR.Open (“POST”, “https://passport.douyu.com/lapi/did/api/get?client_id=1&_=” + 时间_取现行时间戳 () + “&callback=axiosJsonpCallback1”) +httpR.SetRequestHeaders (#常量7) +httpR.SetCookie () +httpR.Send () +str = httpR.GetResponseTextU2A () +did = 文本_取出中间文本 (str, “did” + #引号 + “:” + #引号, #引号) +返回 (did) + +.子程序 登录斗鱼, 逻辑型 +.参数 参_IP, 文本型 +.参数 dy用户账号, 文本型 +.参数 dy密码, 文本型 +.参数 geetest_challenge, 文本型 +.参数 geetest_validate, 文本型 +.参数 code_token, 文本型 +.参数 返回Cookie, 文本型, 参考 +.局部变量 httpR, WinHttpR +.局部变量 str, 文本型 +.局部变量 CK, 文本型 +.局部变量 zyjson, zyJsonValue +.局部变量 did, 文本型 +.局部变量 局_CK, 文本型 +.局部变量 err0, 整数型 + +.' 计次循环首 (5, ) + ' did = 初始化CK (参_IP) + .' 如果 (did ≠ “”) + ' 跳出循环 () + + .否则 + ' 参_IP = GetIP () + .如果结束 + +.' 计次循环尾 () +.' 如果真 (did = “”) + ' 返回 (假) +.如果真结束 + +did = “67f9ad421edfebd03635494f00041601” +CK = “dy_did=” + did + “; acf_did=” + did + “; last_login_way=phone; Hm_lpvt_e99aee90ec1b2106afe7ec3b199020a7=” + 时间_取现行时间戳 (真) +' '调试输出 (“CK--------->”, CK) +httpR.SetProxy (参_IP) +.计次循环首 (5, ) + httpR.Open (“POST”, “https://passport.douyu.com/iframe/loginNew”) + httpR.SetRequestHeaders (#常量login) + httpR.SetCookie (CK) + httpR.Send (“username=” + 编码_URL编码_JS (dy用户账号, ) + “&password=” + 校验_取md5 (到字节集 (dy密码)) + “&room_id=0&code_verify_data%5Bcode_type%5D=1&code_verify_data%5Bcode_token%5D=” + code_token + “&code_verify_data%5Bcode_data%5D%5Bgeetest_challenge%5D=” + geetest_challenge + “&code_verify_data%5Bcode_data%5D%5Bgeetest_validate%5D=” + geetest_validate + “&code_verify_data%5Bcode_data%5D%5Bgeetest_seccode%5D=” + geetest_validate + “%7Cjordan&code_verify_data%5Bcode_data%5D%5Bgt_version%5D=v3&redirect_url=https%3A%2F%2Fpassport.douyu.com%2Findex%2Flogin%3Fpassport_reg_callback%3DPASSPORT_REG_SUCCESS_CALLBACK%26passport_login_callback%3DPASSPORT_LOGIN_SUCCESS_CALLBACK%26passport_close_callback%3DPASSPORT_CLOSE_CALLBACK%26passport_dp_callback%3DPASSPORT_DP_CALLBACK%26type%3Dlogin%26client_id%3D1%26state%3Dhttps%253A%252F%252Fwww.douyu.com%252F%26source%3Dclick_topnavi_login&t=” + 时间_取现行时间戳 () + “&client_id=1&did=&lang=&isMultiAccount=0”) + str = 编码_usc2到ansi (httpR.GetResponseTextU2A ()) + 局_CK = httpR.GetAllResponseCookies () + 调试输出 (“登录XXX----”, str, 局_CK) + zyjson.清空 () + zyjson.解析 (str) + err0 = zyjson.取整数 (“error”) + .如果 (str ≠ “”) + .如果真 (err0 = 130014) + .如果真 (寻找文本 (str, “账号登录异常”, , 假) ≠ -1) + 返回Cookie = “账号登录异常” + 返回 (假) + .如果真结束 + .如果真 (局_CK ≠ “”) + 跳出循环 () + .如果真结束 + + + .如果真结束 + .如果真 (寻找文本 (str, “验证码错误”, , 假) ≠ -1) + 返回Cookie = “验证码错误” + 返回 (假) + .如果真结束 + .如果真 (寻找文本 (str, “网络错误”, , 假) ≠ -1) + 返回Cookie = “网络错误” + 返回 (假) + .如果真结束 + .如果真 (寻找文本 (str, “账号或者密码错误”, , 假) ≠ -1) + 返回Cookie = “账号或者密码错误” + 返回 (假) + .如果真结束 + .如果真 (寻找文本 (str, “账号登录异常”, , 假) ≠ -1) + 返回Cookie = “账号登录异常” + 返回 (假) + .如果真结束 + .如果真 (寻找文本 (str, “账号不存在”, , 假) ≠ -1) + 返回Cookie = “账号不存在” + 返回 (假) + .如果真结束 + .如果真 (寻找文本 (str, “邮箱密码错误”, , 假) ≠ -1) + 返回Cookie = “邮箱密码错误” + 返回 (假) + .如果真结束 + + + + .否则 + 参_IP = GetIP () + httpR.SetProxy (参_IP) + .如果结束 + +.计次循环尾 () +.如果真 (局_CK = “”) + 返回 (假) +.如果真结束 + +返回Cookie = 网页_Cookie合并更新 (CK, 局_CK) +返回 (真) + + +.子程序 登录斗鱼2, 逻辑型 +.参数 参_IP, 文本型 +.参数 dy用户账号, 文本型 +.参数 dy密码, 文本型 +.参数 geetest_challenge, 文本型 +.参数 geetest_validate, 文本型 +.参数 code_token, 文本型 +.参数 返回code, 文本型, 参考 可空 +.参数 返回Cookie, 文本型, 参考 +.局部变量 httpR, WinHttpR +.局部变量 str, 文本型 +.局部变量 CK, 文本型 +.局部变量 zyjson, zyJsonValue +.局部变量 did, 文本型 +.局部变量 局_CK, 文本型 +.局部变量 err0, 整数型 +.局部变量 nicknameOrPhoneEncrypt, 文本型 +.局部变量 password, 文本型 +.局部变量 body, 文本型 +.局部变量 code, 文本型 + +' did = “6ab33184203a7d50d5f9a8fa00021701” +' CK = “dy_did=” + did + “; acf_did=” + did + “; last_login_way=phone; Hm_lpvt_e99aee90ec1b2106afe7ec3b199020a7=” + 时间_取现行时间戳 (真) + +' '调试输出 (“CK--------->”, CK) +httpR.SetProxy (参_IP) + +nicknameOrPhoneEncrypt = 编码_URL编码_JS (登录加密 (dy用户账号), 1) +password = 校验_取md5 (到字节集 (dy密码)) + +.计次循环首 (5, ) + body = “type=1&nicknameOrPhoneEncrypt=” + nicknameOrPhoneEncrypt + “&password=” + password + “&room_id=0&code_type=1&code_token=” + code_token + “>_version=v3&geetest_challenge=” + geetest_challenge + “&geetest_validate=” + geetest_validate + “&geetest_seccode=” + geetest_validate + “%7Cjordan&code_data%5Bgeetest_challenge%5D=” + geetest_challenge + “&code_data%5Bgeetest_validate%5D=” + geetest_validate + “&code_data%5Bgeetest_seccode%5D=” + geetest_validate + “%7Cjordan&code_data%5Bgt_version%5D=v3&code_data%5Bcode%5D=&redirect_url=https%3A%2F%2Fpassport.douyu.com%2Findex%2Flogin%3Fpassport_reg_callback%3DPASSPORT_REG_SUCCESS_CALLBACK%26passport_login_callback%3DPASSPORT_LOGIN_SUCCESS_CALLBACK%26passport_close_callback%3DPASSPORT_CLOSE_CALLBACK%26passport_dp_callback%3DPASSPORT_DP_CALLBACK%26type%3Dlogin%26client_id%3D1%26state%3Dhttps%253A%252F%252Fwww.douyu.com%252F%253Fdyshid%253D0-6ab33184203a7d50d5f9a8fa00021701%26source%3Dclick_topnavi_login&t=” + 时间_取现行时间戳 () + “&client_id=1&did=&lang=&isMultiAccount=0&biz_type=1” + + httpR.Open (“POST”, “https://passport.douyu.com/wgapi/member/passport/login”) + httpR.SetRequestHeaders (#常量login) + httpR.SetCookie (CK) + httpR.Send (body) + + + + str = 编码_usc2到ansi (httpR.GetResponseTextU2A ()) + 局_CK = httpR.GetAllResponseCookies () + ' 调试输出 (“登录XXX----”, str, 局_CK) + zyjson.清空 () + zyjson.解析 (str) + err0 = zyjson.取整数 (“error”) + .如果 (str ≠ “”) + .如果真 (err0 = 130014) + code = zyjson.取文本 (“data.remoteLogin.code”) + ' 调试输出 (“code---”, code) + .如果真 (code ≠ “”) + 返回code = code + 返回Cookie = 网页_Cookie合并更新 (CK, 局_CK) + 返回 (真) + .如果真结束 + + 返回Cookie = “账号登录异常” + 返回 (假) + .' 如果真 (寻找文本 (str, “账号登录异常”, , 假) ≠ -1) + + ' 返回Cookie = “账号登录异常” + ' 返回 (假) + .如果真结束 + .' 如果真 (局_CK ≠ “”) + ' 跳出循环 () + .如果真结束 + + + .如果真结束 + .如果真 (寻找文本 (str, “验证码错误”, , 假) ≠ -1) + 返回Cookie = “验证码错误” + 返回 (假) + .如果真结束 + .如果真 (寻找文本 (str, “网络错误”, , 假) ≠ -1) + 返回Cookie = “网络错误” + 返回 (假) + .如果真结束 + .如果真 (寻找文本 (str, “账号或者密码错误”, , 假) ≠ -1) + 返回Cookie = “账号或者密码错误” + 返回 (假) + .如果真结束 + .如果真 (寻找文本 (str, “账号登录异常”, , 假) ≠ -1) + 返回Cookie = “账号登录异常” + 返回 (假) + .如果真结束 + .如果真 (寻找文本 (str, “账号不存在”, , 假) ≠ -1) + 返回Cookie = “账号不存在” + 返回 (假) + .如果真结束 + .如果真 (寻找文本 (str, “邮箱密码错误”, , 假) ≠ -1) + 返回Cookie = “邮箱密码错误” + 返回 (假) + .如果真结束 + + + + .否则 + 参_IP = GetIP () + httpR.SetProxy (参_IP) + .如果结束 + +.计次循环尾 () +.如果真 (局_CK = “”) + 返回 (假) +.如果真结束 + +返回Cookie = 网页_Cookie合并更新 (CK, 局_CK) +返回 (真) + + +.子程序 邮箱发送验证, 逻辑型 +.参数 参_code, 文本型 +.参数 参_CK, 文本型 +.局部变量 httpR, WinHttpR +.局部变量 str, 文本型 +.局部变量 zyjson, zyJsonValue +.局部变量 err0, 整数型 +.局部变量 runip, 文本型 + +runip = GetIP () + +httpR.SetProxy (runip) +.计次循环首 (3, ) + httpR.Open (“POST”, “https://passport.douyu.com/iframe/sendEmail”) + httpR.SetRequestHeaders (#常量8) + httpR.SetCookie (参_CK) + httpR.Send (“cacheId=” + 参_code) + str = 编码_usc2到ansi (httpR.GetResponseTextU2A ()) + + ' 调试输出 (“邮箱发送验证-”, str) + .如果 (str ≠ “”) + zyjson.解析 (str) + err0 = zyjson.取整数 (“error”) + .如果真 (err0 = 0) + 返回 (真) + .如果真结束 + .如果真 (err0 = -1) + 返回 (假) + .如果真结束 + + .否则 + runip = GetIP () + .如果结束 + + 程序_延时 (500) +.计次循环尾 () +返回 (假) + +.子程序 提交邮件码验证, 逻辑型 +.参数 参_code, 文本型 +.参数 参_验证码, 文本型 +.参数 参_CK, 文本型 +.参数 返回jumURL, 文本型, 参考 +.局部变量 httpR, WinHttpR +.局部变量 str, 文本型 +.局部变量 zyjson, zyJsonValue +.局部变量 局_url, 文本型 +.局部变量 runip, 文本型 +.局部变量 error, 文本型 + +runip = GetIP () +httpR.SetProxy (runip) +.计次循环首 (10, ) + httpR.Open (“POST”, “https://passport.douyu.com/iframe/remoteLogin”) + httpR.SetRequestHeaders (#常量8) + httpR.SetCookie (参_CK) + httpR.Send (“veify_type=2&phoneCaptcha=” + 参_验证码 + “&client_id=1&isMultiAccount=0&cacheId=” + 参_code) + str = httpR.GetResponseTextU2A () + ' 调试输出 (取程序名称 (1), str) + zyjson.解析 (str) + error = zyjson.取文本 (“error”) + 局_url = 编码_usc2到ansi (zyjson.取文本 (“data.url”)) + .如果真 (error = “101”) + 返回jumURL = zyjson.取文本 (“msg”) + 返回 (假) + .如果真结束 + + .如果 (局_url ≠ “”) + 返回jumURL = 局_url + 返回 (真) + + .否则 + runip = GetIP () + .如果结束 + 程序_延时 (300) +.计次循环尾 () +返回 (假) + +.子程序 登录取Cookie, 逻辑型 +.参数 参_IP, 文本型 +.参数 参_ck, 文本型 +.参数 参_url, 文本型 +.参数 runCK, 文本型, 参考 +.局部变量 httpR, WinHttpR +.局部变量 str, 文本型 +.局部变量 局_Cookie, 文本型 +.局部变量 局_代理IP, 文本型 + +' 调试输出 (“参_url---”, 参_url) +httpR.SetProxy (参_IP) +.计次循环首 (5, ) + httpR.Open (“GET”, “https:” + 参_url + “&callback=json_callback&_” + 时间_取现行时间戳 () + “=”) + httpR.SetRequestHeaders (#常量9) + httpR.SetCookie (参_ck) + httpR.Send () + 局_Cookie = httpR.GetAllResponseCookies () + str = 编码_usc2到ansi (httpR.GetResponseTextU2A ()) + ' 调试输出 (“登录---”, str) + ' 调试输出 (“局_Cookie--”, 局_Cookie) + .如果 (str ≠ “”) + .如果 (寻找文本 (str, “登录失败”, , 假) ≠ -1 或 寻找文本 (str, “无效”, , 假) ≠ -1) + runCK = “登录失败” + 返回 (假) + + .否则 + .如果真 (局_Cookie ≠ “”) + runCK = 局_Cookie + 返回 (真) + .如果真结束 + + .如果结束 + + .否则 + 参_IP = GetIP () + httpR.SetProxy (参_IP) + .如果结束 + +.计次循环尾 () +局_Cookie = “取CK失败” +返回 (假) + + +.子程序 查询SM, 逻辑型 +.参数 参_CK, 文本型 +.局部变量 str, 文本型 +.局部变量 zyjson, zyJsonValue +.局部变量 ident, 文本型 +.局部变量 局_IP, 文本型 + +局_IP = GetIP () +.计次循环首 (3, ) + str = 编码_Utf8到Ansi (网页_访问_对象 (“https://www.douyu.com/member/cp/cp_rpc_ajax”, , , 参_CK, , “Host: www.douyu.com” + #换行符 + “Connection: keep-alive” + #换行符 + “sec-ch-ua: ” + #引号 + “Google Chrome” + #引号 + “;v=” + #引号 + “119” + #引号 + “, ” + #引号 + “Chromium” + #引号 + “;v=” + #引号 + “119” + #引号 + “, ” + #引号 + “Not?A_Brand” + #引号 + “;v=” + #引号 + “24” + #引号 + “” + #换行符 + “Accept: application/json, text/javascript, */*; q=0.01” + #换行符 + “X-Requested-With: XMLHttpRequest” + #换行符 + “sec-ch-ua-mobile: ?0” + #换行符 + “User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.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/member/cp”, , , 真, , 局_IP)) + .如果 (str ≠ “”) + zyjson.清空 () + zyjson.解析 (str) + ident = zyjson.取文本 (“info.ident”) + .如果真 (寻找文本 (ident, “******”, , 假) ≠ -1) + 返回 (真) + .如果真结束 + .如果真 (ident = “false”) + 返回 (假) + .如果真结束 + + .否则 + 局_IP = GetIP () + .如果结束 + +.计次循环尾 () +返回 (假) + +.子程序 查询账号状态, 逻辑型 +.参数 参_CK, 文本型 +.局部变量 str, 文本型 +.局部变量 zyjson, zyJsonValue +.局部变量 ident, 文本型 +.局部变量 局_IP, 文本型 + +局_IP = GetIP () +.计次循环首 (3, ) + str = 编码_Utf8到Ansi (网页_访问_对象 (“https://yuba.douyu.com/ybapi/topic/sign?timestamp=17011600”, 1, “group_id=2493219&cur_exp=0”, 参_CK, , “Host: yuba.douyu.com” + #换行符 + “Connection: keep-alive” + #换行符 + “Content-Length: 26” + #换行符 + “sec-ch-ua: ” + #引号 + “Google Chrome” + #引号 + “;v=” + #引号 + “119” + #引号 + “, ” + #引号 + “Chromium” + #引号 + “;v=” + #引号 + “119” + #引号 + “, ” + #引号 + “Not?A_Brand” + #引号 + “;v=” + #引号 + “24” + #引号 + “” + #换行符 + “Content-Type: application/x-www-form-urlencoded” + #换行符 + “X-CSRF-TOKEN: 2gBHQlimO9bVXG3Rj60IJ7siFA8y5Wdg” + #换行符 + “sec-ch-ua-mobile: ?0” + #换行符 + “User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36” + #换行符 + “sec-ch-ua-platform: ” + #引号 + “Windows” + #引号 + “” + #换行符 + “Accept: */*” + #换行符 + “Origin: https://yuba.douyu.com” + #换行符 + “Referer: https://yuba.douyu.com/group/2493219” + #换行符 + “Accept-Language: zh-CN,zh;q=0.9”, , , 真, , 局_IP)) + .如果 (str ≠ “”) + zyjson.清空 () + zyjson.解析 (str) + ident = zyjson.取文本 (“info.ident”) + .如果真 (寻找文本 (ident, “******”, , 假) ≠ -1) + 返回 (真) + .如果真结束 + .如果真 (ident = “false”) + 返回 (假) + .如果真结束 + + .否则 + 局_IP = GetIP () + .如果结束 + +.计次循环尾 () +返回 (假) + + +.子程序 _取单条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/routers/login.py b/web/backend/routers/login.py index be91bf8..7f00d4c 100644 --- a/web/backend/routers/login.py +++ b/web/backend/routers/login.py @@ -60,6 +60,7 @@ async def create_batch( log_queue=log_queue, loop=loop, concurrency=req.concurrency, + api_strategy=req.api_strategy, ) batch_id = runner.batch_id diff --git a/web/backend/schemas.py b/web/backend/schemas.py index ad0b40c..c38beb8 100644 --- a/web/backend/schemas.py +++ b/web/backend/schemas.py @@ -127,6 +127,7 @@ class LoginBatchRequest(BaseModel): max_login_retries: int = 0 # 登录整体重试次数,0=无限重试直到成功(后端兜底 20 次) max_total_time: float = 300 # 单账号登录总时长上限(秒),0=不限制 concurrency: int = 3 # 并发数,1-10 + api_strategy: str = "wgapi" # 接口策略: wgapi(新版)或 iframe(旧版备选) class LoginTaskOut(BaseModel): diff --git a/web/backend/services/login_service.py b/web/backend/services/login_service.py index 8d32233..b48c7f2 100644 --- a/web/backend/services/login_service.py +++ b/web/backend/services/login_service.py @@ -11,11 +11,18 @@ from typing import Optional from sqlalchemy.orm import Session -from core.douyu import DouyuLogin +from core.douyu import DouyuLogin, WgapiLoginAPI, IframeLoginAPI from core.douyu.proxy_fetcher import ProxyFetcher from ..models import Account as AccountModel, LoginTask, ProxyConfig as ProxyConfigModel +def _create_api_strategy(strategy_name: str): + """根据名称创建登录接口策略实例。""" + if strategy_name == "iframe": + return IframeLoginAPI() + return WgapiLoginAPI() + + class LoginBatchRunner: """批量登录执行器,在线程中运行,通过 ThreadPoolExecutor 并发登录多个账号。""" @@ -31,6 +38,7 @@ class LoginBatchRunner: log_queue: Optional[asyncio.Queue] = None, loop: Optional[asyncio.AbstractEventLoop] = None, concurrency: int = 3, + api_strategy: str = "wgapi", ): self.db = db self.account_ids = account_ids @@ -45,6 +53,7 @@ class LoginBatchRunner: self.loop = loop self.batch_id = uuid.uuid4().hex[:12] self.concurrency = max(1, min(concurrency, 10)) # 限制 1-10 + self.api_strategy = _create_api_strategy(api_strategy) self._stop = threading.Event() self._counter_lock = threading.Lock() self._completed = 0 @@ -154,6 +163,7 @@ class LoginBatchRunner: max_total_time=self.max_total_time, proxy_fetcher=self._shared_proxy_fetcher, stop_event=self._stop, + api_strategy=self.api_strategy, ) result = loginer.login() diff --git a/web/frontend/src/api/login.ts b/web/frontend/src/api/login.ts index e0b10d6..0f261eb 100644 --- a/web/frontend/src/api/login.ts +++ b/web/frontend/src/api/login.ts @@ -6,6 +6,7 @@ interface CreateBatchParams { concurrency?: number; max_login_retries?: number; max_total_time?: number; + api_strategy?: string; } export const loginApi = { diff --git a/web/frontend/src/pages/LoginTasksPage.tsx b/web/frontend/src/pages/LoginTasksPage.tsx index d606eb7..24483bc 100644 --- a/web/frontend/src/pages/LoginTasksPage.tsx +++ b/web/frontend/src/pages/LoginTasksPage.tsx @@ -51,12 +51,16 @@ export default function LoginTasksPage() { const v = localStorage.getItem('login_max_login_retries'); return v !== null ? Math.max(0, Number(v) || 0) : 0; }); + const [apiStrategy, setApiStrategy] = useState(() => { + return localStorage.getItem('login_api_strategy') || 'wgapi'; + }); const [settingsOpen, setSettingsOpen] = useState(false); // 值变化时自动持久化 useEffect(() => { localStorage.setItem('login_concurrency', String(concurrency)); }, [concurrency]); useEffect(() => { localStorage.setItem('login_max_total_time', String(maxTotalTime)); }, [maxTotalTime]); useEffect(() => { localStorage.setItem('login_max_login_retries', String(maxLoginRetries)); }, [maxLoginRetries]); + useEffect(() => { localStorage.setItem('login_api_strategy', apiStrategy); }, [apiStrategy]); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const { logs, connected: wsConnected, connect: connectLogs } = useWebSocketLogs(); const { can } = usePermissions(); @@ -156,6 +160,7 @@ export default function LoginTasksPage() { concurrency, max_login_retries: maxLoginRetries, max_total_time: maxTotalTime, + api_strategy: apiStrategy, }); setBatchId(result.batch_id); message.success(`已创建登录任务,共 ${result.count} 个账号`); @@ -342,7 +347,7 @@ export default function LoginTasksPage() { 标签选中 {accounts.filter((a) => selectedTags.includes((a.tag || '').trim())).length} 个 )} - +