Skip to main content

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: u16,
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: u16::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 at most 32 days, and will be rounded to hours towards zero.
119    pub fn duration(self, duration: std::time::Duration) -> CreatePoll<Ready> {
120        const DAYS_32: u16 = 768;
121
122        let hours = duration.as_secs() / 3600;
123
124        CreatePoll {
125            question: self.question,
126            answers: self.answers,
127            duration: hours.try_into().unwrap_or(DAYS_32),
128            allow_multiselect: self.allow_multiselect,
129            layout_type: self.layout_type,
130            _stage: Ready,
131        }
132    }
133}
134
135impl<Stage: Sealed> CreatePoll<Stage> {
136    /// Sets the layout type for the Poll to take.
137    ///
138    /// This is currently only ever [`PollLayoutType::Default`], and is optional.
139    pub fn layout_type(mut self, layout_type: PollLayoutType) -> Self {
140        self.layout_type = Some(layout_type);
141        self
142    }
143
144    /// Allows users to select multiple answers for the Poll.
145    pub fn allow_multiselect(mut self) -> Self {
146        self.allow_multiselect = true;
147        self
148    }
149}
150
151#[derive(serde::Serialize, Clone, Debug, Default)]
152#[must_use = "Builders do nothing unless built"]
153pub struct CreatePollAnswer {
154    poll_media: PollMedia,
155}
156
157impl CreatePollAnswer {
158    /// Creates a builder for a Poll answer.
159    ///
160    /// [`Self::text`] or [`Self::emoji`] must be provided.
161    pub fn new() -> Self {
162        Self::default()
163    }
164
165    pub fn text(mut self, text: impl Into<String>) -> Self {
166        self.poll_media.text = Some(text.into());
167        self
168    }
169
170    pub fn emoji(mut self, emoji: impl Into<PollMediaEmoji>) -> Self {
171        self.poll_media.emoji = Some(emoji.into());
172        self
173    }
174}