mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 16:58:55 +00:00
Merge remote-tracking branch 'origin/master' into osu-app-commands
This commit is contained in:
commit
6ef9ffcfda
9 changed files with 144 additions and 114 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3303,6 +3303,7 @@ dependencies = [
|
||||||
"poise",
|
"poise",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serenity",
|
"serenity",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"youmubot-db",
|
"youmubot-db",
|
||||||
"youmubot-db-sql",
|
"youmubot-db-sql",
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Reference in a new issue