meta: initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
.history/
|
||||||
6816
Cargo.lock
generated
Normal file
6816
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
67
Cargo.toml
Normal file
67
Cargo.toml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
[package]
|
||||||
|
name = "fomo-reducer"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.5.40", features = ["derive", "env"] }
|
||||||
|
opendal = { git = "https://github.com/apache/opendal", features = [
|
||||||
|
"services-azfile",
|
||||||
|
"services-aliyun-drive",
|
||||||
|
"services-alluxio",
|
||||||
|
"services-azblob",
|
||||||
|
"services-azdls",
|
||||||
|
"services-azfile",
|
||||||
|
"services-b2",
|
||||||
|
"services-cacache",
|
||||||
|
"services-d1",
|
||||||
|
"services-dashmap",
|
||||||
|
"services-dbfs",
|
||||||
|
"services-dropbox",
|
||||||
|
"services-etcd",
|
||||||
|
"services-fs",
|
||||||
|
"services-ftp",
|
||||||
|
"services-gcs",
|
||||||
|
"services-gdrive",
|
||||||
|
"services-ghac",
|
||||||
|
"services-github",
|
||||||
|
"services-gridfs",
|
||||||
|
"services-hdfs-native",
|
||||||
|
"services-ipmfs",
|
||||||
|
"services-memory",
|
||||||
|
"services-mini-moka",
|
||||||
|
"services-moka",
|
||||||
|
"services-mongodb",
|
||||||
|
"services-mysql",
|
||||||
|
"services-pcloud",
|
||||||
|
"services-persy",
|
||||||
|
"services-postgresql",
|
||||||
|
"services-redb",
|
||||||
|
"services-redis",
|
||||||
|
"services-s3",
|
||||||
|
"services-sled",
|
||||||
|
"services-webdav",
|
||||||
|
] }
|
||||||
|
rhai = "1.23.6"
|
||||||
|
rustls = "0.23"
|
||||||
|
secrecy = { version = "0.10.3", features = ["serde"] }
|
||||||
|
snafu = { version = "0.8.9", features = ["futures"] }
|
||||||
|
songbird = { version = "0.5.0", default-features = false, features = [
|
||||||
|
"receive",
|
||||||
|
"rustls",
|
||||||
|
"twilight",
|
||||||
|
] }
|
||||||
|
tokio = { version = "1.46.0", features = ["rt-multi-thread", "macros"] }
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
twilight-gateway = { version = "0.17", default-features = false, features = [
|
||||||
|
"rustls-webpki-roots",
|
||||||
|
] }
|
||||||
|
twilight-http = { version = "0.17", default-features = false, features = [
|
||||||
|
"rustls-webpki-roots",
|
||||||
|
"hickory",
|
||||||
|
"decompression",
|
||||||
|
] }
|
||||||
|
twilight-model = "0.17"
|
||||||
|
twilight-util = { version = "0.17", features = ["builder"] }
|
||||||
|
typed-builder = "0.23.2"
|
||||||
24
UNLICENSE
Normal file
24
UNLICENSE
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <https://unlicense.org/>
|
||||||
10
src/lib.rs
Normal file
10
src/lib.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
mod one_to_many;
|
||||||
|
mod one_to_many_with_data;
|
||||||
|
mod one_to_one;
|
||||||
|
mod vc_user;
|
||||||
|
|
||||||
|
pub use one_to_many::OneToManyUniqueBTreeMap;
|
||||||
|
pub use one_to_many_with_data::OneToManyUniqueBTreeMapWithData;
|
||||||
|
pub use one_to_one::OneToOneBTreeMap;
|
||||||
|
|
||||||
|
pub use vc_user::{UserInVCData, VoiceStatus};
|
||||||
212
src/main.rs
Normal file
212
src/main.rs
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use opendal::{IntoOperatorUri, Operator, OperatorUri};
|
||||||
|
use secrecy::{ExposeSecret, SecretString};
|
||||||
|
use tracing_subscriber::fmt::format::FmtSpan;
|
||||||
|
use twilight_gateway::{
|
||||||
|
Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt, error::ReceiveMessageErrorType,
|
||||||
|
};
|
||||||
|
use twilight_model::{
|
||||||
|
channel::ChannelType,
|
||||||
|
id::{
|
||||||
|
Id,
|
||||||
|
marker::{ChannelMarker, GuildMarker, UserMarker},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use typed_builder::TypedBuilder;
|
||||||
|
use vc_notifier::{OneToManyUniqueBTreeMapWithData, UserInVCData, VoiceStatus};
|
||||||
|
|
||||||
|
type VCsInServer = OneToManyUniqueBTreeMapWithData<Id<ChannelMarker>, Id<UserMarker>, UserInVCData>;
|
||||||
|
|
||||||
|
type VCs = BTreeMap<Id<GuildMarker>, VCsInServer>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct OpendalOperator {
|
||||||
|
uri: OperatorUri,
|
||||||
|
operator: Operator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for OpendalOperator {
|
||||||
|
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 OpendalOperator {
|
||||||
|
fn into_inner(self) -> Operator {
|
||||||
|
self.operator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<OpendalOperator> for Operator {
|
||||||
|
fn from(wrapper: OpendalOperator) -> Self {
|
||||||
|
wrapper.into_inner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for OpendalOperator {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Debug::fmt(&self.uri, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
struct Args {
|
||||||
|
#[arg(long, env)]
|
||||||
|
discord_token: SecretString,
|
||||||
|
|
||||||
|
#[arg(long, env)]
|
||||||
|
storage: OpendalOperator,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.pretty()
|
||||||
|
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let args = Parser::parse();
|
||||||
|
|
||||||
|
tracing::debug!(?args, "using");
|
||||||
|
|
||||||
|
let Args {
|
||||||
|
discord_token,
|
||||||
|
storage,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
rustls::crypto::aws_lc_rs::default_provider()
|
||||||
|
.install_default()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let shard_id = ShardId::new(0, 1);
|
||||||
|
let intents = Intents::GUILD_VOICE_STATES;
|
||||||
|
let mut shard = Shard::new(shard_id, discord_token.expose_secret().to_owned(), intents);
|
||||||
|
|
||||||
|
let vc_event_types = EventTypeFlags::GUILD_VOICE_STATES;
|
||||||
|
let mut next_event = shard.next_event(vc_event_types);
|
||||||
|
|
||||||
|
let discord_client = twilight_http::Client::new(discord_token.expose_secret().to_owned());
|
||||||
|
let mut voice_status = initialize_vcs(&discord_client).await;
|
||||||
|
while let Some(event_res) = next_event.await {
|
||||||
|
match event_res {
|
||||||
|
Ok(event) => {
|
||||||
|
tracing::debug!(?voice_status, "before handling");
|
||||||
|
handle_event(event, &mut voice_status).await;
|
||||||
|
tracing::debug!(?voice_status, "after handling");
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(?error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next_event = shard.next_event(vc_event_types);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(discord_client), ret)]
|
||||||
|
async fn initialize_vcs(discord_client: &twilight_http::Client) -> VCs {
|
||||||
|
let mut vcs = VCs::default();
|
||||||
|
|
||||||
|
if let Ok(guilds_res) = discord_client.current_user_guilds().limit(200).await
|
||||||
|
&& let Ok(guilds) = guilds_res.model().await
|
||||||
|
{
|
||||||
|
for guild in guilds {
|
||||||
|
if let Ok(guild_members_res) = discord_client.guild_members(guild.id).limit(999).await
|
||||||
|
&& let Ok(guild_members) = guild_members_res.model().await
|
||||||
|
{
|
||||||
|
for member in guild_members {
|
||||||
|
if let Ok(voice_state_res) = discord_client
|
||||||
|
.user_voice_state(guild.id, member.user.id)
|
||||||
|
.await
|
||||||
|
&& let Ok(voice_state) = voice_state_res.model().await
|
||||||
|
{
|
||||||
|
tracing::info!(?member.user.id, ?voice_state);
|
||||||
|
|
||||||
|
let voice_status = VoiceStatus::builder()
|
||||||
|
.self_deafened(voice_state.self_deaf)
|
||||||
|
.self_muted(voice_state.self_mute)
|
||||||
|
.server_deafened(voice_state.deaf)
|
||||||
|
.server_muted(voice_state.mute)
|
||||||
|
.camming(voice_state.self_video)
|
||||||
|
.streaming(voice_state.self_stream)
|
||||||
|
.build();
|
||||||
|
let user_in_vc_data = voice_status.into();
|
||||||
|
|
||||||
|
if let Some(channel_id) = voice_state.channel_id {
|
||||||
|
vcs.entry(guild.id).or_default().insert(
|
||||||
|
channel_id,
|
||||||
|
member.user.id,
|
||||||
|
user_in_vc_data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vcs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(vcs))]
|
||||||
|
async fn handle_event(event: Event, vcs: &mut VCs) {
|
||||||
|
match event {
|
||||||
|
Event::VoiceStateUpdate(voice_state_update) => {
|
||||||
|
let user_id = voice_state_update.user_id;
|
||||||
|
match voice_state_update.guild_id {
|
||||||
|
Some(guild_id) => match voice_state_update.channel_id {
|
||||||
|
Some(channel_id) => {
|
||||||
|
let voice_status = VoiceStatus::builder()
|
||||||
|
.self_deafened(voice_state_update.self_deaf)
|
||||||
|
.self_muted(voice_state_update.self_mute)
|
||||||
|
.server_deafened(voice_state_update.deaf)
|
||||||
|
.server_muted(voice_state_update.mute)
|
||||||
|
.camming(voice_state_update.self_video)
|
||||||
|
.streaming(voice_state_update.self_stream)
|
||||||
|
.build();
|
||||||
|
let user_in_vc_data = voice_status.into();
|
||||||
|
|
||||||
|
vcs.entry(guild_id).or_default().insert(
|
||||||
|
channel_id,
|
||||||
|
user_id,
|
||||||
|
user_in_vc_data,
|
||||||
|
);
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
?guild_id,
|
||||||
|
?channel_id,
|
||||||
|
?user_id,
|
||||||
|
"connected or otherwise changed state while connected"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
if let Some(channel_vcers) = vcs.get_mut(&guild_id) {
|
||||||
|
channel_vcers.remove_right(&user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!(?guild_id, ?user_id, "disconnected");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
None => {
|
||||||
|
tracing::error!("why doesn't this have a guild id attached?!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
tracing::warn!(?other, "wasn't expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/one_to_many.rs
Normal file
66
src/one_to_many.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct OneToManyUniqueBTreeMap<Left, Right> {
|
||||||
|
left_to_rights: BTreeMap<Left, BTreeSet<Right>>,
|
||||||
|
right_to_left: BTreeMap<Right, Left>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Left, Right> Default for OneToManyUniqueBTreeMap<Left, Right> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
left_to_rights: Default::default(),
|
||||||
|
right_to_left: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Left, Right> OneToManyUniqueBTreeMap<Left, Right>
|
||||||
|
where
|
||||||
|
Left: Ord + Clone,
|
||||||
|
Right: Ord + Clone,
|
||||||
|
{
|
||||||
|
/// Clones `left` and `right` so make sure it's cheap to do so
|
||||||
|
/// Returns whatever `Left` that `right` was already pointing to
|
||||||
|
pub fn insert(&mut self, left: Left, right: Right) -> Option<(Left, Right)> {
|
||||||
|
let old = self.remove_right(&right);
|
||||||
|
|
||||||
|
self.left_to_rights
|
||||||
|
.entry(left.clone())
|
||||||
|
.or_default()
|
||||||
|
.insert(right.clone());
|
||||||
|
self.right_to_left.insert(right, left);
|
||||||
|
|
||||||
|
old
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_rights_for(&self, left: &Left) -> Option<&BTreeSet<Right>> {
|
||||||
|
self.left_to_rights.get(left)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_left_for(&self, right: &Right) -> Option<&Left> {
|
||||||
|
self.right_to_left.get(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_left(&mut self, left: &Left) -> Option<(Left, BTreeSet<Right>)> {
|
||||||
|
let (left, rights) = self.left_to_rights.remove_entry(left)?;
|
||||||
|
|
||||||
|
for right in &rights {
|
||||||
|
self.right_to_left.remove(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((left, rights))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_right(&mut self, right: &Right) -> Option<(Left, Right)> {
|
||||||
|
let left = self.right_to_left.remove(right)?;
|
||||||
|
let rights = self.left_to_rights.get_mut(&left)?;
|
||||||
|
let right = rights.take(right)?;
|
||||||
|
|
||||||
|
if rights.is_empty() {
|
||||||
|
self.left_to_rights.remove(&left);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((left, right))
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/one_to_many_with_data.rs
Normal file
71
src/one_to_many_with_data.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct OneToManyUniqueBTreeMapWithData<Left, Right, RightData> {
|
||||||
|
left_to_rights: BTreeMap<Left, BTreeMap<Right, RightData>>,
|
||||||
|
right_to_left: BTreeMap<Right, Left>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Left, Right, RightData> Default for OneToManyUniqueBTreeMapWithData<Left, Right, RightData> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
left_to_rights: Default::default(),
|
||||||
|
right_to_left: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Left, Right, RightData> OneToManyUniqueBTreeMapWithData<Left, Right, RightData>
|
||||||
|
where
|
||||||
|
Left: Ord + Clone,
|
||||||
|
Right: Ord + Clone,
|
||||||
|
{
|
||||||
|
/// Clones `left` and `right` so make sure it's cheap to do so
|
||||||
|
/// Returns whatever `Left` that `right` was already pointing to
|
||||||
|
pub fn insert(
|
||||||
|
&mut self,
|
||||||
|
left: Left,
|
||||||
|
right: Right,
|
||||||
|
right_data: RightData,
|
||||||
|
) -> Option<(Left, Right, RightData)> {
|
||||||
|
let old = self.remove_right(&right);
|
||||||
|
|
||||||
|
self.left_to_rights
|
||||||
|
.entry(left.clone())
|
||||||
|
.or_default()
|
||||||
|
.insert(right.clone(), right_data);
|
||||||
|
self.right_to_left.insert(right, left);
|
||||||
|
|
||||||
|
old
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_rights_for(&self, left: &Left) -> Option<&BTreeMap<Right, RightData>> {
|
||||||
|
self.left_to_rights.get(left)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_left_for(&self, right: &Right) -> Option<&Left> {
|
||||||
|
self.right_to_left.get(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_left(&mut self, left: &Left) -> Option<(Left, BTreeMap<Right, RightData>)> {
|
||||||
|
let (left, rights) = self.left_to_rights.remove_entry(left)?;
|
||||||
|
|
||||||
|
for (right, _right_data) in &rights {
|
||||||
|
self.right_to_left.remove(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((left, rights))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_right(&mut self, right: &Right) -> Option<(Left, Right, RightData)> {
|
||||||
|
let left = self.right_to_left.remove(right)?;
|
||||||
|
let rights = self.left_to_rights.get_mut(&left)?;
|
||||||
|
let (right, right_data) = rights.remove_entry(right)?;
|
||||||
|
|
||||||
|
if rights.is_empty() {
|
||||||
|
self.left_to_rights.remove(&left);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((left, right, right_data))
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/one_to_one.rs
Normal file
89
src/one_to_one.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct OneToOneBTreeMap<Left, Right> {
|
||||||
|
left_to_right: BTreeMap<Left, Right>,
|
||||||
|
right_to_left: BTreeMap<Right, Left>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Left, Right> Default for OneToOneBTreeMap<Left, Right> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
left_to_right: Default::default(),
|
||||||
|
right_to_left: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Left, Right> OneToOneBTreeMap<Left, Right>
|
||||||
|
where
|
||||||
|
Left: Ord + Clone,
|
||||||
|
Right: Ord + Clone,
|
||||||
|
{
|
||||||
|
/// Clones `left` and `right` so make sure it's cheap to do so
|
||||||
|
/// Returns whatever `(Left, Right)` pair was already in the map if one was
|
||||||
|
pub fn insert(&mut self, left: Left, right: Right) -> Option<(Left, Right)> {
|
||||||
|
let old = self.remove_by_left(&left); // alternatively, remove_by_right
|
||||||
|
|
||||||
|
self.left_to_right.insert(left.clone(), right.clone());
|
||||||
|
self.right_to_left.insert(right, left);
|
||||||
|
|
||||||
|
old
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_right_for(&self, left: &Left) -> Option<&Right> {
|
||||||
|
self.left_to_right.get(left)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_left_for(&self, right: &Right) -> Option<&Left> {
|
||||||
|
self.right_to_left.get(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_by_left(&mut self, left: &Left) -> Option<(Left, Right)> {
|
||||||
|
let right = self.left_to_right.remove(left)?;
|
||||||
|
let left = self.right_to_left.remove(&right)?;
|
||||||
|
Some((left, right))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_by_right(&mut self, right: &Right) -> Option<(Left, Right)> {
|
||||||
|
let left = self.right_to_left.remove(right)?;
|
||||||
|
let right = self.left_to_right.remove(&left)?;
|
||||||
|
Some((left, right))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Left, Right> IntoIterator for OneToOneBTreeMap<Left, Right> {
|
||||||
|
type Item = (Left, Right);
|
||||||
|
|
||||||
|
type IntoIter = <BTreeMap<Left, Right> as IntoIterator>::IntoIter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.left_to_right.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Left, Right> IntoIterator for &'a OneToOneBTreeMap<Left, Right> {
|
||||||
|
type Item = (&'a Left, &'a Right);
|
||||||
|
|
||||||
|
type IntoIter = <&'a BTreeMap<Left, Right> as IntoIterator>::IntoIter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.left_to_right.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Left, Right> FromIterator<(Left, Right)> for OneToOneBTreeMap<Left, Right>
|
||||||
|
where
|
||||||
|
Left: Ord + Clone,
|
||||||
|
Right: Ord + Clone,
|
||||||
|
{
|
||||||
|
fn from_iter<T: IntoIterator<Item = (Left, Right)>>(iter: T) -> Self {
|
||||||
|
let mut this = Self::default();
|
||||||
|
|
||||||
|
for (left, right) in iter {
|
||||||
|
this.insert(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
104
src/vc_user.rs
Normal file
104
src/vc_user.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
use typed_builder::TypedBuilder;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Microphone {
|
||||||
|
Unmuted,
|
||||||
|
ServerMuted,
|
||||||
|
Muted,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Headphone {
|
||||||
|
Undeafened,
|
||||||
|
ServerDeafened,
|
||||||
|
Deafened,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Camera {
|
||||||
|
Showing,
|
||||||
|
Off,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for Camera {
|
||||||
|
fn from(camming: bool) -> Self {
|
||||||
|
if camming {
|
||||||
|
Camera::Showing
|
||||||
|
} else {
|
||||||
|
Camera::Off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Stream {
|
||||||
|
Sharing,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for Stream {
|
||||||
|
fn from(streaming: bool) -> Self {
|
||||||
|
if streaming {
|
||||||
|
Stream::Sharing
|
||||||
|
} else {
|
||||||
|
Stream::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UserInVCData {
|
||||||
|
pub microphone: Microphone,
|
||||||
|
pub headphone: Headphone,
|
||||||
|
pub camera: Camera,
|
||||||
|
pub stream: Stream,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, TypedBuilder)]
|
||||||
|
pub struct VoiceStatus {
|
||||||
|
server_deafened: bool,
|
||||||
|
self_deafened: bool,
|
||||||
|
|
||||||
|
server_muted: bool,
|
||||||
|
self_muted: bool,
|
||||||
|
|
||||||
|
camming: bool,
|
||||||
|
|
||||||
|
streaming: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<VoiceStatus> for UserInVCData {
|
||||||
|
fn from(
|
||||||
|
VoiceStatus {
|
||||||
|
server_deafened,
|
||||||
|
self_deafened,
|
||||||
|
server_muted,
|
||||||
|
self_muted,
|
||||||
|
camming,
|
||||||
|
streaming,
|
||||||
|
}: VoiceStatus,
|
||||||
|
) -> Self {
|
||||||
|
let microphone = match (server_muted, self_muted) {
|
||||||
|
(true, _) => Microphone::ServerMuted,
|
||||||
|
(false, true) => Microphone::Muted,
|
||||||
|
(false, false) => Microphone::Unmuted,
|
||||||
|
};
|
||||||
|
|
||||||
|
let headphone = match (server_deafened, self_deafened) {
|
||||||
|
(true, _) => Headphone::ServerDeafened,
|
||||||
|
(false, true) => Headphone::Deafened,
|
||||||
|
(false, false) => Headphone::Undeafened,
|
||||||
|
};
|
||||||
|
|
||||||
|
let camera = camming.into();
|
||||||
|
|
||||||
|
let stream = streaming.into();
|
||||||
|
|
||||||
|
UserInVCData {
|
||||||
|
microphone,
|
||||||
|
headphone,
|
||||||
|
camera,
|
||||||
|
stream,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user