feat: early steps of storage and configuration
This commit is contained in:
60
Cargo.lock
generated
60
Cargo.lock
generated
@@ -199,6 +199,18 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-compression"
|
||||||
|
version = "0.4.41"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1"
|
||||||
|
dependencies = [
|
||||||
|
"compression-codecs",
|
||||||
|
"compression-core",
|
||||||
|
"futures-io",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-executor"
|
name = "async-executor"
|
||||||
version = "1.14.0"
|
version = "1.14.0"
|
||||||
@@ -479,6 +491,17 @@ dependencies = [
|
|||||||
"piper",
|
"piper",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "brotli"
|
||||||
|
version = "8.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560"
|
||||||
|
dependencies = [
|
||||||
|
"alloc-no-stdlib",
|
||||||
|
"alloc-stdlib",
|
||||||
|
"brotli-decompressor",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brotli-decompressor"
|
name = "brotli-decompressor"
|
||||||
version = "5.0.0"
|
version = "5.0.0"
|
||||||
@@ -768,6 +791,22 @@ dependencies = [
|
|||||||
"tokio-util",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "compression-codecs"
|
||||||
|
version = "0.4.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7"
|
||||||
|
dependencies = [
|
||||||
|
"brotli",
|
||||||
|
"compression-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "compression-core"
|
||||||
|
version = "0.4.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -1652,6 +1691,8 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
|||||||
name = "fomo-reducer"
|
name = "fomo-reducer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-compression",
|
||||||
|
"async-trait",
|
||||||
"capnp",
|
"capnp",
|
||||||
"capnpc",
|
"capnpc",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -1664,6 +1705,7 @@ dependencies = [
|
|||||||
"secrecy 0.10.3",
|
"secrecy 0.10.3",
|
||||||
"snafu",
|
"snafu",
|
||||||
"songbird",
|
"songbird",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -3487,9 +3529,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
@@ -6563,30 +6605,30 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.44"
|
version = "0.3.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde",
|
"serde_core",
|
||||||
"time-core",
|
"time-core",
|
||||||
"time-macros",
|
"time-macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-core"
|
name = "time-core"
|
||||||
version = "0.1.6"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-macros"
|
name = "time-macros"
|
||||||
version = "0.2.24"
|
version = "0.2.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
|
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"time-core",
|
"time-core",
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-compression = { version = "0.4.41", features = ["brotli", "futures-io"] }
|
||||||
|
async-trait = "0.1.89"
|
||||||
capnp = "0.25.3"
|
capnp = "0.25.3"
|
||||||
clap = { version = "4.5.40", features = ["derive", "env"] }
|
clap = { version = "4.5.40", features = ["derive", "env"] }
|
||||||
dashmap = "6.1.0"
|
dashmap = "6.1.0"
|
||||||
@@ -58,6 +60,7 @@ songbird = { version = "0.6.0", default-features = false, features = [
|
|||||||
"twilight",
|
"twilight",
|
||||||
"tws",
|
"tws",
|
||||||
] }
|
] }
|
||||||
|
time = "0.3.47"
|
||||||
tokio = { version = "1.46.0", features = ["rt-multi-thread", "macros", "signal"] }
|
tokio = { version = "1.46.0", features = ["rt-multi-thread", "macros", "signal"] }
|
||||||
tokio-util = "0.7.18"
|
tokio-util = "0.7.18"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
|
|||||||
5
bot.capnp
Normal file
5
bot.capnp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@0x993a671d7aa374f3;
|
||||||
|
|
||||||
|
struct Bot {
|
||||||
|
heatScript @0 :Text;
|
||||||
|
}
|
||||||
7
build.rs
Normal file
7
build.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fn main() {
|
||||||
|
capnpc::CompilerCommand::new()
|
||||||
|
.file("bot.capnp")
|
||||||
|
.file("user.capnp")
|
||||||
|
.run()
|
||||||
|
.expect("couldn't compile capnproto schemas");
|
||||||
|
}
|
||||||
178
src/command/debug.rs
Normal file
178
src/command/debug.rs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use async_compression::futures::bufread::BrotliDecoder;
|
||||||
|
use capnp::message::ReaderOptions;
|
||||||
|
use futures::AsyncReadExt;
|
||||||
|
use opendal::ErrorKind;
|
||||||
|
use snafu::{OptionExt, Snafu};
|
||||||
|
use twilight_model::{
|
||||||
|
application::{
|
||||||
|
command::{Command, CommandType},
|
||||||
|
interaction::Interaction,
|
||||||
|
},
|
||||||
|
channel::message::{Embed, MessageFlags},
|
||||||
|
http::interaction::{InteractionResponse, InteractionResponseType},
|
||||||
|
id::{Id, marker::UserMarker},
|
||||||
|
};
|
||||||
|
use twilight_util::builder::{
|
||||||
|
InteractionResponseDataBuilder,
|
||||||
|
command::CommandBuilder,
|
||||||
|
embed::{EmbedBuilder, EmbedFieldBuilder},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{bot_capnp, command::State};
|
||||||
|
|
||||||
|
const NAME: &str = "debug";
|
||||||
|
const DESCRIPTION: &str =
|
||||||
|
"(Only the bot owner can use this) Show various information for debugging purposes";
|
||||||
|
|
||||||
|
pub static COMMAND: LazyLock<Command> = LazyLock::new(|| {
|
||||||
|
CommandBuilder::new(NAME, DESCRIPTION, CommandType::ChatInput)
|
||||||
|
.validate()
|
||||||
|
.expect("command wasn't correct")
|
||||||
|
.build()
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Debug, Snafu)]
|
||||||
|
enum NoPermission {
|
||||||
|
/// there isn't a user who invoked this command
|
||||||
|
NoUser,
|
||||||
|
|
||||||
|
/// the user isn't allowed to use this command because they're not the bot owner
|
||||||
|
NotInvokedByBotOwner,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn no_permission_to_embed(error: NoPermission) -> Embed {
|
||||||
|
match error {
|
||||||
|
NoPermission::NoUser => {
|
||||||
|
EmbedBuilder::new().title("Not invoked by a user").description("This command works by joining the same VC as the user, but this bot didn't receive any user data. So did no user invoke it?! (This error should be impossible!)").validate().unwrap().build()
|
||||||
|
},
|
||||||
|
NoPermission::NotInvokedByBotOwner => {
|
||||||
|
EmbedBuilder::new().title("No permission to see debug info").description("Only the owner of this bot is allowed to see its information for debugging purposes.").validate().unwrap().build()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_permission(
|
||||||
|
interaction: &Interaction,
|
||||||
|
bot_owner_user_id: Id<UserMarker>,
|
||||||
|
) -> Result<(), NoPermission> {
|
||||||
|
let user_id = interaction
|
||||||
|
.member
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|member| member.user.as_ref().map(|user| user.id))
|
||||||
|
.context(NoUserSnafu)?;
|
||||||
|
|
||||||
|
if user_id != bot_owner_user_id {
|
||||||
|
return Err(NoPermission::NotInvokedByBotOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn handle(state: State, interaction: Interaction) {
|
||||||
|
if let Err(no_permission) = check_permission(&interaction, state.discord_bot_owner_user_id) {
|
||||||
|
state
|
||||||
|
.discord_client
|
||||||
|
.interaction(state.discord_application_id)
|
||||||
|
.create_response(
|
||||||
|
interaction.id,
|
||||||
|
&interaction.token,
|
||||||
|
&InteractionResponse {
|
||||||
|
kind: InteractionResponseType::ChannelMessageWithSource,
|
||||||
|
data: Some(
|
||||||
|
InteractionResponseDataBuilder::new()
|
||||||
|
.embeds([no_permission_to_embed(no_permission)])
|
||||||
|
.flags(MessageFlags::EPHEMERAL)
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("TODO");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state
|
||||||
|
.discord_client
|
||||||
|
.interaction(state.discord_application_id)
|
||||||
|
.create_response(
|
||||||
|
interaction.id,
|
||||||
|
&interaction.token,
|
||||||
|
&InteractionResponse {
|
||||||
|
kind: InteractionResponseType::ChannelMessageWithSource,
|
||||||
|
data: Some(
|
||||||
|
InteractionResponseDataBuilder::new()
|
||||||
|
.flags(MessageFlags::EPHEMERAL)
|
||||||
|
.content("some debug info is coming your way!")
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("TODO");
|
||||||
|
|
||||||
|
let heat_script_description = {
|
||||||
|
let compressed_result = state
|
||||||
|
.bot_data
|
||||||
|
.reader("data.bin.brotli")
|
||||||
|
.await
|
||||||
|
.expect("TODO")
|
||||||
|
.into_futures_async_read(..)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut buf = Vec::default();
|
||||||
|
let mut message = capnp::message::TypedBuilder::<bot_capnp::bot::Owned>::new_default();
|
||||||
|
let fallback = message.init_root();
|
||||||
|
|
||||||
|
let message_reader;
|
||||||
|
let mut bot_data = fallback.into_reader();
|
||||||
|
match compressed_result {
|
||||||
|
Ok(compressed) => {
|
||||||
|
let mut decompressed = BrotliDecoder::new(compressed);
|
||||||
|
decompressed.read_to_end(&mut buf).await.expect("TODO");
|
||||||
|
|
||||||
|
message_reader =
|
||||||
|
capnp::serialize_packed::read_message(&*buf, ReaderOptions::default())
|
||||||
|
.expect("TODO");
|
||||||
|
|
||||||
|
bot_data = message_reader
|
||||||
|
.get_root::<bot_capnp::bot::Reader>()
|
||||||
|
.expect("TODO");
|
||||||
|
}
|
||||||
|
Err(error) if error.kind() == ErrorKind::NotFound => {
|
||||||
|
tracing::error!("TODO: proceeding with fallback");
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(?error, "TODO");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let heat_script_option = bot_data
|
||||||
|
.has_heat_script()
|
||||||
|
.then(|| bot_data.get_heat_script().expect("TODO"));
|
||||||
|
let heat_script_option =
|
||||||
|
heat_script_option.map(|heat_script| heat_script.to_str().expect("TODO"));
|
||||||
|
|
||||||
|
heat_script_option.map_or("none set yet".into(), |heat_script| {
|
||||||
|
format!("```\n{heat_script}\n```")
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
state
|
||||||
|
.discord_client
|
||||||
|
.interaction(state.discord_application_id)
|
||||||
|
.create_followup(&interaction.token)
|
||||||
|
.embeds(&[EmbedBuilder::new()
|
||||||
|
.field(EmbedFieldBuilder::new("Heat Script", heat_script_description).build())
|
||||||
|
.validate()
|
||||||
|
.unwrap()
|
||||||
|
.build()])
|
||||||
|
.flags(MessageFlags::EPHEMERAL)
|
||||||
|
.await
|
||||||
|
.expect("TODO");
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
use crate::{VCs, command::State};
|
use crate::{VCs, command::State};
|
||||||
|
use async_trait::async_trait;
|
||||||
use snafu::{OptionExt, Snafu};
|
use snafu::{OptionExt, Snafu};
|
||||||
use std::sync::LazyLock;
|
use songbird::{CoreEvent, Event, EventContext, EventHandler};
|
||||||
|
use time::UtcDateTime;
|
||||||
|
use std::{sync::LazyLock, time::Instant};
|
||||||
use twilight_model::{
|
use twilight_model::{
|
||||||
application::{
|
application::{
|
||||||
command::{Command, CommandType},
|
command::{Command, CommandType},
|
||||||
@@ -79,6 +82,45 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Handler {
|
||||||
|
start_instant: Instant,
|
||||||
|
start_utc: UtcDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl EventHandler for Handler {
|
||||||
|
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
|
||||||
|
tracing::error!(?ctx, "TODO");
|
||||||
|
|
||||||
|
let Some(core_event) = ctx.to_core_event() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
tracing::error!(?core_event, "TODO");
|
||||||
|
|
||||||
|
let elapsed = self.start_instant.elapsed();
|
||||||
|
let elapsed = elapsed.try_into().expect("TODO");
|
||||||
|
|
||||||
|
let now_utc = self.start_utc.checked_add(elapsed).expect("TODO");
|
||||||
|
tracing::error!(?now_utc, "TODO");
|
||||||
|
|
||||||
|
match core_event {
|
||||||
|
CoreEvent::SpeakingStateUpdate => todo!(),
|
||||||
|
CoreEvent::VoiceTick => todo!(),
|
||||||
|
CoreEvent::RtpPacket => todo!(),
|
||||||
|
CoreEvent::RtcpPacket => todo!(),
|
||||||
|
CoreEvent::ClientDisconnect => todo!(),
|
||||||
|
CoreEvent::DriverConnect => todo!(),
|
||||||
|
CoreEvent::DriverReconnect => todo!(),
|
||||||
|
CoreEvent::DriverDisconnect => todo!(),
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
#[tracing::instrument(skip(state))]
|
||||||
pub async fn handle(state: State, interaction: Interaction) {
|
pub async fn handle(state: State, interaction: Interaction) {
|
||||||
let vcs = state.vcs;
|
let vcs = state.vcs;
|
||||||
@@ -131,6 +173,15 @@ pub async fn handle(state: State, interaction: Interaction) {
|
|||||||
|
|
||||||
tracing::error!(?call, "successfully joined");
|
tracing::error!(?call, "successfully joined");
|
||||||
|
|
||||||
|
let start_instant = Instant::now();
|
||||||
|
let start_utc = UtcDateTime::now();
|
||||||
|
|
||||||
|
let handler = Handler { start_instant, start_utc };
|
||||||
|
call.lock().await.add_global_event(
|
||||||
|
CoreEvent::RtpPacket.into(),
|
||||||
|
handler,
|
||||||
|
);
|
||||||
|
|
||||||
let channel_mention = format!("<#{voice_channel_id}>");
|
let channel_mention = format!("<#{voice_channel_id}>");
|
||||||
|
|
||||||
state
|
state
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ pub async fn handle(state: State, interaction: Interaction) {
|
|||||||
data: Some(
|
data: Some(
|
||||||
InteractionResponseDataBuilder::new()
|
InteractionResponseDataBuilder::new()
|
||||||
.embeds([
|
.embeds([
|
||||||
EmbedBuilder::new().title("No permission to make this bot leave").description("Only the owner of the bot is allowed to make the bot leave RIGHT NOW. You might be looking for the opt out command.").validate().unwrap().build()
|
EmbedBuilder::new().title("No permission to make this bot leave").description("Only the owner of this bot is allowed to make the bot leave RIGHT NOW. You might be looking for the opt out command.").validate().unwrap().build()
|
||||||
])
|
])
|
||||||
.flags(MessageFlags::EPHEMERAL)
|
.flags(MessageFlags::EPHEMERAL)
|
||||||
.build(),
|
.build(),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{fmt::Debug, sync::Arc};
|
use std::{fmt::Debug, sync::Arc};
|
||||||
|
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
|
use opendal::Operator;
|
||||||
use patricia_tree::StringPatriciaMap;
|
use patricia_tree::StringPatriciaMap;
|
||||||
use songbird::Songbird;
|
use songbird::Songbird;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
@@ -14,18 +15,23 @@ use twilight_model::{
|
|||||||
|
|
||||||
use crate::VCs;
|
use crate::VCs;
|
||||||
|
|
||||||
|
mod debug;
|
||||||
mod join;
|
mod join;
|
||||||
mod leave;
|
mod leave;
|
||||||
|
mod opt_in;
|
||||||
mod opt_out;
|
mod opt_out;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
|
pub bot_data: Operator,
|
||||||
pub cancellation_token: CancellationToken,
|
pub cancellation_token: CancellationToken,
|
||||||
pub discord_application_id: Id<ApplicationMarker>,
|
pub discord_application_id: Id<ApplicationMarker>,
|
||||||
pub discord_bot_owner_user_id: Id<UserMarker>,
|
pub discord_bot_owner_user_id: Id<UserMarker>,
|
||||||
pub discord_client: Arc<twilight_http::Client>,
|
pub discord_client: Arc<twilight_http::Client>,
|
||||||
pub discord_user_id: Id<UserMarker>,
|
pub discord_user_id: Id<UserMarker>,
|
||||||
|
pub recording_data: Operator,
|
||||||
pub songbird: Arc<Songbird>,
|
pub songbird: Arc<Songbird>,
|
||||||
|
pub user_data: Operator,
|
||||||
pub vcs: Arc<VCs>,
|
pub vcs: Arc<VCs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,8 +48,10 @@ where
|
|||||||
|
|
||||||
pub fn all() -> Vec<(&'static Command, BoxedHandler)> {
|
pub fn all() -> Vec<(&'static Command, BoxedHandler)> {
|
||||||
vec![
|
vec![
|
||||||
|
(&debug::COMMAND, box_handler(debug::handle)),
|
||||||
(&join::COMMAND, box_handler(join::handle)),
|
(&join::COMMAND, box_handler(join::handle)),
|
||||||
(&leave::COMMAND, box_handler(leave::handle)),
|
(&leave::COMMAND, box_handler(leave::handle)),
|
||||||
|
(&opt_in::COMMAND, box_handler(opt_in::handle)),
|
||||||
(&opt_out::COMMAND, box_handler(opt_out::handle)),
|
(&opt_out::COMMAND, box_handler(opt_out::handle)),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -54,7 +62,7 @@ pub struct Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Router {
|
impl Router {
|
||||||
fn add_route<Fut, Handler>(&mut self, name: &str, handler: Handler)
|
pub fn add_route<Fut, Handler>(&mut self, name: &str, handler: Handler)
|
||||||
where
|
where
|
||||||
Fut: Future<Output = Return> + Send + 'static,
|
Fut: Future<Output = Return> + Send + 'static,
|
||||||
Handler: Send + Sync + Fn(State, Interaction) -> Fut + 'static,
|
Handler: Send + Sync + Fn(State, Interaction) -> Fut + 'static,
|
||||||
|
|||||||
24
src/command/opt_in.rs
Normal file
24
src/command/opt_in.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use twilight_model::application::{
|
||||||
|
command::{Command, CommandType},
|
||||||
|
interaction::Interaction,
|
||||||
|
};
|
||||||
|
use twilight_util::builder::command::CommandBuilder;
|
||||||
|
|
||||||
|
use crate::command::State;
|
||||||
|
|
||||||
|
const NAME: &str = "opt-in";
|
||||||
|
const DESCRIPTION: &str = "Opt in to being recorded";
|
||||||
|
|
||||||
|
pub static COMMAND: LazyLock<Command> = LazyLock::new(|| {
|
||||||
|
CommandBuilder::new(NAME, DESCRIPTION, CommandType::ChatInput)
|
||||||
|
.validate()
|
||||||
|
.expect("command wasn't correct")
|
||||||
|
.build()
|
||||||
|
});
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn handle(state: State, interaction: Interaction) {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ use twilight_util::builder::command::CommandBuilder;
|
|||||||
use crate::command::State;
|
use crate::command::State;
|
||||||
|
|
||||||
const NAME: &str = "opt-out";
|
const NAME: &str = "opt-out";
|
||||||
const DESCRIPTION: &str = "Opt out of being recorded (duration option TODO)";
|
const DESCRIPTION: &str = "Opt out of being recorded";
|
||||||
|
|
||||||
pub static COMMAND: LazyLock<Command> = LazyLock::new(|| {
|
pub static COMMAND: LazyLock<Command> = LazyLock::new(|| {
|
||||||
CommandBuilder::new(NAME, DESCRIPTION, CommandType::ChatInput)
|
CommandBuilder::new(NAME, DESCRIPTION, CommandType::ChatInput)
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ mod command;
|
|||||||
mod one_to_many;
|
mod one_to_many;
|
||||||
mod one_to_many_with_data;
|
mod one_to_many_with_data;
|
||||||
mod one_to_one;
|
mod one_to_one;
|
||||||
|
mod storage;
|
||||||
mod track_vcs;
|
mod track_vcs;
|
||||||
mod vc_user;
|
mod vc_user;
|
||||||
|
|
||||||
|
pub use command::{Router as CommandRouter, State, all as all_commands};
|
||||||
pub use one_to_many::OneToManyUniqueBTreeMap;
|
pub use one_to_many::OneToManyUniqueBTreeMap;
|
||||||
pub use one_to_many_with_data::OneToManyUniqueBTreeMapWithData;
|
pub use one_to_many_with_data::OneToManyUniqueBTreeMapWithData;
|
||||||
pub use one_to_one::OneToOneBTreeMap;
|
pub use one_to_one::OneToOneBTreeMap;
|
||||||
|
pub use storage::Storage;
|
||||||
pub use command::{Router as CommandRouter, State, all as all_commands};
|
|
||||||
pub use track_vcs::{VCs, initialize_vcs, update_vcs};
|
pub use track_vcs::{VCs, initialize_vcs, update_vcs};
|
||||||
pub use vc_user::{UserInVCData, VoiceStatus};
|
pub use vc_user::{UserInVCData, VoiceStatus};
|
||||||
|
|
||||||
|
capnp::generated_code!(pub mod user_capnp);
|
||||||
|
capnp::generated_code!(pub mod bot_capnp);
|
||||||
|
|||||||
49
src/main.rs
49
src/main.rs
@@ -1,55 +1,19 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use fomo_reducer::{CommandRouter, State, all_commands, initialize_vcs, update_vcs};
|
use fomo_reducer::{CommandRouter, State, Storage, all_commands, initialize_vcs, update_vcs};
|
||||||
use opendal::{IntoOperatorUri, Operator, OperatorUri};
|
|
||||||
use secrecy::{ExposeSecret, SecretString};
|
use secrecy::{ExposeSecret, SecretString};
|
||||||
use snafu::Snafu;
|
use snafu::Snafu;
|
||||||
use songbird::{Songbird, shards::TwilightMap};
|
use songbird::{Songbird, shards::TwilightMap};
|
||||||
use std::{fmt::Debug, str::FromStr, sync::Arc};
|
use std::{fmt::Debug, sync::Arc};
|
||||||
use tokio::{select, signal::ctrl_c, task::JoinSet};
|
use tokio::{select, signal::ctrl_c, task::JoinSet};
|
||||||
use tokio_util::{sync::CancellationToken, time::FutureExt as _};
|
use tokio_util::{sync::CancellationToken, time::FutureExt as _};
|
||||||
use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan};
|
use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan};
|
||||||
use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt};
|
use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, StreamExt};
|
||||||
use twilight_model::{
|
use twilight_model::{
|
||||||
application::interaction::InteractionData,
|
application::interaction::InteractionData,
|
||||||
gateway::payload::incoming::InteractionCreate,
|
gateway::payload::incoming::InteractionCreate,
|
||||||
id::{Id, marker::UserMarker},
|
id::{Id, marker::UserMarker},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Storage {
|
|
||||||
uri: OperatorUri,
|
|
||||||
operator: Operator,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Storage {
|
|
||||||
type Err = opendal::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let uri = s.into_operator_uri()?;
|
|
||||||
let operator = Operator::from_uri(&uri)?;
|
|
||||||
|
|
||||||
Ok(Self { uri, operator })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Storage {
|
|
||||||
fn into_inner(self) -> Operator {
|
|
||||||
self.operator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Storage> for Operator {
|
|
||||||
fn from(wrapper: Storage) -> Self {
|
|
||||||
wrapper.into_inner()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Storage {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
Debug::fmt(&self.uri, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
struct AppArgs {
|
struct AppArgs {
|
||||||
#[arg(long, env)]
|
#[arg(long, env)]
|
||||||
@@ -198,13 +162,20 @@ async fn main() -> Result<(), MainError> {
|
|||||||
let songbird = Arc::new(songbird);
|
let songbird = Arc::new(songbird);
|
||||||
let vcs = Arc::new(vcs);
|
let vcs = Arc::new(vcs);
|
||||||
|
|
||||||
|
let bot_data = bot_data.into_inner();
|
||||||
|
let recording_data = recording_data.into_inner();
|
||||||
|
let user_data = user_data.into_inner();
|
||||||
|
|
||||||
let state = State {
|
let state = State {
|
||||||
|
bot_data,
|
||||||
cancellation_token: cancellation_token.clone(),
|
cancellation_token: cancellation_token.clone(),
|
||||||
discord_application_id,
|
discord_application_id,
|
||||||
discord_bot_owner_user_id: bot_owner,
|
discord_bot_owner_user_id: bot_owner,
|
||||||
discord_client,
|
discord_client,
|
||||||
discord_user_id,
|
discord_user_id,
|
||||||
|
recording_data,
|
||||||
songbird,
|
songbird,
|
||||||
|
user_data,
|
||||||
vcs,
|
vcs,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
39
src/storage.rs
Normal file
39
src/storage.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use std::{fmt::Debug, str::FromStr};
|
||||||
|
|
||||||
|
use opendal::{IntoOperatorUri, Operator, OperatorUri};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Storage {
|
||||||
|
uri: OperatorUri,
|
||||||
|
operator: Operator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Storage {
|
||||||
|
type Err = opendal::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let uri = s.into_operator_uri()?;
|
||||||
|
let operator = Operator::from_uri(&uri)?;
|
||||||
|
|
||||||
|
Ok(Self { uri, operator })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Storage {
|
||||||
|
pub fn into_inner(self) -> Operator {
|
||||||
|
self.operator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Storage> for Operator {
|
||||||
|
fn from(wrapper: Storage) -> Self {
|
||||||
|
wrapper.into_inner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Storage {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Debug::fmt(&self.uri, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
7
user.capnp
Normal file
7
user.capnp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
@0xc3e8b8ea9947b0c5;
|
||||||
|
|
||||||
|
struct User {
|
||||||
|
notificationScript @0 :Text;
|
||||||
|
|
||||||
|
voiceRecordingConsent @1 :Bool;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user