serenity/builder/
create_poll.rs

1use crate::model::channel::{PollLayoutType, PollMedia, PollMediaEmoji};
2
3#[derive(serde::Serialize, Clone, Debug)]
4pub struct NeedsQuestion;
5#[derive(serde::Serialize, Clone, Debug)]
6pub struct NeedsAnswers;
7#[derive(serde::Serialize, Clone, Debug)]
8pub struct NeedsDuration;
9#[derive(serde::Serialize, Clone, Debug)]
10pub struct Ready;
11
12mod sealed {
13    use super::*;
14
15    pub trait Sealed {}
16
17    impl Sealed for NeedsQuestion {}
18    impl Sealed for NeedsAnswers {}
19    impl Sealed for NeedsDuration {}
20    impl Sealed for Ready {}
21}
22
23use sealed::*;
24
25/// "Only text is supported."
26#[derive(serde::Serialize, Clone, Debug)]
27struct CreatePollMedia {
28    text: String,
29}
30
31#[derive(serde::Serialize, Clone, Debug)]
32#[must_use = "Builders do nothing unless built"]
33pub struct CreatePoll<Stage: Sealed> {
34    question: CreatePollMedia,
35    answers: Vec<CreatePollAnswer>,
36    duration: u8,
37    allow_multiselect: bool,
38    layout_type: Option<PollLayoutType>,
39
40    #[serde(skip)]
41    _stage: Stage,
42}
43
44impl Default for CreatePoll<NeedsQuestion> {
45    /// See the documentation of [`Self::new`].
46    fn default() -> Self {
47        // Producing dummy values is okay as we must transition through all `Stage`s before firing,
48        // which fills in the values with real values.
49        Self {
50            question: CreatePollMedia {
51                text: String::default(),
52            },
53            answers: Vec::default(),
54            duration: u8::default(),
55            allow_multiselect: false,
56            layout_type: None,
57
58            _stage: NeedsQuestion,
59        }
60    }
61}
62
63impl CreatePoll<NeedsQuestion> {
64    /// Creates a builder for creating a Poll.
65    ///
66    /// This must be transitioned through in order, to provide all required fields.
67    ///
68    /// ```rust
69    /// use serenity::builder::{CreateMessage, CreatePoll, CreatePollAnswer};
70    ///
71    /// let poll = CreatePoll::new()
72    ///     .question("Cats or Dogs?")
73    ///     .answers(vec![
74    ///         CreatePollAnswer::new().emoji("🐱".to_string()).text("Cats!"),
75    ///         CreatePollAnswer::new().emoji("🐶".to_string()).text("Dogs!"),
76    ///         CreatePollAnswer::new().text("Neither..."),
77    ///     ])
78    ///     .duration(std::time::Duration::from_secs(60 * 60 * 24 * 7));
79    ///
80    /// let message = CreateMessage::new().poll(poll);
81    /// ```
82    pub fn new() -> Self {
83        Self::default()
84    }
85
86    /// Sets the question to be polled.
87    pub fn question(self, text: impl Into<String>) -> CreatePoll<NeedsAnswers> {
88        CreatePoll {
89            question: CreatePollMedia {
90                text: text.into(),
91            },
92            answers: self.answers,
93            duration: self.duration,
94            allow_multiselect: self.allow_multiselect,
95            layout_type: self.layout_type,
96            _stage: NeedsAnswers,
97        }
98    }
99}
100
101impl CreatePoll<NeedsAnswers> {
102    /// Sets the answers that can be picked from.
103    pub fn answers(self, answers: Vec<CreatePollAnswer>) -> CreatePoll<NeedsDuration> {
104        CreatePoll {
105            question: self.question,
106            answers,
107            duration: self.duration,
108            allow_multiselect: self.allow_multiselect,
109            layout_type: self.layout_type,
110            _stage: NeedsDuration,
111        }
112    }
113}
114
115impl CreatePoll<NeedsDuration> {
116    /// Sets the duration for the Poll to run for.
117    ///
118    /// This must be less than a week, and will be rounded to hours towards zero.
119    pub fn duration(self, duration: std::time::Duration) -> CreatePoll<Ready> {
120        let hours = duration.as_secs() / 3600;
121
122        CreatePoll {
123            question: self.question,
124            answers: self.answers,
125            duration: hours.try_into().unwrap_or(168),
126            allow_multiselect: self.allow_multiselect,
127            layout_type: self.layout_type,
128            _stage: Ready,
129        }
130    }
131}
132
133impl<Stage: Sealed> CreatePoll<Stage> {
134    /// Sets the layout type for the Poll to take.
135    ///
136    /// This is currently only ever [`PollLayoutType::Default`], and is optional.
137    pub fn layout_type(mut self, layout_type: PollLayoutType) -> Self {
138        self.layout_type = Some(layout_type);
139        self
140    }
141
142    /// Allows users to select multiple answers for the Poll.
143    pub fn allow_multiselect(mut self) -> Self {
144        self.allow_multiselect = true;
145        self
146    }
147}
148
149#[derive(serde::Serialize, Clone, Debug, Default)]
150#[must_use = "Builders do nothing unless built"]
151pub struct CreatePollAnswer {
152    poll_media: PollMedia,
153}
154
155impl CreatePollAnswer {
156    /// Creates a builder for a Poll answer.
157    ///
158    /// [`Self::text`] or [`Self::emoji`] must be provided.
159    pub fn new() -> Self {
160        Self::default()
161    }
162
163    pub fn text(mut self, text: impl Into<String>) -> Self {
164        self.poll_media.text = Some(text.into());
165        self
166    }
167
168    pub fn emoji(mut self, emoji: impl Into<PollMediaEmoji>) -> Self {
169        self.poll_media.emoji = Some(emoji.into());
170        self
171    }
172}