futucli/cmd/sys/
user_info.rs1use std::sync::Arc;
2
3use anyhow::{Result, anyhow, bail};
4use futu_net::client::FutuClient;
5use prost::Message;
6use serde::Serialize;
7use tabled::Tabled;
8
9use crate::common::connect_gateway;
10use crate::output::OutputFormat;
11
12#[derive(Tabled)]
13struct UserInfoRow {
14 #[tabled(rename = "Field")]
15 field: String,
16 #[tabled(rename = "Value")]
17 value: String,
18}
19
20#[derive(Serialize)]
21struct UserInfoJson {
22 nick_name: Option<String>,
23 user_id: Option<i64>,
24 user_attribution: Option<i32>,
25 user_attribution_region: Option<&'static str>,
26 hk_qot_right: Option<i32>,
27 us_qot_right: Option<i32>,
28 cn_qot_right: Option<i32>,
29 cc_qot_right: Option<i32>,
30 sg_stock_qot_right: Option<i32>,
31 my_stock_qot_right: Option<i32>,
32 jp_stock_qot_right: Option<i32>,
33 sub_quota: Option<i32>,
34 history_kl_quota: Option<i32>,
35}
36
37pub(super) fn user_attribution_region_label(user_attribution: Option<i32>) -> Option<&'static str> {
38 user_attribution
39 .and_then(|v| u32::try_from(v).ok())
40 .and_then(futu_backend::auth::UserAttribution::from_u32)
41 .map(|attr| attr.region())
42}
43
44pub async fn run_user_info(gateway: &str, format: OutputFormat) -> Result<()> {
45 let (client, _rx) = connect_gateway(gateway, "futucli-user-info").await?;
46 let resp = fetch_user_info(&client).await?;
47 if resp.ret_type != 0 {
48 bail!(
49 "user_info ret_type={} msg={:?}",
50 resp.ret_type,
51 resp.ret_msg
52 );
53 }
54 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
55 let user_attribution_region = user_attribution_region_label(s.user_attribution);
56 let rows = vec![
57 UserInfoRow {
58 field: "nick_name".into(),
59 value: s.nick_name.clone().unwrap_or_else(|| "-".into()),
60 },
61 UserInfoRow {
62 field: "user_id".into(),
63 value: s
64 .user_id
65 .map(|u| u.to_string())
66 .unwrap_or_else(|| "-".into()),
67 },
68 UserInfoRow {
69 field: "user_attribution".into(),
70 value: s
71 .user_attribution
72 .map(|u| u.to_string())
73 .unwrap_or_else(|| "-".into()),
74 },
75 UserInfoRow {
76 field: "user_attribution_region".into(),
77 value: user_attribution_region.unwrap_or("-").into(),
78 },
79 UserInfoRow {
80 field: "hk_qot_right".into(),
81 value: s
82 .hk_qot_right
83 .map(|v| v.to_string())
84 .unwrap_or_else(|| "-".into()),
85 },
86 UserInfoRow {
87 field: "us_qot_right".into(),
88 value: s
89 .us_qot_right
90 .map(|v| v.to_string())
91 .unwrap_or_else(|| "-".into()),
92 },
93 UserInfoRow {
94 field: "cn_qot_right".into(),
95 value: s
96 .cn_qot_right
97 .map(|v| v.to_string())
98 .unwrap_or_else(|| "-".into()),
99 },
100 UserInfoRow {
101 field: "cc_qot_right".into(),
102 value: s
103 .cc_qot_right
104 .map(|v| v.to_string())
105 .unwrap_or_else(|| "-".into()),
106 },
107 UserInfoRow {
108 field: "sg_stock_qot_right".into(),
109 value: s
110 .sg_stock_qot_right
111 .map(|v| v.to_string())
112 .unwrap_or_else(|| "-".into()),
113 },
114 UserInfoRow {
115 field: "my_stock_qot_right".into(),
116 value: s
117 .my_stock_qot_right
118 .map(|v| v.to_string())
119 .unwrap_or_else(|| "-".into()),
120 },
121 UserInfoRow {
122 field: "jp_stock_qot_right".into(),
123 value: s
124 .jp_stock_qot_right
125 .map(|v| v.to_string())
126 .unwrap_or_else(|| "-".into()),
127 },
128 UserInfoRow {
129 field: "sub_quota".into(),
130 value: s
131 .sub_quota
132 .map(|v| v.to_string())
133 .unwrap_or_else(|| "-".into()),
134 },
135 UserInfoRow {
136 field: "history_kl_quota".into(),
137 value: s
138 .history_kl_quota
139 .map(|v| v.to_string())
140 .unwrap_or_else(|| "-".into()),
141 },
142 ];
143 let json = UserInfoJson {
144 nick_name: s.nick_name,
145 user_id: s.user_id,
146 user_attribution: s.user_attribution,
147 user_attribution_region,
148 hk_qot_right: s.hk_qot_right,
149 us_qot_right: s.us_qot_right,
150 cn_qot_right: s.cn_qot_right,
151 cc_qot_right: s.cc_qot_right,
152 sg_stock_qot_right: s.sg_stock_qot_right,
153 my_stock_qot_right: s.my_stock_qot_right,
154 jp_stock_qot_right: s.jp_stock_qot_right,
155 sub_quota: s.sub_quota,
156 history_kl_quota: s.history_kl_quota,
157 };
158 format.print_rows(&rows, &[json])?;
159 Ok(())
160}
161
162async fn fetch_user_info(client: &Arc<FutuClient>) -> Result<futu_proto::get_user_info::Response> {
163 let req = futu_proto::get_user_info::Request {
164 c2s: futu_proto::get_user_info::C2s { flag: None },
165 };
166 let body = req.encode_to_vec();
167 let frame = client
168 .request(futu_core::proto_id::GET_USER_INFO, body)
169 .await?;
170 futu_proto::get_user_info::Response::decode(frame.body.as_ref())
171 .map_err(|e| anyhow!("decode user_info: {e}"))
172}