Initial event/session management work

main
Joey Hines 2024-09-24 21:20:01 -06:00
parent b2c9213bab
commit 4bddb8371c
No known key found for this signature in database
GPG Key ID: 995E531F7A569DDB
11 changed files with 342 additions and 21 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
/target
/.idea
config.toml
session_config.toml

108
Cargo.lock generated
View File

@ -342,6 +342,27 @@ dependencies = [
"windows-targets 0.52.5",
]
[[package]]
name = "chrono-tz"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6"
dependencies = [
"chrono",
"chrono-tz-build",
"phf",
]
[[package]]
name = "chrono-tz-build"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7"
dependencies = [
"parse-zoneinfo",
"phf_codegen",
]
[[package]]
name = "clap"
version = "2.34.0"
@ -814,7 +835,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap 2.2.6",
"indexmap 2.5.0",
"slab",
"tokio",
"tokio-util",
@ -1021,9 +1042,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.2.6"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"equivalent",
"hashbrown 0.14.5",
@ -1274,6 +1295,15 @@ dependencies = [
"windows-targets 0.52.5",
]
[[package]]
name = "parse-zoneinfo"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
dependencies = [
"regex",
]
[[package]]
name = "pathdiff"
version = "0.2.1"
@ -1338,7 +1368,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
dependencies = [
"fixedbitset",
"indexmap 2.2.6",
"indexmap 2.5.0",
]
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
]
[[package]]
@ -1701,9 +1769,10 @@ dependencies = [
[[package]]
name = "rpb"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"chrono",
"chrono-tz",
"config",
"env_logger",
"log",
@ -1713,6 +1782,7 @@ dependencies = [
"serde",
"structopt",
"tokio",
"toml",
"tonic",
]
@ -1905,9 +1975,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.6"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
dependencies = [
"serde",
]
@ -1979,6 +2049,12 @@ dependencies = [
"digest",
]
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "skeptic"
version = "0.13.7"
@ -2319,9 +2395,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.14"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [
"serde",
"serde_spanned",
@ -2331,20 +2407,20 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.6"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.14"
version = "0.22.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf"
dependencies = [
"indexmap 2.2.6",
"indexmap 2.5.0",
"serde",
"serde_spanned",
"toml_datetime",
@ -2935,9 +3011,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.13"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]

View File

@ -1,7 +1,7 @@
[package]
name = "rpb"
description = "Role Playing Bot!"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
[dependencies]
@ -15,6 +15,8 @@ raas_types = {version = "0.0.9", registry = "jojo-dev"}
tonic = "0.11.0"
roll-rs = "0.3.0"
chrono = "0.4.38"
chrono-tz = "0.10.0"
toml = "0.8.19"
[dependencies.tokio]
version = "1.38.0"

View File

@ -1,8 +1,13 @@
use crate::config::BotConfig;
use crate::context::Context;
use crate::model::session::{Session, SessionConfig};
use chrono::Utc;
use log::{debug, info};
use poise::serenity_prelude::{CreateAttachment, MessageBuilder};
use poise::futures_util::{Stream, StreamExt};
use poise::serenity_prelude::{
futures, AutocompleteChoice, CreateAttachment, EditScheduledEvent, MessageBuilder,
ScheduledEventId, ScheduledEventStatus,
};
use poise::CreateReply;
use raas_types::raas::bot::roll::{roll_response, Roll, RollCmd};
use raas_types::raas::resp::response::Resp;
@ -91,6 +96,128 @@ fn real_roll_help() -> String {
"Roll a real D20!".to_string()
}
#[poise::command(slash_command)]
async fn create_session(
ctx: BotContext<'_>,
#[description = "Create session from event"] event_name: String,
) -> Result<(), Error> {
let guild = ctx.guild_id().unwrap();
let events = guild.scheduled_events(&ctx.http(), false).await?;
let event = events
.iter()
.find(|e| e.name.eq_ignore_ascii_case(&event_name))
.unwrap();
let path = ctx.data().config.session_config_path.clone();
let mut session_config = ctx.data().session_config.lock().await;
session_config.create_session(event.id).await;
session_config.save_config(path).await.unwrap();
ctx.reply(format!("Created session from event '{}'", event.name))
.await
.unwrap();
Ok(())
}
#[poise::command(slash_command)]
async fn start_session(
ctx: BotContext<'_>,
#[description = "Session Name"]
#[autocomplete = "autocomplete_session"]
session: ScheduledEventId,
) -> Result<(), Error> {
let sessions = &mut ctx.data().session_config.lock().await;
let session = sessions
.sessions
.iter()
.find(|s| s.event == session)
.unwrap();
let event = ctx
.guild_id()
.unwrap()
.edit_scheduled_event(
ctx.http(),
session.event,
EditScheduledEvent::new().status(ScheduledEventStatus::Active),
)
.await?;
ctx.reply(format!("Started session '{}'", event.name))
.await?;
sessions.active_session = Some(event.id);
sessions.save_config(ctx.data().config.session_config_path.clone()).await?;
Ok(())
}
#[poise::command(slash_command)]
async fn stop_session(ctx: BotContext<'_>) -> Result<(), Error> {
let sessions = &mut ctx.data().session_config.lock().await;
if let Some(session) = sessions.active_session {
match ctx
.guild_id()
.unwrap()
.edit_scheduled_event(
&ctx.http(),
session,
EditScheduledEvent::new().status(ScheduledEventStatus::Completed),
)
.await
{
Ok(e) => {
ctx.reply(format!("Ending session '{}'", e.name)).await?;
}
Err(err) => {
ctx.reply(format!("Failed to end session: '{}'", err))
.await?;
}
}
} else {
ctx.reply("No sessions in progress!").await?;
}
sessions.active_session = None;
Ok(())
}
async fn autocomplete_session<'a>(
ctx: BotContext<'a>,
partial: &'a str,
) -> impl Stream<Item = poise::serenity_prelude::AutocompleteChoice> + 'a {
let sessions: Vec<Session> = { ctx.data().session_config.lock().await.sessions.clone() };
futures::stream::iter(sessions).filter_map(move |s| async move {
let event = ctx
.guild_id()
.unwrap()
.scheduled_event(ctx.http(), s.event, false)
.await;
if let Ok(event) = event {
if event.status == ScheduledEventStatus::Scheduled
&& event.name.matches(partial).count() > 0
{
Some(AutocompleteChoice::new(event.name, event.id.to_string()))
} else {
None
}
} else {
None
}
})
}
pub async fn start_bot(config: BotConfig) {
info!("Starting bot!");
@ -100,13 +227,26 @@ pub async fn start_bot(config: BotConfig) {
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions {
commands: vec![roll(), real_roll()],
commands: vec![
roll(),
real_roll(),
create_session(),
start_session(),
stop_session(),
],
..Default::default()
})
.setup(|ctx, _ready, framework| {
Box::pin(async move {
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
Ok(Context { config })
Ok(Context {
session_config: tokio::sync::Mutex::new(
SessionConfig::init(config.session_config_path.clone())
.await
.unwrap(),
),
config,
})
})
})
.build();

View File

@ -12,6 +12,7 @@ pub struct Args {
pub struct BotConfig {
pub bot_token: String,
pub raas_url: String,
pub session_config_path: PathBuf,
}
impl BotConfig {

View File

@ -1,6 +1,9 @@
use crate::config::BotConfig;
use crate::model::session::SessionConfig;
use tokio::sync::Mutex;
#[derive(Clone)]
#[derive(Debug)]
pub struct Context {
pub config: BotConfig,
pub session_config: Mutex<SessionConfig>,
}

View File

@ -6,6 +6,7 @@ use structopt::StructOpt;
mod bot;
mod config;
mod context;
mod model;
#[tokio::main]
async fn main() {

3
src/model/mod.rs 100644
View File

@ -0,0 +1,3 @@
pub mod playlist;
pub mod scene;
pub mod session;

View File

@ -0,0 +1,14 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Mode {
InOrder,
Shuffle,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Playlist {
pub name: String,
pub playlist: String,
pub mode: Mode,
}

25
src/model/scene.rs 100644
View File

@ -0,0 +1,25 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "adv_type")]
pub enum SceneAdvancement {
Manual,
Timed { time_s: f32 },
SequenceComplete,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum NextScene {
NextInSequence,
Previous,
JumpTo { scene_name: String },
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Scene {
pub name: String,
pub playlist: String,
pub adv: SceneAdvancement,
pub next_scene: NextScene,
}

View File

@ -0,0 +1,54 @@
use crate::model::playlist::Playlist;
use crate::model::scene::Scene;
use log::info;
use poise::serenity_prelude::ScheduledEventId;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Session {
pub event: ScheduledEventId,
pub scenes: Vec<Scene>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SessionConfig {
pub active_session: Option<ScheduledEventId>,
pub playlists: Vec<Playlist>,
pub global_scenes: Vec<Scene>,
pub sessions: Vec<Session>,
}
impl SessionConfig {
pub async fn init(path: PathBuf) -> Result<Self, std::io::Error> {
if path.exists() {
info!("Initializing with config at {:?}...", path);
Self::load_config(path).await
} else {
info!("Initializing with a blank config...");
let config = Self::default();
config.save_config(path).await?;
Ok(config)
}
}
pub async fn load_config(path: PathBuf) -> Result<Self, std::io::Error> {
let file = tokio::fs::read(path).await?;
let toml_string = String::from_utf8(file).unwrap();
Ok(toml::from_str(&toml_string).unwrap())
}
pub async fn save_config(&self, path: PathBuf) -> Result<(), std::io::Error> {
tokio::fs::write(path, toml::to_string(self).unwrap()).await
}
pub async fn create_session(&mut self, event: ScheduledEventId) {
self.sessions.push(Session {
event,
scenes: vec![],
})
}
}