1use std::collections::HashMap;
2use std::fmt::{self, Display};
3use std::str::FromStr;
4
5use crate::model::{channel::Channel, drive::DriveFile, id::Id, user::User};
6
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10use url::Url;
11
12#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug)]
13#[serde(transparent)]
14pub struct Tag(pub String);
15
16impl Display for Tag {
17 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18 use std::fmt::Write;
19 if !self.0.starts_with('#') {
20 f.write_char('#')?;
21 }
22 Display::fmt(&self.0, f)
23 }
24}
25
26impl FromStr for Tag {
27 type Err = std::convert::Infallible;
28 fn from_str(s: &str) -> Result<Tag, Self::Err> {
29 Ok(Tag(s.to_string()))
30 }
31}
32
33impl<S: Into<String>> From<S> for Tag {
34 fn from(s: S) -> Tag {
35 Tag(s.into())
36 }
37}
38
39#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug)]
40#[serde(transparent)]
41pub struct Reaction(pub String);
42
43impl Display for Reaction {
44 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45 Display::fmt(&self.0, f)
46 }
47}
48
49impl FromStr for Reaction {
50 type Err = std::convert::Infallible;
51 fn from_str(s: &str) -> Result<Reaction, Self::Err> {
52 Ok(Reaction(s.to_string()))
53 }
54}
55
56impl<S: Into<String>> From<S> for Reaction {
57 fn from(s: S) -> Reaction {
58 Reaction(s.into())
59 }
60}
61
62#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy)]
63#[serde(rename_all = "camelCase")]
64pub enum Visibility {
65 Public,
66 Home,
67 Followers,
68 Specified,
69}
70
71#[derive(Debug, Error, Clone)]
72#[error("invalid note visibility")]
73pub struct ParseVisibilityError {
74 _priv: (),
75}
76
77impl std::str::FromStr for Visibility {
78 type Err = ParseVisibilityError;
79
80 fn from_str(s: &str) -> Result<Visibility, Self::Err> {
81 match s {
82 "public" | "Public" => Ok(Visibility::Public),
83 "home" | "Home" => Ok(Visibility::Home),
84 "followers" | "Followers" => Ok(Visibility::Followers),
85 "specified" | "Specified" => Ok(Visibility::Specified),
86 _ => Err(ParseVisibilityError { _priv: () }),
87 }
88 }
89}
90
91#[derive(Serialize, Deserialize, Debug, Clone)]
92#[serde(rename_all = "camelCase")]
93pub struct PollChoice {
94 pub is_voted: bool,
95 pub text: String,
96 pub votes: u64,
97}
98
99#[derive(Serialize, Deserialize, Debug, Clone)]
100#[serde(rename_all = "camelCase")]
101pub struct Poll {
102 pub choices: Vec<PollChoice>,
103 pub multiple: bool,
104 pub expires_at: Option<DateTime<Utc>>,
105}
106
107#[derive(Serialize, Deserialize, Debug, Clone)]
109pub struct NoteEmoji {
110 pub name: String,
111 pub url: Url,
112}
113
114#[derive(Serialize, Deserialize, Debug, Clone)]
116pub struct NoteChannel {
117 pub id: Id<Channel>,
118 pub name: String,
119}
120
121#[derive(Serialize, Deserialize, Debug, Clone)]
122#[serde(rename_all = "camelCase")]
123pub struct Note {
124 pub id: Id<Note>,
125 pub created_at: DateTime<Utc>,
126 pub text: Option<String>,
127 #[serde(default)]
128 pub cw: Option<String>,
129 pub user_id: Id<User>,
130 pub user: User,
131 #[serde(default)]
132 pub reply_id: Option<Id<Note>>,
133 #[serde(default)]
134 pub renote_id: Option<Id<Note>>,
135 #[serde(default)]
136 pub reply: Option<Box<Note>>,
137 #[serde(default)]
138 pub renote: Option<Box<Note>>,
139 #[serde(default = "default_false")]
140 pub via_mobile: bool,
141 #[serde(default = "default_false")]
142 pub is_hidden: bool,
143 #[serde(default = "default_false")]
144 pub local_only: bool,
145 pub visibility: Visibility,
146 #[serde(default)]
147 pub mentions: Vec<Id<User>>,
148 #[serde(default)]
149 pub visible_user_ids: Vec<Id<User>>,
150 pub file_ids: Vec<Id<DriveFile>>,
151 pub files: Vec<DriveFile>,
152 #[serde(default)]
153 pub tags: Vec<Tag>,
154 #[serde(default)]
155 pub poll: Option<Poll>,
156 pub reactions: HashMap<Reaction, u64>,
157 pub emojis: Vec<NoteEmoji>,
158 pub renote_count: u64,
159 pub replies_count: u64,
160 #[cfg(feature = "12-47-0")]
161 #[cfg_attr(docsrs, doc(cfg(feature = "12-47-0")))]
162 #[serde(default)]
163 pub channel_id: Option<Id<Channel>>,
164 #[cfg(feature = "12-47-0")]
165 #[cfg_attr(docsrs, doc(cfg(feature = "12-47-0")))]
166 #[serde(default)]
167 pub channel: Option<NoteChannel>,
168}
169
170fn default_false() -> bool {
171 false
172}
173
174impl_entity!(Note);