Skip to main content

futucli/cmd/sys/
user_info.rs

1use 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}