Functional bot: responds to commands, moves users
This commit is contained in:
5
.envrc
5
.envrc
@@ -1 +1,6 @@
|
||||
use flake
|
||||
|
||||
export DISCORD_TOKEN_FILE=$(mktemp)
|
||||
cat >$DISCORD_TOKEN_FILE <<END
|
||||
MTMwMjQyMTcwMjU5NjIzMTE5OA.G3qd1e.5LqVmTtO0ZxR-j1ueO537IvKHF8YfHdnd-R4zo
|
||||
END
|
||||
|
||||
156
Cargo.lock
generated
156
Cargo.lock
generated
@@ -4,13 +4,19 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
@@ -69,17 +75,17 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
version = "0.3.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
|
||||
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.7.4",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -119,8 +125,13 @@ dependencies = [
|
||||
name = "botc-discord"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"color-eyre",
|
||||
"eyre",
|
||||
"futures",
|
||||
"poise",
|
||||
"rand",
|
||||
"serenity",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -206,6 +217,33 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"color-spantrace",
|
||||
"eyre",
|
||||
"indenter",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-spantrace"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-core",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "command_attr"
|
||||
version = "0.5.2"
|
||||
@@ -396,6 +434,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
|
||||
dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.1.1"
|
||||
@@ -409,7 +457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -435,6 +483,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
@@ -457,6 +506,17 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
@@ -536,9 +596,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
@@ -705,6 +765,12 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.6.0"
|
||||
@@ -736,6 +802,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "levenshtein"
|
||||
version = "1.0.5"
|
||||
@@ -807,6 +879,15 @@ dependencies = [
|
||||
"triomphe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.0"
|
||||
@@ -845,9 +926,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.5"
|
||||
version = "0.32.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
|
||||
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -858,6 +939,12 @@ version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
@@ -1331,6 +1418,15 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
@@ -1489,6 +1585,16 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.36"
|
||||
@@ -1648,6 +1754,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-error"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
dependencies = [
|
||||
"sharded-slab",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1781,6 +1909,12 @@ version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
||||
@@ -6,5 +6,10 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
color-eyre = "0.6.3"
|
||||
eyre = "0.6.12"
|
||||
futures = "0.3.31"
|
||||
poise = "0.6.1"
|
||||
rand = "0.8.5"
|
||||
serenity = "0.12.2"
|
||||
tokio = { version = "1.41.0", features = ["rt", "net"] }
|
||||
|
||||
189
src/main.rs
189
src/main.rs
@@ -1 +1,188 @@
|
||||
fn main() {}
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use eyre::{bail, Context as _, Error, OptionExt, Result};
|
||||
use futures::FutureExt;
|
||||
use poise::serenity_prelude as serenity;
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
// TODO: persist configuration
|
||||
// TODO: buttons?
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct GuildData {
|
||||
town_square: serenity::ChannelId,
|
||||
cottages: serenity::ChannelId,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Data(Arc<RwLock<BTreeMap<serenity::GuildId, GuildData>>>);
|
||||
|
||||
type Context<'a> = poise::Context<'a, Data, Error>;
|
||||
|
||||
async fn run(ctx: Context<'_>, from: serenity::ChannelId, to: serenity::ChannelId) -> Result<()> {
|
||||
let guild = ctx
|
||||
.guild_id()
|
||||
.ok_or_eyre("This bot only works in servers")?;
|
||||
|
||||
let channels = guild.channels(&ctx).await?;
|
||||
let channels_for = |id| {
|
||||
let channel = channels
|
||||
.get(&id)
|
||||
.ok_or_eyre(format!("The channel {id:?} doesn't exist"))?;
|
||||
Ok::<_, Error>(match channel.kind {
|
||||
serenity::ChannelType::Voice => vec![channel],
|
||||
serenity::ChannelType::Category => channels
|
||||
.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());
|
||||
|
||||
let to = channels_for(to)?;
|
||||
|
||||
futures::future::try_join_all(
|
||||
to.into_iter()
|
||||
.cycle()
|
||||
.zip(&users)
|
||||
.map(|(channel, user)| guild.move_member(ctx, user.user.id, channel)),
|
||||
)
|
||||
.await?;
|
||||
|
||||
ctx.reply(format!("Moved {} users", users.len())).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[poise::command(slash_command, prefix_command, ephemeral)]
|
||||
async fn dusk(ctx: Context<'_>) -> Result<()> {
|
||||
let guild = ctx
|
||||
.guild_id()
|
||||
.ok_or_eyre("This bot only works in servers")?;
|
||||
let GuildData {
|
||||
town_square,
|
||||
cottages,
|
||||
} = ctx
|
||||
.data()
|
||||
.0
|
||||
.read()
|
||||
.expect("No poisoned locks")
|
||||
.get(&guild)
|
||||
.copied()
|
||||
.ok_or_eyre("This bot hasn't been configured for this server")?;
|
||||
|
||||
run(ctx, town_square, cottages).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[poise::command(slash_command, prefix_command, ephemeral)]
|
||||
async fn dawn(ctx: Context<'_>) -> Result<()> {
|
||||
let guild = ctx
|
||||
.guild_id()
|
||||
.ok_or_eyre("This bot only works in servers")?;
|
||||
let GuildData {
|
||||
town_square,
|
||||
cottages,
|
||||
} = ctx
|
||||
.data()
|
||||
.0
|
||||
.read()
|
||||
.expect("No poisoned locks")
|
||||
.get(&guild)
|
||||
.copied()
|
||||
.ok_or_eyre("This bot hasn't been configured for this server")?;
|
||||
|
||||
run(ctx, cottages, town_square).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[poise::command(slash_command, prefix_command, ephemeral)]
|
||||
async fn configure(
|
||||
ctx: Context<'_>,
|
||||
#[channel_types("Voice")] town_square: serenity::ChannelId,
|
||||
#[channel_types("Category")] cottages: serenity::ChannelId,
|
||||
) -> Result<()> {
|
||||
let guild = ctx
|
||||
.guild_id()
|
||||
.ok_or_eyre("This bot only works in servers")?;
|
||||
|
||||
ctx.data().0.write().expect("No poisoned locks").insert(
|
||||
guild,
|
||||
GuildData {
|
||||
town_square,
|
||||
cottages,
|
||||
},
|
||||
);
|
||||
ctx.reply("Configured successfully!").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn register_commands(
|
||||
ctx: impl AsRef<serenity::Http>,
|
||||
options: &poise::FrameworkOptions<Data, Error>,
|
||||
guild: serenity::GuildId,
|
||||
) -> Result<()> {
|
||||
poise::builtins::register_in_guild(ctx, &options.commands, guild).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn on_event(
|
||||
ctx: &serenity::Context,
|
||||
event: &serenity::FullEvent,
|
||||
framework: poise::FrameworkContext<'_, Data, Error>,
|
||||
_: &Data,
|
||||
) -> Result<()> {
|
||||
if let serenity::FullEvent::GuildCreate { guild, is_new: _ } = event {
|
||||
register_commands(ctx, framework.options, guild.id).await?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
let token = std::fs::read_to_string(
|
||||
std::env::var_os("DISCORD_TOKEN_FILE").ok_or_eyre("A Discord token is required")?,
|
||||
)
|
||||
.wrap_err("A Discord token is required")?;
|
||||
let intents = serenity::GatewayIntents::non_privileged();
|
||||
|
||||
let framework = poise::Framework::builder()
|
||||
.options(poise::FrameworkOptions {
|
||||
commands: vec![dusk(), dawn(), configure()],
|
||||
event_handler: |ctx, ev, ctxf, data| on_event(ctx, ev, ctxf, data).boxed(),
|
||||
..Default::default()
|
||||
})
|
||||
.setup(|ctx, ready, framework| {
|
||||
Box::pin(async move {
|
||||
for guild in &ready.guilds {
|
||||
register_commands(ctx, framework.options(), guild.id).await?;
|
||||
}
|
||||
Ok(Data::default())
|
||||
})
|
||||
})
|
||||
.build();
|
||||
|
||||
serenity::ClientBuilder::new(token, intents)
|
||||
.framework(framework)
|
||||
.await?
|
||||
.start()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user