1use fluxer::prelude::*;
2use std::time::Instant;
3
4const PREFIX: &str = "sb!";
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
16async fn is_me(ctx: &Context, msg: &Message) -> bool {
17 ctx.cache.current_user().await
18 .map(|u| u.id == msg.author.id)
19 .unwrap_or(false)
20}
21
22#[async_trait]
23impl EventHandler for Handler {
24 async fn on_ready(&self, _ctx: Context, ready: Ready) {
25 println!("[ready] logged in as {}", ready.user.username);
26 }
27
28 async fn on_message(&self, ctx: Context, msg: Message) {
29 if !is_me(&ctx, &msg).await {
30 return;
31 }
32
33 let content = match msg.content.as_deref() {
34 Some(c) => c,
35 None => return,
36 };
37
38 let channel_id = msg.channel_id.as_deref().unwrap_or_default();
39
40 let (cmd, args) = match parse_command(content) {
41 Some(v) => v,
42 None => return,
43 };
44
45 match cmd {
46 "ping" => {
47 let start = Instant::now();
48 let sent = ctx.http.send_message(channel_id, "Pong!").await;
49 let elapsed = start.elapsed().as_millis();
50 if let Ok(sent) = sent {
51 let _ = ctx.http.edit_message(
52 channel_id,
53 &sent.id,
54 &format!("Pong! `{}ms`", elapsed),
55 ).await;
56 }
57 }
58
59 "8ball" => {
60 if args.is_empty() {
61 let _ = ctx.http.edit_message(channel_id, &msg.id, "Ask a question!").await;
62 return;
63 }
64 let responses = [
65 "Yes", "No", "Maybe", "Definitely", "Absolutely not",
66 "Ask again later", "Better not tell you now", "Cannot predict now",
67 "Don't count on it", "It is certain", "Most likely", "Outlook good",
68 "Reply hazy, try again", "Signs point to yes", "Very doubtful", "Without a doubt",
69 ];
70 let idx = (std::time::SystemTime::now()
71 .duration_since(std::time::UNIX_EPOCH)
72 .unwrap()
73 .as_nanos() % responses.len() as u128) as usize;
74 let answer = responses[idx];
75 let _ = ctx.http.edit_message(channel_id, &msg.id, answer).await;
76 }
77
78 "flip" => {
79 let result = if std::time::SystemTime::now()
80 .duration_since(std::time::UNIX_EPOCH)
81 .unwrap()
82 .as_nanos().is_multiple_of(2) { "Heads" } else { "Tails" };
83 let _ = ctx.http.edit_message(channel_id, &msg.id, result).await;
84 }
85
86 "roll" => {
87 let sides: u32 = args.parse().unwrap_or(6);
88 if !(2..=100).contains(&sides) {
89 let _ = ctx.http.edit_message(channel_id, &msg.id, "Use 2-100 sides").await;
90 return;
91 }
92 let result = (std::time::SystemTime::now()
93 .duration_since(std::time::UNIX_EPOCH)
94 .unwrap()
95 .as_nanos() % sides as u128) as u32 + 1;
96 let _ = ctx.http.edit_message(channel_id, &msg.id, &format!("Rolled {}/{}", result, sides)).await;
97 }
98
99 "purge" => {
100 let count: u8 = args.parse().unwrap_or(10).min(100);
101 let query = GetMessagesQuery {
102 limit: Some(count),
103 before: None,
104 after: None,
105 around: None,
106 };
107 match ctx.http.get_messages(channel_id, query).await {
108 Ok(messages) => {
109 let my_id = ctx.cache.current_user().await.map(|u| u.id).unwrap_or_default();
110 let my_messages: Vec<_> = messages.iter()
111 .filter(|m| m.author.id == my_id)
112 .map(|m| m.id.as_str())
113 .collect();
114
115 if !my_messages.is_empty() {
116 let _ = ctx.http.bulk_delete_messages(channel_id, my_messages).await;
117 }
118 }
119 Err(e) => eprintln!("[purge] failed: {}", e),
120 }
121 }
122
123 "embed" => {
124 if args.is_empty() {
125 let _ = ctx.http.edit_message(channel_id, &msg.id, "Usage: sb!embed <text>").await;
126 return;
127 }
128 let embed = EmbedBuilder::new()
129 .description(args)
130 .color(0x5865F2)
131 .build();
132
133 let _ = ctx.http.delete_message(channel_id, &msg.id).await;
134 let _ = ctx.http.send_embed(channel_id, None, vec![embed]).await;
135 }
136
137 "edit" => {
138 if args.is_empty() { return; }
139 let _ = ctx.http.edit_message(channel_id, &msg.id, args).await;
140 }
141
142 "help" => {
143 let help_text = "**Selfbot Commands**\n\
144 `sb!ping` - Check latency\n\
145 `sb!8ball <question>` - Ask the magic 8ball\n\
146 `sb!flip` - Flip a coin\n\
147 `sb!roll [sides]` - Roll a die (default 6)\n\
148 `sb!purge [count]` - Delete your recent messages\n\
149 `sb!embed <text>` - Send an embed\n\
150 `sb!edit <text>` - Edit your message\n\
151 `sb!help` - Show this message";
152 let _ = ctx.http.edit_message(channel_id, &msg.id, help_text).await;
153 }
154
155 _ => {}
156 }
157 }
158}
159
160#[tokio::main]
161async fn main() {
162 let token = std::env::var("FLUXER_TOKEN")
163 .expect("Set FLUXER_TOKEN to your user token");
164
165 let mut client = Client::builder(&token)
166 .user_token()
167 .event_handler(Handler)
168 .build();
169
170 if let Err(e) = client.start().await {
171 eprintln!("Error: {}", e);
172 }
173}