1#![allow(unused_imports)]
8
9use std::io::IsTerminal;
10
11use crate::config::RuntimeConfig;
12
13pub fn print_auth_error_hint(e: &futu_core::error::FutuError, config: &RuntimeConfig) {
18 let err_str = format!("{e}");
19 let hint_21 = err_str.contains("ret_type=21") || err_str.contains("验证码错");
20 let hint_15 = err_str.contains("ret_type=15") || err_str.contains("长时间没有登录");
21 let hint_firewall =
25 err_str.contains("Platform IP pool exhausted") || err_str.contains("timed out");
26 let hint_2 = err_str.contains("ret_type=2,")
28 || err_str.contains("ret_type=2 ")
29 || err_str.contains("账号密码不匹配");
30 let hint_45 = err_str.contains("ret_type=45") || err_str.contains("当前应用版本过低");
31 let hint_dns = err_str.contains("dns error")
32 || err_str.contains("failed to lookup")
33 || err_str.contains("Name or service not known")
34 || err_str.contains("nodename nor servname");
35 let hint_sms_noninteractive = (err_str.contains("ret_type=20")
37 || err_str.contains("require_device_verify")
38 || err_str.contains("device_verify_sig"))
39 && !std::io::IsTerminal::is_terminal(&std::io::stdin())
40 && config.verify_code.is_none();
41 if hint_21 {
42 tracing::error!(
43 error = %e,
44 "gateway init failed: SMS code mismatch (ret_type=21). \
45 Device_id may be locked after multiple wrong codes — try \
46 `futu-opend --reset-device --setup-only ...` to regenerate \
47 and re-verify via SMS."
48 );
49 if config.setup_only
58 && std::io::IsTerminal::is_terminal(&std::io::stdin())
59 && config.verify_code.is_some()
60 {
61 eprintln!();
62 eprintln!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
63 eprintln!("⚠️ v1.4.72 BUG-009 Fix 9b: SMS 验证码错 (ret_type=21)");
64 eprintln!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
65 eprintln!();
66 eprintln!(" daemon 不立即退出,让你有时间读完错误 + 决定下一步。");
67 eprintln!();
68 eprintln!(" 下一步建议(不要盲目重跑 daemon,避免新 SMS + 账户锁):");
69 eprintln!(" 1. 等 30 秒,等服务端限流过");
70 eprintln!(" 2. 检查手机上最新收到的 SMS(可能有多条,用最新那条)");
71 eprintln!(" 3. 重跑 daemon 带新码:");
72 eprintln!(" futu-opend --setup-only --verify-code <新 SMS 码> ...");
73 eprintln!();
74 eprintln!(" 按 Enter 退出(Ctrl+C 也可)...");
75 let _ = std::io::stdin().read_line(&mut String::new());
76 }
77 } else if hint_15 {
78 eprintln!();
83 eprintln!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
84 eprintln!("⚠️ Gateway init failed: server returned ret_type=15");
85 eprintln!(" (\"请重新输入密码\" — 这是服务端状态机错乱的统称)");
86 eprintln!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
87 eprintln!();
88 eprintln!("这个错误有 **5 种可能根因**,按优先级逐条排查:");
89 eprintln!();
90 eprintln!(" [0] **有另一个 Futu OpenD 正在用这个账号**(v1.4.52 新增)");
91 eprintln!(" → 检查所有机器:`lsof -i :11111` + `ps aux | grep -i opend`");
92 eprintln!(" → 停掉另一个 OpenD(C++ 或 Rust),然后重试");
93 eprintln!();
94 eprintln!(" [1] **服务端反刷限流**(最常见,连续重启触发)");
95 eprintln!(" → 等 60 秒后再重试");
96 eprintln!();
97 eprintln!(" [2] **SMS 超限 + device_id 被封**(v1.4.57 新增)");
98 eprintln!(" → 等 3-5 分钟让限流过");
99 eprintln!(" → 手机上登录富途/moomoo App 一次清账号状态");
100 eprintln!(" → 重启 daemon 带 `--verify-code <CODE>` 避免 stdin 延迟");
101 eprintln!();
102 eprintln!(" [3] **device_id 毒化**(空 SMS 提交 / 长时间不用)");
103 eprintln!(" → 先试 `--reset-device --setup-only`");
104 eprintln!(" → 注意:破坏性操作,会触发二次 SMS 验证");
105 eprintln!();
106 eprintln!(" [4] **账号级风控 / 状态异常**(最难)");
107 eprintln!(" → 手机上登录富途/moomoo App 一次清账号状态");
108 eprintln!(" → 若账号从未在 App 登录激活,必须先激活");
109 eprintln!();
110 eprintln!(" 建议试序:[0] → [1] → [2] → [3] → [4]");
111 eprintln!();
112 tracing::error!(
113 error = %e,
114 "ret_type=15 — see stderr for 5-cause diagnostic checklist"
115 );
116 } else if hint_firewall {
117 tracing::error!(
118 error = %e,
119 "gateway init failed: all Platform IPs are unreachable on port 9595. \
120 This is almost always an outbound firewall issue on your host, NOT \
121 a Futu server problem. Quick check: \
122 `timeout 5 bash -c '</dev/tcp/119.29.48.17/9595'` — if this also \
123 fails (it's Tencent Cloud in Guangzhou), your host is blocking \
124 outbound TCP 9595. Fix: open 9595 in your cloud security group \
125 (Tencent Cloud Lighthouse / CVM / AWS / Aliyun all default to \
126 blocking non-standard ports)."
127 );
128 } else if hint_2 {
129 eprintln!();
136 eprintln!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
137 eprintln!("⚠️ ret_type=2 — 账号密码不匹配");
138 eprintln!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
139 eprintln!();
140 eprintln!("💡 Hint: 这个错误可能 4 种根因(按概率):");
141 eprintln!(" 1. 密码真错 → 检查 --login-pwd 或 --login-pwd-md5");
142 eprintln!(" 2. --platform 选错 → futunn 系账号必须 --platform futunn,");
143 eprintln!(" moomoo (US/SG/AU/JP/CA/MY) 系账号必须 --platform moomoo");
144 eprintln!(" 3. 同号双注册 → futunn / moomoo 同手机号 / 邮箱可各注册一个,");
145 eprintln!(" 检查 `futu-opend --login-account ... --platform <对侧>` 是否能登");
146 eprintln!(" 4. 区号写错 → 手机号格式 +86-13900000000 (区号 + dash + 号码本体)");
147 eprintln!();
148 tracing::error!(error = %e, "ret_type=2 — see stderr 4-cause hint");
149 } else if hint_45 {
150 eprintln!();
152 eprintln!("⚠️ ret_type=45 — 当前应用版本过低");
153 eprintln!();
154 eprintln!("💡 Hint: 服务端对海外账号 (user_attribution != 1) 严格校验,");
155 eprintln!(" 客户端版本号需 ≥ 800(X-Futu-Client-Version 字段)。");
156 eprintln!(" 本 Rust daemon 使用 backend 可识别的 Rust 版本号 1030。");
157 eprintln!(" 若仍报 45,可能是 backend 升级了最低版本要求,");
158 eprintln!(" 升级 daemon: brew upgrade futuleaf/tap/futu-opend-rs");
159 eprintln!();
160 tracing::error!(error = %e, "ret_type=45 — version too low, see stderr hint");
161 } else if hint_dns {
162 eprintln!();
164 eprintln!("⚠️ 网络错: DNS 解析 auth server 失败");
165 eprintln!();
166 eprintln!("💡 Hint: 检查域名解析 + 网络连通性:");
167 eprintln!(" 1. ping auth.futunn.com / ping auth.moomoo.com");
168 eprintln!(" 2. 若公司 / 校园 VPN 限制了海外域名 → 切换 VPN 或开放 auth.* 解析");
169 eprintln!(" 3. 中国大陆 ISP 偶尔抽风 auth.moomoo.com → 试 8.8.8.8 DNS");
170 eprintln!(" 4. 检查 /etc/hosts 是否有错误的 auth server override");
171 eprintln!();
172 tracing::error!(error = %e, "DNS lookup failed for auth server");
173 } else if hint_sms_noninteractive {
174 eprintln!();
176 eprintln!("⚠️ SMS 验证码需要交互式终端 (stdin 不是 TTY)");
177 eprintln!();
178 eprintln!("💡 Hint: 后台 / 守护进程模式下 SMS 输入会立即返回空字符串,");
179 eprintln!(" 毒化 device_id 触发 ret_type=15 / 21。");
180 eprintln!();
181 eprintln!(" 正确部署姿势 (systemd / Docker):");
182 eprintln!(" 1. 前台一次完成 SMS:");
183 eprintln!(" futu-opend --setup-only --login-account X --login-pwd Y \\");
184 eprintln!(" --platform <futunn|moomoo>");
185 eprintln!(" (或用 --verify-code <已收到的码> 跳过 stdin)");
186 eprintln!(" 2. 完成后凭据写入 ~/.futu-opend-rs/,daemon 后续自动跳 SMS");
187 eprintln!(" 3. 启动后台 daemon:");
188 eprintln!(" futu-opend --login-account X --login-pwd Y --platform ...");
189 eprintln!();
190 tracing::error!(error = %e, "SMS required but stdin is not a TTY");
191 } else {
192 tracing::error!(error = %e, "gateway initialization failed, starting in offline mode");
193 }
194}