use crate::api::BotApi;
use crate::models::api::AudioAction;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u32)]
pub enum AudioStatus {
#[default]
Start = 0,
Pause = 1,
Resume = 2,
Stop = 3,
}
impl Serialize for AudioStatus {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u32(*self as u32)
}
}
impl<'de> Deserialize<'de> for AudioStatus {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
match u32::deserialize(deserializer)? {
0 => Ok(Self::Start),
1 => Ok(Self::Pause),
2 => Ok(Self::Resume),
3 => Ok(Self::Stop),
value => Err(serde::de::Error::custom(format!(
"invalid audio status {value}"
))),
}
}
}
#[allow(non_upper_case_globals)]
pub const AudioStatusStart: AudioStatus = AudioStatus::Start;
#[allow(non_upper_case_globals)]
pub const AudioStatusPause: AudioStatus = AudioStatus::Pause;
#[allow(non_upper_case_globals)]
pub const AudioStatusResume: AudioStatus = AudioStatus::Resume;
#[allow(non_upper_case_globals)]
pub const AudioStatusStop: AudioStatus = AudioStatus::Stop;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PublicAudioType {
#[default]
Voice,
Live,
Unknown(u8),
}
wire_enum!(PublicAudioType, u8, Unknown, {
Voice = 2,
Live = 5,
});
impl Serialize for PublicAudioType {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u8(u8::from(*self))
}
}
impl<'de> Deserialize<'de> for PublicAudioType {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self::from(u8::deserialize(deserializer)?))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct AudioControl {
#[serde(default)]
pub audio_url: String,
#[serde(default)]
pub text: String,
#[serde(default)]
pub status: AudioStatus,
}
#[derive(Debug, Clone, Serialize)]
pub struct Audio {
#[serde(skip)]
api: BotApi,
pub event_id: Option<String>,
pub channel_id: Option<String>,
pub guild_id: Option<String>,
pub audio_url: Option<String>,
pub text: Option<String>,
}
impl Audio {
pub fn new(api: BotApi, event_id: Option<String>, data: AudioAction) -> Self {
Self {
api,
event_id,
channel_id: non_empty(data.channel_id),
guild_id: non_empty(data.guild_id),
audio_url: non_empty(data.audio_url),
text: non_empty(data.text),
}
}
pub fn api(&self) -> &BotApi {
&self.api
}
}
fn non_empty(value: String) -> Option<String> {
(!value.is_empty()).then_some(value)
}
impl std::fmt::Display for Audio {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Audio {{ channel_id: {:?}, guild_id: {:?}, audio_url: {:?}, text: {:?}, event_id: {:?} }}",
self.channel_id, self.guild_id, self.audio_url, self.text, self.event_id
)
}
}
#[derive(Debug, Clone, Serialize)]
pub struct PublicAudio {
#[serde(skip)]
api: BotApi,
pub guild_id: Option<String>,
pub channel_id: Option<String>,
pub channel_type: Option<PublicAudioType>,
pub user_id: Option<String>,
}
#[derive(Debug, Default, Deserialize)]
struct PublicAudioWire {
#[serde(default)]
guild_id: Option<String>,
#[serde(default)]
channel_id: Option<String>,
#[serde(default)]
channel_type: Option<PublicAudioType>,
#[serde(default)]
user_id: Option<String>,
}
impl PublicAudio {
pub fn new(api: BotApi, data: serde_json::Value) -> Self {
let wire: PublicAudioWire = serde_json::from_value(data).unwrap_or_default();
Self {
api,
guild_id: wire.guild_id,
channel_id: wire.channel_id,
channel_type: wire.channel_type,
user_id: wire.user_id,
}
}
pub fn api(&self) -> &BotApi {
&self.api
}
}
impl std::fmt::Display for PublicAudio {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"PublicAudio {{ guild_id: {:?}, channel_id: {:?}, channel_type: {:?}, user_id: {:?} }}",
self.guild_id, self.channel_id, self.channel_type, self.user_id
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_audio_status() {
assert_eq!(AudioStatus::Start as u32, 0);
assert_eq!(AudioStatus::Pause as u32, 1);
assert_eq!(AudioStatus::Resume as u32, 2);
assert_eq!(AudioStatus::Stop as u32, 3);
}
#[test]
fn audio_status_uses_numeric_json() {
assert_eq!(
serde_json::to_value(AudioStatus::Start).unwrap(),
serde_json::json!(0)
);
assert_eq!(
serde_json::to_value(AudioStatus::Pause).unwrap(),
serde_json::json!(1)
);
assert_eq!(
serde_json::from_value::<AudioStatus>(serde_json::json!(3)).unwrap(),
AudioStatus::Stop
);
assert!(serde_json::from_value::<AudioStatus>(serde_json::json!(4)).is_err());
}
#[test]
fn audio_control_uses_json_shape() {
let control = AudioControl {
audio_url: "https://example.com/audio.mp3".to_string(),
text: "now playing".to_string(),
status: AudioStatus::Start,
};
let value = serde_json::to_value(&control).unwrap();
assert_eq!(value["audio_url"], "https://example.com/audio.mp3");
assert_eq!(value["text"], "now playing");
assert_eq!(value["status"], 0);
}
#[test]
fn test_public_audio_type() {
assert_eq!(u8::from(PublicAudioType::Voice), 2);
assert_eq!(u8::from(PublicAudioType::Live), 5);
}
#[test]
fn audio_event_helper_hides_empty_zero_values() {
let http = crate::http::HttpClient::new(30, false).unwrap();
let api = BotApi::new(http);
let audio = Audio::new(api, None, AudioAction::default());
assert!(audio.guild_id.is_none());
assert!(audio.channel_id.is_none());
assert!(audio.audio_url.is_none());
assert!(audio.text.is_none());
}
}