logo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! Types for voice message events ([MSC3245]).
//!
//! [MSC3245]: https://github.com/matrix-org/matrix-spec-proposals/pull/3245

use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};

use super::{
    audio::AudioContent,
    file::FileContent,
    message::{MessageContent, TryFromExtensibleError},
    room::message::{
        AudioInfo, AudioMessageEventContent, MessageType, Relation, RoomMessageEventContent,
    },
};

/// The payload for an extensible voice message.
///
/// This is the new primary type introduced in [MSC3245] and should not be sent before the end of
/// the transition period. See the documentation of the [`message`] module for more information.
///
/// `VoiceEventContent` can be converted to a [`RoomMessageEventContent`] with a
/// [`MessageType::Audio`] with the `m.voice` flag. You can convert it back with
/// [`VoiceEventContent::try_from_audio_room_message()`].
///
/// [MSC3245]: https://github.com/matrix-org/matrix-spec-proposals/pull/3245
/// [`message`]: super::message
/// [`RoomMessageEventContent`]: super::room::message::RoomMessageEventContent
/// [`MessageType::Audio`]: super::room::message::MessageType::Audio
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.voice", kind = MessageLike)]
pub struct VoiceEventContent {
    /// The text representation of the message.
    #[serde(flatten)]
    pub message: MessageContent,

    /// The file content of the message.
    #[serde(rename = "m.file")]
    pub file: FileContent,

    /// The audio content of the message.
    #[serde(rename = "m.audio")]
    pub audio: AudioContent,

    /// The voice content of the message.
    #[serde(rename = "m.voice")]
    pub voice: VoiceContent,

    /// Information about related messages.
    #[serde(flatten, skip_serializing_if = "Option::is_none")]
    pub relates_to: Option<Relation>,
}

impl VoiceEventContent {
    /// Creates a new `VoiceEventContent` with the given plain text representation and file.
    pub fn plain(message: impl Into<String>, file: FileContent) -> Self {
        Self {
            message: MessageContent::plain(message),
            file,
            audio: Default::default(),
            voice: Default::default(),
            relates_to: None,
        }
    }

    /// Creates a new `VoiceEventContent` with the given message and file.
    pub fn with_message(message: MessageContent, file: FileContent) -> Self {
        Self {
            message,
            file,
            audio: Default::default(),
            voice: Default::default(),
            relates_to: None,
        }
    }

    /// Create a new `VoiceEventContent` from the given `AudioMessageEventContent` and optional
    /// relation.
    ///
    /// This can fail if the `AudioMessageEventContent` is not a voice message.
    pub fn try_from_audio_room_message(
        content: AudioMessageEventContent,
        relates_to: Option<Relation>,
    ) -> Result<Self, TryFromExtensibleError> {
        let AudioMessageEventContent { body, source, info, message, file, audio, voice } = content;
        let AudioInfo { duration, mimetype, size } = info.map(|info| *info).unwrap_or_default();

        let message = message.unwrap_or_else(|| MessageContent::plain(body));
        let file = file.unwrap_or_else(|| {
            FileContent::from_room_message_content(source, None, mimetype, size)
        });
        let audio = audio
            .or_else(|| duration.map(AudioContent::from_room_message_content))
            .unwrap_or_default();
        let voice = if let Some(voice) = voice {
            voice
        } else {
            return Err(TryFromExtensibleError::MissingField("m.voice".to_owned()));
        };

        Ok(Self { message, file, audio, voice, relates_to })
    }
}

impl From<VoiceEventContent> for RoomMessageEventContent {
    fn from(content: VoiceEventContent) -> Self {
        let VoiceEventContent { message, file, audio, voice, relates_to } = content;

        Self {
            msgtype: MessageType::Audio(AudioMessageEventContent::from_extensible_voice_content(
                message, file, audio, voice,
            )),
            relates_to,
        }
    }
}

/// Voice content.
///
/// This is currently empty and used as a flag to mark an audio event that should be displayed as a
/// voice message.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct VoiceContent {}

impl VoiceContent {
    /// Creates a new empty `VoiceContent`.
    pub fn new() -> Self {
        Self::default()
    }
}