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