global project update, migrate to poise

This commit is contained in:
L-Nafaryus 2024-04-19 00:54:21 +05:00
parent e437445861
commit 3cf5977bb6
Signed by: L-Nafaryus
GPG Key ID: 582F8B0866B294A1
10 changed files with 1388 additions and 800 deletions

1907
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,12 @@
[package] [package]
name = "oscuro-discord-bot" name = "oscuro"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.66" oscuro-core = { version = "0.1.0", path = "crates/oscuro-core" }
shuttle-serenity = "0.20.0" tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
shuttle-runtime = "0.20.0"
serenity = { version = "0.11.5", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] } [workspace]
shuttle-secrets = "0.20.0" resolver = "2"
tokio = "1.26.0" members = ["crates/oscuro-core", "crates/oscuro-shuttle"]
tracing = "0.1.37"

View File

@ -0,0 +1,12 @@
[package]
name = "oscuro-core"
version = "0.1.0"
edition = "2021"
[dependencies]
poise = "0.6.1"
serde = { version = "1.0.198", features = ["derive"] }
serde_json = "1.0.116"
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
toml = "0.8.12"
tracing = "0.1.37"

View File

@ -0,0 +1,21 @@
use poise::serenity_prelude as serenity;
use super::errors::BoxedError;
use super::Context;
#[poise::command(prefix_command)]
pub async fn register(ctx: Context<'_>) -> Result<(), BoxedError> {
poise::builtins::register_application_commands_buttons(ctx).await?;
Ok(())
}
#[poise::command(slash_command, prefix_command)]
pub async fn age(
ctx: Context<'_>,
#[description = "Ooph user"] user: Option<serenity::User>,
) -> Result<(), BoxedError> {
let u = user.as_ref().unwrap_or_else(|| ctx.author());
let response = format!("{}'s account was created at {}", u.name, u.created_at());
ctx.say(response).await?;
Ok(())
}

View File

@ -0,0 +1,85 @@
use std::fs;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub discord_token: String,
}
impl Config {
pub fn data_dir() -> Result<std::path::PathBuf, ConfigError> {
let cwd = std::env::current_dir()?;
if cfg!(debug_assertions) {
Ok(cwd.join("temp"))
} else {
Ok(cwd)
}
}
pub fn open(path: &std::path::Path) -> Result<Config, ConfigError> {
fs::read_to_string(path)?.parse()
}
pub fn to_string(&self) -> Result<String, ConfigError> {
Ok(toml::to_string(self)?)
}
pub fn write(&self, path: &std::path::Path) -> Result<(), ConfigError> {
Ok(fs::write(path, self.to_string()?)?)
}
}
impl Default for Config {
fn default() -> Self {
Self {
discord_token: String::from("Bot ###"),
}
}
}
impl std::str::FromStr for Config {
type Err = ConfigError;
fn from_str(s: &str) -> Result<Self, ConfigError> {
toml::from_str(s).map_err(|_| ConfigError::Parse)
}
}
#[derive(Debug)]
pub enum ConfigError {
Parse,
StringParse,
Serialize,
IO,
}
impl std::error::Error for ConfigError {}
impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Parse => write!(f, "Failed to parse Config from string"),
Self::StringParse => write!(f, "Failed to parse environment variable"),
Self::Serialize => write!(f, "Failed to serialize Config to TOML"),
Self::IO => write!(f, "Faild to write file"),
}
}
}
impl From<toml::ser::Error> for ConfigError {
fn from(_: toml::ser::Error) -> Self {
ConfigError::Serialize
}
}
impl From<std::io::Error> for ConfigError {
fn from(_: std::io::Error) -> Self {
ConfigError::IO
}
}
impl From<std::num::ParseIntError> for ConfigError {
fn from(_: std::num::ParseIntError) -> Self {
ConfigError::StringParse
}
}

View File

@ -0,0 +1 @@
pub type BoxedError = Box<dyn std::error::Error + Send + Sync>;

View File

