403 lines
12 KiB
Rust
403 lines
12 KiB
Rust
use async_trait::async_trait;
|
|
use scc::HashSet;
|
|
use twilight_model::id::Id;
|
|
|
|
use crate::{
|
|
database::models::{Call, Channel},
|
|
discord::sanitizing::SanitizeDiscordMarkdown,
|
|
framework::commands::{Command, CommandError, context::CommandContext},
|
|
groups::calls::get_display_profile,
|
|
state::State,
|
|
utils::Bye,
|
|
};
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct CallCommand {
|
|
/// A set of channel IDs that are currently searching for a call. This works as a lock for two
|
|
/// users not to use p.call at the same time in the same channel, as it could create a race
|
|
/// condition. IDs are removed from this set once the DB is properly updated with the channel's
|
|
/// state, meaning that not all searching channels will be in this set at all times.
|
|
channel_lock: HashSet<u64>,
|
|
}
|
|
|
|
impl CallCommand {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
channel_lock: HashSet::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Command<State> for CallCommand {
|
|
fn name(&self) -> &'static str {
|
|
"call"
|
|
}
|
|
|
|
fn aliases(&self) -> &'static [&'static str] {
|
|
&["c"]
|
|
}
|
|
|
|
async fn execute(
|
|
&self,
|
|
ctx: CommandContext<'_, State>,
|
|
_args: &str,
|
|
) -> Result<(), CommandError> {
|
|
let channel = Channel::get_or_create_by_id(
|
|
ctx.event.channel_id.get(),
|
|
&ctx.rt.state.db,
|
|
&ctx.rt.http,
|
|
)
|
|
.await
|
|
.inspect_err(|e| {
|
|
log::error!("Failed to get or create channel: {e:?}");
|
|
})
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
|
|
|
if channel.is_searching || self.channel_lock.contains(&ctx.event.channel_id.get()) {
|
|
ctx.send()
|
|
.content(format!(
|
|
"☎️ You are already searching for a call! Use `{}hangup` to hang up the phone.",
|
|
ctx.prefix
|
|
))
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
if channel.call_id.is_some() {
|
|
ctx.send()
|
|
.content(format!("☎️ You are already on a call! Use `{}hangup` to hang up the phone, or `{}skip` to skip to the next call.", ctx.prefix, ctx.prefix))
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
self.channel_lock
|
|
.insert_async(ctx.event.channel_id.get())
|
|
.await
|
|
.bye();
|
|
|
|
let call = Call::start(&channel, &ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| {
|
|
log::error!("Failed to start call: {e:?}");
|
|
self.channel_lock.remove(&ctx.event.channel_id.get());
|
|
})
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
|
|
|
self.channel_lock
|
|
.remove_async(&ctx.event.channel_id.get())
|
|
.await;
|
|
|
|
if let Some(call) = call {
|
|
ctx.send().content("☎️ Call connected. Say hi!").await.bye();
|
|
ctx.rt
|
|
.http
|
|
.create_message(Id::new(call.other_party_channel_id(channel.id)))
|
|
.content("☎️ Call connected. Say hi!")
|
|
.await
|
|
.bye();
|
|
} else {
|
|
ctx.send()
|
|
.content("☎️ Searching for a call...")
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SkipCommand;
|
|
|
|
#[async_trait]
|
|
impl Command<State> for SkipCommand {
|
|
fn name(&self) -> &'static str {
|
|
"skip"
|
|
}
|
|
|
|
fn aliases(&self) -> &'static [&'static str] {
|
|
&["s", "next"]
|
|
}
|
|
|
|
async fn execute(
|
|
&self,
|
|
ctx: CommandContext<'_, State>,
|
|
_args: &str,
|
|
) -> Result<(), CommandError> {
|
|
let channel = Channel::get_or_create_by_id(
|
|
ctx.event.channel_id.get(),
|
|
&ctx.rt.state.db,
|
|
&ctx.rt.http,
|
|
)
|
|
.await
|
|
.inspect_err(|e| {
|
|
log::error!("Failed to get or create channel: {e:?}");
|
|
})
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
|
|
|
if channel.call_id.is_none() {
|
|
ctx.send()
|
|
.content(format!(
|
|
"❌ You're not on a call! Use `{}call` to start one.",
|
|
ctx.prefix
|
|
))
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
if channel.is_searching {
|
|
ctx.send()
|
|
.content(format!(
|
|
"☎️ You're already searching for a call! Use `{}hangup` to cancel the search.",
|
|
ctx.prefix
|
|
))
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
let call = Call::get_by_id(channel.call_id.unwrap(), &ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
|
.ok_or(CommandError::DatabaseInconsistentState)?;
|
|
|
|
call.hangup(&ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| log::error!("Failed to hang up call: {e:?}"))
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
|
|
|
let other_channel_id = call.other_party_channel_id(ctx.event.channel_id.get());
|
|
|
|
ctx.rt
|
|
.http
|
|
.create_message(Id::new(other_channel_id))
|
|
.content("☎️ The other party hung up the phone.")
|
|
.await
|
|
.inspect_err(|e| {
|
|
log::error!("Failed to send message to other party: {e:?}");
|
|
})
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
let call = Call::start(&channel, &ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| {
|
|
log::error!("Failed to start (skipped) call: {e:?}");
|
|
})
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
|
|
|
if let Some(call) = call {
|
|
ctx.send().content("☎️ Call connected. Say hi!").await.bye();
|
|
ctx.rt
|
|
.http
|
|
.create_message(Id::new(call.other_party_channel_id(channel.id)))
|
|
.content("☎️ Call connected. Say hi!")
|
|
.await
|
|
.bye();
|
|
} else {
|
|
ctx.send()
|
|
.content("☎️ Skipped! Searching for a call...")
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct HangupCommand;
|
|
|
|
#[async_trait]
|
|
impl Command<State> for HangupCommand {
|
|
fn name(&self) -> &'static str {
|
|
"hangup"
|
|
}
|
|
|
|
fn aliases(&self) -> &'static [&'static str] {
|
|
&["h", "hang"]
|
|
}
|
|
|
|
async fn execute(
|
|
&self,
|
|
ctx: CommandContext<'_, State>,
|
|
_args: &str,
|
|
) -> Result<(), CommandError> {
|
|
let Some(channel) = Channel::get_by_id(ctx.event.channel_id.get(), &ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| log::error!("Failed to get channel: {e:?}"))
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
|
else {
|
|
ctx.send()
|
|
.content(format!(
|
|
"❌ You're not on a call! Use `{}call` to start one.",
|
|
ctx.prefix
|
|
))
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
return Ok(());
|
|
};
|
|
|
|
if channel.is_searching {
|
|
channel
|
|
.cancel_search(&ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| {
|
|
log::error!("Failed to cancel search: {e:?}");
|
|
})
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
|
|
|
ctx.send()
|
|
.content("☎️ The phone was hung up.")
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(call_id) = channel.call_id {
|
|
let Some(call) = Call::get_by_id(call_id, &ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
|
else {
|
|
ctx.send()
|
|
.content("❌ Call not found.")
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
return Err(CommandError::DatabaseInconsistentState);
|
|
};
|
|
|
|
call.hangup(&ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| log::error!("Failed to hang up call: {e:?}"))
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
|
|
|
ctx.send().content("☎️ You hung up the phone.").await.bye();
|
|
|
|
ctx.rt
|
|
.http
|
|
.create_message(Id::new(call.other_party_channel_id(channel.id)))
|
|
.content("☎️ The other party hung up the phone.")
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
} else {
|
|
ctx.send()
|
|
.content(format!(
|
|
"❌ You're not on a call! Use `{}call` to start one.",
|
|
ctx.prefix
|
|
))
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct FriendRequestCommand;
|
|
|
|
#[async_trait]
|
|
impl Command<State> for FriendRequestCommand {
|
|
fn name(&self) -> &'static str {
|
|
"friendreq"
|
|
}
|
|
|
|
fn aliases(&self) -> &'static [&'static str] {
|
|
&["f", "fr", "friendrequest"]
|
|
}
|
|
|
|
fn description(&self) -> &'static str {
|
|
"Shows your username to the other party and asks them to send you a friend request on Discord."
|
|
}
|
|
|
|
async fn execute(
|
|
&self,
|
|
ctx: CommandContext<'_, State>,
|
|
_args: &str,
|
|
) -> Result<(), CommandError> {
|
|
let channel = Channel::get_by_id(ctx.event.channel_id.get(), &ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| log::error!("Failed to get channel: {e:?}"))
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
|
|
|
let Some(channel) = channel else {
|
|
ctx.send()
|
|
.content(format!(
|
|
"❌ You're not on a call! Use `{}call` to start one.",
|
|
ctx.prefix
|
|
))
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
return Ok(());
|
|
};
|
|
|
|
let call = if let Some(call_id) = channel.call_id {
|
|
Call::get_by_id(call_id, &ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
|
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
|
} else {
|
|
ctx.send()
|
|
.content(format!(
|
|
"❌ You're not on a call! Use `{}call` to start one.",
|
|
ctx.prefix
|
|
))
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
return Ok(());
|
|
};
|
|
|
|
let Some(call) = call else {
|
|
ctx.send()
|
|
.content("❌ Oops! We could not find that call.")
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
return Err(CommandError::DatabaseInconsistentState);
|
|
};
|
|
|
|
let other_channel_id = call.other_party_channel_id(channel.id);
|
|
|
|
let profile = get_display_profile(ctx.event);
|
|
|
|
ctx
|
|
.rt
|
|
.http
|
|
.create_message(
|
|
Id::new(other_channel_id)
|
|
)
|
|
.content(
|
|
&format!(
|
|
"📬 **{}** wants you to send them a friend request on Discord! Their username is `{}`.",
|
|
profile.name.sanitize(false),
|
|
ctx.event.author.name.sanitize(false)
|
|
)
|
|
)
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
ctx.send()
|
|
.content("📬 Asked the other party to send you a friend request!")
|
|
.await
|
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|