NOTE本文由现有代码,在 AI 辅助下完成。若在实现时遇到问题,欢迎询问。
所需内容
| 变量 | 说明 |
|---|---|
verify_session | 验证码 ID,随机生成的 48 位十六进制字符串 |
DEVICE_IDENTIFIER | 设备标识符,格式:yyyyMMddHHmmss + 38 位随机十六进制 |
DEVICE_IDENTIFIER_SM | 副设备标识符,生成规则同上 |
username | 4399 账号用户名 |
password | 4399 账号密码 |
udid | 随机生成的 16 位十六进制字符串(用于最终 sauth_json) |
步骤详解
1. 获取文字验证码
GET https://ptlogin.4399.com/ptlogin/captcha.do?captchaId={{verify_session}}返回一张验证码图片,读出验证码记为 verify_code。
2. 获取 OAuth 登录 URL
POST https://m.4399api.com/openapiv2/oauth.htmlUser-Agent: Dalvik/2.1.0 (Linux; U; Android 16; Build/UKQ1.231108.001) 4399android 4399OperateSDKContent-Type: application/x-www-form-urlencoded; charset=UTF-8
usernames=&top_bar=1&state=&device={{device_json}}device_json 为以下结构序列化后的字符串:
| 字段 | 值 |
|---|---|
DEVICE_IDENTIFIER | 见上文 |
DEVICE_IDENTIFIER_SM | 见上文 |
DEVICE_MODEL | Android |
DEVICE_MODEL_VERSION | 16 |
SYSTEM_VERSION | 16 |
PLATFORM_TYPE | Android |
SDK_VERSION | 3.12.2.503 |
GAME_KEY | 115716 |
GAME_VERSION | 3.7.15.287957 |
BID | com.netease.mc.m4399 |
RUNTIME | Android |
NETWORK_TYPE | WIFI |
TEAM | 2 |
| 其余字段 | 空字符串 |
从响应 JSON 中取 result.login_url,解析其 Query 参数,记录 client_id、ref、state。
3. 提交账号密码登录
POST https://ptlogin.4399.com/oauth2/loginAndAuthorize.do?channel=&sdk=op&sdk_version=3.12.2.503User-Agent: Mozilla/5.0 (Linux; Android 16; ...) 4399android 4399OperateSDKOrigin: https://ptlogin.4399.comReferer: https://ptlogin.4399.com/oauth2/authorize.do?channel=&sdk=op&sdk_version=3.12.2.503Content-Type: application/x-www-form-urlencoded; charset=UTF-8
username={{username}}&password={{password}}&captcha_id={{verify_session}}&captcha={{verify_code}}&response_type=TOKEN&client_id={{client_id}}&ref={{ref}}&state={{state}}&scope=basic&bizId=2100001792&auth_action=ORILOGIN&redirect_uri=https://m.4399api.com/openapi/oauth-callback.html?gamekey=44770&game_key=115716®_mode=reg_phone&isInputRealname=false&isValidRealname=false&sec=0&show_topbar=false&css=&show_close_button=&show_4399=&username_history=&uid=&expand_ext_login_list=&autoCreateAccount=&show_ext_login=&show_back_button=&auto_scroll=&access_token=&show_forget_password=&aid=&cid=WARNING不跟随重定向,期望收到 3xx 响应。若非 3xx,则登录失败,可从响应 HTML 中提取错误信息(如实名认证失效、验证码错误等)。
取响应头 Location,记为 callback_url,然后 GET 该地址获取登录结果 JSON。
4. 处理登录结果
响应 JSON 中的 code 字段:
code | 含义 |
|---|---|
100 | 登录成功,取 result.uid 和 result.state |
103 | 需要二次人机验证,取 result.url 作为验证码接口地址,进入步骤 5 |
| 其他 | 登录失败 |
5. 处理二次验证码(code=103 时)
5.1 获取验证码数据
GET {{result.url}}根据 URL 路径判断验证码类型,取对应字段:
| URL 含 | 类型 | 响应字段 |
|---|---|---|
jigsaw | 拼图滑块 | result.img、result.img2、result.captchaId |
click | 文字点击 | result.img、result.text、result.captchaId |
5.2 拼图滑块(jigsaw)
图片说明
result.img:背景图(含缺口),base64 编码的 PNG,从图片左上角开始剪切 544×270 作为背景图result.img2:滑块图片,base64 编码的 PNG,需叠加在背景图左侧起始位置
用户操作
将两张图片叠加展示,滑块初始位于最左侧(x=0)。用户横向拖动滑块至缺口位置后释放,记录拖动距离(像素)。
若展示容器宽度与原始图片宽度不一致(存在缩放),需按比例换算为实际坐标:
actualDistance = round(displayDistance / containerWidth * 544)answer 构造
将实际距离组装为 JSON:
{"x": {{actualDistance}}}然后用 AES-CBC 加密该 JSON 字符串:
| 参数 | 值 |
|---|---|
| Key | MD5(“df0a5f98c337de97”)(16 字节) |
| IV | UTF-8 bytes of “79c83220d9974edf”(16 字节) |
| Mode | CBC |
| Padding | PKCS7 |
| 输出 | Base64 字符串 |
最终 answer = 上述 Base64 密文,URL 编码后作为 v 参数提交:
GET https://m.4399api.com/captcha/jigsaw-check.html?refer=sdk&v={{answer}}&captchaId={{captchaId}}5.3 文字点击(click)
图片说明
result.img:待点击的图片,base64 编码的 PNG,原始尺寸 544×306result.text:需要点击的文字,逗号分隔,如"字A,字B,字C",点击次数由逗号分隔数量决定
用户操作
展示图片,用户按 result.text 的顺序依次点击图中对应文字,共需点击 N 次(N = result.text 中逗号分隔项的数量)。
每次点击记录坐标,若展示尺寸与原始尺寸不一致,需换算:
actualX = round(displayX / displayWidth * 544)actualY = round(displayY / displayHeight * 306)answer 构造
将所有点击坐标按顺序组装为 JSON 字符串(不加密):
{"w": 544, "h": 306, "c": [{"x": {{x1}}, "y": {{y1}}}, {"x": {{x2}}, "y": {{y2}}}, ...]}URL 编码后作为 v 参数提交:
GET https://m.4399api.com/captcha/click-check.html?refer=sdk&v={{answer}}&captchaId={{captchaId}}5.4 处理校验结果
响应 code | 处理 |
|---|---|
!= 100 | 重新 GET result.url 刷新验证码数据,要求用户重试 |
100 | 取 result.token,进入下一步完成登录 |
取得 result.token 后,构造验证码凭据 JSON 字符串:
{"v_token": "{{token}}", "captcha_id": "{{captchaId}}", "type": "0"}将该字符串 URL 编码,作为 captcha 参数追加到 callback_url,再次 GET 完成登录,回到步骤 4 处理结果。
6. 构造 SAuth
{ "sauth_json": "{{sauth_json_dump}}"}sauth_json_dump 为以下 JSON 序列化后的字符串:
{ "aim_info": "{\"aim\":\"127.0.0.1\",\"country\":\"CN\",\"tz\":\"+0800\",\"tzid\":\"\"}", "realname": "{\"realname_type\":2}", "app_channel": "4399com", "platform": "ad", "client_login_sn": "{{DEVICE_IDENTIFIER}}", "gameid": "x19", "login_channel": "4399com", "sdk_version": "3.12.2", "sdkuid": "{{uid}}", "sessionid": "{{state}}", "udid": "{{udid}}", "deviceid": "{{DEVICE_IDENTIFIER}}"}