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
|
||||
/.direnv/
|
||||
/db.sqlite
|
||||
/*.sqlite
|
||||
/token
|
||||
/token-*
|
||||
|
||||
115
Cargo.lock
generated
115
Cargo.lock
generated
@@ -145,6 +145,9 @@ dependencies = [
|
||||
"rusqlite",
|
||||
"serenity",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-error",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -895,6 +898,15 @@ version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
@@ -962,6 +974,16 @@ dependencies = [
|
||||
"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]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
@@ -992,6 +1014,12 @@ version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.5.0"
|
||||
@@ -1204,8 +1232,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
"regex-automata 0.4.8",
|
||||
"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]]
|
||||
@@ -1216,9 +1253,15 @@ checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"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]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
@@ -1874,9 +1917,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
@@ -1886,9 +1929,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.27"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1897,9 +1940,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.32"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
@@ -1907,25 +1950,43 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-error"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
|
||||
checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.18"
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"sharded-slab",
|
||||
"thread_local",
|
||||
"log",
|
||||
"once_cell",
|
||||
"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]]
|
||||
name = "triomphe"
|
||||
version = "0.1.14"
|
||||
@@ -2214,6 +2275,22 @@ dependencies = [
|
||||
"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]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
@@ -2223,6 +2300,12 @@ dependencies = [
|
||||
"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]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
|
||||
@@ -14,3 +14,6 @@ rand = "0.8.5"
|
||||
rusqlite = { version = "0.32.1", features = ["rusqlite-macros"] }
|
||||
serenity = "0.12.2"
|
||||
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"] }
|
||||
|
||||
328
src/main.rs
328
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 futures::FutureExt;
|
||||
use ::serenity::all::{ChannelId, GuildId, Mentionable, UserId};
|
||||
use eyre::{Context as _, Error, OptionExt, Result};
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use poise::serenity_prelude as serenity;
|
||||
use rand::seq::SliceRandom;
|
||||
use rusqlite::OptionalExtension;
|
||||
|
||||
// TODO: buttons?
|
||||
use tracing_subscriber::{
|
||||
fmt::{self, format::FmtSpan},
|
||||
layer::SubscriberExt,
|
||||
EnvFilter,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct GuildData {
|
||||
guild: serenity::GuildId,
|
||||
town_square: serenity::ChannelId,
|
||||
cottages: serenity::ChannelId,
|
||||
st: serenity::RoleId,
|
||||
currently_playing: serenity::RoleId,
|
||||
}
|
||||
|
||||
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 {
|
||||
#[tracing::instrument(skip(path))]
|
||||
fn open(path: impl AsRef<Path>) -> Result<Self> {
|
||||
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)))
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
async fn get(&self, guild: serenity::GuildId) -> Result<GuildData> {
|
||||
self.0
|
||||
.lock()
|
||||
.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| {
|
||||
Ok(GuildData {
|
||||
guild: row.get_unwrap::<_, String>("guild").parse().unwrap(),
|
||||
town_square: row.get_unwrap::<_, String>("town_square").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()?
|
||||
.ok_or_eyre("This bot has not been configured in this server!")
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
async fn insert(
|
||||
&self,
|
||||
GuildData {
|
||||
guild,
|
||||
town_square,
|
||||
cottages,
|
||||
st,
|
||||
currently_playing,
|
||||
}: GuildData,
|
||||
) -> Result<()> {
|
||||
self.0
|
||||
.lock()
|
||||
.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![
|
||||
guild.to_string(),
|
||||
town_square.to_string(),
|
||||
cottages.to_string()
|
||||
cottages.to_string(),
|
||||
st.to_string(),
|
||||
currently_playing.to_string(),
|
||||
])?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -63,45 +90,69 @@ impl Data {
|
||||
|
||||
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
|
||||
.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<_>>();
|
||||
ctx.defer_ephemeral().await?;
|
||||
|
||||
let from = from
|
||||
.to_channel(ctx)
|
||||
.await?
|
||||
.guild()
|
||||
.ok_or_eyre("This bot only works in servers")?;
|
||||
let mut to = channel_children(&ctx, to).await?;
|
||||
let mut users = from.members(ctx)?;
|
||||
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());
|
||||
|
||||
futures::future::try_join_all(
|
||||
move_users(
|
||||
ctx,
|
||||
guild,
|
||||
to.into_iter()
|
||||
.cycle()
|
||||
.zip(&users)
|
||||
.map(|(channel, user)| guild.move_member(ctx, user.user.id, channel)),
|
||||
.map(|(channel, user)| (channel.id, user.user.id))
|
||||
.collect(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -109,7 +160,40 @@ async fn run(ctx: Context<'_>, from: serenity::ChannelId, to: serenity::ChannelI
|
||||
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)]
|
||||
#[tracing::instrument]
|
||||
async fn dusk(ctx: Context<'_>) -> Result<()> {
|
||||
let guild = ctx
|
||||
.guild_id()
|
||||
@@ -120,12 +204,13 @@ async fn dusk(ctx: Context<'_>) -> Result<()> {
|
||||
..
|
||||
} = ctx.data().get(guild).await?;
|
||||
|
||||
run(ctx, town_square, cottages).await?;
|
||||
spread(ctx, town_square, cottages).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[poise::command(slash_command, prefix_command, ephemeral)]
|
||||
#[tracing::instrument]
|
||||
async fn dawn(ctx: Context<'_>) -> Result<()> {
|
||||
let guild = ctx
|
||||
.guild_id()
|
||||
@@ -136,30 +221,158 @@ async fn dawn(ctx: Context<'_>) -> Result<()> {
|
||||
..
|
||||
} = 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(())
|
||||
}
|
||||
|
||||
#[poise::command(slash_command, prefix_command, ephemeral)]
|
||||
#[tracing::instrument]
|
||||
async fn configure(
|
||||
ctx: Context<'_>,
|
||||
#[channel_types("Voice")] town_square: serenity::ChannelId,
|
||||
#[channel_types("Category")] cottages: serenity::ChannelId,
|
||||
st: serenity::RoleId,
|
||||
currently_playing: serenity::RoleId,
|
||||
) -> Result<()> {
|
||||
let guild = ctx
|
||||
.guild_id()
|
||||
.ok_or_eyre("This bot only works in servers")?;
|
||||
|
||||
ctx.data().insert(GuildData {
|
||||
guild,
|
||||
town_square,
|
||||
cottages,
|
||||
}).await?;
|
||||
ctx.data()
|
||||
.insert(GuildData {
|
||||
guild,
|
||||
town_square,
|
||||
cottages,
|
||||
st,
|
||||
currently_playing,
|
||||
})
|
||||
.await?;
|
||||
|
||||
ctx.reply("Configured successfully!").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(ctx))]
|
||||
async fn register_commands(
|
||||
ctx: impl AsRef<serenity::Http>,
|
||||
options: &poise::FrameworkOptions<Data, Error>,
|
||||
@@ -182,18 +395,39 @@ async fn on_event(
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
#[tracing::instrument]
|
||||
async fn main() -> Result<()> {
|
||||
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(
|
||||
std::env::var_os("DISCORD_TOKEN_FILE").ok_or_eyre("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 intents = serenity::GatewayIntents::non_privileged();
|
||||
let intents =
|
||||
serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::GUILD_MEMBERS;
|
||||
|
||||
let framework = poise::Framework::builder()
|
||||
.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(),
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user