misskey_util/builder/
note.rs

1use crate::Error;
2
3use chrono::{DateTime, Duration, Utc};
4#[cfg(feature = "12-47-0")]
5use misskey_api::model::channel::Channel;
6use misskey_api::model::{
7    drive::DriveFile,
8    note::{Note, Visibility},
9    user::User,
10};
11use misskey_api::{endpoint, EntityRef};
12use misskey_core::Client;
13
14fn initial_notes_create_request() -> endpoint::notes::create::Request {
15    endpoint::notes::create::Request {
16        visibility: Some(Visibility::Public),
17        visible_user_ids: None,
18        text: None,
19        cw: None,
20        via_mobile: Some(false),
21        local_only: Some(false),
22        no_extract_mentions: Some(false),
23        no_extract_hashtags: Some(false),
24        no_extract_emojis: Some(false),
25        file_ids: None,
26        reply_id: None,
27        renote_id: None,
28        poll: None,
29        #[cfg(feature = "12-47-0")]
30        channel_id: None,
31    }
32}
33
34fn initial_poll_request() -> endpoint::notes::create::PollRequest {
35    endpoint::notes::create::PollRequest {
36        choices: Vec::new(),
37        multiple: Some(false),
38        expires_at: None,
39        expired_after: None,
40    }
41}
42
43/// Builder for the [`build_note`][`crate::ClientExt::build_note`] method.
44pub struct NoteBuilder<C> {
45    client: C,
46    request: endpoint::notes::create::Request,
47}
48
49impl<C> NoteBuilder<C> {
50    /// Creates a builder with the client.
51    pub fn new(client: C) -> Self {
52        NoteBuilder {
53            client,
54            request: initial_notes_create_request(),
55        }
56    }
57
58    /// Gets the request object for reuse.
59    pub fn as_request(&self) -> &endpoint::notes::create::Request {
60        &self.request
61    }
62
63    /// Sets the text of the note.
64    pub fn text(&mut self, text: impl Into<String>) -> &mut Self {
65        self.request.text.replace(text.into());
66        self
67    }
68
69    /// Creates a poll and sets the choices.
70    pub fn poll(&mut self, choices: impl IntoIterator<Item = impl Into<String>>) -> &mut Self {
71        let choices = choices.into_iter().map(Into::into).collect();
72        if let Some(poll) = self.request.poll.as_mut() {
73            poll.choices = choices;
74        } else {
75            self.request.poll = Some(endpoint::notes::create::PollRequest {
76                choices,
77                ..initial_poll_request()
78            });
79        }
80        self
81    }
82
83    /// Sets whether to allow multiple votes.
84    pub fn poll_multiple(&mut self, multiple: bool) -> &mut Self {
85        if let Some(poll) = self.request.poll.as_mut() {
86            poll.multiple.replace(multiple);
87        } else {
88            self.request.poll = Some(endpoint::notes::create::PollRequest {
89                multiple: Some(multiple),
90                ..initial_poll_request()
91            });
92        }
93        self
94    }
95
96    /// Sets when the vote will expire.
97    pub fn poll_expires_at(&mut self, at: DateTime<Utc>) -> &mut Self {
98        if let Some(poll) = self.request.poll.as_mut() {
99            poll.expires_at.replace(at);
100            poll.expired_after.take();
101        } else {
102            self.request.poll = Some(endpoint::notes::create::PollRequest {
103                expires_at: Some(at),
104                expired_after: None,
105                ..initial_poll_request()
106            });
107        }
108        self
109    }
110
111    /// Sets how long the vote will expire after.
112    pub fn poll_expires_after(&mut self, after: Duration) -> &mut Self {
113        if let Some(poll) = self.request.poll.as_mut() {
114            poll.expired_after.replace(after);
115            poll.expires_at.take();
116        } else {
117            self.request.poll = Some(endpoint::notes::create::PollRequest {
118                expired_after: Some(after),
119                expires_at: None,
120                ..initial_poll_request()
121            });
122        }
123        self
124    }
125
126    /// Attaches the specified file to the note.
127    pub fn attach_file(&mut self, file: impl EntityRef<DriveFile>) -> &mut Self {
128        let file_id = file.entity_ref();
129        if let Some(ids) = self.request.file_ids.as_mut() {
130            ids.push(file_id);
131        } else {
132            self.request.file_ids = Some(vec![file_id]);
133        }
134        self
135    }
136
137    /// Adds attachments to the note.
138    pub fn attach_files(
139        &mut self,
140        files: impl IntoIterator<Item = impl EntityRef<DriveFile>>,
141    ) -> &mut Self {
142        let it = files.into_iter().map(|file| file.entity_ref());
143        if let Some(ids) = self.request.file_ids.as_mut() {
144            ids.extend(it);
145        } else {
146            self.request.file_ids = Some(it.collect());
147        }
148        self
149    }
150
151    /// Sets the visibility of the note.
152    pub fn visibility(&mut self, visibility: Visibility) -> &mut Self {
153        self.request.visibility = Some(visibility);
154        if !matches!(visibility, Visibility::Specified) {
155            self.request.visible_user_ids.take();
156        }
157        self
158    }
159
160    /// Sets the note to be visible to everyone.
161    ///
162    /// This is equivalent to `.visibility(Visibility::Public)`.
163    pub fn public(&mut self) -> &mut Self {
164        self.visibility(Visibility::Public)
165    }
166
167    /// Sets the note to be visible only to the home timeline.
168    ///
169    /// This is equivalent to `.visibility(Visibility::Home)`.
170    pub fn home_only(&mut self) -> &mut Self {
171        self.visibility(Visibility::Home)
172    }
173
174    /// Sets the note to be visible only to the followers.
175    ///
176    /// This is equivalent to `.visibility(Visibility::Followers)`.
177    pub fn followers_only(&mut self) -> &mut Self {
178        self.visibility(Visibility::Followers)
179    }
180
181    /// Sets the note to be visible only to the specified users.
182    pub fn direct(
183        &mut self,
184        recipients: impl IntoIterator<Item = impl EntityRef<User>>,
185    ) -> &mut Self {
186        self.visibility(Visibility::Specified);
187        self.request.visible_user_ids.replace(
188            recipients
189                .into_iter()
190                .map(|user| user.entity_ref())
191                .collect(),
192        );
193        self
194    }
195
196    /// Hides the contents of the note as a content warning with the specified text.
197    pub fn hide_content(&mut self, cw_text: impl Into<String>) -> &mut Self {
198        self.request.cw.replace(cw_text.into());
199        self
200    }
201
202    /// Sets whether to show the note as posted from a mobile device.
203    pub fn via_mobile(&mut self, via_mobile: bool) -> &mut Self {
204        self.request.via_mobile.replace(via_mobile);
205        self
206    }
207
208    /// Sets the note to be visible only to users on the same instance.
209    pub fn local_only(&mut self, local_only: bool) -> &mut Self {
210        self.request.local_only.replace(local_only);
211        self
212    }
213
214    /// Sets whether or not to extract mentions (i.e. `@username`) from the text of the note.
215    ///
216    /// Mentions are extracted by default, and you would need this method if you want to disable this behavior.
217    pub fn extract_mentions(&mut self, extract_mentions: bool) -> &mut Self {
218        self.request.no_extract_mentions.replace(!extract_mentions);
219        self
220    }
221
222    /// Sets whether or not to extract hashtags (i.e. `#tag`) from the text of the note.
223    ///
224    /// Hashtags are extracted by default, and you would need this method if you want to disable this behavior.
225    pub fn extract_hashtags(&mut self, extract_hashtags: bool) -> &mut Self {
226        self.request.no_extract_hashtags.replace(!extract_hashtags);
227        self
228    }
229
230    /// Sets whether or not to extract emojis (i.e. `:emoji:`) from the text of the note.
231    ///
232    /// Emojis are extracted by default, and you would need this method if you want to disable this behavior.
233    pub fn extract_emojis(&mut self, extract_emojis: bool) -> &mut Self {
234        self.request.no_extract_emojis.replace(!extract_emojis);
235        self
236    }
237
238    /// Sets the note as a reply to the specified note.
239    pub fn reply(&mut self, note: impl EntityRef<Note>) -> &mut Self {
240        self.request.reply_id.replace(note.entity_ref());
241        self
242    }
243
244    /// Sets the note as a renote of the specified note.
245    pub fn renote(&mut self, note: impl EntityRef<Note>) -> &mut Self {
246        self.request.renote_id.replace(note.entity_ref());
247        self
248    }
249
250    /// Sets the note to be posted to the specified channel.
251    #[cfg(feature = "12-47-0")]
252    #[cfg_attr(docsrs, doc(cfg(feature = "12-47-0")))]
253    pub fn channel(&mut self, channel: impl EntityRef<Channel>) -> &mut Self {
254        self.request.channel_id.replace(channel.entity_ref());
255        self
256    }
257}
258
259impl<C: Client> NoteBuilder<C> {
260    /// Creates the note.
261    pub async fn create(&self) -> Result<Note, Error<C::Error>> {
262        let response = self
263            .client
264            .request(&self.request)
265            .await
266            .map_err(Error::Client)?
267            .into_result()?;
268        Ok(response.created_note)
269    }
270}