Skip to main content

bpi_rs/user/
model.rs

1use serde::{Deserialize, Deserializer, Serialize, de};
2
3use crate::ids::{Aid, Bvid, Mid};
4
5/// Payload returned by `/x/web-interface/card`.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct UserCardProfile {
8    /// User card summary.
9    pub card: UserCardSummary,
10    /// Whether the current session follows this user.
11    pub following: bool,
12    /// Number of uploaded archives.
13    #[serde(default)]
14    pub archive_count: u64,
15    /// Number of articles.
16    #[serde(default)]
17    pub article_count: u64,
18    /// Follower count.
19    #[serde(default)]
20    pub follower: u64,
21    /// Total likes received.
22    #[serde(default)]
23    pub like_num: u64,
24}
25
26/// Stable fields from the nested `card` object.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct UserCardSummary {
29    /// User member ID.
30    #[serde(deserialize_with = "deserialize_mid_from_string_or_number")]
31    pub mid: Mid,
32    /// Display name.
33    pub name: String,
34    /// Profile gender text.
35    #[serde(default)]
36    pub sex: Option<String>,
37    /// Avatar URL.
38    pub face: String,
39    /// Profile signature.
40    #[serde(default)]
41    pub sign: String,
42    /// Fan count embedded in the card summary.
43    #[serde(default)]
44    pub fans: u64,
45    /// Following count embedded in the card summary.
46    #[serde(default)]
47    pub attention: u64,
48}
49
50/// Compact user card returned by `/account/v1/user/cards`.
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52pub struct UserBatchCard {
53    /// User member ID.
54    pub mid: Mid,
55    /// Display name.
56    pub name: String,
57    /// Avatar URL.
58    pub face: String,
59    /// Profile signature.
60    #[serde(default)]
61    pub sign: String,
62    /// Display rank returned by Bilibili.
63    #[serde(default)]
64    pub rank: i32,
65    /// Current account level.
66    #[serde(default)]
67    pub level: i32,
68    /// User silence/ban state returned by Bilibili.
69    #[serde(default)]
70    pub silence: i32,
71}
72
73/// Detailed batch user information returned by `/x/im/user_infos`.
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct UserBatchInfo {
76    /// User member ID.
77    pub mid: Mid,
78    /// Display name.
79    pub name: String,
80    /// Profile signature.
81    #[serde(default)]
82    pub sign: String,
83    /// Display rank returned by Bilibili.
84    #[serde(default)]
85    pub rank: i32,
86    /// Current account level.
87    #[serde(default)]
88    pub level: i32,
89    /// User silence/ban state returned by Bilibili.
90    #[serde(default)]
91    pub silence: i32,
92    /// Profile gender text.
93    #[serde(default, deserialize_with = "deserialize_optional_string")]
94    pub sex: Option<String>,
95    /// Avatar URL.
96    pub face: String,
97    /// VIP status summary.
98    #[serde(default)]
99    pub vip: Option<UserBatchVip>,
100    /// Official verification summary.
101    #[serde(default)]
102    pub official: Option<UserOfficialSummary>,
103    /// Whether Bilibili marks this account as fake.
104    #[serde(default)]
105    pub is_fake_account: Option<u32>,
106    /// Expert payload returned by Bilibili, kept raw because the shape is display-oriented.
107    #[serde(default)]
108    pub expert_info: Option<serde_json::Value>,
109}
110
111/// VIP status fields embedded in `/x/im/user_infos` batch responses.
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
113pub struct UserBatchVip {
114    /// VIP type code.
115    #[serde(default, rename = "type")]
116    pub kind: i32,
117    /// VIP status code.
118    #[serde(default)]
119    pub status: i32,
120    /// VIP due timestamp in milliseconds.
121    #[serde(default)]
122    pub due_date: i64,
123    /// VIP payment type code.
124    #[serde(default)]
125    pub vip_pay_type: i32,
126    /// VIP theme type code.
127    #[serde(default)]
128    pub theme_type: i32,
129}
130
131/// Public user space information returned by `/x/space/wbi/acc/info`.
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct UserSpaceProfile {
134    /// User member ID.
135    pub mid: Mid,
136    /// Display name.
137    pub name: String,
138    /// Profile gender text.
139    #[serde(default, deserialize_with = "deserialize_optional_string")]
140    pub sex: Option<String>,
141    /// Avatar URL.
142    pub face: String,
143    /// Profile signature.
144    #[serde(default)]
145    pub sign: String,
146    /// Current account level.
147    pub level: u8,
148    /// User silence/ban state returned by Bilibili.
149    pub silence: u8,
150    /// Visible coin count. Bilibili returns `0` for other users.
151    #[serde(default)]
152    pub coins: f64,
153    /// Whether this user has fan-medal information.
154    #[serde(default)]
155    pub fans_badge: bool,
156    /// Whether the current session follows this user.
157    #[serde(default)]
158    pub is_followed: bool,
159    /// Space top photo URL when Bilibili returns one.
160    #[serde(default, deserialize_with = "deserialize_optional_string")]
161    pub top_photo: Option<String>,
162    /// Official verification summary.
163    #[serde(default)]
164    pub official: Option<UserOfficialSummary>,
165    /// VIP status summary.
166    #[serde(default)]
167    pub vip: Option<UserVipSummary>,
168    /// Live-room summary.
169    #[serde(default)]
170    pub live_room: Option<UserSpaceLiveRoom>,
171}
172
173/// Official verification fields commonly returned in user space payloads.
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
175pub struct UserOfficialSummary {
176    /// Official role code.
177    #[serde(default)]
178    pub role: i32,
179    /// Verification title.
180    #[serde(default)]
181    pub title: String,
182    /// Verification description.
183    #[serde(default)]
184    pub desc: String,
185    /// Verification type code.
186    #[serde(default, rename = "type")]
187    pub kind: i32,
188}
189
190/// VIP status fields commonly returned in user space payloads.
191#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
192pub struct UserVipSummary {
193    /// VIP type code.
194    #[serde(default, rename = "type")]
195    pub kind: i32,
196    /// VIP status code.
197    #[serde(default)]
198    pub status: i32,
199}
200
201/// Live-room fields embedded in a user space payload.
202#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
203pub struct UserSpaceLiveRoom {
204    /// Whether a room exists for this user.
205    #[serde(default, rename = "roomStatus")]
206    pub room_status: u8,
207    /// Whether the room is currently live.
208    #[serde(default, rename = "liveStatus")]
209    pub live_status: u8,
210    /// Live room URL.
211    #[serde(default)]
212    pub url: String,
213    /// Live room title.
214    #[serde(default)]
215    pub title: String,
216    /// Live room ID.
217    #[serde(default, rename = "roomid")]
218    pub room_id: u64,
219}
220
221/// Public space notice returned by `/x/space/notice`.
222#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
223#[serde(transparent)]
224pub struct UserSpaceNotice {
225    /// Notice text. Empty when the user has no public notice.
226    pub content: String,
227}
228
229/// Followed bangumi or cinema seasons returned by `/x/space/bangumi/follow/list`.
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct UserBangumiFollowList {
232    /// Followed seasons on the current page.
233    #[serde(default, rename = "list")]
234    pub items: Vec<UserBangumiFollow>,
235    /// Current page number.
236    #[serde(default, rename = "pn")]
237    pub page: u32,
238    /// Page size.
239    #[serde(default, rename = "ps")]
240    pub page_size: u32,
241    /// Total followed seasons for this category.
242    #[serde(default)]
243    pub total: u64,
244}
245
246/// One followed bangumi or cinema season.
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct UserBangumiFollow {
249    /// Season ID.
250    pub season_id: i64,
251    /// Media ID.
252    pub media_id: i64,
253    /// Season type code returned by Bilibili.
254    #[serde(default)]
255    pub season_type: i64,
256    /// Season type display name.
257    #[serde(default)]
258    pub season_type_name: String,
259    /// Season title.
260    #[serde(default)]
261    pub title: String,
262    /// Cover image URL.
263    #[serde(default)]
264    pub cover: String,
265    /// Total episode count.
266    #[serde(default)]
267    pub total_count: i64,
268    /// Whether the season has finished.
269    #[serde(default)]
270    pub is_finish: i64,
271    /// Whether the season has started.
272    #[serde(default)]
273    pub is_started: i64,
274    /// Whether this season is playable.
275    #[serde(default)]
276    pub is_play: i64,
277    /// Badge text returned by Bilibili.
278    #[serde(default)]
279    pub badge: String,
280    /// Badge type code.
281    #[serde(default)]
282    pub badge_type: i64,
283    /// Latest episode summary.
284    #[serde(default, rename = "new_ep")]
285    pub latest_episode: UserBangumiLatestEpisode,
286    /// Season rating when Bilibili returns it.
287    #[serde(default)]
288    pub rating: Option<UserBangumiRating>,
289    /// Season page URL.
290    #[serde(default)]
291    pub url: String,
292    /// Short URL returned by Bilibili.
293    #[serde(default)]
294    pub short_url: String,
295    /// Season description.
296    #[serde(default)]
297    pub summary: String,
298    /// Style tags.
299    #[serde(default)]
300    pub styles: Vec<String>,
301    /// Follow status code.
302    #[serde(default)]
303    pub follow_status: i64,
304    /// User progress text.
305    #[serde(default)]
306    pub progress: String,
307    /// Whether both users follow this season.
308    #[serde(default)]
309    pub both_follow: bool,
310}
311
312/// Latest episode summary embedded in a bangumi follow-list item.
313#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
314pub struct UserBangumiLatestEpisode {
315    /// Episode ID.
316    #[serde(default)]
317    pub id: i64,
318    /// Display episode index.
319    #[serde(default)]
320    pub index_show: String,
321    /// Episode cover image URL.
322    #[serde(default)]
323    pub cover: String,
324    /// Episode title.
325    #[serde(default)]
326    pub title: String,
327    /// Episode long title.
328    #[serde(default)]
329    pub long_title: Option<String>,
330    /// Publish time text returned by Bilibili.
331    #[serde(default)]
332    pub pub_time: String,
333    /// Episode duration in seconds.
334    #[serde(default)]
335    pub duration: i64,
336}
337
338/// Rating summary embedded in a bangumi follow-list item.
339#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
340pub struct UserBangumiRating {
341    /// Rating score.
342    #[serde(default)]
343    pub score: f64,
344    /// Rating count.
345    #[serde(default)]
346    pub count: i64,
347}
348
349/// Relation counts returned by `/x/relation/stat`.
350#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
351pub struct UserRelationStat {
352    /// User member ID.
353    pub mid: Mid,
354    /// Public following count.
355    pub following: u64,
356    /// Whisper-following count.
357    #[serde(default)]
358    pub whisper: u64,
359    /// Blacklist count.
360    #[serde(default)]
361    pub black: u64,
362    /// Follower count.
363    pub follower: u64,
364}
365
366/// Following-list payload returned by `/x/relation/followings`.
367#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct UserFollowings {
369    /// Users followed by the requested member.
370    #[serde(default)]
371    pub list: Vec<UserFollowing>,
372    /// Bilibili relation-list schema version.
373    #[serde(default)]
374    pub re_version: u32,
375    /// Total number of followed users.
376    #[serde(default)]
377    pub total: u64,
378}
379
380/// One followed user in [`UserFollowings`].
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct UserFollowing {
383    /// Followed member ID.
384    pub mid: Mid,
385    /// Relation attribute returned by Bilibili.
386    #[serde(default)]
387    pub attribute: u8,
388    /// Follow timestamp in seconds.
389    #[serde(default)]
390    pub mtime: u64,
391    /// Relation tag IDs.
392    #[serde(default)]
393    pub tag: Option<Vec<u64>>,
394    /// Whether this user is specially followed.
395    #[serde(default)]
396    pub special: u8,
397    /// Display name.
398    #[serde(default, rename = "uname")]
399    pub name: String,
400    /// Avatar URL.
401    #[serde(default)]
402    pub face: String,
403    /// Profile signature.
404    #[serde(default)]
405    pub sign: String,
406    /// Whether Bilibili marks the avatar as NFT.
407    #[serde(default)]
408    pub face_nft: u8,
409    /// Official verification summary returned by the relation list.
410    #[serde(default)]
411    pub official_verify: Option<UserRelationOfficialVerify>,
412    /// VIP summary. Kept raw because Bilibili changes this display payload often.
413    #[serde(default)]
414    pub vip: Option<serde_json::Value>,
415}
416
417/// Follower-list payload returned by `/x/relation/fans`.
418#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct UserFollowers {
420    /// Users following the requested member.
421    #[serde(default)]
422    pub list: Vec<UserFollower>,
423    /// Pagination offset to pass to the next request.
424    #[serde(default)]
425    pub offset: String,
426    /// Bilibili relation-list schema version.
427    #[serde(default)]
428    pub re_version: u32,
429    /// Total number of followers.
430    #[serde(default)]
431    pub total: u64,
432}
433
434/// One follower in [`UserFollowers`].
435#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct UserFollower {
437    /// Follower member ID.
438    pub mid: Mid,
439    /// Relation attribute returned by Bilibili.
440    #[serde(default)]
441    pub attribute: u8,
442    /// Follow timestamp in seconds when Bilibili returns it.
443    #[serde(default)]
444    pub mtime: Option<u64>,
445    /// Relation tag IDs.
446    #[serde(default)]
447    pub tag: Option<Vec<u64>>,
448    /// Whether this user is specially followed.
449    #[serde(default)]
450    pub special: u8,
451    /// Contract display payload. Kept raw because this nested schema is unstable.
452    #[serde(default)]
453    pub contract_info: Option<serde_json::Value>,
454    /// Display name.
455    #[serde(default, rename = "uname")]
456    pub name: String,
457    /// Avatar URL.
458    #[serde(default)]
459    pub face: String,
460    /// Profile signature.
461    #[serde(default)]
462    pub sign: String,
463    /// Whether Bilibili marks the avatar as NFT.
464    #[serde(default)]
465    pub face_nft: u8,
466    /// Official verification summary returned by the relation list.
467    #[serde(default)]
468    pub official_verify: Option<UserRelationOfficialVerify>,
469    /// VIP summary. Kept raw because Bilibili changes this display payload often.
470    #[serde(default)]
471    pub vip: Option<serde_json::Value>,
472}
473
474/// Follow-group entry returned by `/x/relation/tags`.
475#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
476pub struct UserFollowTag {
477    /// Follow group ID. Bilibili uses negative IDs for built-in groups.
478    #[serde(rename = "tagid")]
479    pub id: i64,
480    /// Follow group display name.
481    pub name: String,
482    /// Number of users in the group.
483    #[serde(default)]
484    pub count: i64,
485    /// Optional UI hint returned by Bilibili.
486    #[serde(default)]
487    pub tip: Option<String>,
488}
489
490/// Fan-medal wall returned by `/xlive/web-ucenter/user/MedalWall`.
491#[derive(Debug, Clone, Serialize, Deserialize)]
492pub struct UserMedalWall {
493    /// Medal entries visible on the user's medal wall.
494    #[serde(default)]
495    pub list: Vec<UserMedalWallItem>,
496    /// Number of visible medal entries.
497    #[serde(default)]
498    pub count: u32,
499    /// Whether this user closes the space medal wall.
500    #[serde(default)]
501    pub close_space_medal: u32,
502    /// Whether this user only shows the wearing medal.
503    #[serde(default)]
504    pub only_show_wearing: u32,
505    /// Medal wall owner display name.
506    #[serde(default)]
507    pub name: String,
508    /// Medal wall owner avatar URL.
509    #[serde(default)]
510    pub icon: String,
511    /// Medal wall owner member ID.
512    pub uid: Mid,
513    /// Medal wall owner level.
514    #[serde(default)]
515    pub level: u32,
516}
517
518/// One fan-medal entry in [`UserMedalWall`].
519#[derive(Debug, Clone, Serialize, Deserialize)]
520pub struct UserMedalWallItem {
521    /// Medal metadata for the target creator.
522    pub medal_info: UserMedalInfo,
523    /// Target creator display name.
524    #[serde(default)]
525    pub target_name: String,
526    /// Target creator avatar URL.
527    #[serde(default)]
528    pub target_icon: String,
529    /// Target live-room link.
530    #[serde(default)]
531    pub link: String,
532    /// Current target live status.
533    #[serde(default)]
534    pub live_status: u32,
535    /// Official status field. Bilibili spells this key as `offical`.
536    #[serde(default, rename = "offical")]
537    pub official: Option<u32>,
538    /// Medal display fields from the wall owner's point of view.
539    #[serde(default)]
540    pub uinfo_medal: Option<UserMedalOwnerInfo>,
541}
542
543impl UserMedalWallItem {
544    /// Returns the target creator member ID for this medal.
545    pub fn target_id(&self) -> Mid {
546        self.medal_info.target_id
547    }
548}
549
550/// Stable fan-medal metadata for one target creator.
551#[derive(Debug, Clone, Serialize, Deserialize)]
552pub struct UserMedalInfo {
553    /// Target creator member ID.
554    pub target_id: Mid,
555    /// Medal level.
556    #[serde(default)]
557    pub level: u32,
558    /// Medal display name.
559    #[serde(default, rename = "medal_name")]
560    pub name: String,
561    /// Medal gradient start color.
562    #[serde(default)]
563    pub medal_color_start: u32,
564    /// Medal gradient end color.
565    #[serde(default)]
566    pub medal_color_end: u32,
567    /// Medal border color.
568    #[serde(default)]
569    pub medal_color_border: u32,
570    /// Guard level associated with this medal.
571    #[serde(default)]
572    pub guard_level: u32,
573    /// Whether this medal is currently worn.
574    #[serde(default)]
575    pub wearing_status: u32,
576    /// Medal ID.
577    #[serde(default)]
578    pub medal_id: u64,
579    /// Current intimacy score.
580    #[serde(default)]
581    pub intimacy: u64,
582    /// Required intimacy score for the next level.
583    #[serde(default)]
584    pub next_intimacy: u64,
585    /// Intimacy fed today.
586    #[serde(default)]
587    pub today_feed: u64,
588    /// Daily intimacy feed limit.
589    #[serde(default)]
590    pub day_limit: u64,
591    /// Guard icon URL when Bilibili returns it.
592    #[serde(default)]
593    pub guard_icon: Option<String>,
594    /// Honor icon URL when Bilibili returns it.
595    #[serde(default)]
596    pub honor_icon: Option<String>,
597}
598
599/// Medal display fields from the wall owner's point of view.
600#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct UserMedalOwnerInfo {
602    /// Medal display name.
603    #[serde(default)]
604    pub name: String,
605    /// Medal level.
606    #[serde(default)]
607    pub level: u32,
608    /// Medal gradient start color.
609    #[serde(default)]
610    pub color_start: u32,
611    /// Medal gradient end color.
612    #[serde(default)]
613    pub color_end: u32,
614    /// Medal border color.
615    #[serde(default)]
616    pub color_border: u32,
617    /// Medal text color.
618    #[serde(default)]
619    pub color: u32,
620    /// Medal ID.
621    #[serde(default)]
622    pub id: u64,
623    /// Medal type returned by Bilibili.
624    #[serde(default)]
625    pub typ: u32,
626    /// Whether the medal is lit.
627    #[serde(default)]
628    pub is_light: u32,
629    /// Target creator member ID.
630    pub ruid: Mid,
631    /// Guard level associated with this medal.
632    #[serde(default)]
633    pub guard_level: u32,
634    /// Current intimacy score.
635    #[serde(default)]
636    pub score: u64,
637    /// Guard icon URL when Bilibili returns it.
638    #[serde(default)]
639    pub guard_icon: Option<String>,
640    /// Honor icon URL when Bilibili returns it.
641    #[serde(default)]
642    pub honor_icon: Option<String>,
643    /// V2 medal start color token.
644    #[serde(default)]
645    pub v2_medal_color_start: Option<String>,
646    /// V2 medal end color token.
647    #[serde(default)]
648    pub v2_medal_color_end: Option<String>,
649    /// V2 medal border color token.
650    #[serde(default)]
651    pub v2_medal_color_border: Option<String>,
652    /// V2 medal text color token.
653    #[serde(default)]
654    pub v2_medal_color_text: Option<String>,
655    /// V2 medal level color token.
656    #[serde(default)]
657    pub v2_medal_color_level: Option<String>,
658    /// Number of users who received this medal when Bilibili returns it.
659    #[serde(default)]
660    pub user_receive_count: Option<u32>,
661}
662
663/// Official verification fields embedded in relation-list payloads.
664#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
665pub struct UserRelationOfficialVerify {
666    /// Verification type code.
667    #[serde(default, rename = "type")]
668    pub kind: i8,
669    /// Verification description.
670    #[serde(default)]
671    pub desc: String,
672}
673
674/// Creator content statistics returned by `/x/space/upstat`.
675#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
676pub struct UserUpStat {
677    /// Uploaded-video view summary.
678    pub archive: UserUpStatArchive,
679    /// Article view summary.
680    pub article: UserUpStatArticle,
681    /// Total likes received by this user.
682    pub likes: u64,
683}
684
685/// Uploaded-video view summary in [`UserUpStat`].
686#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
687pub struct UserUpStatArchive {
688    /// Total video views.
689    #[serde(default)]
690    pub view: u64,
691}
692
693/// Article view summary in [`UserUpStat`].
694#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
695pub struct UserUpStatArticle {
696    /// Total article views.
697    #[serde(default)]
698    pub view: u64,
699}
700
701/// User space navigation counters returned by `/x/space/navnum`.
702#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
703pub struct UserNavStat {
704    /// Uploaded video count.
705    #[serde(default)]
706    pub video: u64,
707    /// Followed bangumi count.
708    #[serde(default)]
709    pub bangumi: u64,
710    /// Followed cinema count.
711    #[serde(default)]
712    pub cinema: u64,
713    /// Video-list counters.
714    #[serde(default)]
715    pub channel: UserNavStatPair,
716    /// Favorite-folder counters.
717    #[serde(default)]
718    pub favourite: UserNavStatPair,
719    /// Follow tag count.
720    #[serde(default)]
721    pub tag: u64,
722    /// Article count.
723    #[serde(default)]
724    pub article: u64,
725    /// Playlist count.
726    #[serde(default)]
727    pub playlist: u64,
728    /// Album count.
729    #[serde(default)]
730    pub album: u64,
731    /// Audio count.
732    #[serde(default)]
733    pub audio: u64,
734    /// Course count.
735    #[serde(default)]
736    pub pugv: u64,
737    /// Opus count.
738    #[serde(default)]
739    pub opus: u64,
740    /// Video season/collection count.
741    #[serde(default, rename = "season_num")]
742    pub season_count: u64,
743}
744
745/// Master/guest split counters used by [`UserNavStat`].
746#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
747pub struct UserNavStatPair {
748    /// Total count visible to the owner.
749    #[serde(default)]
750    pub master: u64,
751    /// Public count visible to visitors.
752    #[serde(default)]
753    pub guest: u64,
754}
755
756/// Album submission counters returned by `/link_draw/v1/doc/upload_count`.
757#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
758pub struct UserAlbumCount {
759    /// Total album submissions.
760    #[serde(default)]
761    pub all_count: u64,
762    /// Drawing submissions.
763    #[serde(default)]
764    pub draw_count: u64,
765    /// Photo submissions.
766    #[serde(default)]
767    pub photo_count: u64,
768    /// Daily image-dynamic submissions.
769    #[serde(default)]
770    pub daily_count: u64,
771}
772
773/// Username lookup payload returned by `/x/polymer/web-dynamic/v1/name-to-uid`.
774#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
775pub struct UserNameToUid {
776    /// Matched username and member-ID entries.
777    #[serde(default)]
778    pub uid_list: Vec<UserNameToUidItem>,
779}
780
781/// One username lookup result.
782#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
783pub struct UserNameToUidItem {
784    /// Display name that matched the lookup.
785    pub name: String,
786    /// Matched member ID.
787    #[serde(
788        rename = "uid",
789        deserialize_with = "deserialize_mid_from_string_or_number"
790    )]
791    pub mid: Mid,
792}
793
794/// Uploaded videos returned by `/x/space/wbi/arc/search`.
795#[derive(Debug, Clone, Serialize, Deserialize)]
796pub struct UserUploadedVideos {
797    /// Uploaded video list and partition summary.
798    pub list: UserUploadedVideoList,
799    /// Pagination summary.
800    pub page: UserUploadedVideosPage,
801    /// Optional play-all button returned by Bilibili.
802    #[serde(default)]
803    pub episodic_button: Option<UserUploadedVideosButton>,
804    /// Whether the response was risk-controlled by Bilibili.
805    #[serde(default)]
806    pub is_risk: bool,
807}
808
809/// Uploaded video list payload.
810#[derive(Debug, Clone, Serialize, Deserialize)]
811pub struct UserUploadedVideoList {
812    /// Partition summary keyed by partition ID.
813    #[serde(default)]
814    pub tlist: serde_json::Value,
815    /// Uploaded videos on the current page.
816    #[serde(default, rename = "vlist")]
817    pub videos: Vec<UserUploadedVideo>,
818}
819
820/// One uploaded video in a user's space.
821#[derive(Debug, Clone, Serialize, Deserialize)]
822pub struct UserUploadedVideo {
823    /// AV numeric video ID.
824    pub aid: Aid,
825    /// BV string video ID.
826    pub bvid: Bvid,
827    /// Uploader member ID.
828    pub mid: Mid,
829    /// Video title.
830    pub title: String,
831    /// Uploader display name.
832    #[serde(default)]
833    pub author: String,
834    /// Cover image URL.
835    #[serde(default)]
836    pub pic: String,
837    /// Video duration text returned by Bilibili.
838    #[serde(default)]
839    pub length: String,
840    /// Video description.
841    #[serde(default)]
842    pub description: String,
843    /// Publish timestamp in seconds.
844    #[serde(default)]
845    pub created: u64,
846    /// View count.
847    #[serde(default)]
848    pub play: u64,
849    /// Reply count.
850    #[serde(default)]
851    pub comment: u64,
852    /// Partition ID.
853    #[serde(default)]
854    pub typeid: u64,
855    /// Danmaku count.
856    #[serde(default)]
857    pub video_review: u64,
858    /// Whether Bilibili hides click details for this video.
859    #[serde(default)]
860    pub hide_click: bool,
861}
862
863/// Pagination summary for uploaded videos.
864#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
865pub struct UserUploadedVideosPage {
866    /// Total matched videos.
867    #[serde(default)]
868    pub count: u64,
869    /// Current page number.
870    #[serde(default)]
871    pub pn: u32,
872    /// Page size.
873    #[serde(default)]
874    pub ps: u32,
875}
876
877/// Optional play-all button returned by uploaded-video searches.
878#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
879pub struct UserUploadedVideosButton {
880    /// Button text.
881    #[serde(default)]
882    pub text: String,
883    /// Button target URI.
884    #[serde(default)]
885    pub uri: String,
886}
887
888fn deserialize_mid_from_string_or_number<'de, D>(deserializer: D) -> Result<Mid, D::Error>
889where
890    D: Deserializer<'de>,
891{
892    let value = serde_json::Value::deserialize(deserializer)?;
893
894    match value {
895        serde_json::Value::Number(number) => {
896            let mid = number
897                .as_u64()
898                .ok_or_else(|| de::Error::custom("mid must be a non-negative integer"))?;
899            Mid::new(mid).map_err(de::Error::custom)
900        }
901        serde_json::Value::String(text) => text.parse::<Mid>().map_err(de::Error::custom),
902        _ => Err(de::Error::custom("mid must be a string or number")),
903    }
904}
905
906fn deserialize_optional_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
907where
908    D: Deserializer<'de>,
909{
910    Ok(Option::<String>::deserialize(deserializer)?
911        .and_then(|value| (!value.trim().is_empty()).then_some(value)))
912}