use fluxer::prelude::*;
use std::time::Instant;
const PREFIX: &str = "!";
struct Handler;
fn parse_command(content: &str) -> Option<(&str, &str)> {
let trimmed = content.strip_prefix(PREFIX)?;
match trimmed.find(' ') {
Some(pos) => Some((&trimmed[..pos], trimmed[pos + 1..].trim())),
None => Some((trimmed, "")),
}
}
#[async_trait]
impl EventHandler for Handler {
async fn on_ready(&self, ctx: Context, ready: Ready) {
println!("Logged in as {}", ready.user.username);
println!("current_user from cache: {:?}", ctx.cache.current_user().await);
}
async fn on_guild_create(&self, ctx: Context, guild: Guild) {
println!("GUILD_CREATE: {} (cache size: {})", guild.id, ctx.cache.guild_count().await);
}
async fn on_message(&self, ctx: Context, msg: Message) {
let user_cached = ctx.cache.user(&msg.author.id).await.is_some();
let ch_cached = ctx.cache.channel(msg.channel_id.as_deref().unwrap_or("")).await.is_some();
let content_preview = msg.content.as_deref().unwrap_or("").chars().take(60).collect::<String>();
let attachments = msg.attachments.as_ref().map(|a| a.len()).unwrap_or(0);
let embeds = msg.embeds.as_ref().map(|e| e.len()).unwrap_or(0);
println!(
"[msg] author={}#{} channel={} guild={} | \"{}\" | attach={} embeds={} | cache: user={} ch={}",
msg.author.username,
msg.author.discriminator.as_deref().unwrap_or("0"),
msg.channel_id.as_deref().unwrap_or("?"),
msg.guild_id.as_deref().unwrap_or("DM"),
content_preview,
attachments,
embeds,
user_cached,
ch_cached,
);
if msg.author.bot.unwrap_or(false) {
return;
}
let content = match msg.content.as_deref() {
Some(c) => c,
None => return,
};
let channel_id = msg.channel_id.as_deref().unwrap_or_default();
let (cmd, args) = match parse_command(content) {
Some(v) => v,
None => return,
};
match cmd {
"ping" => {
let start = Instant::now();
let sent = ctx.http.send_message(channel_id, "Pong!").await;
let elapsed = start.elapsed().as_millis();
if let Ok(sent) = sent {
let _ = ctx.http.edit_message(
channel_id,
&sent.id,
&format!("Pong! {}ms", elapsed),
).await;
}
}
"say" => {
if args.is_empty() {
let _ = ctx.http.send_message(channel_id, "Say what?").await;
return;
}
let _ = ctx.http.delete_message(channel_id, &msg.id).await;
let _ = ctx.http.send_message(channel_id, args).await;
}
"embed" => {
let (title, desc) = match args.split_once('|') {
Some((t, d)) => (t.trim(), d.trim()),
None => {
let _ = ctx.http.send_message(channel_id, "`!embed title | description`").await;
return;
}
};
let embed = EmbedBuilder::new()
.title(title)
.description(desc)
.color(0x5865F2)
.build();
let _ = ctx.http.send_embed(channel_id, None, vec![embed]).await;
}
"react" => {
let _ = ctx.http.add_reaction(channel_id, &msg.id, "❤️").await;
}
"purge" => {
let count: u8 = args.parse().unwrap_or(0);
if count == 0 || count > 100 {
let _ = ctx.http.send_message(channel_id, "1-100.").await;
return;
}
let query = GetMessagesQuery {
limit: Some(count),
..Default::default()
};
if let Ok(messages) = ctx.http.get_messages(channel_id, query).await {
let ids: Vec<&str> = messages.iter().map(|m| m.id.as_str()).collect();
let _ = ctx.http.bulk_delete_messages(channel_id, ids).await;
}
}
"serverinfo" => {
let guild_id = match &msg.guild_id {
Some(id) => id.as_str(),
None => return,
};
if let Ok(guild) = ctx.http.get_guild(guild_id).await {
let name = guild.name.as_deref().unwrap_or("Unknown");
let members = ctx.http.get_guild_members(guild_id, Some(1000), None).await
.map(|m| m.len().to_string())
.unwrap_or("?".into());
let embed = EmbedBuilder::new()
.title(name)
.field("Members", &members, true)
.color(0x5865F2)
.build();
let _ = ctx.http.send_embed(channel_id, None, vec![embed]).await;
}
}
"attach" => {
if args.is_empty() {
let _ = ctx.http.send_message(channel_id, "Usage: `!attach <file path>`").await;
return;
}
let path = std::path::Path::new(args);
let filename = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("file")
.to_string();
let content_type = match path.extension().and_then(|e| e.to_str()) {
Some("mp3") => "audio/mpeg",
Some("mp4") => "video/mp4",
Some("mov") => "video/quicktime",
Some("webm") => "video/webm",
Some("png") => "image/png",
Some("jpg") | Some("jpeg") => "image/jpeg",
Some("gif") => "image/gif",
Some("webp") => "image/webp",
Some("txt") => "text/plain",
Some("pdf") => "application/pdf",
_ => "application/octet-stream",
};
match tokio::fs::read(path).await {
Ok(data) => {
let file = AttachmentFile {
filename,
data,
content_type: Some(content_type.to_string()),
};
match ctx.http.send_files(channel_id, vec![file], None).await {
Ok(msg) => println!("[attach] sent message {}", msg.id),
Err(e) => eprintln!("[attach] error: {}", e),
}
}
Err(e) => {
eprintln!("[attach] failed to read file: {}", e);
let _ = ctx.http.send_message(channel_id, &format!("Failed to read file: {}", e)).await;
}
}
}
"addrole" => {
let guild_id = match &msg.guild_id {
Some(id) => id.as_str(),
None => return,
};
let parts: Vec<&str> = args.split_whitespace().collect();
if parts.len() != 2 {
let _ = ctx.http.send_message(channel_id, "Usage: `!addrole <user_id> <role_id>`").await;
return;
}
match ctx.http.add_member_role(guild_id, parts[0], parts[1]).await {
Ok(_) => { let _ = ctx.http.send_message(channel_id, "Role added").await; }
Err(e) => { let _ = ctx.http.send_message(channel_id, &format!("Error: {}", e)).await; }
}
}
"removerole" => {
let guild_id = match &msg.guild_id {
Some(id) => id.as_str(),
None => return,
};
let parts: Vec<&str> = args.split_whitespace().collect();
if parts.len() != 2 {
let _ = ctx.http.send_message(channel_id, "Usage: `!removerole <user_id> <role_id>`").await;
return;
}
match ctx.http.remove_member_role(guild_id, parts[0], parts[1]).await {
Ok(_) => { let _ = ctx.http.send_message(channel_id, "Role removed").await; }
Err(e) => { let _ = ctx.http.send_message(channel_id, &format!("Error: {}", e)).await; }
}
}
_ => {}
}
}
}
#[tokio::main]
async fn main() {
let token = std::env::var("FLUXER_TOKEN")
.expect("Set FLUXER_TOKEN to your bot token");
let mut client = Client::builder(&token)
.event_handler(Handler)
.build();
if let Err(e) = client.start().await {
eprintln!("Error: {}", e);
}
}