Refactor to allow only spreading into empty channels
Split `run` into `spread` and `join`
This commit is contained in:
106
src/main.rs
106
src/main.rs
@@ -1,13 +1,11 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use eyre::{bail, Context as _, Error, OptionExt, Result};
|
use eyre::{Context as _, Error, OptionExt, Result};
|
||||||
use futures::{FutureExt, TryStreamExt};
|
use futures::{FutureExt, TryStreamExt};
|
||||||
use poise::serenity_prelude as serenity;
|
use poise::serenity_prelude as serenity;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use rusqlite::OptionalExtension;
|
use rusqlite::OptionalExtension;
|
||||||
|
|
||||||
// TODO: buttons?
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
struct GuildData {
|
struct GuildData {
|
||||||
guild: serenity::GuildId,
|
guild: serenity::GuildId,
|
||||||
@@ -77,43 +75,45 @@ impl Data {
|
|||||||
|
|
||||||
type Context<'a> = poise::Context<'a, Data, Error>;
|
type Context<'a> = poise::Context<'a, Data, Error>;
|
||||||
|
|
||||||
async fn run(ctx: Context<'_>, from: serenity::ChannelId, to: serenity::ChannelId) -> Result<()> {
|
async fn channel_children(
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
channel: serenity::ChannelId,
|
||||||
|
) -> Result<Vec<serenity::GuildChannel>> {
|
||||||
|
let channels = ctx
|
||||||
|
.guild_id()
|
||||||
|
.ok_or_eyre("This bot only works in servers")?
|
||||||
|
.channels(&ctx)
|
||||||
|
.await?;
|
||||||
|
Ok(channels
|
||||||
|
.into_values()
|
||||||
|
.filter(|chan| chan.parent_id == Some(channel))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn spread(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
from: serenity::ChannelId,
|
||||||
|
to: serenity::ChannelId,
|
||||||
|
) -> Result<()> {
|
||||||
let guild = ctx
|
let guild = ctx
|
||||||
.guild_id()
|
.guild_id()
|
||||||
.ok_or_eyre("This bot only works in servers")?;
|
.ok_or_eyre("This bot only works in servers")?;
|
||||||
|
|
||||||
let channels = guild.channels(&ctx).await?;
|
let from = from
|
||||||
let channels_for = |id| {
|
.to_channel(ctx)
|
||||||
let channel = channels
|
.await?
|
||||||
.get(&id)
|
.guild()
|
||||||
.ok_or_eyre(format!("The channel {id:?} doesn't exist"))?;
|
.ok_or_eyre("This bot only works in servers")?;
|
||||||
Ok::<_, Error>(match channel.kind {
|
let mut to = channel_children(&ctx, to).await?;
|
||||||
serenity::ChannelType::Voice => vec![channel],
|
|
||||||
serenity::ChannelType::Category => channels
|
let mut users = from.members(ctx)?;
|
||||||
.values()
|
|
||||||
.filter(|chan| {
|
|
||||||
chan.kind == serenity::ChannelType::Voice && chan.parent_id == Some(channel.id)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
ty => bail!(
|
|
||||||
"This bot doesn't work with non-voice channels: {:?} has type {:?}",
|
|
||||||
&channel.name,
|
|
||||||
ty
|
|
||||||
),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
let mut users = channels_for(from)?
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|chan| chan.members(ctx).unwrap())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
users.shuffle(&mut rand::thread_rng());
|
users.shuffle(&mut rand::thread_rng());
|
||||||
|
|
||||||
let mut to = channels_for(to)?;
|
to.retain(|x| x.members(ctx).is_ok_and(|x| x.is_empty()));
|
||||||
to.shuffle(&mut rand::thread_rng());
|
to.shuffle(&mut rand::thread_rng());
|
||||||
|
|
||||||
futures::future::try_join_all(
|
futures::future::try_join_all(
|
||||||
to.into_iter()
|
to.into_iter()
|
||||||
.cycle()
|
|
||||||
.zip(&users)
|
.zip(&users)
|
||||||
.map(|(channel, user)| guild.move_member(ctx, user.user.id, channel)),
|
.map(|(channel, user)| guild.move_member(ctx, user.user.id, channel)),
|
||||||
)
|
)
|
||||||
@@ -123,6 +123,25 @@ async fn run(ctx: Context<'_>, from: serenity::ChannelId, to: serenity::ChannelI
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn join(ctx: Context<'_>, from: serenity::ChannelId, to: serenity::ChannelId) -> Result<()> {
|
||||||
|
let guild = ctx
|
||||||
|
.guild_id()
|
||||||
|
.ok_or_eyre("This bot only works in servers")?;
|
||||||
|
|
||||||
|
let users = channel_children(&ctx, from)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|x| x.members(ctx))
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
futures::future::try_join_all(users.iter().map(|user| guild.move_member(&ctx, user, to)))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
ctx.reply(format!("Moved {} users", users.len())).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, ephemeral)]
|
#[poise::command(slash_command, prefix_command, ephemeral)]
|
||||||
async fn dusk(ctx: Context<'_>) -> Result<()> {
|
async fn dusk(ctx: Context<'_>) -> Result<()> {
|
||||||
let guild = ctx
|
let guild = ctx
|
||||||
@@ -134,7 +153,7 @@ async fn dusk(ctx: Context<'_>) -> Result<()> {
|
|||||||
..
|
..
|
||||||
} = ctx.data().get(guild).await?;
|
} = ctx.data().get(guild).await?;
|
||||||
|
|
||||||
run(ctx, town_square, cottages).await?;
|
spread(ctx, town_square, cottages).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -150,7 +169,7 @@ async fn dawn(ctx: Context<'_>) -> Result<()> {
|
|||||||
..
|
..
|
||||||
} = ctx.data().get(guild).await?;
|
} = ctx.data().get(guild).await?;
|
||||||
|
|
||||||
run(ctx, cottages, town_square).await?;
|
join(ctx, cottages, town_square).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -163,7 +182,6 @@ async fn st(ctx: Context<'_>, mut spectators: Vec<serenity::UserId>) -> Result<(
|
|||||||
let GuildData { st, .. } = ctx.data().get(guild).await?;
|
let GuildData { st, .. } = ctx.data().get(guild).await?;
|
||||||
spectators.push(ctx.author().id);
|
spectators.push(ctx.author().id);
|
||||||
|
|
||||||
|
|
||||||
guild
|
guild
|
||||||
.members_iter(&ctx)
|
.members_iter(&ctx)
|
||||||
.try_for_each(|member| {
|
.try_for_each(|member| {
|
||||||
@@ -186,12 +204,21 @@ async fn st(ctx: Context<'_>, mut spectators: Vec<serenity::UserId>) -> Result<(
|
|||||||
|
|
||||||
#[poise::command(slash_command, ephemeral)]
|
#[poise::command(slash_command, ephemeral)]
|
||||||
async fn currently_playing(ctx: Context<'_>, players: Vec<serenity::UserId>) -> Result<()> {
|
async fn currently_playing(ctx: Context<'_>, players: Vec<serenity::UserId>) -> Result<()> {
|
||||||
let guild = ctx.guild_id().ok_or_eyre("This bot only works in servers")?;
|
let guild = ctx
|
||||||
let GuildData { currently_playing, .. } = ctx.data().get(guild).await?;
|
.guild_id()
|
||||||
|
.ok_or_eyre("This bot only works in servers")?;
|
||||||
|
let GuildData {
|
||||||
|
currently_playing, ..
|
||||||
|
} = ctx.data().get(guild).await?;
|
||||||
for player in &players {
|
for player in &players {
|
||||||
guild.member(&ctx, player).await?.add_role(&ctx, currently_playing).await?;
|
guild
|
||||||
|
.member(&ctx, player)
|
||||||
|
.await?
|
||||||
|
.add_role(&ctx, currently_playing)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
ctx.reply(format!("Given {} members currently playing", players.len())).await?;
|
ctx.reply(format!("Given {} members currently playing", players.len()))
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +228,7 @@ async fn configure(
|
|||||||
#[channel_types("Voice")] town_square: serenity::ChannelId,
|
#[channel_types("Voice")] town_square: serenity::ChannelId,
|
||||||
#[channel_types("Category")] cottages: serenity::ChannelId,
|
#[channel_types("Category")] cottages: serenity::ChannelId,
|
||||||
st: serenity::RoleId,
|
st: serenity::RoleId,
|
||||||
currently_playing: serenity::RoleId
|
currently_playing: serenity::RoleId,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let guild = ctx
|
let guild = ctx
|
||||||
.guild_id()
|
.guild_id()
|
||||||
@@ -250,7 +277,8 @@ async fn main() -> Result<()> {
|
|||||||
)
|
)
|
||||||
.wrap_err("A Discord token is required")?;
|
.wrap_err("A Discord token is required")?;
|
||||||
let dbfile = std::env::var_os("DB_FILE").ok_or_eyre("A database file is required")?;
|
let dbfile = std::env::var_os("DB_FILE").ok_or_eyre("A database file is required")?;
|
||||||
let intents = serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::GUILD_MEMBERS;
|
let intents =
|
||||||
|
serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::GUILD_MEMBERS;
|
||||||
|
|
||||||
let framework = poise::Framework::builder()
|
let framework = poise::Framework::builder()
|
||||||
.options(poise::FrameworkOptions {
|
.options(poise::FrameworkOptions {
|
||||||
|
|||||||
Reference in New Issue
Block a user