1use fluxer::prelude::*;
2use std::time::Instant;
3
4const PREFIX: &str = "!";
5
6struct Handler;
7
8fn parse_command(content: &str) -> Option<(&str, &str)> {
9 let trimmed = content.strip_prefix(PREFIX)?;
10 match trimmed.find(' ') {
11 Some(pos) => Some((&trimmed[..pos], trimmed[pos + 1..].trim())),
12 None => Some((trimmed, "")),
13 }
14}
15
16#[async_trait]
17impl EventHandler for Handler {
18 async fn on_ready(&self, ctx: Context, ready: Ready) {
19 println!("Logged in as {}", ready.user.username);
20 println!("current_user from cache: {:?}", ctx.cache.current_user().await);
21 }
22
23 async fn on_guild_create(&self, ctx: Context, guild: Guild) {
24 println!("GUILD_CREATE: {} (cache size: {})", guild.id, ctx.cache.guild_count().await);
25 }
26
27 async fn on_message(&self, ctx: Context, msg: Message) {
28 let user_cached = ctx.cache.user(&msg.author.id).await.is_some();
29 let ch_cached = ctx.cache.channel(msg.channel_id.as_deref().unwrap_or("")).await.is_some();
30 let content_preview = msg.content.as_deref().unwrap_or("").chars().take(60).collect::<String>();
31 let attachments = msg.attachments.as_ref().map(|a| a.len()).unwrap_or(0);
32 let embeds = msg.embeds.as_ref().map(|e| e.len()).unwrap_or(0);
33 println!(
34 "[msg] author={}#{} channel={} guild={} | \"{}\" | attach={} embeds={} | cache: user={} ch={}",
35 msg.author.username,
36 msg.author.discriminator.as_deref().unwrap_or("0"),
37 msg.channel_id.as_deref().unwrap_or("?"),
38 msg.guild_id.as_deref().unwrap_or("DM"),
39 content_preview,
40 attachments,
41 embeds,
42 user_cached,
43 ch_cached,
44 );
45
46 if msg.author.bot.unwrap_or(false) {
47 return;
48 }
49
50 let content = match msg.content.as_deref() {
51 Some(c) => c,
52 None => return,
53 };
54
55 let channel_id = msg.channel_id.as_deref().unwrap_or_default();
56
57 let (cmd, args) = match parse_command(content) {
58 Some(v) => v,
59 None => return,
60 };
61
62 match cmd {
63 "ping" => {
64 let start = Instant::now();
65 let sent = ctx.http.send_message(channel_id, "Pong!").await;
66 let elapsed = start.elapsed().as_millis();
67
68 if let Ok(sent) = sent {
69 let _ = ctx.http.edit_message(
70 channel_id,
71 &sent.id,
72 &format!("Pong! {}ms", elapsed),
73 ).await;
74 }
75 }
76
77 "say" => {
78 if args.is_empty() {
79 let _ = ctx.http.send_message(channel_id, "Say what?").await;
80 return;
81 }
82 let _ = ctx.http.delete_message(channel_id, &msg.id).await;
83 let _ = ctx.http.send_message(channel_id, args).await;
84 }
85
86 "embed" => {
87 let (title, desc) = match args.split_once('|') {
88 Some((t, d)) => (t.trim(), d.trim()),
89 None => {
90 let _ = ctx.http.send_message(channel_id, "`!embed title | description`").await;
91 return;
92 }
93 };
94
95 let embed = EmbedBuilder::new()
96 .title(title)
97 .description(desc)
98 .color(0x5865F2)
99 .build();
100
101 let _ = ctx.http.send_embed(channel_id, None, vec![embed]).await;
102 }
103
104 "react" => {
105 let _ = ctx.http.add_reaction(channel_id, &msg.id, "❤️").await;
106 }
107
108 "purge" => {
109 let count: u8 = args.parse().unwrap_or(0);
110 if count == 0 || count > 100 {
111 let _ = ctx.http.send_message(channel_id, "1-100.").await;
112 return;
113 }
114
115 let query = GetMessagesQuery {
116 limit: Some(count),
117 ..Default::default()
118 };
119
120 if let Ok(messages) = ctx.http.get_messages(channel_id, query).await {
121 let ids: Vec<&str> = messages.iter().map(|m| m.id.as_str()).collect();
122 let _ = ctx.http.bulk_delete_messages(channel_id, ids).await;
123 }
124 }
125
126 "serverinfo" => {
127 let guild_id = match &msg.guild_id {
128 Some(id) => id.as_str(),
129 None => return,
130 };
131
132 if let Ok(guild) = ctx.http.get_guild(guild_id).await {
133 let name = guild.name.as_deref().unwrap_or("Unknown");
134
135 let members = ctx.http.get_guild_members(guild_id, Some(1000), None).await
136 .map(|m| m.len().to_string())
137 .unwrap_or("?".into());
138
139 let embed = EmbedBuilder::new()
140 .title(name)
141 .field("Members", &members, true)
142 .color(0x5865F2)
143 .build();
144
145 let _ = ctx.http.send_embed(channel_id, None, vec![embed]).await;
146 }
147 }
148
149 "attach" => {
150 if args.is_empty() {
151 let _ = ctx.http.send_message(channel_id, "Usage: `!attach <file path>`").await;
152 return;
153 }
154 let path = std::path::Path::new(args);
155 let filename = path
156 .file_name()
157 .and_then(|n| n.to_str())
158 .unwrap_or("file")
159 .to_string();
160 let content_type = match path.extension().and_then(|e| e.to_str()) {
161 Some("mp3") => "audio/mpeg",
162 Some("mp4") => "video/mp4",
163 Some("mov") => "video/quicktime",
164 Some("webm") => "video/webm",
165 Some("png") => "image/png",
166 Some("jpg") | Some("jpeg") => "image/jpeg",
167 Some("gif") => "image/gif",
168 Some("webp") => "image/webp",
169 Some("txt") => "text/plain",
170 Some("pdf") => "application/pdf",
171 _ => "application/octet-stream",
172 };
173 match tokio::fs::read(path).await {
174 Ok(data) => {
175 let file = AttachmentFile {
176 filename,
177 data,
178 content_type: Some(content_type.to_string()),
179 };
180 match ctx.http.send_files(channel_id, vec![file], None).await {
181 Ok(msg) => println!("[attach] sent message {}", msg.id),
182 Err(e) => eprintln!("[attach] error: {}", e),
183 }
184 }
185 Err(e) => {
186 eprintln!("[attach] failed to read file: {}", e);
187 let _ = ctx.http.send_message(channel_id, &format!("Failed to read file: {}", e)).await;
188 }
189 }
190 }
191
192 _ => {}
193 }
194 }
195}
196
197#[tokio::main]
198async fn main() {
199 let token = std::env::var("FLUXER_TOKEN")
200 .expect("Set FLUXER_TOKEN to your bot token");
201
202 let mut client = Client::builder(&token)
203 .event_handler(Handler)
205 .build();
206
207 if let Err(e) = client.start().await {
208 eprintln!("Error: {}", e);
209 }
210}