Skip to main content

bot/
bot.rs

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