1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::utils::error::Error;
5
6#[derive(Clone, Serialize, Deserialize, Default)]
7#[allow(non_snake_case)]
8pub struct UserInfo {
9 #[serde(rename = "oId")]
11 oId: String,
12 userNo: String,
14 userName: String,
16 userNickname: String,
18 #[serde(rename = "userURL")]
20 URL: String,
21 #[serde(rename = "userCity")]
23 city: String,
24 #[serde(rename = "userIntro")]
26 intro: String,
27 #[serde(rename = "userOnlineFlag")]
29 online: bool,
30 #[serde(rename = "userPoint")]
32 points: i32,
33 #[serde(rename = "userRole")]
35 role: String,
36 #[serde(rename = "userAppRole")]
38 appRole: UserAppRole,
39 #[serde(rename = "userAvatarURL")]
41 avatar: String,
42 cardBg: String,
44 #[serde(rename = "followingUserCount")]
46 following: i32,
47 #[serde(rename = "followerCount")]
49 follower: i32,
50 #[serde(rename = "onlineMinute")]
52 onlineMinutes: i32,
53 sysMetal: Vec<Metal>,
59 }
62
63impl UserInfo {
64 pub fn from_value(data: &Value) -> Result<Self, Error> {
65 let mut data = data.clone();
66
67 if let Some(sys_metal_str) = data["sysMetal"].as_str() {
68 let metals = to_metal(sys_metal_str).map_err(|e| Error::Parse(e.to_string()))?;
69 data["sysMetal"] =
70 serde_json::to_value(metals).map_err(|e| Error::Parse(e.to_string()))?;
71 }
72
73 if let Some(owned_metal_str) = data["ownedMetal"].as_str() {
74 let metals = to_metal(owned_metal_str).map_err(|e| Error::Parse(e.to_string()))?;
75 data["ownedMetal"] =
76 serde_json::to_value(metals).map_err(|e| Error::Parse(e.to_string()))?;
77 }
78
79 serde_json::from_value(data)
80 .map_err(|e| Error::Parse(format!("Failed to parse UserInfo: {}", e)))
81 }
82}
83
84#[derive(Clone, Serialize, Deserialize)]
86#[allow(non_snake_case)]
87pub struct UpdateUserInfoParams {
88 pub nickName: Option<String>,
90 pub userTag: Option<String>,
92 pub userUrl: Option<String>,
94 pub userIntro: Option<String>,
96 pub mbti: Option<String>,
98}
99
100#[derive(Clone, Serialize, Deserialize, Default)]
101#[repr(u8)]
102#[serde(try_from = "String")]
103enum UserAppRole {
104 #[default]
106 Hack = 0,
107 Artist = 1,
109}
110
111#[derive(Clone, Serialize, Deserialize, Default, Debug)]
112pub struct MetalBase {
113 pub attr: MetalAttrOrString,
114 pub name: String,
115 pub description: String,
116 pub data: String,
117}
118
119#[derive(Clone, Serialize, Deserialize, Default, Debug)]
120pub struct MetalAttr {
121 url: String,
123 backcolor: String,
125 fontcolor: String,
127 ver: f32,
129 scale: f32,
131}
132
133#[derive(Clone, Serialize, Deserialize, Debug)]
134pub enum MetalAttrOrString {
135 Attr(MetalAttr),
136 Str(String),
137}
138
139impl std::fmt::Display for MetalAttrOrString {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 match self {
142 MetalAttrOrString::Attr(attr) => {
143 write!(
144 f,
145 "ver={}&scale={}&backcolor={}&fontcolor={}&url={}",
146 attr.ver, attr.scale, attr.backcolor, attr.fontcolor, attr.url
147 )
148 }
149 MetalAttrOrString::Str(s) => write!(f, "{}", s),
150 }
151 }
152}
153
154#[derive(Clone, Serialize, Deserialize, Debug)]
155pub struct Metal {
156 base: MetalBase,
158 url: String,
160 icon: String,
162 enable: bool,
164}
165
166#[derive(Clone, Deserialize)]
167#[allow(non_snake_case)]
168pub struct AtUser {
169 pub userName: String,
171 pub userAvatarURL: String,
173 pub userNameLowerCase: String,
175}
176
177impl AtUser {
178 pub fn from_value(value: &Value) -> Result<Self, Error> {
179 serde_json::from_value(value.clone())
180 .map_err(|e| Error::Parse(format!("Failed to parse AtUser: {}", e)))
181 }
182}
183
184impl From<u8> for UserAppRole {
185 fn from(value: u8) -> Self {
186 match value {
187 0 => UserAppRole::Hack,
188 1 => UserAppRole::Artist,
189 _ => UserAppRole::Hack,
190 }
191 }
192}
193
194impl From<UserAppRole> for u8 {
195 fn from(value: UserAppRole) -> Self {
196 match value {
197 UserAppRole::Hack => 0,
198 UserAppRole::Artist => 1,
199 }
200 }
201}
202
203impl TryFrom<String> for UserAppRole {
204 type Error = String;
205
206 fn try_from(value: String) -> Result<Self, Self::Error> {
207 let num: u8 = value.parse().map_err(|_| "Invalid number".to_string())?;
208 Ok(UserAppRole::from(num))
209 }
210}
211
212impl UserInfo {
213 pub fn name(&self) -> &str {
214 if self.userNickname.is_empty() {
215 &self.userName
216 } else {
217 &self.userNickname
218 }
219 }
220
221 pub fn username(&self) -> &str {
222 &self.userName
223 }
224
225 pub fn nickname(&self) -> &str {
226 &self.userNickname
227 }
228
229 pub fn intro(&self) -> &str {
230 &self.intro
231 }
232
233 pub fn city(&self) -> &str {
234 &self.city
235 }
236
237 pub fn url(&self) -> &str {
238 &self.URL
239 }
240
241 pub fn avatar(&self) -> &str {
242 &self.avatar
243 }
244
245 pub fn points(&self) -> i32 {
246 self.points
247 }
248
249 pub fn role(&self) -> &str {
250 &self.role
251 }
252
253 pub fn following(&self) -> i32 {
254 self.following
255 }
256
257 pub fn follower(&self) -> i32 {
258 self.follower
259 }
260
261 pub fn online_minutes(&self) -> i32 {
262 self.onlineMinutes
263 }
264}
265
266impl Default for MetalAttrOrString {
267 fn default() -> Self {
268 MetalAttrOrString::Attr(MetalAttr::default())
269 }
270}
271
272#[allow(dead_code)]
274trait MetalCommon {
275 fn attr(&self) -> &MetalAttrOrString;
276 fn name(&self) -> &str;
277 fn description(&self) -> &str;
278 fn data(&self) -> &str;
279 fn to_url(&self, include_text: bool) -> String {
280 let domain = "fishpi.cn";
281 let attr_str = match self.attr() {
282 MetalAttrOrString::Attr(attr) => {
283 format!(
284 "ver={}&scale={}&backcolor={}&fontcolor={}",
285 attr.ver, attr.scale, attr.backcolor, attr.fontcolor
286 )
287 }
288 MetalAttrOrString::Str(s) => s.clone(),
289 };
290 let text_str = if include_text {
291 self.name().to_string()
292 } else {
293 "".to_string()
294 };
295 format!("https://{}/gen?txt={}&{}", domain, text_str, attr_str)
296 }
297}
298
299impl MetalCommon for MetalBase {
300 fn attr(&self) -> &MetalAttrOrString {
301 &self.attr
302 }
303 fn name(&self) -> &str {
304 &self.name
305 }
306 fn description(&self) -> &str {
307 &self.description
308 }
309 fn data(&self) -> &str {
310 &self.data
311 }
312}
313
314#[allow(dead_code)]
315impl MetalBase {
316 fn new(metal: Option<&Self>) -> Self {
317 metal.cloned().unwrap_or_default()
318 }
319}
320
321impl Default for Metal {
322 fn default() -> Self {
323 Self {
324 base: MetalBase::default(),
325 url: String::default(),
326 icon: String::default(),
327 enable: true,
328 }
329 }
330}
331
332impl MetalCommon for Metal {
333 fn attr(&self) -> &MetalAttrOrString {
334 self.base.attr()
335 }
336 fn name(&self) -> &str {
337 self.base.name()
338 }
339 fn description(&self) -> &str {
340 self.base.description()
341 }
342 fn data(&self) -> &str {
343 self.base.data()
344 }
345}
346
347#[allow(dead_code)]
348impl Metal {
349 fn new(metal: Option<&Self>) -> Self {
350 let base = MetalBase::new(metal.map(|m| &m.base));
351 Self {
352 base,
353 ..Default::default()
354 }
355 }
356}
357
358pub fn to_metal(sys_metal: &str) -> Result<Vec<Metal>, Box<dyn std::error::Error>> {
359 let parsed: Value = serde_json::from_str(sys_metal)?;
360 let list = parsed["list"].as_array().ok_or("no list in sysMetal")?;
361 let mut metals = Vec::new();
362 for item in list {
363 let attr_str = item["attr"].as_str().unwrap_or("");
364 let base = MetalBase {
365 attr: analyze_metal_attr(attr_str),
366 name: item["name"].as_str().unwrap_or("").to_string(),
367 description: item["description"].as_str().unwrap_or("").to_string(),
368 data: item["data"].as_str().unwrap_or("").to_string(),
369 };
370 let url = base.to_url(true);
371 let icon = base.to_url(false);
372 let enable = item["enabled"].as_bool().unwrap_or(true);
373 metals.push(Metal {
374 base,
375 url,
376 icon,
377 enable,
378 });
379 }
380 Ok(metals)
381}
382
383pub fn analyze_metal_attr(attr_str: &str) -> MetalAttrOrString {
384 if attr_str.is_empty() {
385 return MetalAttrOrString::Str("".to_string());
386 }
387 let mut url = String::new();
388 let mut backcolor = String::new();
389 let mut fontcolor = String::new();
390 let mut ver = 1.0;
391 let mut scale = 0.79;
392 for pair in attr_str.split('&') {
393 let mut parts = pair.split('=');
394 if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
395 match key {
396 "url" => url = value.to_string(),
397 "backcolor" => backcolor = value.to_string(),
398 "fontcolor" => fontcolor = value.to_string(),
399 "ver" => ver = value.parse().unwrap_or(1.0),
400 "scale" => scale = value.parse().unwrap_or(0.79),
401 _ => {}
402 }
403 }
404 }
405 if url.is_empty() && backcolor.is_empty() && fontcolor.is_empty() {
406 MetalAttrOrString::Str(attr_str.to_string())
407 } else {
408 MetalAttrOrString::Attr(MetalAttr {
409 url,
410 backcolor,
411 fontcolor,
412 ver,
413 scale,
414 })
415 }
416}
417
418#[derive(Clone, Serialize, Deserialize, Debug)]
419pub struct UserPoint {
420 #[serde(rename = "userPoint")]
421 pub point: u32,
422 #[serde(rename = "userName")]
423 pub name: String,
424}
425
426impl UserPoint {
427 pub fn from_value(data: &Value) -> Result<Self, Error> {
428 serde_json::from_value(data["data"].clone())
429 .map_err(|e| Error::Parse(format!("Failed to parse UserPoint: {}", e)))
430 }
431}