I'm new to Rust and trying to learn by developing a Discord bot with serenity, tokio, async, observable pattern and all that good stuff. I've been ganging my had on how to keep having access to an even handler one it is passed on to the serenity client.start()
(line 104), so that I can use it later on, once the serenity thread is about to end (line 116) (or even later outside of that thread). I've tried to get an Arc to work, since serenity supports it, but I think I should also use a Mutex. The problem I have (apart from that I'm not exactly sure if that's the right approach), I'm missing some traits implementation in serenity to make it work.
So, here's my full code. Can you please have a look and give me some hints on how to approach this problem? I understand why it doesn't work, but with all that async
I'm quite lost on how to make it work. I'd appreciate any help I can get. Thanks in advance!
use serenity::all::ChannelId;
use serenity::async_trait;
use serenity::http::Http;
use serenity::model::{channel::Message, gateway::Ready, guild::Guild};
use serenity::prelude::*;
use std::env;
use std::sync::Arc;
use tokio::io::{self, AsyncBufReadExt, BufReader};
#[derive(Default)]
struct Listener {}
impl Listener {
fn notify(&self) {
println!("Notified");
}
}
struct GetFeed {
name: String,
listener: Option<Listener>,
}
#[allow(dead_code)]
impl GetFeed {
fn new(name: String) -> GetFeed {
Self {
name,
listener: None,
}
}
fn subscribe(&mut self, listener: Listener) {
self.listener = Some(listener);
}
fn unsubscribe(&mut self) {
self.listener = None;
}
}
#[async_trait]
impl EventHandler for GetFeed {
async fn message(&self, ctx: Context, msg: Message) {
if msg.content == "!ping" {
if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
println!("Error sending message: {why:?}");
}
}
}
async fn ready(&self, ctx: Context, ready: Ready) {
let guilds = ctx.cache.guilds().len();
println!("Guilds in the Cache: {}", guilds);
for guild in ctx.cache.guilds() {
println!("Guild name: {}", guild);
}
println!("My name is: {}!", self.name);
println!("{} is connected!", ready.user.name);
if let Some(listener) = self.listener.as_ref() {
listener.notify();
}
}
async fn guild_create(&self, _ctx: Context, guild: Guild, _is_new: Option<bool>) {
println!("Added to: {}", guild.name);
}
}
async fn say_to_channel(token: &str, channel_id: u64, msg: &str) {
let channel_id = ChannelId::new(channel_id);
let http = Http::new(token);
if let Err(why) = channel_id.say(http, msg).await {
println!("Error sending message: {why:?}");
}
}
async fn print_guilds(token: &str) {
let http = Http::new(token);
if let Ok(guilds) = http.get_guilds(None, None).await {
for guild in guilds {
println!("{}", guild.name);
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
let channel_id = env::var("DISCORD_CHANNEL_ID")
.expect("Expected a channel ID in the environment")
.parse::<u64>()
.expect("DISCORD Channel ID must be u64 type");
let bot_name = env::var("DISCORD_BOT_NAME").unwrap_or("GET FEED".to_string());
let intents = GatewayIntents::all();
let listener = Listener::default();
let mut get_feed = GetFeed::new(bot_name);
get_feed.subscribe(listener);
let mut client = Client::builder(token.clone(), intents)
.event_handler_arc(Arc::new(get_feed))
.await
.expect("Err creating client");
let shard_manager = client.shard_manager.clone();
tokio::spawn(async move {
if let Err(why) = client.start().await {
println!("client error: {:?}", why);
}
// TODO: do something to make this work
get_feed.unsubscribe();
});
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
println!("Bot is running. Type 'exit' to shut down.");
let stdin = io::stdin();
let mut reader = BufReader::new(stdin);
let mut line = String::new();
let main = tokio::spawn(async move {
loop {
line.clear();
println!("Enter a command:");
match reader.read_line(&mut line).await {
Ok(_) => {
if line.trim().starts_with("say:") {
if let Some((_, right)) = line.split_once("say:") {
say_to_channel(token.as_str(), channel_id, right).await;
} else {
println!("What?");
}
} else if line.trim() == "guilds" {
print_guilds(token.as_str()).await;
} else if line.trim() == "exit" {
println!("Shutting down bot...");
break;
} else {
println!("What?");
}
}
Err(e) => {
eprintln!("Failed to read line: {}", e);
}
}
}
shard_manager.shutdown_all().await;
});
let _ = main.await;
println!("Ok, I'm done.");
Ok(())
}
EDIT:
The error I'm getting is as follows:
--> src/main.rs:111:18
|
102 | let mut get_feed = GetFeed::new(bot_name);
| ------------ move occurs because `get_feed` has type `GetFeed`, which does not implement the `Copy` trait
...
105 | .event_handler_arc(Arc::new(get_feed))
| -------- value moved here
...
111 | tokio::spawn(async move {
| ^^^^^^^^^^ value used here after move
...
116 | get_feed.unsubscribe();
| -------- use occurs due to use in coroutine