10 Commits
Author SHA1 Message Date
nyeki ee01d2160d Update tip strings 2026-05-07 23:16:22 -03:00
nyeki 16cbda838e Fix p.help shard display 2026-04-06 17:30:19 -03:00
nyeki ce0cef5db7 Bump version in cargo.roml 2026-04-06 17:25:56 -03:00
nyeki afcfdad13d Add confirmation message to p.rh 2026-04-06 17:20:02 -03:00
nyeki 2823868460 Add shard number to p.help 2026-04-06 17:18:10 -03:00
nyeki 78fcfe386d Add p.rh command 2026-04-06 17:11:08 -03:00
nyeki 5fdea76f37 Fix more linter warnings 2026-04-03 06:02:50 -03:00
nyeki 6248dc72f0 Fix some linting warningss 2026-04-03 06:01:48 -03:00
nyeki 53e066f868 Add exception to on call message event handler 2026-04-03 05:58:50 -03:00
nyeki c9b81219e5 Make exception for bot owner in NonNewAccountPermission 2026-04-03 05:57:35 -03:00
17 changed files with 250 additions and 138 deletions
Generated
+1 -1
View File
@@ -2013,7 +2013,7 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "payphone"
version = "4.4.46"
version = "4.4.52"
dependencies = [
"anyhow",
"async-trait",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "payphone"
version = "4.4.46"
version = "4.4.52"
edition = "2024"
default-run = "payphone"
+23 -23
View File
@@ -1,24 +1,24 @@
$ANSIBLE_VAULT;1.1;AES256
38323862623762363734393366316436666137373937653630656665666232333435326166306633
6438393065663630383537316230336633363932646663300a666666393930376638663537663837
64366231366561623365363634323466626333626534326631323964613361373339333639623137
3335376535313338390a373262393230353238373332316464653165363762323634646335613564
31356633643837643437366130623638643133333336343264623536346339313365663533346566
61333564323066386639353032306639656465383263616263386563343539313530396662613831
61393030363264346564626239663531343433616231643732343835333338326538393838396132
36316331666131616264613461323432663430616562303865636461306264336330646263313439
66633261333063313162393765653263356263333934366333363037363765646264386632393733
39663339313939366162656336353563643836376366363032363430306162666331373165643462
32363930373231646135306466613935646636386639643137313830346531393735373938373531
63356665613434353162366638396339396130643066336135616433633830313736343265646165
61373461616363623239616534396431646561333630666230633766363832613365636664633865
32626636393262303235626132666661623134373766663135643131626363383238363836326232
62363962613965356165653134366235376364333965303361643634636231323639373132623336
33383532383237636436303164396531343134633838373936646132353132386465636330363161
65653937313032346138373435656663653639653133373564366137363261326438336436303330
32386434663236643132326536306437653231383366366363666461323931613839383762343361
38343138613466346335396136313334343731313265633365646666366131653837343664663666
62616336386339393465626138613465316334303239643335633239666165373630333135373062
35353530643164643732633732336364326161366666333233313833313939663535653763636237
62633064363561323065653162336134643338623065356536613961303034323235363631343530
663832616435656433306266643463373930
30363364303339383037643864396632306537333737383933363164663838333364323837633838
3561323032663461663834613634633734636333306638380a396436646332373330393931396530
36353439343736656432333437653764653132393364626136396230623038626330313366386263
3630663539303665340a623961656435373337363534363863353934326336653361663538616664
36313865646530346633626639393739653131313835383631613833393832396434633038343639
62656236326662343236303133306137653533633064626161636165646164646165653534633036
63623863353438616237323530383033366138656665613331666532316562336232353239323535
33326237653463663862383266373534343434383765303237613436643439313233396338376134
65303161313930383165636133616236386435663232313531633461383938326131353930353761
35303238333863666137313330336366623536613230353639666133333633353566303131393666
32313964636138343632373064366166333134343437613937303538613834623934623161626261
63303832663438356331363534316230613561613961306561376432646132663733303366393961
64373166633938633730643339646433623235306161386338316330336264623031363365376634
38306639356236383464653038663732653465626265663939643234306563346330336139373235
36616662313139356139363532633664333762623235326237636339323039373131666162613930
63336161396635646565393565373736616431643830366163356532383034396636323163623836
37613632343563363032303133333930653431343039633239306561303266393861366134643734
66326534343464363634383461326330346432613266653066643139636232656638346635343130
61383336623763353034316238313536336261616433356461666537633665636430393139663465
61643066356466646264616562343939616336343935663939313537356237303264303761376139
34623231623264633934363537643132643336313633663430643265633564646363623130396437
61383763396633663439626661333630373662616633353232356165663034336131343666643633
373138346631323465303932633962613234
+1 -1
View File
@@ -101,7 +101,7 @@
"--redis-urls",
"redis://redis-cluster.redis",
"--bot-owner-id",
"1357199907512914051"
"1486270291410817114"
]
envFrom:
- secretRef:
+3 -52
View File
@@ -18,7 +18,7 @@ use twilight_model::channel::{Message as TwilightMessage, Webhook};
use twilight_model::guild::Guild as TwilightGuild;
use twilight_model::id::Id;
use twilight_model::user::User as TwilightUser;
use twilight_validate::request::{ValidationError, ValidationErrorType, webhook_username};
use twilight_validate::request::{ValidationErrorType, webhook_username};
use crate::colors::AvatarColorFetchingRequest;
use crate::constants::DEFAULT_PREFIX;
@@ -345,23 +345,6 @@ impl User {
is_first_incident: incidents.is_empty(),
})
}
pub async fn count(session: &Session) -> Result<usize, DatabaseError> {
let result = session
.query_unpaged(
r#"
SELECT
user_id
FROM
payphone.users;
"#,
(),
)
.await?
.into_rows_result()?;
Ok(result.rows_num())
}
}
impl From<TwilightUser> for User {
@@ -1098,23 +1081,6 @@ impl Call {
Ok(())
}
pub async fn count(session: &Session) -> Result<usize, DatabaseError> {
let result = session
.query_unpaged(
r#"
SELECT
call_id
FROM
payphone.calls;
"#,
(),
)
.await?
.into_rows_result()?;
Ok(result.rows_num())
}
}
#[derive(Debug, Clone, SerializeValue, DeserializeValue)]
@@ -1403,20 +1369,6 @@ impl Message {
Ok(())
}
pub async fn count(session: &Session) -> Result<usize, DatabaseError> {
let result = session
.query_unpaged(
r#"
SELECT message_id FROM payphone.messages;
"#,
(),
)
.await?
.into_rows_result()?;
Ok(result.rows_num())
}
}
pub struct MessageByAuthorId {
@@ -1840,8 +1792,8 @@ impl<'a> Object<'a> for Participant {
}
fn make_name_valid(mut name: String) -> String {
if let Err(error) = webhook_username(&name) {
if let ValidationErrorType::WebhookUsername { len, .. } = error.kind() {
if let Err(error) = webhook_username(&name)
&& let ValidationErrorType::WebhookUsername { len, .. } = error.kind() {
if let Some(len) = len {
if *len < 2 {
name = format!("{name}--");
@@ -1852,7 +1804,6 @@ fn make_name_valid(mut name: String) -> String {
name = "Invalid name".into();
}
}
}
name
}
+1 -1
View File
@@ -104,7 +104,7 @@ pub(super) fn get_all_command_help_embed(
.unwrap())
.color(COLOR_WHITE)
.field(EmbedFieldBuilder::new("Commands", get_commands_table(rt, extras)).build())
.footer(EmbedFooterBuilder::new(format!("v{VERSION} - Use {}help <command> to get more information about a specific command.", prefix)))
.footer(EmbedFooterBuilder::new(format!("v{VERSION}-{} - Use {}help <command> to get more information about a specific command.", rt.shard_number + 1, prefix)))
.build()
}
-1
View File
@@ -2,7 +2,6 @@ use async_trait::async_trait;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use twilight_model::gateway::payload::outgoing::UpdatePresence;
use twilight_model::gateway::payload::outgoing::update_presence::UpdatePresencePayload;
use twilight_model::gateway::presence::{Activity, ActivityType, Status};
use crate::extras::Extras;
+1 -3
View File
@@ -599,8 +599,7 @@ impl Command<State, Extras> for MaskCommand {
if let Some(channel) = channel
&& let Some(call_id) = channel.call_id
{
if let Some(mut participant) = Participant::get_for_user_and_channel_in_call(
&& let Some(mut participant) = Participant::get_for_user_and_channel_in_call(
ctx.extras.user.id,
ctx.event.channel_id.get(),
call_id,
@@ -621,7 +620,6 @@ impl Command<State, Extras> for MaskCommand {
);
}
}
}
ctx.send().content(mask_message).await?;
+1
View File
@@ -102,6 +102,7 @@ impl OnMessageHandler<State, Extras> for CallMessageHandler {
if UtcDateTime::from_unix_timestamp(event.author.id.timestamp() / 1000).unwrap()
> UtcDateTime::now() - Duration::days(30)
&& event.author.id.get() != rt.state.bot_owner_id
{
return Ok(());
}
+4 -5
View File
@@ -2,7 +2,7 @@ use async_trait::async_trait;
use time::{Duration, UtcDateTime};
use twilight_util::snowflake::Snowflake;
use crate::database::models::{Call, Channel, Participant, UserIncident};
use crate::database::models::{Channel, Participant, UserIncident};
use crate::extras::Extras;
use crate::framework::commands::CommandError;
use crate::framework::commands::context::CommandContext;
@@ -50,10 +50,9 @@ pub struct NonNewAccountPermission;
#[async_trait]
impl Permission<State, Extras> for NonNewAccountPermission {
async fn check(&self, ctx: &CommandContext<State, Extras>) -> Result<bool, CommandError> {
Ok(
UtcDateTime::from_unix_timestamp(ctx.event.author.id.timestamp() / 1000).unwrap()
< UtcDateTime::now() - Duration::days(30),
)
Ok(ctx.event.author.id.get() == ctx.rt.state.bot_owner_id
|| UtcDateTime::from_unix_timestamp(ctx.event.author.id.timestamp() / 1000).unwrap()
< UtcDateTime::now() - Duration::days(30))
}
async fn on_handle_denied(
+18 -1
View File
@@ -1,3 +1,20 @@
[
"Thank you X and Anonymous Person for your support! We have covered next month's hosting costs."
"Automod features are still under development! Join the [official Discord server](<https://discord.gg/9daSsrfTmE>) if you detect a bug and we'll help you out.",
"You can report messages using `Message > Apps > Report Message`.",
"Help us sharing Payphone to make it more active!",
"Emojis will only show if they're from a server Payphone's in.",
"You cannot send links.",
"[Vote for Payphone on top.gg](<https://top.gg/bot/1212940413795827763/vote>) to see more people around!",
"[Write a review](<https://top.gg/bot/1212940413795827763#reviews>) to tell everyone your opinion about Payphone!",
"Calls will be automatically hung up after 15 minutes if nobody talks.",
"You can change Payphone's prefix with `{prefix}prefix`.",
"You can use `{prefix}skip` to directly search for a new call.",
"Want to stay hidden? Use `{prefix}mask on`!",
"Did you know you can check your mask with `{prefix}mask`?",
"Do you like Payphone? Consider [making a donation](<https://ko-fi.com/payphonedev>) to help us grow Payphone (and keep it running)!",
"Using `{prefix}friendreq` is the best way to ask the other party to add you.",
"There's a secret easter egg command. Try to find it! (Hint: cat)",
"Make sure to read Payphone's [Code of Conduct](<https://payphone.lat/code-of-conduct>). Let's keep Payphone safe and fun for everyone!",
"Did you join [Payphone's new official server](<https://discord.gg/9daSsrfTmE>) yet? We have a lot of fun stuff there!",
"How high are you on the leaderboard? Use `{prefix}leaderboard` to check it out!"
]
+3
View File
@@ -0,0 +1,3 @@
[
"Thank you X and Anonymous Person for your support! We have covered next month's hosting costs."
]
-31
View File
@@ -1,31 +0,0 @@
[
"Automod features are still under development! Join the [official Discord server](<https://discord.gg/9daSsrfTmE>) if you detect a bug and we'll help you out.",
"Automod features are still under development! Join the [official Discord server](<https://discord.gg/9daSsrfTmE>) if you detect a bug and we'll help you out.",
"Automod features are still under development! Join the [official Discord server](<https://discord.gg/9daSsrfTmE>) if you detect a bug and we'll help you out.",
"Automod features are still under development! Join the [official Discord server](<https://discord.gg/9daSsrfTmE>) if you detect a bug and we'll help you out.",
"Automod features are still under development! Join the [official Discord server](<https://discord.gg/9daSsrfTmE>) if you detect a bug and we'll help you out.",
"Automod features are still under development! Join the [official Discord server](<https://discord.gg/9daSsrfTmE>) if you detect a bug and we'll help you out.",
"Payphone needs your help! We're running out of money to pay for next month's hosting. If you can and want to help, consider [donating](<https://ko-fi.com/payphonedev>) to help Payphone stay alive. All help is greatly appreciated!",
"Payphone needs your help! We're running out of money to pay for next month's hosting. If you can and want to help, consider [donating](<https://ko-fi.com/payphonedev>) to help Payphone stay alive. All help is greatly appreciated!",
"Payphone needs your help! We're running out of money to pay for next month's hosting. If you can and want to help, consider [donating](<https://ko-fi.com/payphonedev>) to help Payphone stay alive. All help is greatly appreciated!",
"Payphone needs your help! We're running out of money to pay for next month's hosting. If you can and want to help, consider [donating](<https://ko-fi.com/payphonedev>) to help Payphone stay alive. All help is greatly appreciated!",
"Payphone needs your help! We're running out of money to pay for next month's hosting. If you can and want to help, consider [donating](<https://ko-fi.com/payphonedev>) to help Payphone stay alive. All help is greatly appreciated!",
"Payphone needs your help! We're running out of money to pay for next month's hosting. If you can and want to help, consider [donating](<https://ko-fi.com/payphonedev>) to help Payphone stay alive. All help is greatly appreciated!",
"You can report messages using `Message > Apps > Report Message`.",
"Help us sharing Payphone to make it more active!",
"Emojis will only show if they're from a server Payphone's in.",
"You cannot send links.",
"[Vote for Payphone on top.gg](<https://top.gg/bot/1212940413795827763/vote>) to see more people around!",
"[Write a review](<https://top.gg/bot/1212940413795827763#reviews>) to tell everyone your opinion about Payphone!",
"Calls will be automatically hung up after 15 minutes if nobody talks.",
"You can change Payphone's prefix with `{prefix}prefix`.",
"You can use `{prefix}skip` to directly search for a new call.",
"Want to stay hidden? Use `{prefix}mask on`!",
"Did you know you can check your mask with `{prefix}mask`?",
"Do you like Payphone? Consider [making a donation](<https://ko-fi.com/payphonedev>) to help us grow Payphone (and keep it running)!",
"Using `{prefix}friendreq` is the best way to ask the other party to add you.",
"There's a secret easter egg command. Try to find it! (Hint: cat)",
"Make sure to read Payphone's [Code of Conduct](<https://payphone.nyeki.dev/code-of-conduct>). Let's keep Payphone safe and fun for everyone!",
"Did you join [Payphone's new official server](<https://discord.gg/9daSsrfTmE>) yet? We have a lot of fun stuff there!",
"How high are you on the leaderboard? Use `{prefix}leaderboard` to check it out!"
]
+61 -1
View File
@@ -3,7 +3,7 @@ use std::time::Duration;
use scylla::client::session::Session;
use crate::database::models::{Message, User};
use crate::database::models::{Channel, Message, User};
use crate::extras::Extras;
use crate::framework::commands::arguments::{ArgumentParsingError, FromArguments, NeedyString};
use crate::framework::commands::context::CommandContext;
@@ -206,3 +206,63 @@ impl DerefMut for DurationArgument {
&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
}
}
+117 -3
View File
@@ -1,7 +1,8 @@
use async_trait::async_trait;
use deadpool_redis::redis::AsyncTypedCommands;
use time::UtcDateTime;
use crate::database::models::{UserIncident, UserIncidentType};
use crate::database::models::{Call, UserIncident, UserIncidentType};
use crate::extras::Extras;
use crate::framework::commands::arguments::{
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::{Command, CommandError};
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::permissions::{BotModeratorPermission, BotOwnerPermission};
use crate::state::State;
use crate::utils::get_random_u64;
use crate::utils::{Bye, get_random_u64};
#[derive(Debug)]
pub struct ModeratorCommand;
@@ -246,3 +247,116 @@ impl Command<State, Extras> for BanCommand {
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:?}"))?;
ctx.send().content(format!("<#{}> had its call hung up.", channel.id)).await?;
} 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::leveling::commands::LeaderboardCommand;
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::events::{
ServerCreateHandler, ServerDeleteHandler, ServerUpdateHandler,
@@ -298,6 +298,7 @@ async fn start(
.command(ModeratorCommand)
.command(IncidentsCommand)
.command(BanCommand)
.command(RemoteHangupCommand)
.component(HelpGoBackButton)
.cache_resource_type(ResourceType::GUILD)
.cache_resource_type(ResourceType::CHANNEL)
+2 -2
View File
@@ -111,9 +111,9 @@ pub fn u64_to_i64(u: u64) -> i64 {
/// Returns:
/// [`Option<(&str, &str)>`] - The splitted parts of the string, or [`None`] if no occurrences of
/// `pattern` were found in `s`.
pub fn split_at_nth_from_end<'a, 'b>(
pub fn split_at_nth_from_end<'a>(
s: &'a str,
pattern: &'b str,
pattern: &str,
n: usize,
) -> Option<(&'a str, &'a str)> {
let mut end = s.len();