This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
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!
|
||||
Copyright 2025, all rights reserved
|
||||
"#
|
||||
.to_string()
|
||||
pub fn run_core() -> String {
|
||||
"A bot that rolls limitless dice, randomly!\nCopyright 2025, all rights reserved".to_string()
|
||||
}
|
||||
|
||||
pub async fn run(_options: &[ResolvedOption<'_>]) -> String {
|
||||
run_core()
|
||||
}
|
||||
|
||||
pub fn register() -> CreateCommand {
|
||||
|
||||
+14
-11
@@ -3,24 +3,27 @@ use crate::util::{junk, random::RandomGen, validate};
|
||||
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.range_random_from_one(999).to_string();
|
||||
pub fn run_core(input: Option<&str>) -> String {
|
||||
let mut rng = RandomGen::new();
|
||||
match input {
|
||||
None => rng.range_random_from_one(999).to_string(),
|
||||
Some(s) => match validate::parse_str_into_num::<i32>(s.trim()) {
|
||||
Some(n) => rng.range_random_from_one(n).to_string(),
|
||||
None => junk::get_random_insult(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// options exist, process first option
|
||||
pub async fn run(options: &[ResolvedOption<'_>]) -> String {
|
||||
if options.is_empty() {
|
||||
return run_core(None);
|
||||
}
|
||||
if let Some(ResolvedOption {
|
||||
value: ResolvedValue::String(input),
|
||||
..
|
||||
}) = options.first()
|
||||
{
|
||||
let mut rng = RandomGen::new();
|
||||
return match validate::parse_str_into_num::<i32>(input.trim()) {
|
||||
Some(n) => rng.range_random_from_one(n).to_string(),
|
||||
None => return junk::get_random_insult(),
|
||||
};
|
||||
run_core(Some(input))
|
||||
} else {
|
||||
junk::get_random_insult()
|
||||
}
|
||||
|
||||
+21
-13
@@ -6,25 +6,33 @@ use crate::{
|
||||
use serenity::builder::{CreateCommand, CreateCommandOption};
|
||||
use serenity::model::application::{CommandOptionType, ResolvedOption, ResolvedValue};
|
||||
|
||||
pub fn run_core(input: Option<&str>) -> String {
|
||||
let Some(input) = input else {
|
||||
return junk::get_random_insult();
|
||||
};
|
||||
let split = match input.split('d').nth(1) {
|
||||
Some(s) => s,
|
||||
None => return junk::get_random_insult(),
|
||||
};
|
||||
let die_num = match validate::parse_str_into_num::<i32>(split.trim()) {
|
||||
Some(d) => d,
|
||||
None => return junk::get_random_insult(),
|
||||
};
|
||||
match dietype::DieType::from_sides(die_num) {
|
||||
Some(_) => {}
|
||||
None => return junk::get_random_insult(),
|
||||
};
|
||||
let mut rng = random::RandomGen::new();
|
||||
rng.range_random_from_one(die_num).to_string()
|
||||
}
|
||||
|
||||
pub async fn run(options: &[ResolvedOption<'_>]) -> String {
|
||||
if let Some(ResolvedOption {
|
||||
value: ResolvedValue::String(input),
|
||||
..
|
||||
}) = options.first()
|
||||
{
|
||||
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.range_random_from_one(die_num).to_string();
|
||||
return format!("{result}");
|
||||
run_core(Some(input))
|
||||
} else {
|
||||
junk::get_random_insult()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
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::*;
|
||||
|
||||
use crate::commands;
|
||||
|
||||
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 cmds = guild_id
|
||||
.set_commands(
|
||||
&ctx.http,
|
||||
vec![
|
||||
commands::roll::register(),
|
||||
commands::random::register(),
|
||||
commands::about::register(),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
|
||||
match cmds {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run() {
|
||||
let token = env::var("DISCORD_TOKEN").expect("Expected DISCORD_TOKEN in environment");
|
||||
let intents = GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MESSAGES;
|
||||
let mut client = Client::builder(token, intents)
|
||||
.event_handler(Handler)
|
||||
.await
|
||||
.expect("Error creating Discord client");
|
||||
|
||||
if let Err(why) = client.start().await {
|
||||
println!("Discord client error: {why:?}");
|
||||
}
|
||||
}
|
||||
+24
-72
@@ -1,86 +1,38 @@
|
||||
mod commands;
|
||||
mod config;
|
||||
mod discord;
|
||||
mod matrix;
|
||||
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");
|
||||
let has_discord = env::var("DISCORD_TOKEN").is_ok();
|
||||
let has_matrix = env::var("MATRIX_HOMESERVER").is_ok()
|
||||
&& env::var("MATRIX_USERNAME").is_ok()
|
||||
&& env::var("MATRIX_PASSWORD").is_ok();
|
||||
|
||||
// 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:?}");
|
||||
match (has_discord, has_matrix) {
|
||||
(true, true) => {
|
||||
println!("Both Discord and Matrix credentials found; defaulting to Discord.");
|
||||
discord::run().await;
|
||||
}
|
||||
(true, false) => {
|
||||
discord::run().await;
|
||||
}
|
||||
(false, true) => {
|
||||
matrix::run().await;
|
||||
}
|
||||
(false, false) => {
|
||||
eprintln!(
|
||||
"No credentials found. Set DISCORD_TOKEN or \
|
||||
MATRIX_HOMESERVER + MATRIX_USERNAME + MATRIX_PASSWORD."
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
use std::env;
|
||||
|
||||
use matrix_sdk::{
|
||||
Client, Room, RoomState,
|
||||
config::SyncSettings,
|
||||
ruma::events::room::{
|
||||
member::{MembershipState, StrippedRoomMemberEvent},
|
||||
message::{
|
||||
AddMentions, ForwardThread, MessageType, OriginalSyncRoomMessageEvent,
|
||||
RoomMessageEventContent,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::commands;
|
||||
|
||||
async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) {
|
||||
if room.state() != RoomState::Joined {
|
||||
return;
|
||||
}
|
||||
|
||||
let MessageType::Text(text_content) = event.content.msgtype.to_owned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let body = text_content.body.trim();
|
||||
if !body.starts_with('!') {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut parts = body.splitn(3, ' ');
|
||||
let command = parts.next().unwrap_or("");
|
||||
let arg = parts.next();
|
||||
|
||||
let response = match command {
|
||||
"!roll" => commands::roll::run_core(arg),
|
||||
"!random" => commands::random::run_core(arg),
|
||||
"!about" => commands::about::run_core(),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let reply = RoomMessageEventContent::text_plain(response).make_reply_to(
|
||||
&event.into_full_event(room.room_id().to_owned()),
|
||||
ForwardThread::No,
|
||||
AddMentions::Yes,
|
||||
);
|
||||
|
||||
match room.send(reply).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!("Failed to send message: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_stripped_member(event: StrippedRoomMemberEvent, client: Client, room: Room) {
|
||||
let user_id = match client.user_id() {
|
||||
Some(u) => u,
|
||||
None => {
|
||||
println!("Irregular Matrix ID");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if event.state_key != user_id {
|
||||
return;
|
||||
}
|
||||
|
||||
if event.content.membership != MembershipState::Invite {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(e) = room.join().await {
|
||||
println!("Failed to join room: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run() {
|
||||
let homeserver =
|
||||
env::var("MATRIX_HOMESERVER").expect("Expected MATRIX_HOMESERVER in environment");
|
||||
let username = env::var("MATRIX_USERNAME").expect("Expected MATRIX_USERNAME in environment");
|
||||
let password = env::var("MATRIX_PASSWORD").expect("Expected MATRIX_PASSWORD in environment");
|
||||
|
||||
let client = Client::builder()
|
||||
.homeserver_url(&homeserver)
|
||||
.build()
|
||||
.await
|
||||
.expect("Failed to build Matrix client");
|
||||
|
||||
client
|
||||
.matrix_auth()
|
||||
.login_username(&username, &password)
|
||||
.initial_device_display_name("caitsith")
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to log in to Matrix");
|
||||
|
||||
println!("Logged in to Matrix as {username}");
|
||||
|
||||
// Advance past existing messages before registering handlers so the bot
|
||||
// doesn't reply to messages that ever existed in a given room.
|
||||
let sync_response = client
|
||||
.sync_once(SyncSettings::default())
|
||||
.await
|
||||
.expect("Matrix initial sync error");
|
||||
|
||||
client.add_event_handler(on_room_message);
|
||||
client.add_event_handler(on_stripped_member);
|
||||
|
||||
client
|
||||
.sync(SyncSettings::default().token(sync_response.next_batch))
|
||||
.await
|
||||
.expect("Matrix sync error");
|
||||
}
|
||||
Reference in New Issue
Block a user