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    sub_quota: Option<i32>,
31    history_kl_quota: Option<i32>,
32}
33
34pub(super) fn user_attribution_region_label(user_attribution: Option<i32>) -> Option<&'static str> {
35    user_attribution
36        .and_then(|v| u32::try_from(v).ok())
37        .and_then(futu_backend::auth::UserAttribution::from_u32)
38        .map(|attr| attr.region())
39}
40
41pub async fn run_user_info(gateway: &str, format: OutputFormat) -> Result<()> {
42    let (client, _rx) = connect_gateway(gateway, "futucli-user-info").await?;
43    let resp = fetch_user_info(&client).await?;
44    if resp.ret_type != 0 {
45        bail!(
46            "user_info ret_type={} msg={:?}",
47            resp.ret_type,
48            resp.ret_msg
49        );
50    }
51    let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
52    let user_attribution_region = user_attribution_region_label(s.user_attribution);
53    let rows = vec![
54        UserInfoRow {
55            field: "nick_name".into(),
56            value: s.nick_name.clone().unwrap_or_else(|| "-".into()),
57        },
58        UserInfoRow {
59            field: "user_id".into(),
60            value: s
61                .user_id
62                .map(|u| u.to_string())
63                .unwrap_or_else(|| "-".into()),
64        },
65        UserInfoRow {
66            field: "user_attribution".into(),
67            value: s
68                .user_attribution
69                .map(|u| u.to_string())
70                .unwrap_or_else(|| "-".into()),
71        },
72        UserInfoRow {
73            field: "user_attribution_region".into(),
74            value: user_attribution_region.unwrap_or("-").into(),
75        },
76        UserInfoRow {
77            field: "hk_qot_right".into(),
78            value: s
79                .hk_qot_right
80                .map(|v| v.to_string())
81                .unwrap_or_else(|| "-".into()),
82        },
83        UserInfoRow {
84            field: "us_qot_right".into(),
85            value: s
86                .us_qot_right
87                .map(|v| v.to_string())
88                .unwrap_or_else(|| "-".into()),
89        },
90        UserInfoRow {
91            field: "cn_qot_right".into(),
92            value: s
93                .cn_qot_right
94                .map(|v| v.to_string())
95                .unwrap_or_else(|| "-".into()),
96        },
97        UserInfoRow {
98            field: "cc_qot_right".into(),
99            value: s
100                .cc_qot_right
101                .map(|v| v.to_string())
102                .unwrap_or_else(|| "-".into()),
103        },
104        UserInfoRow {
105            field: "sub_quota".into(),
106            value: s
107                .sub_quota
108                .map(|v| v.to_string())
109                .unwrap_or_else(|| "-".into()),
110        },
111        UserInfoRow {
112            field: "history_kl_quota".into(),
113            value: s
114                .history_kl_quota
115                .map(|v| v.to_string())
116                .unwrap_or_else(|| "-".into()),
117        },
118    ];
119    let json = UserInfoJson {
120        nick_name: s.nick_name,
121        user_id: s.user_id,
122        user_attribution: s.user_attribution,
123        user_attribution_region,
124        hk_qot_right: s.hk_qot_right,
125        us_qot_right: s.us_qot_right,
126        cn_qot_right: s.cn_qot_right,
127        cc_qot_right: s.cc_qot_right,
128        sub_quota: s.sub_quota,
129        history_kl_quota: s.history_kl_quota,
130    };
131    format.print_rows(&rows, &[json])?;
132    Ok(())
133}
134
135async fn fetch_user_info(client: &Arc<FutuClient>) -> Result<futu_proto::get_user_info::Response> {
136    let req = futu_proto::get_user_info::Request {
137        c2s: futu_proto::get_user_info::C2s { flag: None },
138    };
139    let body = req.encode_to_vec();
140    let frame = client
141        .request(futu_core::proto_id::GET_USER_INFO, body)
142        .await?;
143    futu_proto::get_user_info::Response::decode(frame.body.as_ref())
144        .map_err(|e| anyhow!("decode user_info: {e}"))
145}