1use std::ops::Deref;
4
5use js_int::UInt;
6use ruma_macros::EventContent;
7use serde::{Deserialize, Serialize};
8
9mod content_serde;
10mod unstable_poll_answers_serde;
11mod unstable_poll_kind_serde;
12
13use ruma_common::{MilliSecondsSinceUnixEpoch, OwnedEventId, room_version_rules::RedactionRules};
14
15use self::unstable_poll_answers_serde::UnstablePollAnswersDeHelper;
16use super::{
17 PollResponseData, compile_unstable_poll_results, generate_poll_end_fallback_text,
18 start::{PollAnswers, PollAnswersError, PollContentBlock, PollKind},
19 unstable_end::UnstablePollEndEventContent,
20};
21use crate::{
22 MessageLikeEventContent, MessageLikeEventType, RedactContent, RedactedMessageLikeEventContent,
23 StaticEventContent, relation::Replacement, room::message::RelationWithoutReplacement,
24};
25
26#[derive(Clone, Debug, Serialize, EventContent)]
37#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
38#[ruma_event(type = "org.matrix.msc3381.poll.start", kind = MessageLike, custom_redacted)]
39#[serde(untagged)]
40#[allow(clippy::large_enum_variant)]
41pub enum UnstablePollStartEventContent {
42 New(NewUnstablePollStartEventContent),
44
45 Replacement(ReplacementUnstablePollStartEventContent),
47}
48
49impl UnstablePollStartEventContent {
50 pub fn poll_start(&self) -> &UnstablePollStartContentBlock {
52 match self {
53 Self::New(c) => &c.poll_start,
54 Self::Replacement(c) => &c.relates_to.new_content.poll_start,
55 }
56 }
57}
58
59impl RedactContent for UnstablePollStartEventContent {
60 type Redacted = RedactedUnstablePollStartEventContent;
61
62 fn redact(self, _rules: &RedactionRules) -> Self::Redacted {
63 RedactedUnstablePollStartEventContent::default()
64 }
65}
66
67impl From<NewUnstablePollStartEventContent> for UnstablePollStartEventContent {
68 fn from(value: NewUnstablePollStartEventContent) -> Self {
69 Self::New(value)
70 }
71}
72
73impl From<ReplacementUnstablePollStartEventContent> for UnstablePollStartEventContent {
74 fn from(value: ReplacementUnstablePollStartEventContent) -> Self {
75 Self::Replacement(value)
76 }
77}
78
79impl OriginalSyncUnstablePollStartEvent {
80 pub fn compile_results<'a>(
87 &'a self,
88 responses: impl IntoIterator<Item = PollResponseData<'a>>,
89 ) -> UnstablePollEndEventContent {
90 let poll_start = self.content.poll_start();
91
92 let full_results = compile_unstable_poll_results(
93 poll_start,
94 responses,
95 Some(MilliSecondsSinceUnixEpoch::now()),
96 );
97 let results =
98 full_results.into_iter().map(|(id, users)| (id, users.len())).collect::<Vec<_>>();
99
100 let answers =
102 poll_start.answers.iter().map(|a| (a.id.as_str(), a.text.as_str())).collect::<Vec<_>>();
103 let plain_text = generate_poll_end_fallback_text(&answers, results.into_iter());
104
105 UnstablePollEndEventContent::new(plain_text, self.event_id.clone())
106 }
107}
108
109#[derive(Clone, Debug, Serialize)]
111#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
112pub struct NewUnstablePollStartEventContent {
113 #[serde(rename = "org.matrix.msc3381.poll.start")]
115 pub poll_start: UnstablePollStartContentBlock,
116
117 #[serde(rename = "org.matrix.msc1767.text")]
119 pub text: Option<String>,
120
121 #[serde(rename = "m.relates_to", skip_serializing_if = "Option::is_none")]
123 pub relates_to: Option<RelationWithoutReplacement>,
124}
125
126impl NewUnstablePollStartEventContent {
127 pub fn new(poll_start: UnstablePollStartContentBlock) -> Self {
129 Self { poll_start, text: None, relates_to: None }
130 }
131
132 pub fn plain_text(text: impl Into<String>, poll_start: UnstablePollStartContentBlock) -> Self {
135 Self { poll_start, text: Some(text.into()), relates_to: None }
136 }
137}
138
139impl StaticEventContent for NewUnstablePollStartEventContent {
140 const TYPE: &'static str = UnstablePollStartEventContent::TYPE;
141 type IsPrefix = <UnstablePollStartEventContent as StaticEventContent>::IsPrefix;
142}
143
144impl MessageLikeEventContent for NewUnstablePollStartEventContent {
145 fn event_type(&self) -> MessageLikeEventType {
146 MessageLikeEventType::UnstablePollStart
147 }
148}
149
150#[derive(Clone, Debug, Serialize, Deserialize)]
155#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
156pub struct NewUnstablePollStartEventContentWithoutRelation {
157 #[serde(rename = "org.matrix.msc3381.poll.start")]
159 pub poll_start: UnstablePollStartContentBlock,
160
161 #[serde(rename = "org.matrix.msc1767.text")]
163 pub text: Option<String>,
164}
165
166impl From<NewUnstablePollStartEventContent> for NewUnstablePollStartEventContentWithoutRelation {
167 fn from(value: NewUnstablePollStartEventContent) -> Self {
168 let NewUnstablePollStartEventContent { poll_start, text, .. } = value;
169 Self { poll_start, text }
170 }
171}
172
173#[derive(Clone, Debug)]
175#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
176pub struct ReplacementUnstablePollStartEventContent {
177 pub poll_start: Option<UnstablePollStartContentBlock>,
179
180 pub text: Option<String>,
182
183 pub relates_to: Replacement<NewUnstablePollStartEventContentWithoutRelation>,
185}
186
187impl ReplacementUnstablePollStartEventContent {
188 pub fn new(poll_start: UnstablePollStartContentBlock, replaces: OwnedEventId) -> Self {
193 Self {
194 poll_start: None,
195 text: None,
196 relates_to: Replacement {
197 event_id: replaces,
198 new_content: NewUnstablePollStartEventContent::new(poll_start).into(),
199 },
200 }
201 }
202
203 pub fn plain_text(
208 text: impl Into<String>,
209 poll_start: UnstablePollStartContentBlock,
210 replaces: OwnedEventId,
211 ) -> Self {
212 Self {
213 poll_start: None,
214 text: None,
215 relates_to: Replacement {
216 event_id: replaces,
217 new_content: NewUnstablePollStartEventContent::plain_text(text, poll_start).into(),
218 },
219 }
220 }
221}
222
223impl StaticEventContent for ReplacementUnstablePollStartEventContent {
224 const TYPE: &'static str = UnstablePollStartEventContent::TYPE;
225 type IsPrefix = <UnstablePollStartEventContent as StaticEventContent>::IsPrefix;
226}
227
228impl MessageLikeEventContent for ReplacementUnstablePollStartEventContent {
229 fn event_type(&self) -> MessageLikeEventType {
230 MessageLikeEventType::UnstablePollStart
231 }
232}
233
234#[derive(Clone, Debug, Default, Serialize, Deserialize)]
236#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
237pub struct RedactedUnstablePollStartEventContent {}
238
239impl RedactedUnstablePollStartEventContent {
240 pub fn new() -> RedactedUnstablePollStartEventContent {
242 Self::default()
243 }
244}
245
246impl StaticEventContent for RedactedUnstablePollStartEventContent {
247 const TYPE: &'static str = UnstablePollStartEventContent::TYPE;
248 type IsPrefix = <UnstablePollStartEventContent as StaticEventContent>::IsPrefix;
249}
250
251impl RedactedMessageLikeEventContent for RedactedUnstablePollStartEventContent {
252 fn event_type(&self) -> MessageLikeEventType {
253 MessageLikeEventType::UnstablePollStart
254 }
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
260pub struct UnstablePollStartContentBlock {
261 pub question: UnstablePollQuestion,
263
264 #[serde(default, with = "unstable_poll_kind_serde")]
266 pub kind: PollKind,
267
268 #[serde(default = "PollContentBlock::default_max_selections")]
274 pub max_selections: UInt,
275
276 pub answers: UnstablePollAnswers,
278}
279
280impl UnstablePollStartContentBlock {
281 pub fn new(question: impl Into<String>, answers: UnstablePollAnswers) -> Self {
283 Self {
284 question: UnstablePollQuestion::new(question),
285 kind: Default::default(),
286 max_selections: PollContentBlock::default_max_selections(),
287 answers,
288 }
289 }
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
294#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
295pub struct UnstablePollQuestion {
296 #[serde(rename = "org.matrix.msc1767.text")]
298 pub text: String,
299}
300
301impl UnstablePollQuestion {
302 pub fn new(text: impl Into<String>) -> Self {
304 Self { text: text.into() }
305 }
306}
307
308#[derive(Clone, Debug, Deserialize, Serialize)]
314#[serde(try_from = "UnstablePollAnswersDeHelper")]
315pub struct UnstablePollAnswers(Vec<UnstablePollAnswer>);
316
317impl TryFrom<Vec<UnstablePollAnswer>> for UnstablePollAnswers {
318 type Error = PollAnswersError;
319
320 fn try_from(value: Vec<UnstablePollAnswer>) -> Result<Self, Self::Error> {
321 if value.len() < PollAnswers::MIN_LENGTH {
322 Err(PollAnswersError::NotEnoughValues)
323 } else if value.len() > PollAnswers::MAX_LENGTH {
324 Err(PollAnswersError::TooManyValues)
325 } else {
326 Ok(Self(value))
327 }
328 }
329}
330
331impl TryFrom<&[UnstablePollAnswer]> for UnstablePollAnswers {
332 type Error = PollAnswersError;
333
334 fn try_from(value: &[UnstablePollAnswer]) -> Result<Self, Self::Error> {
335 Self::try_from(value.to_owned())
336 }
337}
338
339impl Deref for UnstablePollAnswers {
340 type Target = [UnstablePollAnswer];
341
342 fn deref(&self) -> &Self::Target {
343 &self.0
344 }
345}
346
347#[derive(Clone, Debug, Serialize, Deserialize)]
349#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
350pub struct UnstablePollAnswer {
351 pub id: String,
355
356 #[serde(rename = "org.matrix.msc1767.text")]
358 pub text: String,
359}
360
361impl UnstablePollAnswer {
362 pub fn new(id: impl Into<String>, text: impl Into<String>) -> Self {
364 Self { id: id.into(), text: text.into() }
365 }
366}