made bot in four hours
can run roll and random
This commit is contained in:
commit
a98f2498a3
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
.env
|
||||||
|
.env.dev
|
2955
Cargo.lock
generated
Normal file
2955
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "setzer"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serenity = { version = "0.12.2", features = [
|
||||||
|
"client",
|
||||||
|
"gateway",
|
||||||
|
"rustls_backend",
|
||||||
|
"model",
|
||||||
|
"collector",
|
||||||
|
] }
|
||||||
|
tokio = { version = "1.41.0", features = ["macros", "rt-multi-thread"] }
|
||||||
|
reqwest = { version = "0.12.9", features = ["json", "rustls-tls"] }
|
||||||
|
tracing = "0.1.40"
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
rand = "0.8.5"
|
||||||
|
chrono = { version = "0.4.38" }
|
||||||
|
serde = "1.0.214"
|
||||||
|
serde_json = "1.0.132"
|
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-schemas": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1721999734,
|
||||||
|
"narHash": "sha256-G5CxYeJVm4lcEtaO87LKzOsVnWeTcHGKbKxNamNWgOw=",
|
||||||
|
"rev": "0a5c42297d870156d9c57d8f99e476b738dcd982",
|
||||||
|
"revCount": 75,
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.5/0190ef2f-61e0-794b-ba14-e82f225e55e6/source.tar.gz"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://flakehub.com/f/DeterminateSystems/flake-schemas/%2A"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1744440957,
|
||||||
|
"narHash": "sha256-FHlSkNqFmPxPJvy+6fNLaNeWnF1lZSgqVCl/eWaJRc4=",
|
||||||
|
"rev": "26d499fc9f1d567283d5d56fcf367edd815dba1d",
|
||||||
|
"revCount": 716947,
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2411.716947%2Brev-26d499fc9f1d567283d5d56fcf367edd815dba1d/01962e50-af41-7ff9-8765-ebb3d39458ba/source.tar.gz"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://flakehub.com/f/NixOS/nixpkgs/%2A"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-schemas": "flake-schemas",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1745029910,
|
||||||
|
"narHash": "sha256-9CtbfTTQWMoOkXejxc5D+K3z/39wkQQt2YfYJW50tnI=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "50fefac8cdfd1587ac6d8678f6181e7d348201d2",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
65
flake.nix
Normal file
65
flake.nix
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# This flake was initially generated by fh, the CLI for FlakeHub (version 0.1.18)
|
||||||
|
{
|
||||||
|
|
||||||
|
description = "A dice roller bot";
|
||||||
|
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
flake-schemas.url = "https://flakehub.com/f/DeterminateSystems/flake-schemas/*";
|
||||||
|
|
||||||
|
nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/*";
|
||||||
|
|
||||||
|
rust-overlay = {
|
||||||
|
url = "github:oxalica/rust-overlay";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
outputs = { self, flake-schemas, nixpkgs, rust-overlay }:
|
||||||
|
let
|
||||||
|
|
||||||
|
overlays = [
|
||||||
|
rust-overlay.overlays.default
|
||||||
|
(final: prev: {
|
||||||
|
rustToolchain = final.rust-bin.stable.latest.default.override { extensions = [ "rust-src"]; };
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
supportedSystems = [ "x86_64-linux" "aarch64-darwin" "aarch64-linux" ];
|
||||||
|
forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
|
||||||
|
pkgs = import nixpkgs { inherit overlays system; };
|
||||||
|
});
|
||||||
|
in {
|
||||||
|
|
||||||
|
schemas = flake-schemas.schemas;
|
||||||
|
|
||||||
|
|
||||||
|
devShells = forEachSupportedSystem ({ pkgs }: {
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
|
||||||
|
packages = with pkgs; [
|
||||||
|
rustToolchain
|
||||||
|
cargo-bloat
|
||||||
|
cargo-edit
|
||||||
|
cargo-outdated
|
||||||
|
cargo-udeps
|
||||||
|
cargo-watch
|
||||||
|
rust-analyzer
|
||||||
|
curl
|
||||||
|
git
|
||||||
|
jq
|
||||||
|
wget
|
||||||
|
nixpkgs-fmt
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
env = {
|
||||||
|
RUST_BACKTRACE = "1";
|
||||||
|
RUST_SRC_PATH = "${pkgs.rustToolchain}/lib/rustlib/src/rust/library";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
7
rust-analyzer.json
Normal file
7
rust-analyzer.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"rust-analyzer": {
|
||||||
|
"files": {
|
||||||
|
"excludeDirs": [".direnv"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/commands/about.rs
Normal file
15
src/commands/about.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use serenity::builder::CreateCommand;
|
||||||
|
use serenity::model::application::ResolvedOption;
|
||||||
|
|
||||||
|
pub async fn run(options: &[ResolvedOption<'_>]) -> String {
|
||||||
|
r#"
|
||||||
|
A Discord bot that rolls limitless dice, randomly!
|
||||||
|
Designed, developed, and maintained by Arnkell Warner of Maduin
|
||||||
|
Copyright 2025, all rights reserved
|
||||||
|
"#
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register() -> CreateCommand {
|
||||||
|
CreateCommand::new("about").description("Get information about this bot")
|
||||||
|
}
|
3
src/commands/mod.rs
Normal file
3
src/commands/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod about;
|
||||||
|
pub mod random;
|
||||||
|
pub mod roll;
|
42
src/commands/random.rs
Normal file
42
src/commands/random.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use crate::util::{junk, random::RandomGen, validate};
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use serenity::builder::{CreateCommand, CreateCommandOption};
|
||||||
|
use serenity::model::application::{CommandOptionType, ResolvedOption, ResolvedValue};
|
||||||
|
|
||||||
|
pub async fn run(options: &[ResolvedOption<'_>]) -> String {
|
||||||
|
// check if options array is empty first
|
||||||
|
if options.is_empty() {
|
||||||
|
let mut rng = RandomGen::new();
|
||||||
|
return rng.rng.gen_range(1..999).to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// options exist, process first option
|
||||||
|
if let Some(ResolvedOption {
|
||||||
|
value: ResolvedValue::String(input),
|
||||||
|
..
|
||||||
|
}) = options.first()
|
||||||
|
{
|
||||||
|
println!("input: {}", input);
|
||||||
|
let mut rng = RandomGen::new();
|
||||||
|
return match validate::parse_str_into_num::<i32>(input.trim()) {
|
||||||
|
Some(n) => rng.rng.gen_range(1..n).to_string(),
|
||||||
|
None => return junk::get_random_insult(),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
junk::get_random_insult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register() -> CreateCommand {
|
||||||
|
CreateCommand::new("random")
|
||||||
|
.description("Start a random dice roll, commonly used for deathrolls!")
|
||||||
|
.add_option(
|
||||||
|
CreateCommandOption::new(
|
||||||
|
CommandOptionType::String,
|
||||||
|
"limit",
|
||||||
|
"Set an upper limit to display a random number between 1 and the specified number.",
|
||||||
|
)
|
||||||
|
.required(false),
|
||||||
|
)
|
||||||
|
}
|
46
src/commands/roll.rs
Normal file
46
src/commands/roll.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use crate::{
|
||||||
|
types::dietype,
|
||||||
|
util::{junk, random, validate},
|
||||||
|
};
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use serenity::builder::{CreateCommand, CreateCommandOption};
|
||||||
|
use serenity::model::application::{CommandOptionType, ResolvedOption, ResolvedValue};
|
||||||
|
|
||||||
|
pub async fn run(options: &[ResolvedOption<'_>]) -> String {
|
||||||
|
if let Some(ResolvedOption {
|
||||||
|
value: ResolvedValue::String(input),
|
||||||
|
..
|
||||||
|
}) = options.first()
|
||||||
|
{
|
||||||
|
println!("got to the roll command! input: {}", input);
|
||||||
|
let split = input.split("d").nth(1).unwrap();
|
||||||
|
let die_num = match validate::parse_str_into_num::<i32>(split.trim()) {
|
||||||
|
Some(d) => d,
|
||||||
|
None => return junk::get_random_insult(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let _die_type = match dietype::DieType::from_sides(die_num) {
|
||||||
|
Some(d) => d.to_sides(),
|
||||||
|
None => return junk::get_random_insult(),
|
||||||
|
};
|
||||||
|
let mut rng = random::RandomGen::new();
|
||||||
|
let result = rng.rng.gen_range(1..die_num).to_string();
|
||||||
|
return format!("{result}");
|
||||||
|
} else {
|
||||||
|
junk::get_random_insult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register() -> CreateCommand {
|
||||||
|
CreateCommand::new("roll")
|
||||||
|
.description("Roll dice")
|
||||||
|
.add_option(
|
||||||
|
CreateCommandOption::new(
|
||||||
|
CommandOptionType::String,
|
||||||
|
"input",
|
||||||
|
"The number sides for the dice roll, typically starts with \'d\' then a number",
|
||||||
|
)
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
}
|
11
src/config.rs
Normal file
11
src/config.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub struct Configuration {
|
||||||
|
env: Result<PathBuf, dotenvy::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config() -> Configuration {
|
||||||
|
Configuration {
|
||||||
|
env: dotenvy::dotenv(),
|
||||||
|
}
|
||||||
|
}
|
86
src/main.rs
Normal file
86
src/main.rs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
mod commands;
|
||||||
|
mod config;
|
||||||
|
mod types;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use serenity::async_trait;
|
||||||
|
use serenity::builder::{CreateInteractionResponse, CreateInteractionResponseMessage};
|
||||||
|
use serenity::model::application::Interaction;
|
||||||
|
use serenity::model::gateway::Ready;
|
||||||
|
use serenity::model::id::GuildId;
|
||||||
|
use serenity::prelude::*;
|
||||||
|
|
||||||
|
struct Handler;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl EventHandler for Handler {
|
||||||
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||||
|
if let Interaction::Command(command) = interaction {
|
||||||
|
let content = match command.data.name.as_str() {
|
||||||
|
"roll" => Some(commands::roll::run(&command.data.options()).await),
|
||||||
|
"random" => Some(commands::random::run(&command.data.options()).await),
|
||||||
|
"about" => Some(commands::about::run(&command.data.options()).await),
|
||||||
|
_ => Some("not implemented :(".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(content) = content {
|
||||||
|
let data = CreateInteractionResponseMessage::new().content(content);
|
||||||
|
let builder = CreateInteractionResponse::Message(data);
|
||||||
|
if let Err(why) = command.create_response(&ctx.http, builder).await {
|
||||||
|
println!("Cannot respond to slash command: {why}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ready(&self, ctx: Context, ready: Ready) {
|
||||||
|
println!("{} is connected!", ready.user.name);
|
||||||
|
|
||||||
|
let guild_id = GuildId::new(
|
||||||
|
env::var("GUILD_ID")
|
||||||
|
.expect("Expected GUILD_ID in environment")
|
||||||
|
.parse()
|
||||||
|
.expect("GUILD_ID must be an integer"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let commands = guild_id
|
||||||
|
.set_commands(
|
||||||
|
&ctx.http,
|
||||||
|
vec![
|
||||||
|
commands::roll::register(),
|
||||||
|
commands::random::register(),
|
||||||
|
commands::about::register(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match commands {
|
||||||
|
Ok(c) => println!("Registered {} commands!", c.len()),
|
||||||
|
Err(e) => println!("Error registering commands! Reason: {e}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{} is ready to rock and roll!", ready.user.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// setting up configuration
|
||||||
|
let _ = config::config();
|
||||||
|
|
||||||
|
// setting up the discord bot
|
||||||
|
let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
|
||||||
|
|
||||||
|
// setting up the discord client
|
||||||
|
let intents = GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MESSAGES;
|
||||||
|
let mut client = Client::builder(token, intents)
|
||||||
|
.event_handler(Handler)
|
||||||
|
.await
|
||||||
|
.expect("Error creating client");
|
||||||
|
|
||||||
|
if let Err(why) = client.start().await {
|
||||||
|
println!("Client error: {why:?}");
|
||||||
|
}
|
||||||
|
}
|
1
src/types/die.rs
Normal file
1
src/types/die.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
trait Die {}
|
34
src/types/dietype.rs
Normal file
34
src/types/dietype.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum DieType {
|
||||||
|
DieFour,
|
||||||
|
DieSix,
|
||||||
|
DieEight,
|
||||||
|
DieTen,
|
||||||
|
DieTwelve,
|
||||||
|
DieTwenty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DieType {
|
||||||
|
pub fn from_sides(sides: i32) -> Option<DieType> {
|
||||||
|
match sides {
|
||||||
|
4 => Some(DieType::DieFour),
|
||||||
|
6 => Some(DieType::DieSix),
|
||||||
|
8 => Some(DieType::DieEight),
|
||||||
|
10 => Some(DieType::DieTen),
|
||||||
|
12 => Some(DieType::DieTwelve),
|
||||||
|
20 => Some(DieType::DieTwenty),
|
||||||
|
_ => None, // Return None for values that don't map to a die
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_sides(&self) -> i32 {
|
||||||
|
match self {
|
||||||
|
DieType::DieFour => 4,
|
||||||
|
DieType::DieSix => 6,
|
||||||
|
DieType::DieEight => 8,
|
||||||
|
DieType::DieTen => 10,
|
||||||
|
DieType::DieTwelve => 12,
|
||||||
|
DieType::DieTwenty => 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
src/types/mod.rs
Normal file
2
src/types/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod die;
|
||||||
|
pub mod dietype;
|
17
src/util/junk.rs
Normal file
17
src/util/junk.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use rand::seq::SliceRandom;
|
||||||
|
|
||||||
|
pub fn get_random_insult() -> String {
|
||||||
|
let insults = get_insults();
|
||||||
|
let result = insults.choose(&mut rand::thread_rng()).unwrap();
|
||||||
|
result.to_owned().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_insults<'a>() -> Vec<&'a str> {
|
||||||
|
Vec::from([
|
||||||
|
"You're the type to use Limit Break on trash mobs.",
|
||||||
|
"Your DPS is lower than a healer who only casts Medica II.",
|
||||||
|
"Your glamour looks like you raided a NPC vendor in Limsa Lominsa.",
|
||||||
|
"I've seen Lalafells with better threat generation than you.",
|
||||||
|
"Your gear is more broken than the Dragonsong War timeline.",
|
||||||
|
])
|
||||||
|
}
|
3
src/util/mod.rs
Normal file
3
src/util/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod junk;
|
||||||
|
pub mod random;
|
||||||
|
pub mod validate;
|
13
src/util/random.rs
Normal file
13
src/util/random.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use rand::rngs::ThreadRng;
|
||||||
|
|
||||||
|
pub struct RandomGen {
|
||||||
|
pub rng: ThreadRng,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RandomGen {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
RandomGen {
|
||||||
|
rng: rand::thread_rng(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/util/validate.rs
Normal file
8
src/util/validate.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
pub fn parse_str_into_num<T: FromStr>(input: &str) -> Option<T> {
|
||||||
|
match input.parse::<T>() {
|
||||||
|
Ok(n) => Some(n),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user