use std::future::Future;
use std::pin::Pin;
use flush_core::{EmbedBuilder, MessageSend, Snowflake};
use flush_framework::{
with_mod_action, Bot, BotConfig, CaseStore, CaseType, Command, CommandCtx, CommandError,
CommandResult, HookEvent, Hooks, ModAction, PermissionAction,
};
struct Ping;
impl Command for Ping {
fn name(&self) -> &str {
"ping"
}
fn description(&self) -> &str {
"Latência (round-trip) — espelho do Sour"
}
fn cooldown_ms(&self) -> u64 {
3_000
}
fn execute<'a>(
&'a self,
ctx: CommandCtx<'a>,
) -> Pin<Box<dyn Future<Output = CommandResult> + Send + 'a>> {
Box::pin(async move {
let sent = ctx
.rest
.reply(
ctx.channel_id(),
ctx.message,
MessageSend::text("🏓 Pong! (editando com round-trip…)"),
false,
)
.await?;
let dt = sent.id.get().saturating_sub(ctx.message.id.get()) >> 22;
let embed = EmbedBuilder::new()
.title("🏓 Pong!")
.description(format!(
"**Latência**\n-# ╰› Round-trip: **{dt}ms**\n-# ╰› Use `{p}help` para ver meus comandos.",
p = ctx.prefix
))
.color(0xFCE300)
.footer("Flush")
.build()
.map_err(|e| CommandError::Internal(e.to_string()))?;
ctx.rest
.edit_message(
ctx.channel_id(),
sent.id,
&flush_rest::EditMessageBody {
content: None,
embeds: Some(vec![embed]),
},
)
.await?;
for r in &sent.reactions {
let _ = ctx
.rest
.reaction_users(ctx.channel_id(), sent.id, &r.emoji, 5)
.await;
}
Ok(())
})
}
}
struct Warn;
impl Command for Warn {
fn name(&self) -> &str {
"warn"
}
fn description(&self) -> &str {
"Dá um warning a um membro: warn <alvo> [motivo]"
}
fn usage(&self) -> Option<&str> {
Some("warn <alvo> [motivo]")
}
fn required_permission(&self) -> Option<PermissionAction> {
Some(PermissionAction::Moderate)
}
fn execute<'a>(
&'a self,
ctx: CommandCtx<'a>,
) -> Pin<Box<dyn Future<Output = CommandResult> + Send + 'a>> {
Box::pin(async move {
let Some(guild) = ctx.guild_id() else {
ctx.reply_no_ping("❌ Este comando só funciona em servidores.")
.await?;
return Ok(());
};
let Some(mention) = ctx.args.first() else {
ctx.reply_no_ping("❌ Uso: `warn <alvo> [motivo]`").await?;
return Ok(());
};
let reason = ctx
.args
.iter()
.skip(1)
.cloned()
.collect::<Vec<_>>()
.join(" ");
let reason = if reason.is_empty() {
"sem motivo".to_owned()
} else {
reason
};
let target = ctx.target_member(mention).await?;
let mut store = CaseStore::with_default_path();
let case = store.add(
&guild.to_string(),
CaseType::Warn,
&target
.as_ref()
.map(|t| t.user.id.to_string())
.unwrap_or_default(),
&ctx.message.author.id.to_string(),
&reason,
);
let log_channel = flush_framework::GuildStore::with_default_path()
.get(&guild.to_string())
.mod_log_channel_id
.and_then(|s| s.parse::<Snowflake>().ok());
with_mod_action(ModAction {
ctx: &ctx,
executor: ctx.member.as_ref(),
action: PermissionAction::Moderate,
target: target.as_ref(),
reason: &reason,
success_icon: "⚠️",
success_description: &format!("**{mention}** recebeu um warning."),
case_id: case.id,
extra: None,
log_channel,
run: || async move { Ok::<(), String>(()) },
})
.await?;
Ok(())
})
}
}
struct JoinLogger;
impl Hooks for JoinLogger {
fn dispatch<'a>(&'a self, ev: HookEvent<'a>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
match ev {
HookEvent::MemberJoin { guild, member } => {
println!(
"👤 {} ({}) entrou em {} ({})",
member.user.username, member.user.id, guild.name, guild.id
);
}
HookEvent::Ready { .. }
| HookEvent::GuildAdd(_)
| HookEvent::MemberLeave { .. }
| HookEvent::Message(_) => {}
}
})
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
if std::env::var("FLUXER_BOT_TOKEN").is_err() {
return Err("FLUXER_BOT_TOKEN não definido".into());
}
async_std::task::block_on(run())
}
async fn run() -> Result<(), Box<dyn std::error::Error>> {
let token = std::env::var("FLUXER_BOT_TOKEN").unwrap();
let mut cfg = BotConfig::new(token);
cfg.presence_lines = vec!["flush/ping".into(), "flush framework".into()];
let mut bot = Bot::new(cfg)?
.command(Ping)
.command(Warn)
.hooks(JoinLogger)
.enable_help();
bot.run().await.map_err(|e| e.to_string())?;
Ok(())
}