Merge remote-tracking branch 'origin/master' into osu-app-commands

This commit is contained in:
Natsu Kagami 2024-02-29 15:39:24 +01:00
commit 6ef9ffcfda
Signed by: nki
GPG key ID: 55A032EB38B49ADB
9 changed files with 144 additions and 114 deletions

1
Cargo.lock generated
View file

@ -3303,6 +3303,7 @@ dependencies = [
"poise", "poise",
"reqwest", "reqwest",
"serenity", "serenity",
"thiserror",
"tokio", "tokio",
"youmubot-db", "youmubot-db",
"youmubot-db-sql", "youmubot-db-sql",

View file

@ -5,10 +5,7 @@ use serenity::{
macros::{command, group}, macros::{command, group},
Args, CommandResult, Args, CommandResult,
}, },
model::{ model::channel::{Channel, Message},
channel::{Channel, Message},
id::UserId,
},
}; };
use soft_ban::{SOFT_BAN_COMMAND, SOFT_BAN_INIT_COMMAND}; use soft_ban::{SOFT_BAN_COMMAND, SOFT_BAN_INIT_COMMAND};
use youmubot_prelude::*; use youmubot_prelude::*;
@ -69,7 +66,7 @@ async fn clean(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
#[max_args(2)] #[max_args(2)]
#[only_in("guilds")] #[only_in("guilds")]
async fn ban(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { async fn ban(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let user = args.single::<UserId>()?.to_user(&ctx).await?; let user = args.single::<UserId>()?.0.to_user(&ctx).await?;
let reason = args.single::<String>().map(|v| format!("`{}`", v)).ok(); let reason = args.single::<String>().map(|v| format!("`{}`", v)).ok();
let dmds = args.single::<u8>().unwrap_or(0); let dmds = args.single::<u8>().unwrap_or(0);
@ -105,7 +102,7 @@ async fn ban(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
#[num_args(1)] #[num_args(1)]
#[only_in("guilds")] #[only_in("guilds")]
async fn kick(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { async fn kick(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let user = args.single::<UserId>()?.to_user(&ctx).await?; let user = args.single::<UserId>()?.0.to_user(&ctx).await?;
msg.reply(&ctx, format!("🔫 Kicking user {}.", user.tag())) msg.reply(&ctx, format!("🔫 Kicking user {}.", user.tag()))
.await?; .await?;

View file

@ -3,10 +3,7 @@ use chrono::offset::Utc;
use futures_util::{stream, TryStreamExt}; use futures_util::{stream, TryStreamExt};
use serenity::{ use serenity::{
framework::standard::{macros::command, Args, CommandResult}, framework::standard::{macros::command, Args, CommandResult},
model::{ model::{channel::Message, id},
channel::Message,
id::{GuildId, RoleId, UserId},
},
}; };
use youmubot_prelude::*; use youmubot_prelude::*;
@ -19,7 +16,7 @@ use youmubot_prelude::*;
#[max_args(2)] #[max_args(2)]
#[only_in("guilds")] #[only_in("guilds")]
pub async fn soft_ban(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { pub async fn soft_ban(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let user = args.single::<UserId>()?.to_user(&ctx).await?; let user = args.single::<UserId>()?.0.to_user(&ctx).await?;
let data = ctx.data.read().await; let data = ctx.data.read().await;
let duration = if args.is_empty() { let duration = if args.is_empty() {
None None
@ -81,7 +78,7 @@ pub async fn soft_ban(ctx: &Context, msg: &Message, mut args: Args) -> CommandRe
#[num_args(1)] #[num_args(1)]
#[only_in("guilds")] #[only_in("guilds")]
pub async fn soft_ban_init(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { pub async fn soft_ban_init(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let role_id = args.single::<RoleId>()?; let role_id = args.single::<RoleId>()?.0;
let data = ctx.data.read().await; let data = ctx.data.read().await;
let guild = msg.guild_id.unwrap().to_partial_guild(&ctx).await?; let guild = msg.guild_id.unwrap().to_partial_guild(&ctx).await?;
// Check whether the role_id is the one we wanted // Check whether the role_id is the one we wanted
@ -152,10 +149,10 @@ pub async fn watch_soft_bans(cache_http: impl CacheHttp, data: AppData) {
async fn lift_soft_ban_for( async fn lift_soft_ban_for(
cache_http: impl CacheHttp, cache_http: impl CacheHttp,
server_id: GuildId, server_id: id::GuildId,
server_name: &str, server_name: &str,
ban_role: RoleId, ban_role: id::RoleId,
user_id: UserId, user_id: id::UserId,
) -> Result<()> { ) -> Result<()> {
let m = server_id.member(&cache_http, user_id).await?; let m = server_id.member(&cache_http, user_id).await?;
println!( println!(

View file

@ -7,7 +7,7 @@ use serenity::{
macros::{command, group}, macros::{command, group},
Args, CommandResult, Args, CommandResult,
}, },
model::{channel::Message, id::UserId}, model::channel::Message,
utils::MessageBuilder, utils::MessageBuilder,
}; };
use youmubot_prelude::*; use youmubot_prelude::*;
@ -159,7 +159,7 @@ async fn name(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let user_id = if args.is_empty() { let user_id = if args.is_empty() {
msg.author.id msg.author.id
} else { } else {
args.single::<UserId>()? args.single::<UserId>()?.0
}; };
let user_mention = if user_id == msg.author.id { let user_mention = if user_id == msg.author.id {

View file

@ -25,7 +25,7 @@ async fn check<T: AsRef<crate::discord::Env> + Sync>(
ctx.author(), ctx.author(),
None, None,
osu_id, osu_id,
member, member.map(|m| m.user.id),
mods, mods,
style, style,
) )

View file

@ -8,14 +8,14 @@ use crate::{
}; };
use rand::seq::IteratorRandom; use rand::seq::IteratorRandom;
use serenity::{ use serenity::{
all::{ChannelId, Member, Mention}, all::ChannelId,
builder::{CreateMessage, EditMessage}, builder::{CreateMessage, EditMessage},
collector, collector,
framework::standard::{ framework::standard::{
macros::{command, group}, macros::{command, group},
Args, CommandResult, Args, CommandResult,
}, },
model::channel::Message, model::{channel::Message, id},
utils::MessageBuilder, utils::MessageBuilder,
}; };
use std::{str::FromStr, sync::Arc}; use std::{str::FromStr, sync::Arc};
@ -329,14 +329,7 @@ pub async fn save(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
pub async fn forcesave(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { pub async fn forcesave(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let data = ctx.data.read().await; let data = ctx.data.read().await;
let osu = data.get::<OsuClient>().unwrap(); let osu = data.get::<OsuClient>().unwrap();
let target = match args.single::<Mention>()? { let target = args.single::<UserId>()?.0;
Mention::User(id) => id,
m => {
msg.reply(&ctx, format!("Expected user_id, got {}", m))
.await?;
return Ok(());
}
};
let username = args.quoted().trimmed().single::<String>()?; let username = args.quoted().trimmed().single::<String>()?;
let user: Option<User> = osu let user: Option<User> = osu
@ -619,61 +612,29 @@ pub async fn last(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let data = ctx.data.read().await; let data = ctx.data.read().await;
let env = data.get::<Env>().unwrap(); let env = data.get::<Env>().unwrap();
let bm = load_beatmap(&env, Some(msg), msg.channel_id).await;
match bm { let mods = args.find::<Mods>().ok();
None => { let style = args.single::<ScoreListStyle>().ok();
msg.reply(&ctx, "No beatmap queried on this channel.") let username_arg = args.single::<UsernameArg>().ok();
.await?; let (osu_id, member) = match username_arg {
} Some(UsernameArg::Tagged(user_id)) => (None, Some(user_id)),
Some((bm, mods_def)) => { Some(UsernameArg::Raw(s)) => (Some(s), None),
let mods = args.find::<Mods>().ok().or(mods_def).unwrap_or(Mods::NOMOD); None => (None, None),
let b = &bm.0; };
let m = bm.1;
let style = args
.single::<ScoreListStyle>()
.unwrap_or(ScoreListStyle::Grid);
let username_arg = args.single::<UsernameArg>().ok();
let user_id = match username_arg.as_ref() {
Some(UsernameArg::Tagged(v)) => Some(*v),
None => Some(msg.author.id),
_ => None,
};
let user = to_user_id_query(username_arg, &env, &msg.author).await?;
let osu = data.get::<OsuClient>().unwrap();
let user = osu
.user(user, |f| f)
.await?
.ok_or_else(|| Error::msg("User not found"))?;
let mut scores = osu
.scores(b.beatmap_id, |f| f.user(UserID::ID(user.id)).mode(m))
.await?
.into_iter()
.filter(|s| s.mods.contains(mods))
.collect::<Vec<_>>();
scores.sort_by(|a, b| b.pp.unwrap().partial_cmp(&a.pp.unwrap()).unwrap());
if scores.is_empty() {
msg.reply(&ctx, "No scores found").await?;
return Ok(());
}
if let Some(user_id) = user_id {
// Save to database
data.get::<OsuUserBests>()
.unwrap()
.save(user_id, m, scores.clone())
.await
.pls_ok();
}
display::scores::display_scores(style, scores, m, ctx, msg.clone(), msg.channel_id)
.await?;
}
}
check_impl(
env,
ctx,
msg.clone(),
msg.channel_id,
&msg.author,
Some(msg),
osu_id,
member,
mods,
style,
)
.await?;
Ok(()) Ok(())
} }
@ -685,49 +646,48 @@ pub async fn check_impl(
sender: &serenity::all::User, sender: &serenity::all::User,
msg: Option<&Message>, msg: Option<&Message>,
osu_id: Option<String>, osu_id: Option<String>,
member: Option<Member>, member: Option<id::UserId>,
mods: Option<Mods>, mods: Option<Mods>,
style: Option<ScoreListStyle>, style: Option<ScoreListStyle>,
) -> CommandResult { ) -> CommandResult {
let bm = load_beatmap(&env, msg, channel_id).await; let bm = load_beatmap(&env, msg, channel_id).await;
match bm { let BeatmapWithMode(b, m) = match bm {
Some((bm, _)) => bm,
None => { None => {
reply reply
.reply(&ctx, "No beatmap queried on this channel.") .reply(&ctx, "No beatmap queried on this channel.")
.await?; .await?;
return Ok(());
} }
Some((bm, mods_def)) => { };
let mods = mods.unwrap_or_default();
let b = &bm.0;
let m = bm.1;
let style = style.unwrap_or_default();
let username_arg = member
.map(|m| UsernameArg::Tagged(m.user.id))
.or(osu_id.map(|id| UsernameArg::Raw(id)));
let user = to_user_id_query(username_arg, env, sender).await?;
let user = env let mods = mods.unwrap_or_default();
.client let style = style.unwrap_or_default();
.user(user, |f| f) let username_arg = member
.await? .map(|m| UsernameArg::Tagged(m))
.ok_or_else(|| Error::msg("User not found"))?; .or(osu_id.map(|id| UsernameArg::Raw(id)));
let mut scores = env let user = to_user_id_query(username_arg, env, sender).await?;
.client
.scores(b.beatmap_id, |f| f.user(UserID::ID(user.id)).mode(m))
.await?
.into_iter()
.filter(|s| s.mods.contains(mods))
.collect::<Vec<_>>();
scores.sort_by(|a, b| b.pp.unwrap().partial_cmp(&a.pp.unwrap()).unwrap());
if scores.is_empty() { let user = env
reply.reply(&ctx, "No scores found").await?; .client
return Ok(()); .user(user, |f| f)
} .await?
display::scores::display_scores(style, scores, m, ctx, reply, channel_id).await?; .ok_or_else(|| Error::msg("User not found"))?;
} let mut scores = env
.client
.scores(b.beatmap_id, |f| f.user(UserID::ID(user.id)).mode(m))
.await?
.into_iter()
.filter(|s| s.mods.contains(mods))
.collect::<Vec<_>>();
scores.sort_by(|a, b| b.pp.unwrap().partial_cmp(&a.pp.unwrap()).unwrap());
if scores.is_empty() {
reply.reply(&ctx, "No scores found").await?;
return Ok(());
} }
display::scores::display_scores(style, scores, m, ctx, reply, channel_id).await?;
Ok(()) Ok(())
} }

View file

@ -17,6 +17,7 @@ reqwest = { version = "0.11.10", features = ["json"] }
chrono = "0.4.19" chrono = "0.4.19"
flume = "0.10.13" flume = "0.10.13"
dashmap = "5.3.4" dashmap = "5.3.4"
thiserror = "1"
poise = "0.6" poise = "0.6"
[dependencies.serenity] [dependencies.serenity]

View file

@ -1,4 +1,5 @@
pub use duration::Duration; pub use duration::Duration;
pub use ids::*;
pub use username_arg::UsernameArg; pub use username_arg::UsernameArg;
mod duration { mod duration {
@ -181,6 +182,73 @@ mod duration {
} }
} }
mod ids {
use serenity::{model::id, utils};
use std::str::FromStr;
use super::ParseError;
/// An `UserId` parsed the old way.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UserId(pub id::UserId);
impl FromStr for UserId {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
utils::parse_user_mention(s)
.map(UserId)
.ok_or(ParseError::InvalidId)
}
}
impl AsRef<id::UserId> for UserId {
fn as_ref(&self) -> &id::UserId {
&self.0
}
}
/// An `ChannelId` parsed the old way.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChannelId(pub id::ChannelId);
impl FromStr for ChannelId {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
utils::parse_channel_mention(s)
.map(ChannelId)
.ok_or(ParseError::InvalidId)
}
}
impl AsRef<id::ChannelId> for ChannelId {
fn as_ref(&self) -> &id::ChannelId {
&self.0
}
}
/// An `RoleId` parsed the old way.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RoleId(pub id::RoleId);
impl FromStr for RoleId {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
utils::parse_role_mention(s)
.map(RoleId)
.ok_or(ParseError::InvalidId)
}
}
impl AsRef<id::RoleId> for RoleId {
fn as_ref(&self) -> &id::RoleId {
&self.0
}
}
}
mod username_arg { mod username_arg {
use serenity::model::id::UserId; use serenity::model::id::UserId;
use std::str::FromStr; use std::str::FromStr;
@ -193,8 +261,8 @@ mod username_arg {
impl FromStr for UsernameArg { impl FromStr for UsernameArg {
type Err = String; type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.parse::<UserId>() { match s.parse::<super::UserId>() {
Ok(v) => Ok(UsernameArg::Tagged(v)), Ok(v) => Ok(UsernameArg::Tagged(v.0)),
Err(_) if !s.is_empty() => Ok(UsernameArg::Raw(s.to_owned())), Err(_) if !s.is_empty() => Ok(UsernameArg::Raw(s.to_owned())),
Err(_) => Err("username arg cannot be empty".to_owned()), Err(_) => Err("username arg cannot be empty".to_owned()),
} }
@ -208,3 +276,9 @@ mod username_arg {
} }
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("invalid id format")]
InvalidId,
}

View file

@ -15,7 +15,7 @@ pub mod replyable;
pub mod setup; pub mod setup;
pub use announcer::{Announcer, AnnouncerHandler}; pub use announcer::{Announcer, AnnouncerHandler};
pub use args::{Duration, UsernameArg}; pub use args::{ChannelId, Duration, RoleId, UserId, UsernameArg};
pub use flags::Flags; pub use flags::Flags;
pub use hook::Hook; pub use hook::Hook;
pub use member_cache::MemberCache; pub use member_cache::MemberCache;