Compare commits

..

1 Commits

Author SHA1 Message Date
bluepython508
4f8f16ee77 Add/remove '(ST)' prefix for nickname 2025-07-12 18:20:02 +01:00

View File

@@ -1,6 +1,6 @@
use std::{io, path::Path, time::Duration}; use std::{future::Future, io, path::Path};
use ::serenity::all::{ChannelId, EditMessage, GuildId, Mentionable, UserId}; use ::serenity::all::{ChannelId, EditMember, GuildId, UserId};
use eyre::{Context as _, Error, OptionExt, Result}; use eyre::{Context as _, Error, OptionExt, Result};
use futures::{FutureExt, StreamExt, TryStreamExt}; use futures::{FutureExt, StreamExt, TryStreamExt};
use poise::serenity_prelude as serenity; use poise::serenity_prelude as serenity;
@@ -107,21 +107,19 @@ async fn channel_children(
} }
#[tracing::instrument] #[tracing::instrument]
async fn move_users<'a>( fn move_users<'a>(
ctx: Context<'a>, ctx: Context<'a>,
guild: GuildId, guild: GuildId,
users: Vec<(ChannelId, UserId)>, users: Vec<(ChannelId, UserId)>,
) -> Result<()> { ) -> impl Future<Output = Result<()>> + Send + 'a {
futures::stream::iter(users.into_iter()) futures::stream::iter(users.into_iter().map(move |(channel, user)| async move {
.map(Ok::<_, eyre::Error>) tracing::info!(?channel, ?user, "Moving user");
.try_for_each_concurrent(10, move |(channel, user)| async move { guild.move_member(ctx, user, channel).await?;
tracing::info!(?channel, ?user, "Moving user"); tracing::info!(?channel, ?user, "Moved user");
guild.move_member(ctx, user, channel).await?; Ok(())
tracing::info!(?channel, ?user, "Moved user"); }))
Ok(()) .buffer_unordered(10)
}) .try_collect::<()>()
.await?;
Ok(())
} }
#[tracing::instrument] #[tracing::instrument]
@@ -269,88 +267,62 @@ async fn st(ctx: Context<'_>) -> Result<()> {
.guild_id() .guild_id()
.ok_or_eyre("This bot only works in servers")?; .ok_or_eyre("This bot only works in servers")?;
let GuildData { st, .. } = ctx.data().get(guild).await?; 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( futures::future::try_join_all(guild.members(&ctx, None, None).await?.into_iter().map(
|member| async move { |mut member| async move {
match (sender == member.user.id, member.roles.contains(&st)) { match (&member.user == ctx.author(), member.roles.contains(&st)) {
(true, true) | (false, false) => Ok(()), (true, true) | (false, false) => Ok(()),
(true, false) => member.add_role(&ctx, st).await, (true, false) => {
(false, true) => member.remove_role(&ctx, st).await, member
.edit(
&ctx,
EditMember::new().nickname(format!("(ST) {}", member.display_name())),
)
.await?;
member.add_role(&ctx, st).await
}
(false, true) => {
member
.edit(
&ctx,
EditMember::new().nickname(
member
.display_name()
.strip_prefix("(ST) ")
.unwrap_or(member.display_name()),
),
)
.await?;
member.remove_role(&ctx, st).await
}
} }
}, },
)) ))
.await?; .await?;
ctx.reply("You are now the ST").await?; ctx.reply("You have been granted ST").await?;
Ok(()) Ok(())
} }
#[poise::command(slash_command, ephemeral)] #[poise::command(slash_command, ephemeral)]
#[tracing::instrument] #[tracing::instrument]
async fn spectate(ctx: Context<'_>, player: Option<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 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 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 GuildData { let GuildData {
currently_playing, .. currently_playing, ..
} = ctx.data().get(guild).await?; } = ctx.data().get(guild).await?;
for player in &players {
let member = guild guild
.member(&ctx, player.unwrap_or(ctx.author().id)) .member(&ctx, player)
.await?
.add_role(&ctx, currently_playing)
.await?;
}
ctx.reply(format!("Given {} members currently playing", players.len()))
.await?; .await?;
let msg = if member.roles.contains(&currently_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(())
} }
@@ -391,38 +363,16 @@ async fn register_commands(
Ok(()) Ok(())
} }
#[tracing::instrument(skip(ctx))] #[tracing::instrument(skip(ctx, framework))]
async fn handle_message(
ctx: impl AsRef<serenity::Http>,
message: &serenity::Message,
) -> Result<()> {
tracing::info!("Got message: {}", message.content);
if message.content.contains("https://clocktower.live") {
let mut message = message.clone();
tokio::time::sleep(Duration::from_secs(2)).await;
message
.edit(&ctx.as_ref(), EditMessage::new().suppress_embeds(true))
.await?;
}
Ok(())
}
async fn on_event( async fn on_event(
ctx: &serenity::Context, ctx: &serenity::Context,
event: &serenity::FullEvent, event: &serenity::FullEvent,
framework: poise::FrameworkContext<'_, Data, Error>, framework: poise::FrameworkContext<'_, Data, Error>,
_: &Data, _: &Data,
) -> Result<()> { ) -> Result<()> {
match event { if let serenity::FullEvent::GuildCreate { guild, is_new: _ } = event {
serenity::FullEvent::GuildCreate { guild, is_new: _ } => { register_commands(ctx, framework.options, guild.id).await?;
register_commands(ctx, framework.options, guild.id).await? };
}
serenity::FullEvent::Message { new_message: msg }
| serenity::FullEvent::MessageUpdate { new: Some(msg), .. } => {
handle_message(ctx, msg).await?
}
_ => {}
}
Ok(()) Ok(())
} }
@@ -446,10 +396,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() let intents =
| serenity::GatewayIntents::GUILD_MEMBERS serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::GUILD_MEMBERS;
| serenity::GatewayIntents::GUILD_MESSAGES
| serenity::GatewayIntents::MESSAGE_CONTENT;
let framework = poise::Framework::builder() let framework = poise::Framework::builder()
.options(poise::FrameworkOptions { .options(poise::FrameworkOptions {
@@ -457,7 +405,6 @@ async fn main() -> Result<()> {
dusk(), dusk(),
dawn(), dawn(),
st(), st(),
spectate(),
currently_playing(), currently_playing(),
join(), join(),
configure(), configure(),