@ -0,0 +1,63 @@
pub mod commands;
pub mod config;
pub mod errors;
use errors::BoxedError;
use poise::serenity_prelude::{self as serenity, prelude::TypeMapKey, Client};
#[derive(Debug, Clone)]
pub struct AppState {
pub config: config::Config,
}
impl TypeMapKey for AppState {
type Value = AppState;
}
type Context<'a> = poise::Context<'a, AppState, BoxedError>;
pub async fn client(state: AppState) -> Result<Client, BoxedError> {
let intents = serenity::GatewayIntents::non_privileged();
let state_copy = state.clone();
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions {
commands: vec![commands::register(), commands::age()],
event_handler: |ctx, event, framework, data| {
Box::pin(event_handler(ctx, event, framework, data))
},
..Default::default()
})
.setup(|ctx, _ready, framework| {
Box::pin(async move {
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
Ok(state_copy)
})
})
.build();
let client = serenity::ClientBuilder::new(state.clone().config.discord_token, intents)
.framework(framework)
.await?;
{
let mut data = client.data.write().await;
data.insert::<AppState>(state);
}
Ok(client)
}
async fn event_handler(
_ctx: &serenity::Context,
event: &serenity::FullEvent,
_framework: poise::FrameworkContext<'_, AppState, BoxedError>,
_state: &AppState,
) -> Result<(), BoxedError> {
match event {
serenity::FullEvent::Ready { data_about_bot, .. } => {
println!("Logged in as {}", data_about_bot.user.name);
}
_ => {}
}
Ok(())
}

View File

@ -0,0 +1,9 @@
[package]
name = "oscuro-shuttle"
version = "0.1.0"
edition = "2021"
[dependencies]
oscuro-core = { version = "0.1.0", path = "../oscuro-core" }
shuttle-runtime = "0.43.0"
shuttle-serenity = "0.43.0"

View File

@ -0,0 +1,20 @@
use oscuro_core::{client, config::Config, AppState};
#[shuttle_runtime::main]
async fn main(
#[shuttle_runtime::Secrets] secrets: shuttle_runtime::SecretStore,
) -> shuttle_serenity::ShuttleSerenity {
let token = secrets
.get("discord_token")
.expect("Variable 'DISCORD_TOKEN' must be set");
let state = AppState {
config: Config {
discord_token: token,
},
};
let client = client(state).await.expect("Failed to create client");
Ok(client.into())
}

View File

@ -1,46 +1,19 @@
use anyhow::anyhow; use oscuro_core::{client, config::Config, AppState};
use serenity::async_trait; use std::env;
use serenity::model::channel::Message;
use serenity::model::gateway::Ready;
use serenity::prelude::*;
use shuttle_secrets::SecretStore;
use tracing::{error, info};
struct Bot; #[tokio::main]
async fn main() {
#[async_trait] let token = env::var("DISCORD_TOKEN").expect("Variable 'DISCORD_TOKEN' must be set");
impl EventHandler for Bot { let state = AppState {
async fn message(&self, ctx: Context, msg: Message) { config: Config {
if msg.content == "!hello" { discord_token: token,
if let Err(e) = msg.channel_id.say(&ctx.http, "world!").await { },
error!("Error sending message: {:?}", e);
}
}
}
async fn ready(&self, _: Context, ready: Ready) {
info!("{} is connected!", ready.user.name);
}
}
#[shuttle_runtime::main]
async fn serenity(
#[shuttle_secrets::Secrets] secret_store: SecretStore,
) -> shuttle_serenity::ShuttleSerenity {
// Get the discord token set in `Secrets.toml`
let token = if let Some(token) = secret_store.get("DISCORD_TOKEN") {
token
} else {
return Err(anyhow!("'DISCORD_TOKEN' was not found").into());
}; };
// Set gateway intents, which decides what events the bot will be notified about client(state)
let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT;
let client = Client::builder(&token, intents)
.event_handler(Bot)
.await .await
.expect("Err creating client"); .expect("Failed to create client")
.start()
Ok(client.into()) .await
.expect("Failed to start client");
} }