|
|
|
|
|
|
|
|
|
mod commands;
|
|
|
|
|
mod data_loader;
|
|
|
|
|
mod utils;
|
|
|
|
|
mod racetime;
|
|
|
|
|
|
|
|
|
|
use racetime::{race::Races};
|
|
|
|
|
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
use std::sync::atomic::AtomicU64;
|
|
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
|
|
use serenity::async_trait;
|
|
|
|
|
use serenity::prelude::*;
|
|
|
|
|
use serenity::model::channel::Message;
|
|
|
|
|
use serenity::model::gateway::Ready;
|
|
|
|
|
use serenity::model::id::GuildId;
|
|
|
|
|
use serenity::http::Http;
|
|
|
|
|
|
|
|
|
|
struct Owner;
|
|
|
|
|
|
|
|
|
|
impl TypeMapKey for Owner
|
|
|
|
|
{
|
|
|
|
|
type Value = Arc<AtomicU64>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Handler
|
|
|
|
|
{
|
|
|
|
|
is_loop_running: AtomicBool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl EventHandler for Handler
|
|
|
|
|
{
|
|
|
|
|
// Set a handler for the `message` event - so that whenever a new message
|
|
|
|
|
// is received - the closure (or function) passed will be called.
|
|
|
|
|
//
|
|
|
|
|
// Event handlers are dispatched through a threadpool, and so multiple
|
|
|
|
|
// events can be dispatched simultaneously.
|
|
|
|
|
async fn message(&self, ctx: Context, msg: Message)
|
|
|
|
|
{
|
|
|
|
|
commands::parse(ctx, msg).await;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set a handler to be called on the `ready` event. This is called when a
|
|
|
|
|
// shard is booted, and a READY payload is sent by Discord. This payload
|
|
|
|
|
// contains data like the current user's guild Ids, current user data,
|
|
|
|
|
// private channels, and more.
|
|
|
|
|
//
|
|
|
|
|
// In this case, just print what the current user's username is.
|
|
|
|
|
async fn ready(&self, ctx: Context, ready: Ready)
|
|
|
|
|
{
|
|
|
|
|
println!("{} is connected!", ready.user.name);
|
|
|
|
|
utils::Logger::log_message(ctx, "Start up and connection successful!").await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CODE TAKEN FROM EXAMPLE:
|
|
|
|
|
// https://github.com/serenity-rs/serenity/blob/current/examples/e13_parallel_loops/src/main.rs
|
|
|
|
|
// We use the cache_ready event just in case some cache operation is required in whatever use
|
|
|
|
|
// case you have for this.
|
|
|
|
|
async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>)
|
|
|
|
|
{
|
|
|
|
|
println!("Cache built successfully!");
|
|
|
|
|
utils::Logger::log_message(ctx.clone(), "Cache built successfully!").await;
|
|
|
|
|
|
|
|
|
|
// it's safe to clone Context, but Arc is cheaper for this use case.
|
|
|
|
|
// Untested claim, just theoretically. :P
|
|
|
|
|
let ctx = Arc::new(ctx);
|
|
|
|
|
|
|
|
|
|
// We need to check that the loop is not already running when this event triggers,
|
|
|
|
|
// as this event triggers every time the bot enters or leaves a guild, along every time the
|
|
|
|
|
// ready shard event triggers.
|
|
|
|
|
//
|
|
|
|
|
// An AtomicBool is used because it doesn't require a mutable reference to be changed, as
|
|
|
|
|
// we don't have one due to self being an immutable reference.
|
|
|
|
|
if !self.is_loop_running.load(Ordering::Relaxed) {
|
|
|
|
|
// We have to clone the Arc, as it gets moved into the new thread.
|
|
|
|
|
let ctx1 = Arc::clone(&ctx);
|
|
|
|
|
// tokio::spawn creates a new green thread that can run in parallel with the rest of
|
|
|
|
|
// the application.
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
loop {
|
|
|
|
|
// We clone Context again here, because Arc is owned, so it moves to the
|
|
|
|
|
// new function.
|
|
|
|
|
//log_system_load(Arc::clone(&ctx1)).await;
|
|
|
|
|
|
|
|
|
|
// TODO: function we want to run called here
|
|
|
|
|
// Run racetime.gg and twitch api checks
|
|
|
|
|
// Post new stuff to discord channel
|
|
|
|
|
racetime::poll_races(Arc::clone(&ctx1)).await;
|
|
|
|
|
|
|
|
|
|
tokio::time::sleep(Duration::from_secs(60)).await;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Now that the loop is running, we set the bool to true
|
|
|
|
|
self.is_loop_running.swap(true, Ordering::Relaxed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
|
async fn main()
|
|
|
|
|
{
|
|
|
|
|
let token = match data_loader::load_token("secrets/test.txt")
|
|
|
|
|
{
|
|
|
|
|
Ok(t) => t,
|
|
|
|
|
Err(why) => panic!("Could not load app token: {}", why),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Fetch the application owner id
|
|
|
|
|
let http = Http::new(&token);
|
|
|
|
|
let owner = match http.get_current_application_info().await
|
|
|
|
|
{
|
|
|
|
|
Ok(info) => info.owner.id,
|
|
|
|
|
Err(why) => panic!("Could not access application info: {:?}", why),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//let race = Race { name: String::from("Test"), game: String::from("Test"), goal: String::from("Test"), status: Status::Open };
|
|
|
|
|
|
|
|
|
|
println!("Connecting...");
|
|
|
|
|
|
|
|
|
|
let intents = GatewayIntents::GUILD_MESSAGES
|
|
|
|
|
| GatewayIntents::DIRECT_MESSAGES
|
|
|
|
|
| GatewayIntents::GUILDS
|
|
|
|
|
| GatewayIntents::MESSAGE_CONTENT;
|
|
|
|
|
|
|
|
|
|
// Create a new instance of the Client, logging in as a bot. This will
|
|
|
|
|
// automatically prepend your bot token with "Bot ", which is a requirement
|
|
|
|
|
// by Discord for bot users.
|
|
|
|
|
let mut client = Client::builder(&token, intents).event_handler(Handler
|
|
|
|
|
{
|
|
|
|
|
is_loop_running: AtomicBool::new(false),
|
|
|
|
|
}).await.expect("Err creating client");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Set the global data
|
|
|
|
|
// https://github.com/serenity-rs/serenity/blob/current/examples/e12_global_data/src/main.rs
|
|
|
|
|
{
|
|
|
|
|
// Open the data lock in write mode, so keys can be inserted to it.
|
|
|
|
|
let mut data = client.data.write().await;
|
|
|
|
|
|
|
|
|
|
data.insert::<Owner>(Arc::new(AtomicU64::new(owner.into())));
|
|
|
|
|
data.insert::<Races>(Arc::new(RwLock::new(HashMap::new())));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Finally, start a single shard, and start listening to events.
|
|
|
|
|
//
|
|
|
|
|
// Shards will automatically attempt to reconnect, and will perform
|
|
|
|
|
// exponential backoff until it reconnects.
|
|
|
|
|
if let Err(why) = client.start().await
|
|
|
|
|
{
|
|
|
|
|
println!("Client error: {:?}", why);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|