mirror of
https://github.com/mii443/audio-receiver.git
synced 2025-08-22 15:05:28 +00:00
first commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
config.toml
|
2221
Cargo.lock
generated
Normal file
2221
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
Cargo.toml
Normal file
30
Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "audio-receiver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
toml = "0.5"
|
||||
async-trait = "0.1.57"
|
||||
hound = "3.5.1"
|
||||
byteorder = "1.3.4"
|
||||
serde_json = "1.0"
|
||||
serde = "1.0"
|
||||
|
||||
[dependencies.uuid]
|
||||
version = "0.8"
|
||||
features = ["serde", "v4"]
|
||||
|
||||
[dependencies.songbird]
|
||||
version = "0.3.2"
|
||||
features = ["builtin-queue"]
|
||||
|
||||
[dependencies.serenity]
|
||||
version = "0.11.6"
|
||||
features = ["builder", "cache", "client", "gateway", "model", "utils", "unstable_discord_api", "collector", "rustls_backend", "framework", "voice"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.0"
|
||||
features = ["macros", "rt-multi-thread"]
|
127
src/audio_receiver.rs
Normal file
127
src/audio_receiver.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use std::{collections::HashMap, io::Write, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use byteorder::{LittleEndian, WriteBytesExt};
|
||||
use serenity::{futures::lock::Mutex, model::channel::Message, prelude::Context};
|
||||
use songbird::{
|
||||
model::payload::Speaking, CoreEvent, Event, EventContext, EventHandler as VoiceEventHandler,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Receiver {
|
||||
pub ssrc_map: Arc<Mutex<HashMap<u32, u64>>>,
|
||||
pub audio_data: Arc<Mutex<HashMap<u32, Vec<i16>>>>,
|
||||
}
|
||||
|
||||
impl Receiver {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ssrc_map: Arc::new(Mutex::new(HashMap::new())),
|
||||
audio_data: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pcm_to_wav(pcm: Vec<i16>) -> Vec<u8> {
|
||||
let spec = hound::WavSpec {
|
||||
channels: 2,
|
||||
sample_rate: 48000,
|
||||
bits_per_sample: 16,
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
|
||||
let mut wav: Vec<u8> = vec![];
|
||||
wav.write_all(&spec.into_header_for_infinite_file())
|
||||
.unwrap();
|
||||
for &n in &pcm {
|
||||
wav.write_i16::<LittleEndian>(n).unwrap();
|
||||
}
|
||||
|
||||
wav
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl VoiceEventHandler for Receiver {
|
||||
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
|
||||
match ctx {
|
||||
EventContext::SpeakingStateUpdate(Speaking { ssrc, user_id, .. }) => {
|
||||
self.ssrc_map.lock().await.insert(*ssrc, user_id.unwrap().0);
|
||||
}
|
||||
EventContext::SpeakingUpdate(data) => {
|
||||
if !data.speaking {
|
||||
let audio =
|
||||
if let Some(audio) = self.audio_data.lock().await.clone().get(&data.ssrc) {
|
||||
Some(audio.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if !self.ssrc_map.lock().await.contains_key(&data.ssrc) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(audio) = audio {
|
||||
let wav = pcm_to_wav(audio.clone());
|
||||
let mut file =
|
||||
std::fs::File::create(format!("./audio/{}.wav", data.ssrc)).unwrap();
|
||||
|
||||
file.write_all(&wav).unwrap();
|
||||
file.flush().unwrap();
|
||||
|
||||
self.audio_data.lock().await.remove(&data.ssrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
EventContext::VoicePacket(data) => {
|
||||
let ssrc = data.packet.ssrc;
|
||||
|
||||
if let Some(audio) = data.audio {
|
||||
if !self.audio_data.lock().await.contains_key(&ssrc) {
|
||||
self.audio_data.lock().await.insert(ssrc, vec![]);
|
||||
}
|
||||
let mut prev_data = self.audio_data.lock().await.get(&ssrc).unwrap().clone();
|
||||
prev_data.append(&mut audio.clone());
|
||||
self.audio_data.lock().await.insert(ssrc, prev_data);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn audio_receive(ctx: &Context, message: &Message) {
|
||||
let guild = message
|
||||
.guild_id
|
||||
.unwrap()
|
||||
.to_guild_cached(&ctx.cache)
|
||||
.unwrap();
|
||||
let channel_id = guild
|
||||
.voice_states
|
||||
.get(&message.author.id)
|
||||
.and_then(|state| state.channel_id);
|
||||
|
||||
if channel_id.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let manager = songbird::get(ctx).await.unwrap();
|
||||
|
||||
let (handler_lock, conn_result) = manager.join(guild.id.0, channel_id.unwrap().0).await;
|
||||
|
||||
let receiver = Receiver::new();
|
||||
|
||||
if let Ok(_) = conn_result {
|
||||
let mut handler = handler_lock.lock().await;
|
||||
|
||||
handler.add_global_event(CoreEvent::SpeakingStateUpdate.into(), receiver.clone());
|
||||
|
||||
handler.add_global_event(CoreEvent::SpeakingUpdate.into(), receiver.clone());
|
||||
|
||||
handler.add_global_event(CoreEvent::VoicePacket.into(), receiver.clone());
|
||||
|
||||
handler.add_global_event(CoreEvent::RtcpPacket.into(), receiver.clone());
|
||||
|
||||
handler.add_global_event(CoreEvent::ClientDisconnect.into(), receiver);
|
||||
}
|
||||
}
|
7
src/config.rs
Normal file
7
src/config.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub token: String,
|
||||
pub application_id: u64,
|
||||
}
|
22
src/event_handler.rs
Normal file
22
src/event_handler.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use async_trait::async_trait;
|
||||
use serenity::{
|
||||
model::{channel::Message, prelude::Ready},
|
||||
prelude::{Context, EventHandler},
|
||||
};
|
||||
|
||||
use crate::audio_receiver::audio_receive;
|
||||
|
||||
pub struct Handler;
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for Handler {
|
||||
async fn message(&self, ctx: Context, message: Message) {
|
||||
if message.content == "audio-receive" {
|
||||
audio_receive(&ctx, &message).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn ready(&self, _ctx: Context, _ready: Ready) {
|
||||
println!("bot ready");
|
||||
}
|
||||
}
|
49
src/main.rs
Normal file
49
src/main.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use std::env;
|
||||
|
||||
use config::Config;
|
||||
use event_handler::Handler;
|
||||
use serenity::{framework::StandardFramework, prelude::GatewayIntents, Client};
|
||||
use songbird::{driver::DecodeMode, SerenityInit};
|
||||
|
||||
mod audio_receiver;
|
||||
mod config;
|
||||
mod event_handler;
|
||||
|
||||
async fn create_client(token: &str, id: u64) -> Result<Client, serenity::Error> {
|
||||
let framework = StandardFramework::new().configure(|c| c.with_whitespace(true));
|
||||
|
||||
let songbird_config = songbird::Config::default().decode_mode(DecodeMode::Decode);
|
||||
|
||||
Client::builder(token, GatewayIntents::all())
|
||||
.event_handler(Handler)
|
||||
.application_id(id)
|
||||
.framework(framework)
|
||||
.register_songbird_from_config(songbird_config)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let config = {
|
||||
let config = std::fs::read_to_string("./config.toml");
|
||||
if let Ok(config) = config {
|
||||
toml::from_str::<Config>(&config).expect("Cannot load config file.")
|
||||
} else {
|
||||
let token = env::var("BOT_TOKEN").unwrap();
|
||||
let application_id = env::var("BOT_ID").unwrap();
|
||||
|
||||
Config {
|
||||
token,
|
||||
application_id: u64::from_str_radix(&application_id, 10).unwrap(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut client = create_client(&config.token, config.application_id)
|
||||
.await
|
||||
.expect("Err creating client");
|
||||
|
||||
if let Err(err) = client.start().await {
|
||||
println!("Client error: {err:?}");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user