Compare commits
11 Commits
49538af0ad
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2565a53496 | ||
|
|
1482e1770d | ||
|
|
e38ce4e568 | ||
|
|
0e4df70f39 | ||
|
|
994babaacf | ||
|
|
06f116976d | ||
|
|
25f4ed332a | ||
|
|
78fa121404 | ||
|
|
062a419c94 | ||
|
|
4245102bf5 | ||
|
|
eac793aca8 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
/target
|
/target
|
||||||
/.direnv/
|
/.direnv/
|
||||||
/db.sqlite
|
/*.sqlite
|
||||||
/token
|
/token
|
||||||
|
/token-*
|
||||||
|
|||||||
115
Cargo.lock
generated
115
Cargo.lock
generated
@@ -145,6 +145,9 @@ dependencies = [
|
|||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serenity",
|
"serenity",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"tracing-error",
|
||||||
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -895,6 +898,15 @@ version = "0.4.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchers"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||||
|
dependencies = [
|
||||||
|
"regex-automata 0.1.10",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
@@ -962,6 +974,16 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-ansi-term"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||||
|
dependencies = [
|
||||||
|
"overload",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -992,6 +1014,12 @@ version = "1.20.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "overload"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "owo-colors"
|
name = "owo-colors"
|
||||||
version = "3.5.0"
|
version = "3.5.0"
|
||||||
@@ -1204,8 +1232,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata",
|
"regex-automata 0.4.8",
|
||||||
"regex-syntax",
|
"regex-syntax 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
dependencies = [
|
||||||
|
"regex-syntax 0.6.29",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1216,9 +1253,15 @@ checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax 0.8.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
@@ -1874,9 +1917,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.40"
|
version = "0.1.41"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -1886,9 +1929,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.27"
|
version = "0.1.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1897,9 +1940,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.32"
|
version = "0.1.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"valuable",
|
"valuable",
|
||||||
@@ -1907,25 +1950,43 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-error"
|
name = "tracing-error"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
|
checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-log"
|
||||||
version = "0.3.18"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sharded-slab",
|
"log",
|
||||||
"thread_local",
|
"once_cell",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||||
|
dependencies = [
|
||||||
|
"matchers",
|
||||||
|
"nu-ansi-term",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
|
"tracing",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "triomphe"
|
name = "triomphe"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
@@ -2214,6 +2275,22 @@ dependencies = [
|
|||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -2223,6 +2300,12 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|||||||
@@ -14,3 +14,6 @@ rand = "0.8.5"
|
|||||||
rusqlite = { version = "0.32.1", features = ["rusqlite-macros"] }
|
rusqlite = { version = "0.32.1", features = ["rusqlite-macros"] }
|
||||||
serenity = "0.12.2"
|
serenity = "0.12.2"
|
||||||
tokio = { version = "1.41.0", features = ["rt", "net"] }
|
tokio = { version = "1.41.0", features = ["rt", "net"] }
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-error = "0.2.1"
|
||||||
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
|||||||
322
src/main.rs
322
src/main.rs
@@ -1,61 +1,88 @@
|
|||||||
use std::path::Path;
|
use std::{future::Future, io, path::Path};
|
||||||
|
|
||||||
use eyre::{bail, Context as _, Error, OptionExt, Result};
|
use ::serenity::all::{ChannelId, GuildId, Mentionable, UserId};
|
||||||
use futures::FutureExt;
|
use eyre::{Context as _, Error, OptionExt, Result};
|
||||||
|
use futures::{FutureExt, StreamExt, 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;
|
||||||
|
use tracing_subscriber::{
|
||||||
// TODO: buttons?
|
fmt::{self, format::FmtSpan},
|
||||||
|
layer::SubscriberExt,
|
||||||
|
EnvFilter,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
struct GuildData {
|
struct GuildData {
|
||||||
guild: serenity::GuildId,
|
guild: serenity::GuildId,
|
||||||
town_square: serenity::ChannelId,
|
town_square: serenity::ChannelId,
|
||||||
cottages: serenity::ChannelId,
|
cottages: serenity::ChannelId,
|
||||||
|
st: serenity::RoleId,
|
||||||
|
currently_playing: serenity::RoleId,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Data(tokio::sync::Mutex<rusqlite::Connection>);
|
struct Data(tokio::sync::Mutex<rusqlite::Connection>);
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Data {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Data {{}}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Data {
|
impl Data {
|
||||||
|
#[tracing::instrument(skip(path))]
|
||||||
fn open(path: impl AsRef<Path>) -> Result<Self> {
|
fn open(path: impl AsRef<Path>) -> Result<Self> {
|
||||||
let conn = rusqlite::Connection::open(path)?;
|
let conn = rusqlite::Connection::open(path)?;
|
||||||
conn.execute_batch("CREATE TABLE IF NOT EXISTS guilds(guild TEXT PRIMARY KEY NOT NULL, town_square TEXT NOT NULL, cottages TEXT NOT NULL);")?;
|
conn.execute_batch("CREATE TABLE IF NOT EXISTS guilds(guild TEXT PRIMARY KEY NOT NULL, town_square TEXT NOT NULL, cottages TEXT NOT NULL, st TEXT, currently_playing TEXT);")?;
|
||||||
Ok(Self(tokio::sync::Mutex::new(conn)))
|
Ok(Self(tokio::sync::Mutex::new(conn)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
async fn get(&self, guild: serenity::GuildId) -> Result<GuildData> {
|
async fn get(&self, guild: serenity::GuildId) -> Result<GuildData> {
|
||||||
self.0
|
self.0
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.prepare_cached("SELECT guild, town_square, cottages FROM guilds WHERE guild = ?;")?
|
.prepare_cached("SELECT guild, town_square, cottages, st, currently_playing FROM guilds WHERE guild = ?;")?
|
||||||
.query_row(rusqlite::params![guild.to_string()], |row| {
|
.query_row(rusqlite::params![guild.to_string()], |row| {
|
||||||
Ok(GuildData {
|
Ok(GuildData {
|
||||||
guild: row.get_unwrap::<_, String>("guild").parse().unwrap(),
|
guild: row.get_unwrap::<_, String>("guild").parse().unwrap(),
|
||||||
town_square: row.get_unwrap::<_, String>("town_square").parse().unwrap(),
|
town_square: row.get_unwrap::<_, String>("town_square").parse().unwrap(),
|
||||||
cottages: row.get_unwrap::<_, String>("cottages").parse().unwrap(),
|
cottages: row.get_unwrap::<_, String>("cottages").parse().unwrap(),
|
||||||
|
st: row.get_unwrap::<_, String>("st").parse().unwrap(),
|
||||||
|
currently_playing: row.get_unwrap::<_, String>("currently_playing").parse().unwrap(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.optional()?
|
.optional()?
|
||||||
.ok_or_eyre("This bot has not been configured in this server!")
|
.ok_or_eyre("This bot has not been configured in this server!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
async fn insert(
|
async fn insert(
|
||||||
&self,
|
&self,
|
||||||
GuildData {
|
GuildData {
|
||||||
guild,
|
guild,
|
||||||
town_square,
|
town_square,
|
||||||
cottages,
|
cottages,
|
||||||
|
st,
|
||||||
|
currently_playing,
|
||||||
}: GuildData,
|
}: GuildData,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.0
|
self.0
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.prepare_cached("INSERT INTO guilds (guild, town_square, cottages) VALUES (?, ?, ?) ON CONFLICT(guild) DO UPDATE SET town_square = excluded.town_square, cottages = excluded.cottages")?
|
.prepare_cached("INSERT INTO guilds (guild, town_square, cottages, st, currently_playing) VALUES (?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(guild) DO UPDATE SET
|
||||||
|
town_square = excluded.town_square,
|
||||||
|
cottages = excluded.cottages,
|
||||||
|
st = excluded.st,
|
||||||
|
currently_playing = excluded.currently_playing;
|
||||||
|
")?
|
||||||
.execute(rusqlite::params![
|
.execute(rusqlite::params![
|
||||||
guild.to_string(),
|
guild.to_string(),
|
||||||
town_square.to_string(),
|
town_square.to_string(),
|
||||||
cottages.to_string()
|
cottages.to_string(),
|
||||||
|
st.to_string(),
|
||||||
|
currently_playing.to_string(),
|
||||||
])?;
|
])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -63,45 +90,69 @@ 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<()> {
|
#[tracing::instrument]
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn move_users<'a>(
|
||||||
|
ctx: Context<'a>,
|
||||||
|
guild: GuildId,
|
||||||
|
users: Vec<(ChannelId, UserId)>,
|
||||||
|
) -> impl Future<Output = Result<()>> + Send + 'a {
|
||||||
|
futures::stream::iter(users.into_iter().map(move |(channel, user)| async move {
|
||||||
|
tracing::info!(?channel, ?user, "Moving user");
|
||||||
|
guild.move_member(ctx, user, channel).await?;
|
||||||
|
tracing::info!(?channel, ?user, "Moved user");
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
.buffer_unordered(10)
|
||||||
|
.try_collect::<()>()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
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?;
|
ctx.defer_ephemeral().await?;
|
||||||
let channels_for = |id| {
|
|
||||||
let channel = channels
|
let from = from
|
||||||
.get(&id)
|
.to_channel(ctx)
|
||||||
.ok_or_eyre(format!("The channel {id:?} doesn't exist"))?;
|
.await?
|
||||||
Ok::<_, Error>(match channel.kind {
|
.guild()
|
||||||
serenity::ChannelType::Voice => vec![channel],
|
.ok_or_eyre("This bot only works in servers")?;
|
||||||
serenity::ChannelType::Category => channels
|
let mut to = channel_children(&ctx, to).await?;
|
||||||
.values()
|
let mut users = from.members(ctx)?;
|
||||||
.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(
|
move_users(
|
||||||
|
ctx,
|
||||||
|
guild,
|
||||||
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)| (channel.id, user.user.id))
|
||||||
|
.collect(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -109,7 +160,40 @@ async fn run(ctx: Context<'_>, from: serenity::ChannelId, to: serenity::ChannelI
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
async fn collect(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
from: serenity::ChannelId,
|
||||||
|
to: serenity::ChannelId,
|
||||||
|
) -> Result<()> {
|
||||||
|
let guild = ctx
|
||||||
|
.guild_id()
|
||||||
|
.ok_or_eyre("This bot only works in servers")?;
|
||||||
|
|
||||||
|
ctx.defer_ephemeral().await?;
|
||||||
|
|
||||||
|
let users = channel_children(&ctx, from)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|x| x.members(ctx))
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
move_users(
|
||||||
|
ctx,
|
||||||
|
guild,
|
||||||
|
users.iter().map(|user| (to, user.user.id)).collect(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
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)]
|
||||||
|
#[tracing::instrument]
|
||||||
async fn dusk(ctx: Context<'_>) -> Result<()> {
|
async fn dusk(ctx: Context<'_>) -> Result<()> {
|
||||||
let guild = ctx
|
let guild = ctx
|
||||||
.guild_id()
|
.guild_id()
|
||||||
@@ -120,12 +204,13 @@ 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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, ephemeral)]
|
#[poise::command(slash_command, prefix_command, ephemeral)]
|
||||||
|
#[tracing::instrument]
|
||||||
async fn dawn(ctx: Context<'_>) -> Result<()> {
|
async fn dawn(ctx: Context<'_>) -> Result<()> {
|
||||||
let guild = ctx
|
let guild = ctx
|
||||||
.guild_id()
|
.guild_id()
|
||||||
@@ -136,30 +221,158 @@ async fn dawn(ctx: Context<'_>) -> Result<()> {
|
|||||||
..
|
..
|
||||||
} = ctx.data().get(guild).await?;
|
} = ctx.data().get(guild).await?;
|
||||||
|
|
||||||
run(ctx, cottages, town_square).await?;
|
collect(ctx, cottages, town_square).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command, ephemeral)]
|
||||||
|
#[tracing::instrument]
|
||||||
|
async fn join(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
to: serenity::UserId,
|
||||||
|
user: Option<serenity::UserId>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let guild = ctx
|
||||||
|
.guild_id()
|
||||||
|
.ok_or_eyre("This bot only works in servers")?;
|
||||||
|
let GuildData { cottages, .. } = ctx.data().get(guild).await?;
|
||||||
|
|
||||||
|
let channel = channel_children(&ctx, cottages)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.find(|chan| {
|
||||||
|
chan.members(ctx)
|
||||||
|
.is_ok_and(|members| members.iter().any(|member| member.user.id == to))
|
||||||
|
});
|
||||||
|
|
||||||
|
match channel {
|
||||||
|
Some(channel) => {
|
||||||
|
guild
|
||||||
|
.move_member(ctx, user.unwrap_or(ctx.author().id), channel)
|
||||||
|
.await?;
|
||||||
|
ctx.reply("Joined player").await?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
ctx.reply("User is not in cottages").await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command, ephemeral)]
|
||||||
|
#[tracing::instrument]
|
||||||
|
async fn st(ctx: Context<'_>) -> Result<()> {
|
||||||
|
let guild = ctx
|
||||||
|
.guild_id()
|
||||||
|
.ok_or_eyre("This bot only works in servers")?;
|
||||||
|
let GuildData { st, .. } = ctx.data().get(guild).await?;
|
||||||
|
let sender = ctx.author().id;
|
||||||
|
|
||||||
|
futures::future::try_join_all(guild.members(&ctx, None, None).await?.into_iter().map(
|
||||||
|
|member| async move {
|
||||||
|
match (sender == member.user.id, member.roles.contains(&st)) {
|
||||||
|
(true, true) | (false, false) => Ok(()),
|
||||||
|
(true, false) => member.add_role(&ctx, st).await,
|
||||||
|
(false, true) => member.remove_role(&ctx, st).await,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
ctx.reply("You are now the ST").await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command, ephemeral)]
|
||||||
|
#[tracing::instrument]
|
||||||
|
async fn spectate(ctx: Context<'_>, player: Option<serenity::UserId>) -> Result<()> {
|
||||||
|
let guild = ctx
|
||||||
|
.guild_id()
|
||||||
|
.ok_or_eyre("This bot only works in servers")?;
|
||||||
|
let GuildData { st, .. } = ctx.data().get(guild).await?;
|
||||||
|
|
||||||
|
let member = guild
|
||||||
|
.member(&ctx, player.unwrap_or(ctx.author().id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let msg = if member.roles.contains(&st) {
|
||||||
|
member.remove_role(&ctx, st).await?;
|
||||||
|
"no longer a spectator"
|
||||||
|
} else {
|
||||||
|
member.add_role(&ctx, st).await?;
|
||||||
|
"now a spectator"
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.reply(if player.is_some() {
|
||||||
|
format!("{} is {}", member.mention(), msg)
|
||||||
|
} else {
|
||||||
|
format!("You are {}", msg)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command, ephemeral)]
|
||||||
|
#[tracing::instrument]
|
||||||
|
async fn currently_playing(ctx: Context<'_>, player: Option<serenity::UserId>) -> Result<()> {
|
||||||
|
let guild = ctx
|
||||||
|
.guild_id()
|
||||||
|
.ok_or_eyre("This bot only works in servers")?;
|
||||||
|
let GuildData { currently_playing, .. } = ctx.data().get(guild).await?;
|
||||||
|
|
||||||
|
let member = guild
|
||||||
|
.member(&ctx, player.unwrap_or(ctx.author().id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let msg = if member.roles.contains(¤tly_playing) {
|
||||||
|
member.remove_role(&ctx, currently_playing).await?;
|
||||||
|
"no longer"
|
||||||
|
} else {
|
||||||
|
member.add_role(&ctx, currently_playing).await?;
|
||||||
|
"now"
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.reply(if player.is_some() {
|
||||||
|
format!("{} is {} {}", member.mention(), msg, currently_playing.mention())
|
||||||
|
} else {
|
||||||
|
format!("You are {} {}", msg, currently_playing.mention())
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, ephemeral)]
|
#[poise::command(slash_command, prefix_command, ephemeral)]
|
||||||
|
#[tracing::instrument]
|
||||||
async fn configure(
|
async fn configure(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[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,
|
||||||
|
currently_playing: serenity::RoleId,
|
||||||
) -> Result<()> {
|
) -> 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")?;
|
||||||
|
|
||||||
ctx.data().insert(GuildData {
|
ctx.data()
|
||||||
|
.insert(GuildData {
|
||||||
guild,
|
guild,
|
||||||
town_square,
|
town_square,
|
||||||
cottages,
|
cottages,
|
||||||
}).await?;
|
st,
|
||||||
|
currently_playing,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
ctx.reply("Configured successfully!").await?;
|
ctx.reply("Configured successfully!").await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(ctx))]
|
||||||
async fn register_commands(
|
async fn register_commands(
|
||||||
ctx: impl AsRef<serenity::Http>,
|
ctx: impl AsRef<serenity::Http>,
|
||||||
options: &poise::FrameworkOptions<Data, Error>,
|
options: &poise::FrameworkOptions<Data, Error>,
|
||||||
@@ -182,18 +395,39 @@ async fn on_event(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
#[tracing::instrument]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
|
tracing::subscriber::set_global_default(
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(
|
||||||
|
fmt::layer()
|
||||||
|
.event_format(fmt::format().with_ansi(true).pretty())
|
||||||
|
.with_span_events(FmtSpan::ACTIVE)
|
||||||
|
.with_writer(io::stderr),
|
||||||
|
)
|
||||||
|
.with(EnvFilter::from_default_env())
|
||||||
|
.with(tracing_error::ErrorLayer::default()),
|
||||||
|
)?;
|
||||||
let token = std::fs::read_to_string(
|
let token = std::fs::read_to_string(
|
||||||
std::env::var_os("DISCORD_TOKEN_FILE").ok_or_eyre("A Discord token is required")?,
|
std::env::var_os("DISCORD_TOKEN_FILE").ok_or_eyre("A Discord token is required")?,
|
||||||
)
|
)
|
||||||
.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();
|
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 {
|
||||||
commands: vec![dusk(), dawn(), configure()],
|
commands: vec![
|
||||||
|
dusk(),
|
||||||
|
dawn(),
|
||||||
|
st(),
|
||||||
|
spectate(),
|
||||||
|
currently_playing(),
|
||||||
|
join(),
|
||||||
|
configure(),
|
||||||
|
],
|
||||||
event_handler: |ctx, ev, ctxf, data| on_event(ctx, ev, ctxf, data).boxed(),
|
event_handler: |ctx, ev, ctxf, data| on_event(ctx, ev, ctxf, data).boxed(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user