Core/Prelude: Fix lifetime unsoundness

This commit is contained in:
Natsu Kagami 2020-09-07 02:09:06 -04:00
parent c672a8836c
commit f1719019d1
Signed by: nki
GPG key ID: 73376E117CD20735
9 changed files with 150 additions and 112 deletions

View file

@ -40,7 +40,7 @@ async fn clean(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
messages messages
.into_iter() .into_iter()
.filter(|v| v.author.id == self_id) .filter(|v| v.author.id == self_id)
.map(|m| m.delete(&ctx)) .map(|m| async move { m.delete(&ctx).await })
.collect::<stream::FuturesUnordered<_>>() .collect::<stream::FuturesUnordered<_>>()
.try_collect::<()>() .try_collect::<()>()
.await?; .await?;
@ -54,7 +54,7 @@ async fn clean(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
msg.react(&ctx, '🌋').await?; msg.react(&ctx, '🌋').await?;
if let Channel::Guild(_) = &channel { if let Channel::Guild(_) = &channel {
tokio::time::delay_for(std::time::Duration::from_secs(2)).await; tokio::time::delay_for(std::time::Duration::from_secs(2)).await;
msg.delete(&ctx).await; msg.delete(&ctx).await.ok();
} }
Ok(()) Ok(())

View file

@ -30,7 +30,7 @@ pub async fn soft_ban(ctx: &Context, msg: &Message, mut args: Args) -> CommandRe
}; };
let guild = msg.guild_id.ok_or(Error::msg("Command is guild only"))?; let guild = msg.guild_id.ok_or(Error::msg("Command is guild only"))?;
let db = SoftBans::open(&*data); let mut db = SoftBans::open(&*data);
let val = db let val = db
.borrow()? .borrow()?
.get(&guild) .get(&guild)
@ -82,6 +82,7 @@ pub async fn soft_ban(ctx: &Context, msg: &Message, mut args: Args) -> CommandRe
#[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>()?;
let data = ctx.data.read().await;
let guild = msg.guild(&ctx).await.unwrap(); let guild = msg.guild(&ctx).await.unwrap();
// Check whether the role_id is the one we wanted // Check whether the role_id is the one we wanted
if !guild.roles.contains_key(&role_id) { if !guild.roles.contains_key(&role_id) {
@ -91,7 +92,7 @@ pub async fn soft_ban_init(ctx: &Context, msg: &Message, mut args: Args) -> Comm
)))?; )))?;
} }
// Check if we already set up // Check if we already set up
let db = SoftBans::open(&*ctx.data.read().await); let mut db = SoftBans::open(&*data);
let set_up = db.borrow()?.contains_key(&guild.id); let set_up = db.borrow()?.contains_key(&guild.id);
if !set_up { if !set_up {
@ -104,7 +105,7 @@ pub async fn soft_ban_init(ctx: &Context, msg: &Message, mut args: Args) -> Comm
Ok(()) Ok(())
} }
// Watch the soft bans. // Watch the soft bans. Blocks forever.
pub async fn watch_soft_bans(cache_http: Arc<CacheAndHttp>, data: AppData) { pub async fn watch_soft_bans(cache_http: Arc<CacheAndHttp>, data: AppData) {
loop { loop {
// Scope so that locks are released // Scope so that locks are released

View file

@ -41,10 +41,7 @@ pub async fn choose(ctx: &Context, m: &Message, mut args: Args) -> CommandResult
} else { } else {
args.single::<String>()? args.single::<String>()?
}; };
let role = match args.single::<RoleId>().ok() { let role = args.single::<RoleId>().ok();
Some(v) => v.to_role_cached(&ctx).await,
None => None,
};
let users: Result<Vec<_>, Error> = { let users: Result<Vec<_>, Error> = {
let guild = m.guild(&ctx).await.unwrap(); let guild = m.guild(&ctx).await.unwrap();
@ -68,16 +65,21 @@ pub async fn choose(ctx: &Context, m: &Message, mut args: Args) -> CommandResult
}) })
.map(|mem| future::ready(mem)) .map(|mem| future::ready(mem))
.collect::<stream::FuturesUnordered<_>>() .collect::<stream::FuturesUnordered<_>>()
.filter(|member| async { .filter_map(|member| async move {
// Filter by role if provided // Filter by role if provided
if let Some(role) = role { if let Some(role) = role {
member if member
.roles(&ctx) .roles(&ctx)
.await .await
.map(|roles| roles.into_iter().any(|r| role.id == r.id)) .map(|roles| roles.into_iter().any(|r| role == r.id))
.unwrap_or(false) .unwrap_or(false)
{
Some(member)
} else {
None
}
} else { } else {
true Some(member)
} }
}) })
.collect() .collect()

