//! Requires the "client", "standard_framework", and "voice" features be enabled //! in your Cargo.toml, like so: //! //! ```toml //! [dependencies.serenity] //! git = "https://github.com/serenity-rs/serenity.git" //! features = ["client", "standard_framework", "voice"] //! ``` #[macro_use] extern crate serenity; extern crate typemap; use std::{env, sync::Arc}; use serenity::{ client::{bridge::voice::ClientVoiceManager, CACHE, Client, Context, EventHandler}, framework::StandardFramework, model::{channel::Message, gateway::Ready, id::ChannelId, misc::Mentionable}, prelude::*, voice::AudioReceiver, Result as SerenityResult, }; use typemap::Key; struct VoiceManager; impl Key for VoiceManager { type Value = Arc>; } struct Handler; impl EventHandler for Handler { fn ready(&self, _: Context, ready: Ready) { println!("{} is connected!", ready.user.name); } } struct Receiver; impl Receiver { pub fn new() -> Self { // You can manage state here, such as a buffer of audio packet bytes so // you can later store them in intervals. Self { } } } impl AudioReceiver for Receiver { fn speaking_update(&mut self, _ssrc: u32, _user_id: u64, _speaking: bool) { // You can implement logic here so that you can differentiate users' // SSRCs and map the SSRC to the User ID and maintain a state in // `Receiver`. Using this map, you can map the `ssrc` in `voice_packet` // to the user ID and handle their audio packets separately. } fn voice_packet(&mut self, ssrc: u32, sequence: u16, _timestamp: u32, _stereo: bool, data: &[i16]) { println!("Audio packet's first 5 bytes: {:?}", data.get(..5)); println!( "Audio packet sequence {:05} has {:04} bytes, SSRC {}", sequence, data.len(), ssrc, ); } } fn main() { // Configure the client with your Discord bot token in the environment. let token = env::var("DISCORD_TOKEN") .expect("Expected a token in the environment"); let mut client = Client::new(&token, Handler).expect("Err creating client"); // Obtain a lock to the data owned by the client, and insert the client's // voice manager into it. This allows the voice manager to be accessible by // event handlers and framework commands. { let mut data = client.data.lock(); data.insert::(Arc::clone(&client.voice_manager)); } client.with_framework(StandardFramework::new() .configure(|c| c .prefix("~") .on_mention(true)) .cmd("join", join) .cmd("leave", leave) .cmd("ping", ping)); let _ = client.start().map_err(|why| println!("Client ended: {:?}", why)); } command!(join(ctx, msg, args) { let connect_to = match args.single::() { Ok(id) => ChannelId(id), Err(_) => { check_msg(msg.reply("Requires a valid voice channel ID be given")); return Ok(()); }, }; let guild_id = match CACHE.read().guild_channel(msg.channel_id) { Some(channel) => channel.read().guild_id, None => { check_msg(msg.channel_id.say("Groups and DMs not supported")); return Ok(()); }, }; let mut manager_lock = ctx.data.lock().get::().cloned().expect("Expected VoiceManager in ShareMap."); let mut manager = manager_lock.lock(); if let Some(handler) = manager.join(guild_id, connect_to) { handler.listen(Some(Box::new(Receiver::new()))); check_msg(msg.channel_id.say(&format!("Joined {}", connect_to.mention()))); } else { check_msg(msg.channel_id.say("Error joining the channel")); } }); command!(leave(ctx, msg) { let guild_id = match CACHE.read().guild_channel(msg.channel_id) { Some(channel) => channel.read().guild_id, None => { check_msg(msg.channel_id.say("Groups and DMs not supported")); return Ok(()); }, }; let mut manager_lock = ctx.data.lock().get::().cloned().expect("Expected VoiceManager in ShareMap."); let mut manager = manager_lock.lock(); let has_handler = manager.get(guild_id).is_some(); if has_handler { manager.remove(guild_id); check_msg(msg.channel_id.say("Left voice channel")); } else { check_msg(msg.reply("Not in a voice channel")); } }); command!(ping(_context, msg) { check_msg(msg.channel_id.say("Pong!")); }); /// Checks that a message successfully sent; if not, then logs why to stdout. fn check_msg(result: SerenityResult) { if let Err(why) = result { println!("Error sending message: {:?}", why); } }