Add p.rh command

This commit is contained in:
2026-04-06 17:11:08 -03:00
parent 5fdea76f37
commit 78fcfe386d
5 changed files with 180 additions and 7 deletions
Generated
+1 -1
View File
@@ -2013,7 +2013,7 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]] [[package]]
name = "payphone" name = "payphone"
version = "4.4.47" version = "4.4.48"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "payphone" name = "payphone"
version = "4.4.47" version = "4.4.48"
edition = "2024" edition = "2024"
default-run = "payphone" default-run = "payphone"
+61 -1
View File
@@ -3,7 +3,7 @@ use std::time::Duration;
use scylla::client::session::Session; use scylla::client::session::Session;
use crate::database::models::{Message, User}; use crate::database::models::{Channel, Message, User};
use crate::extras::Extras; use crate::extras::Extras;
use crate::framework::commands::arguments::{ArgumentParsingError, FromArguments, NeedyString}; use crate::framework::commands::arguments::{ArgumentParsingError, FromArguments, NeedyString};
use crate::framework::commands::context::CommandContext; use crate::framework::commands::context::CommandContext;
@@ -206,3 +206,63 @@ impl DerefMut for DurationArgument {
&mut self.0 &mut self.0
} }
} }
#[derive(Debug, Clone)]
pub struct ChannelArgument(pub Channel);
impl FromArguments<State, Extras> for ChannelArgument {
async fn from_args<'a>(
args: &'a str,
ctx: &'a CommandContext<'_, State, Extras>,
) -> Result<(Self, &'a str), ArgumentParsingError> {
let (identifier, args) = NeedyString::from_args(args, ctx).await?;
let identifier = identifier.0.trim();
if identifier == "." || identifier == "this" || identifier == "here" {
let channel = Channel::get_by_id(ctx.event.channel_id.get(), &ctx.rt.state.db).await?;
if let Some(channel) = channel {
return Ok((Self(channel), args));
}
} else if identifier.starts_with("<#") && identifier.ends_with(">") {
// The argument is a Discord mention.
let channel_id: u64 = identifier[2..identifier.len() - 1]
.to_string()
.parse()
.map_err(|_| ArgumentParsingError::InvalidArgument)?;
let channel = Channel::get_by_id(channel_id, &ctx.rt.state.db).await?;
if let Some(channel) = channel {
return Ok((Self(channel), args));
}
} else {
let channel_id: u64 = identifier
.parse()
.map_err(|_| ArgumentParsingError::InvalidArgument)?;
let channel = Channel::get_by_id(channel_id, &ctx.rt.state.db).await?;
if let Some(channel) = channel {
return Ok((Self(channel), args));
}
}
Err(ArgumentParsingError::MissingArgument)
}
}
impl Deref for ChannelArgument {
type Target = Channel;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ChannelArgument {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
+115 -3
View File
@@ -1,7 +1,8 @@
use async_trait::async_trait; use async_trait::async_trait;
use deadpool_redis::redis::AsyncTypedCommands;
use time::UtcDateTime; use time::UtcDateTime;
use crate::database::models::{UserIncident, UserIncidentType}; use crate::database::models::{Call, UserIncident, UserIncidentType};
use crate::extras::Extras; use crate::extras::Extras;
use crate::framework::commands::arguments::{ use crate::framework::commands::arguments::{
Arg, Argument, ArgumentParsingError, NeedyGreedyString, ParseArguments, Arg, Argument, ArgumentParsingError, NeedyGreedyString, ParseArguments,
@@ -10,11 +11,11 @@ use crate::framework::commands::context::CommandContext;
use crate::framework::commands::permissions::Permission; use crate::framework::commands::permissions::Permission;
use crate::framework::commands::{Command, CommandError}; use crate::framework::commands::{Command, CommandError};
use crate::framework::runtime::Runtime; use crate::framework::runtime::Runtime;
use crate::groups::moderation::arguments::{DurationArgument, UserArgument}; use crate::groups::moderation::arguments::{ChannelArgument, DurationArgument, UserArgument};
use crate::groups::moderation::get_incident_page; use crate::groups::moderation::get_incident_page;
use crate::groups::moderation::permissions::{BotModeratorPermission, BotOwnerPermission}; use crate::groups::moderation::permissions::{BotModeratorPermission, BotOwnerPermission};
use crate::state::State; use crate::state::State;
use crate::utils::get_random_u64; use crate::utils::{Bye, get_random_u64};
#[derive(Debug)] #[derive(Debug)]
pub struct ModeratorCommand; pub struct ModeratorCommand;
@@ -246,3 +247,114 @@ impl Command<State, Extras> for BanCommand {
Ok(()) Ok(())
} }
} }
#[derive(Debug)]
pub struct RemoteHangupCommand;
#[async_trait]
impl Command<State, Extras> for RemoteHangupCommand {
fn name(&self) -> &'static str {
"remotehangup"
}
fn aliases(&self) -> &'static [&'static str] {
&["rh"]
}
fn permissions(&self) -> Vec<Box<dyn Permission<State, Extras> + Send + Sync + 'static>> {
vec![Box::new(BotModeratorPermission)]
}
fn show_in_help(&self, _rt: &Runtime<State, Extras>, extras: &Extras) -> bool {
extras.user.is_moderator
}
fn arguments(&self) -> &'static [Argument] {
&[Argument {
name: "channel",
is_optional: false,
is_greedy: false,
}]
}
async fn execute(
&self,
ctx: CommandContext<'_, State, Extras>,
args: &str,
) -> Result<(), CommandError> {
let Arg(mut channel) = match Arg::<ChannelArgument>::parse(args, &ctx).await {
Ok(arg) => arg,
Err(e) => match e {
ArgumentParsingError::InvalidArgument => {
ctx.send().content("❌ Channel not found in DB.").await?;
return Ok(());
}
ArgumentParsingError::DatabaseFailed(e) => {
return Err(CommandError::DatabaseFailed(e));
}
e => {
unreachable!("Unexpected error: {e}");
}
},
};
if channel.is_searching {
channel
.cancel_search(&ctx.rt.state.db)
.await
.inspect_err(|e| {
log::error!("Failed to cancel search: {e:?}");
})?;
ctx.send()
.content("The channel was remotely made to stop searching for a call!")
.await?;
return Ok(());
}
if let Some(call_id) = channel.call_id {
let mut redis = ctx.rt.state.redis.get().await?;
let Some(call) = Call::get_by_id(call_id, &ctx.rt.state.db)
.await
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))?
else {
ctx.send().content("❌ The call could not be found.\n-# This is not supposed to happen. The channel is pointing to an ID in the database that does not exist.").await?;
channel.call_id = None;
channel.upsert(&ctx.rt.state.db).await.bye();
redis
.zrem("active-calls", format!("{}:{}", call_id, channel.id))
.await
.bye();
return Err(CommandError::DatabaseInconsistentState);
};
let channel_keys = [
format!("{call_id}:{}", call.channel_ids[0]),
format!("{call_id}:{}", call.channel_ids[1]),
];
redis
.zrem("active-calls", &channel_keys)
.await
.inspect_err(|e| log::error!("Failed to remove inactive call from redis: {e}"))
.ok();
call.hangup(&ctx.rt.state.db)
.await
.inspect_err(|e| log::error!("Failed to hang up call: {e:?}"))?;
} else {
ctx.send()
.content("❌ This channel is not in a call.")
.await?;
return Ok(());
}
Ok(())
}
}
+2 -1
View File
@@ -137,7 +137,7 @@ use crate::groups::calls::events::{
use crate::groups::gateway::events::{ReadyHandler, ReconnectHandler}; use crate::groups::gateway::events::{ReadyHandler, ReconnectHandler};
use crate::groups::leveling::commands::LeaderboardCommand; use crate::groups::leveling::commands::LeaderboardCommand;
use crate::groups::leveling::events::MessageLevelingHandler; use crate::groups::leveling::events::MessageLevelingHandler;
use crate::groups::moderation::commands::{BanCommand, IncidentsCommand, ModeratorCommand}; use crate::groups::moderation::commands::{BanCommand, IncidentsCommand, ModeratorCommand, RemoteHangupCommand};
use crate::groups::servers::commands::PrefixCommand; use crate::groups::servers::commands::PrefixCommand;
use crate::groups::servers::events::{ use crate::groups::servers::events::{
ServerCreateHandler, ServerDeleteHandler, ServerUpdateHandler, ServerCreateHandler, ServerDeleteHandler, ServerUpdateHandler,
@@ -298,6 +298,7 @@ async fn start(
.command(ModeratorCommand) .command(ModeratorCommand)
.command(IncidentsCommand) .command(IncidentsCommand)
.command(BanCommand) .command(BanCommand)
.command(RemoteHangupCommand)
.component(HelpGoBackButton) .component(HelpGoBackButton)
.cache_resource_type(ResourceType::GUILD) .cache_resource_type(ResourceType::GUILD)
.cache_resource_type(ResourceType::CHANNEL) .cache_resource_type(ResourceType::CHANNEL)