1use std::fmt;
2
3use serde::{Deserialize, Serializer};
4use time::OffsetDateTime;
5
6use crate::{prelude::Username, request::GetUser, Osu, OsuResult};
7
8use super::{serde_util, user::User, CacheUserFn, ContainedUsers};
9
10#[derive(Clone, Debug, Deserialize)]
12#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
13pub struct Comment {
14 #[serde(rename = "id")]
16 pub comment_id: u32,
17 pub commentable_id: u32,
19 pub commentable_type: String,
21 #[serde(with = "serde_util::datetime")]
23 pub created_at: OffsetDateTime,
24 #[serde(
26 default,
27 skip_serializing_if = "Option::is_none",
28 with = "serde_util::option_datetime"
29 )]
30 pub deleted_at: Option<OffsetDateTime>,
31 #[serde(
33 default,
34 skip_serializing_if = "Option::is_none",
35 with = "serde_util::option_datetime"
36 )]
37 pub edited_at: Option<OffsetDateTime>,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub edited_by_id: Option<u32>,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub legacy_name: Option<Username>,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub message: Option<String>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub message_html: Option<String>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub parent_id: Option<u32>,
53 pub pinned: bool,
55 pub replies_count: u32,
57 #[serde(with = "serde_util::datetime")]
59 pub updated_at: OffsetDateTime,
60 pub user_id: Option<u32>,
62 pub votes_count: u32,
64}
65
66impl Comment {
67 #[inline]
71 pub fn get_user<'o>(&self, osu: &'o Osu) -> Option<GetUser<'o>> {
72 self.user_id.map(|id| osu.user(id))
73 }
74}
75
76impl PartialEq for Comment {
77 #[inline]
78 fn eq(&self, other: &Self) -> bool {
79 self.comment_id == other.comment_id && self.user_id == other.user_id
80 }
81}
82
83impl Eq for Comment {}
84
85#[derive(Clone, Debug, Deserialize, PartialEq)]
87#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
88pub struct CommentBundle {
89 pub commentable_meta: Vec<CommentableMeta>,
91 pub comments: Vec<Comment>,
93 #[serde(
94 default,
95 rename = "cursor_string",
96 skip_serializing_if = "Option::is_none"
97 )]
98 pub(crate) cursor: Option<Box<str>>,
99 pub(crate) has_more: bool,
101 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub has_more_id: Option<u32>,
103 pub included_comments: Vec<Comment>,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub pinned_comments: Option<Vec<Comment>>,
108 pub sort: CommentSort,
110 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub top_level_count: Option<u32>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
115 pub total: Option<u32>,
116 pub user_follow: bool,
118 pub user_votes: Vec<u32>,
120 pub users: Vec<User>,
122}
123
124impl CommentBundle {
125 #[inline]
128 pub const fn has_more(&self) -> bool {
129 self.has_more
130 }
131
132 #[inline]
136 pub async fn get_next(&self, osu: &Osu) -> Option<OsuResult<CommentBundle>> {
137 debug_assert!(self.has_more == self.cursor.is_some());
138
139 Some(osu.comments().cursor(self.cursor.as_deref()?).await)
140 }
141}
142
143impl ContainedUsers for CommentBundle {
144 fn apply_to_users(&self, f: impl CacheUserFn) {
145 self.users.apply_to_users(f);
146 }
147}
148
149#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq)]
151#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
152pub enum CommentSort {
153 #[serde(rename = "new")]
155 New,
156 #[serde(rename = "old")]
158 Old,
159 #[serde(rename = "top")]
161 Top,
162}
163
164impl CommentSort {
165 pub const fn as_str(self) -> &'static str {
166 match self {
167 Self::New => "new",
168 Self::Old => "old",
169 Self::Top => "top",
170 }
171 }
172
173 #[allow(clippy::trivially_copy_pass_by_ref)]
174 pub(crate) fn serialize_as_query<S: Serializer>(
175 &self,
176 serializer: S,
177 ) -> Result<S::Ok, S::Error> {
178 serializer.serialize_str(self.as_str())
179 }
180}
181
182impl fmt::Display for CommentSort {
183 #[inline]
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 f.write_str(self.as_str())
186 }
187}
188
189#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
191#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
192#[serde(untagged)]
193pub enum CommentableMeta {
194 Full {
195 id: u32,
197 #[serde(rename = "type")]
199 kind: String,
200 owner_id: u32,
201 owner_title: String,
202 title: String,
204 url: String,
206 },
207 Title {
208 title: String,
210 },
211}