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