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