Initial commit
This commit is contained in:
commit
45556f2e3f
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
||||
2611
Cargo.lock
generated
Normal file
2611
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "denim-bot"
|
||||
version = "3002.0.0-alpha.1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
denim-bot-macros = { path = "./denim-bot-macros" }
|
||||
automod = "1.0.16"
|
||||
diacritics = "0.2.2"
|
||||
dotenvy = "0.15.7"
|
||||
inventory = "0.3.22"
|
||||
levenshtein = "1.0.5"
|
||||
serenity = "0.12.5"
|
||||
tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] }
|
||||
11
denim-bot-macros/Cargo.toml
Normal file
11
denim-bot-macros/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "denim-bot-macros"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "2.0"
|
||||
quote = "1.0"
|
||||
24
denim-bot-macros/src/lib.rs
Normal file
24
denim-bot-macros/src/lib.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{ExprArray, ItemFn, parse_macro_input};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn bot_command(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(item as ItemFn);
|
||||
let aliases = parse_macro_input!(attr as ExprArray);
|
||||
let name = &input.sig.ident; // This is the function name
|
||||
|
||||
let expanded = quote! {
|
||||
#input
|
||||
|
||||
inventory::submit! {
|
||||
Komand {
|
||||
name: stringify!(#name),
|
||||
aliases: &#aliases,
|
||||
implementation: KomandType::Fn(&|args| Box::pin(#name(args))),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expanded.into()
|
||||
}
|
||||
4
readme.md
Normal file
4
readme.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Denim bot v rustu
|
||||

|
||||
|
||||
Certifikovaný to udělám™
|
||||
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@ -0,0 +1 @@
|
||||
max_width = 150
|
||||
129
src/main.rs
Normal file
129
src/main.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use crate::module::{Komand, KomandType, RunnableArgs, RunnableResult};
|
||||
use std::collections::BTreeMap;
|
||||
use std::env;
|
||||
|
||||
use levenshtein::levenshtein;
|
||||
use serenity::async_trait;
|
||||
use serenity::model::channel::Message;
|
||||
use serenity::prelude::*;
|
||||
|
||||
mod module;
|
||||
mod modules {
|
||||
automod::dir!(pub "src/modules");
|
||||
}
|
||||
|
||||
pub struct Handler {
|
||||
commands: BTreeMap<String, KomandType>,
|
||||
aliases: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
const PREFIX: &str = "more"; // TODO: custom
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for Handler {
|
||||
async fn message(&self, ctx: Context, msg: Message) {
|
||||
println!("Received message: {}", msg.content);
|
||||
|
||||
let mut args = msg.content.split_whitespace();
|
||||
|
||||
let Some(prefix) = args.next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let prefix_diff = levenshtein(prefix, PREFIX);
|
||||
if prefix_diff > 0 {
|
||||
// TODO: run messagecreate event to check if vypni se
|
||||
// TODO: make messageReply which checks antispam
|
||||
if prefix_diff <= 1 && msg.author.id != ctx.cache.current_user().id {
|
||||
msg.channel_id
|
||||
.say(&ctx.http, format!("{}*", PREFIX))
|
||||
.await
|
||||
.expect("Failed to send message"); // TODO: ?
|
||||
println!("poslano: {}*", PREFIX);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(command_with_diacritics) = args.next() else {
|
||||
// TODO: run messagecreate event to check if vypni se
|
||||
msg.channel_id.say(&ctx.http, "coe voe").await.expect("Failed to send message"); // TODO: ?
|
||||
return;
|
||||
};
|
||||
|
||||
let clean_command_name = diacritics::remove_diacritics(command_with_diacritics).to_lowercase(); // TODO: perhaps replace with something better
|
||||
println!("Command name: {}", clean_command_name);
|
||||
let command_name = {
|
||||
if let Some(alias_target) = self.aliases.get(&clean_command_name) {
|
||||
alias_target
|
||||
} else {
|
||||
&clean_command_name
|
||||
}
|
||||
};
|
||||
|
||||
let arguments: Vec<&str> = args.collect();
|
||||
|
||||
// TODO: run messagecreate event to check if vypni se
|
||||
|
||||
let command = self.commands.get(command_name);
|
||||
// TODO: slash if not allowed in dms
|
||||
|
||||
if let Some(command) = command {
|
||||
match command {
|
||||
KomandType::Str(output) => {
|
||||
msg.channel_id.say(&ctx.http, *output).await.expect("Failed to send message"); // TODO: ?
|
||||
}
|
||||
KomandType::Fn(func) => {
|
||||
let params = RunnableArgs {
|
||||
ctx: &ctx,
|
||||
msg: &msg,
|
||||
args: &arguments,
|
||||
};
|
||||
match func(¶ms).await {
|
||||
RunnableResult::Nil => {}
|
||||
RunnableResult::Str(output) => {
|
||||
msg.channel_id.say(&ctx.http, output).await.expect("Failed to send message"); // TODO: ?
|
||||
}
|
||||
RunnableResult::String(output) => {
|
||||
msg.channel_id.say(&ctx.http, output).await.expect("Failed to send message"); // TODO: ?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
msg.channel_id.say(&ctx.http, "neznam").await.expect("Failed to send message");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenvy::dotenv().expect("Failed to load .env file");
|
||||
|
||||
let mut commands = BTreeMap::new();
|
||||
let mut aliases = BTreeMap::new();
|
||||
for komand in inventory::iter::<Komand> {
|
||||
let komand_name = komand.name.to_string();
|
||||
commands.insert(komand_name.clone(), komand.implementation);
|
||||
for alias in komand.aliases {
|
||||
aliases.insert(alias.to_string(), komand_name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Login with a bot token from the environment
|
||||
let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
|
||||
// Set gateway intents, which decides what events the bot will be notified about
|
||||
let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::DIRECT_MESSAGES | GatewayIntents::MESSAGE_CONTENT;
|
||||
|
||||
// Create a new instance of the Client, logging in as a bot.
|
||||
let mut client = Client::builder(&token, intents)
|
||||
.event_handler(Handler { commands, aliases })
|
||||
.await
|
||||
.expect("Err creating client");
|
||||
|
||||
// Start listening for events by starting a single shard
|
||||
if let Err(why) = client.start().await {
|
||||
println!("Client error: {why:?}");
|
||||
}
|
||||
}
|
||||
55
src/module.rs
Normal file
55
src/module.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use serenity::all::{Context, Message};
|
||||
|
||||
pub struct Komand {
|
||||
pub name: &'static str,
|
||||
pub aliases: &'static [&'static str],
|
||||
pub implementation: KomandType,
|
||||
}
|
||||
|
||||
inventory::collect!(Komand);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum KomandType {
|
||||
Str(&'static str),
|
||||
Fn(&'static (dyn for<'a> Fn(&'a RunnableArgs<'a>) -> Pin<Box<dyn Future<Output = RunnableResult> + Send + 'a>> + Send + Sync)),
|
||||
}
|
||||
|
||||
pub struct RunnableArgs<'a> {
|
||||
pub ctx: &'a Context,
|
||||
pub msg: &'a Message,
|
||||
pub args: &'a [&'a str],
|
||||
}
|
||||
|
||||
pub enum RunnableResult {
|
||||
Nil,
|
||||
Str(&'static str),
|
||||
String(String),
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! simple_command {
|
||||
($name:literal, $aliases:expr, $output:literal) => {
|
||||
inventory::submit! {
|
||||
Komand {
|
||||
name: $name,
|
||||
aliases: &$aliases,
|
||||
implementation: KomandType::Str($output),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($name:literal, $output:literal) => {
|
||||
simple_command!($name, [], $output);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! simple_commands {
|
||||
( $( $name:literal => $output:literal ),* ) => {
|
||||
$(
|
||||
simple_command!($name, $output);
|
||||
)*
|
||||
};
|
||||
}
|
||||
47
src/modules/hello.rs
Normal file
47
src/modules/hello.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use serenity::all::{Context, OnlineStatus};
|
||||
|
||||
use crate::{
|
||||
module::{Komand, KomandType, RunnableArgs, RunnableResult},
|
||||
simple_command, simple_commands,
|
||||
};
|
||||
|
||||
simple_command!("hello", ["hi", "hey"], "Hello, world!");
|
||||
|
||||
simple_command!("greet", "heyy");
|
||||
|
||||
simple_commands!(
|
||||
"ping" => "Pong!",
|
||||
"pong" => "Ping!"
|
||||
);
|
||||
|
||||
fn change_status(ctx: &Context, status: OnlineStatus) -> &'static str {
|
||||
ctx.set_presence(None, status);
|
||||
return "ano pane";
|
||||
}
|
||||
|
||||
#[denim_bot_macros::bot_command(["reknicau"])]
|
||||
async fn pozdrav(args: &RunnableArgs<'_>) -> RunnableResult {
|
||||
RunnableResult::String(format!("zdravim {}", args.args.get(0).unwrap_or(&"stranger")))
|
||||
}
|
||||
|
||||
#[denim_bot_macros::bot_command(["onlajn"])]
|
||||
async fn online(args: &RunnableArgs<'_>) -> RunnableResult {
|
||||
return RunnableResult::Str(change_status(&args.ctx, OnlineStatus::Online));
|
||||
}
|
||||
|
||||
#[denim_bot_macros::bot_command(["ajdl"])]
|
||||
async fn idle(args: &RunnableArgs<'_>) -> RunnableResult {
|
||||
return RunnableResult::Str(change_status(&args.ctx, OnlineStatus::Idle));
|
||||
}
|
||||
|
||||
#[denim_bot_macros::bot_command([])]
|
||||
async fn zareaguj(args: &RunnableArgs<'_>) -> RunnableResult {
|
||||
args.msg.react(&args.ctx.http, '👋').await.expect("Failed to react to message");
|
||||
return RunnableResult::Nil;
|
||||
}
|
||||
|
||||
#[denim_bot_macros::bot_command([])]
|
||||
async fn cekej(_: &RunnableArgs<'_>) -> RunnableResult {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
return RunnableResult::Str("hotovo");
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user