View file

@ -34,66 +34,69 @@ async fn list(ctx: &Context, m: &Message, _: Args) -> CommandResult {
let pages = (roles.len() + ROLES_PER_PAGE - 1) / ROLES_PER_PAGE; let pages = (roles.len() + ROLES_PER_PAGE - 1) / ROLES_PER_PAGE;
paginate( paginate(
|page, ctx, msg| async move { |page, ctx, msg| {
let page = page as usize; let roles = roles.clone();
let start = page * ROLES_PER_PAGE; Box::pin(async move {
let end = roles.len().min(start + ROLES_PER_PAGE); let page = page as usize;
if end <= start { let start = page * ROLES_PER_PAGE;
return Ok(false); let end = roles.len().min(start + ROLES_PER_PAGE);
} if end <= start {
let roles = &roles[start..end]; return Ok(false);
let nw = roles // name width }
.iter() let roles = &roles[start..end];
.map(|(r, _)| r.name.len()) let nw = roles // name width
.max() .iter()
.unwrap() .map(|(r, _)| r.name.len())
.max(6); .max()
let idw = roles[0].0.id.to_string().len(); .unwrap()
let dw = roles .max(6);
.iter() let idw = roles[0].0.id.to_string().len();
.map(|v| v.1.len()) let dw = roles
.max() .iter()
.unwrap() .map(|v| v.1.len())
.max(" Description ".len()); .max()
let mut m = MessageBuilder::new(); .unwrap()
m.push_line("```"); .max(" Description ".len());
let mut m = MessageBuilder::new();
m.push_line("```");
// Table header // Table header
m.push_line(format!(
"{:nw$} | {:idw$} | {:dw$}",
"Name",
"ID",
"Description",
nw = nw,
idw = idw,
dw = dw,
));
m.push_line(format!(
"{:->nw$}---{:->idw$}---{:->dw$}",
"",
"",
"",
nw = nw,
idw = idw,
dw = dw,
));
for (role, description) in roles.iter() {
m.push_line(format!( m.push_line(format!(
"{:nw$} | {:idw$} | {:dw$}", "{:nw$} | {:idw$} | {:dw$}",
role.name, "Name",
role.id, "ID",
description, "Description",
nw = nw,
idw = idw,
dw = dw,
));
m.push_line(format!(
"{:->nw$}---{:->idw$}---{:->dw$}",
"",
"",
"",
nw = nw, nw = nw,
idw = idw, idw = idw,
dw = dw, dw = dw,
)); ));
}
m.push_line("```");
m.push(format!("Page **{}/{}**", page + 1, pages));
msg.edit(ctx, |f| f.content(m.to_string())).await?; for (role, description) in roles.iter() {
Ok(true) m.push_line(format!(
"{:nw$} | {:idw$} | {:dw$}",
role.name,
role.id,
description,
nw = nw,
idw = idw,
dw = dw,
));
}
m.push_line("```");
m.push(format!("Page **{}/{}**", page + 1, pages));
msg.edit(ctx, |f| f.content(m.to_string())).await?;
Ok(true)
})
}, },
ctx, ctx,
m.channel_id, m.channel_id,
@ -155,6 +158,7 @@ async fn toggle(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
#[only_in(guilds)] #[only_in(guilds)]
async fn add(ctx: &Context, m: &Message, mut args: Args) -> CommandResult { async fn add(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
let role = args.single_quoted::<String>()?; let role = args.single_quoted::<String>()?;
let data = ctx.data.read().await;
let description = args.single::<String>()?; let description = args.single::<String>()?;
let guild_id = m.guild_id.unwrap(); let guild_id = m.guild_id.unwrap();
let roles = guild_id.to_partial_guild(&ctx).await?.roles; let roles = guild_id.to_partial_guild(&ctx).await?.roles;
@ -164,7 +168,7 @@ async fn add(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
m.reply(&ctx, "No such role exists").await?; m.reply(&ctx, "No such role exists").await?;
} }
Some(role) Some(role)
if DB::open(&*ctx.data.read().await) if DB::open(&*data)
.borrow()? .borrow()?
.get(&guild_id) .get(&guild_id)
.map(|g| g.contains_key(&role.id)) .map(|g| g.contains_key(&role.id))
@ -174,7 +178,7 @@ async fn add(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
.await?; .await?;
} }
Some(role) => { Some(role) => {
DB::open(&*ctx.data.read().await) DB::open(&*data)
.borrow_mut()? .borrow_mut()?
.entry(guild_id) .entry(guild_id)
.or_default() .or_default()
@ -200,6 +204,7 @@ async fn add(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
#[only_in(guilds)] #[only_in(guilds)]
async fn remove(ctx: &Context, m: &Message, mut args: Args) -> CommandResult { async fn remove(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
let role = args.single_quoted::<String>()?; let role = args.single_quoted::<String>()?;
let data = ctx.data.read().await;
let guild_id = m.guild_id.unwrap(); let guild_id = m.guild_id.unwrap();
let roles = guild_id.to_partial_guild(&ctx).await?.roles; let roles = guild_id.to_partial_guild(&ctx).await?.roles;
let role = role_from_string(&role, &roles); let role = role_from_string(&role, &roles);
@ -208,7 +213,7 @@ async fn remove(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
m.reply(&ctx, "No such role exists").await?; m.reply(&ctx, "No such role exists").await?;
} }
Some(role) Some(role)
if !DB::open(&*ctx.data.read().await) if !DB::open(&*data)
.borrow()? .borrow()?
.get(&guild_id) .get(&guild_id)
.map(|g| g.contains_key(&role.id)) .map(|g| g.contains_key(&role.id))
@ -218,7 +223,7 @@ async fn remove(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
.await?; .await?;
} }
Some(role) => { Some(role) => {
DB::open(&*ctx.data.read().await) DB::open(&*data)
.borrow_mut()? .borrow_mut()?
.entry(guild_id) .entry(guild_id)
.or_default() .or_default()

View file

@ -28,7 +28,7 @@ pub async fn vote(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
let args = args.quoted(); let args = args.quoted();
let _duration = args.single::<ParseDuration>()?; let _duration = args.single::<ParseDuration>()?;
let duration = &_duration.0; let duration = &_duration.0;
if *duration < Duration::from_secs(2 * 60) || *duration > Duration::from_secs(60 * 60 * 24) { if *duration < Duration::from_secs(2) || *duration > Duration::from_secs(60 * 60 * 24) {
msg.reply(ctx, format!("😒 Invalid duration ({}). The voting time should be between **2 minutes** and **1 day**.", _duration)).await?; msg.reply(ctx, format!("😒 Invalid duration ({}). The voting time should be between **2 minutes** and **1 day**.", _duration)).await?;
return Ok(()); return Ok(());
} }
@ -97,6 +97,8 @@ pub async fn vote(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
}) })
}).await?; }).await?;
msg.delete(&ctx).await?; msg.delete(&ctx).await?;
drop(msg);
// React on all the choices // React on all the choices
choices choices
.iter() .iter()
@ -110,16 +112,18 @@ pub async fn vote(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
.await?; .await?;
// A handler for votes. // A handler for votes.
let mut user_reactions: Map<String, Set<UserId>> = choices let user_reactions: Map<String, Set<UserId>> = choices
.iter() .iter()
.map(|(emote, _)| (emote.clone(), Set::new())) .map(|(emote, _)| (emote.clone(), Set::new()))
.collect(); .collect();
// Collect reactions... // Collect reactions...
msg.await_reactions(&ctx) let user_reactions = panel
.await_reactions(&ctx)
.removed(true)
.timeout(*duration) .timeout(*duration)
.await .await
.scan(user_reactions, |set, reaction| async move { .fold(user_reactions, |mut set, reaction| async move {
let (reaction, is_add) = match &*reaction { let (reaction, is_add) = match &*reaction {
ReactionAction::Added(r) => (r, true), ReactionAction::Added(r) => (r, true),
ReactionAction::Removed(r) => (r, false), ReactionAction::Removed(r) => (r, false),
@ -128,23 +132,22 @@ pub async fn vote(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
if let Some(users) = set.get_mut(s.as_str()) { if let Some(users) = set.get_mut(s.as_str()) {
users users
} else { } else {
return None; return set;
} }
} else { } else {
return None; return set;
}; };
let user_id = match reaction.user_id { let user_id = match reaction.user_id {
Some(v) => v, Some(v) => v,
None => return None, None => return set,
}; };
if is_add { if is_add {
users.insert(user_id); users.insert(user_id);
} else { } else {
users.remove(&user_id); users.remove(&user_id);
} }
Some(()) set
}) })
.collect::<()>()
.await; .await;
// Handle choices // Handle choices
@ -233,3 +236,4 @@ const REACTIONS: [&'static str; 90] = [
// Assertions // Assertions
static_assertions::const_assert!(MAX_CHOICES <= REACTIONS.len()); static_assertions::const_assert!(MAX_CHOICES <= REACTIONS.len());
static_assertions::const_assert!(MAX_CHOICES <= REACTIONS.len());

View file

@ -20,26 +20,30 @@ pub use fun::FUN_GROUP;
pub fn setup( pub fn setup(
path: &std::path::Path, path: &std::path::Path,
client: &serenity::client::Client, client: &serenity::client::Client,
data: &mut youmubot_prelude::ShareMap, data: &mut TypeMap,
) -> serenity::framework::standard::CommandResult { ) -> serenity::framework::standard::CommandResult {
db::SoftBans::insert_into(&mut *data, &path.join("soft_bans.yaml"))?; db::SoftBans::insert_into(&mut *data, &path.join("soft_bans.yaml"))?;
db::Roles::insert_into(&mut *data, &path.join("roles.yaml"))?; db::Roles::insert_into(&mut *data, &path.join("roles.yaml"))?;
// Create handler threads // Create handler threads
std::thread::spawn(admin::watch_soft_bans(client)); tokio::spawn(admin::watch_soft_bans(
client.cache_and_http.clone(),
client.data.clone(),
));
Ok(()) Ok(())
} }
// A help command // A help command
#[help] #[help]
pub fn help( pub async fn help(
context: &mut Context, context: &Context,
msg: &Message, msg: &Message,
args: Args, args: Args,
help_options: &'static HelpOptions, help_options: &'static HelpOptions,
groups: &[&'static CommandGroup], groups: &[&'static CommandGroup],
owners: HashSet<UserId>, owners: HashSet<UserId>,
) -> CommandResult { ) -> CommandResult {
help_commands::with_embeds(context, msg, args, help_options, groups, owners) help_commands::with_embeds(context, msg, args, help_options, groups, owners).await;
Ok(())
} }

View file

@ -1,6 +1,5 @@
use crate::{AppData, Result}; use crate::{AppData, Result};
use async_trait::async_trait; use async_trait::async_trait;
use crossbeam_channel::after;
use futures_util::{ use futures_util::{
future::{join_all, ready, FutureExt}, future::{join_all, ready, FutureExt},
stream::{FuturesUnordered, StreamExt}, stream::{FuturesUnordered, StreamExt},
@ -78,7 +77,7 @@ impl MemberToChannels {
pub struct AnnouncerHandler { pub struct AnnouncerHandler {
cache_http: Arc<CacheAndHttp>, cache_http: Arc<CacheAndHttp>,
data: AppData, data: AppData,
announcers: HashMap<&'static str, RwLock<Box<dyn Announcer>>>, announcers: HashMap<&'static str, RwLock<Box<dyn Announcer + Send + Sync>>>,
} }
// Querying for the AnnouncerHandler in the internal data returns a vec of keys. // Querying for the AnnouncerHandler in the internal data returns a vec of keys.
@ -100,7 +99,11 @@ impl AnnouncerHandler {
/// Insert a new announcer into the handler. /// Insert a new announcer into the handler.
/// ///
/// The handler must take an unique key. If a duplicate is found, this method panics. /// The handler must take an unique key. If a duplicate is found, this method panics.
pub fn add(&mut self, key: &'static str, announcer: impl Announcer + 'static) -> &mut Self { pub fn add(
&mut self,
key: &'static str,
announcer: impl Announcer + Send + Sync + 'static,
) -> &mut Self {
if let Some(_) = self if let Some(_) = self
.announcers .announcers
.insert(key, RwLock::new(Box::new(announcer))) .insert(key, RwLock::new(Box::new(announcer)))
@ -132,7 +135,7 @@ impl AnnouncerHandler {
data: AppData, data: AppData,
cache_http: Arc<CacheAndHttp>, cache_http: Arc<CacheAndHttp>,
key: &'static str, key: &'static str,
announcer: &'_ RwLock<Box<dyn Announcer>>, announcer: &'_ RwLock<Box<dyn Announcer + Send + Sync>>,
) -> Result<()> { ) -> Result<()> {
let channels = MemberToChannels(Self::get_guilds(&data, key).await?); let channels = MemberToChannels(Self::get_guilds(&data, key).await?);
announcer announcer
@ -151,7 +154,8 @@ impl AnnouncerHandler {
self.data.write().await.insert::<Self>(keys.clone()); self.data.write().await.insert::<Self>(keys.clone());
loop { loop {
eprintln!("{}: announcer started scanning", chrono::Utc::now()); eprintln!("{}: announcer started scanning", chrono::Utc::now());
let after_timer = after(cooldown); // let after_timer = after(cooldown);
let after = tokio::time::delay_for(cooldown);
join_all(self.announcers.iter().map(|(key, announcer)| { join_all(self.announcers.iter().map(|(key, announcer)| {
eprintln!(" - scanning key `{}`", key); eprintln!(" - scanning key `{}`", key);
Self::announce(self.data.clone(), self.cache_http.clone(), *key, announcer).map( Self::announce(self.data.clone(), self.cache_http.clone(), *key, announcer).map(
@ -164,7 +168,7 @@ impl AnnouncerHandler {
})) }))
.await; .await;
eprintln!("{}: announcer finished scanning", chrono::Utc::now()); eprintln!("{}: announcer finished scanning", chrono::Utc::now());
after_timer.recv().ok(); after.await;
} }
} }
} }

View file

@ -13,18 +13,39 @@ use tokio::time as tokio_time;
const ARROW_RIGHT: &'static str = "➡️"; const ARROW_RIGHT: &'static str = "➡️";
const ARROW_LEFT: &'static str = "⬅️"; const ARROW_LEFT: &'static str = "⬅️";
/// Paginate! with a pager function. #[async_trait::async_trait]
pub trait Paginate {
async fn render(&mut self, page: u8, ctx: &Context, m: &mut Message) -> Result<bool>;
}
#[async_trait::async_trait]
impl<T> Paginate for T
where
T: for<'m> FnMut(
u8,
&'m Context,
&'m mut Message,
) -> std::pin::Pin<Box<dyn Future<Output = Result<bool>> + Send + 'm>>
+ Send,
{
async fn render(&mut self, page: u8, ctx: &Context, m: &mut Message) -> Result<bool> {
self(page, ctx, m).await
}
}
// Paginate! with a pager function.
/// If awaited, will block until everything is done. /// If awaited, will block until everything is done.
pub async fn paginate<'a, T, F>( pub async fn paginate(
mut pager: T, mut pager: impl for<'m> FnMut(
ctx: &'a Context, u8,
&'m Context,
&'m mut Message,
) -> std::pin::Pin<Box<dyn Future<Output = Result<bool>> + Send + 'm>>
+ Send,
ctx: &Context,
channel: ChannelId, channel: ChannelId,
timeout: std::time::Duration, timeout: std::time::Duration,
) -> Result<()> ) -> Result<()> {
where
T: for<'m> FnMut(u8, &'a Context, &'m mut Message) -> F,
F: Future<Output = Result<bool>>,
{
let mut message = channel let mut message = channel
.send_message(&ctx, |e| e.content("Youmu is loading the first page...")) .send_message(&ctx, |e| e.content("Youmu is loading the first page..."))
.await?; .await?;
@ -35,8 +56,9 @@ where
message message
.react(&ctx, ReactionType::try_from(ARROW_RIGHT)?) .react(&ctx, ReactionType::try_from(ARROW_RIGHT)?)
.await?; .await?;
pager(0, ctx, &mut message).await?;
// Build a reaction collector // Build a reaction collector
let mut reaction_collector = message.await_reactions(&ctx).await; let mut reaction_collector = message.await_reactions(&ctx).removed(true).await;
let mut page = 0; let mut page = 0;
// Loop the handler function. // Loop the handler function.
@ -59,29 +81,25 @@ where
} }
// Handle the reaction and return a new page number. // Handle the reaction and return a new page number.
async fn handle_reaction<'a, T, F>( async fn handle_reaction(
page: u8, page: u8,
pager: &mut T, pager: &mut impl Paginate,
ctx: &'a Context, ctx: &Context,
message: &'_ mut Message, message: &mut Message,
reaction: &ReactionAction, reaction: &ReactionAction,
) -> Result<u8> ) -> Result<u8> {
where
T: for<'m> FnMut(u8, &'a Context, &'m mut Message) -> F,
F: Future<Output = Result<bool>>,
{
let reaction = match reaction { let reaction = match reaction {
ReactionAction::Added(v) | ReactionAction::Removed(v) => v, ReactionAction::Added(v) | ReactionAction::Removed(v) => v,
}; };
match &reaction.emoji { match &reaction.emoji {
ReactionType::Unicode(ref s) => match s.as_str() { ReactionType::Unicode(ref s) => match s.as_str() {
ARROW_LEFT if page == 0 => Ok(page), ARROW_LEFT if page == 0 => Ok(page),
ARROW_LEFT => Ok(if pager(page - 1, ctx, message).await? { ARROW_LEFT => Ok(if pager.render(page - 1, ctx, message).await? {
page - 1 page - 1
} else { } else {
page page
}), }),
ARROW_RIGHT => Ok(if pager(page + 1, ctx, message).await? { ARROW_RIGHT => Ok(if pager.render(page + 1, ctx, message).await? {
page + 1 page + 1
} else { } else {
page page

View file

@ -1,10 +1,10 @@
use serenity::{framework::standard::StandardFramework, prelude::*}; use serenity::prelude::*;
use std::path::Path; use std::path::Path;
/// Set up the prelude libraries. /// Set up the prelude libraries.
/// ///
/// Panics on failure: Youmubot should *NOT* attempt to continue when this function fails. /// Panics on failure: Youmubot should *NOT* attempt to continue when this function fails.
pub fn setup_prelude(db_path: &Path, data: &mut TypeMap, _: &mut StandardFramework) { pub fn setup_prelude(db_path: &Path, data: &mut TypeMap) {
// Setup the announcer DB. // Setup the announcer DB.
crate::announcer::AnnouncerChannels::insert_into(data, db_path.join("announcers.yaml")) crate::announcer::AnnouncerChannels::insert_into(data, db_path.join("announcers.yaml"))
.expect("Announcers DB set up"); .expect("Announcers DB set up");