aboutsummaryrefslogtreecommitdiffstats
path: root/src/teamspeak
diff options
context:
space:
mode:
authorFelix Kaaman <tmtu@tmtu.ee>2020-02-22 19:24:05 +0200
committerFelix Kaaman <tmtu@tmtu.ee>2020-02-22 19:24:05 +0200
commitdbaaeb68bdf4143b9dca55b2baa86a2656a3249e (patch)
tree5bc007e03ef7782bbfeeb81ca936c97c992be1a2 /src/teamspeak
parent00b60b8f210b93a5d6cbdde2bc98feff2f2ec1ca (diff)
downloadpokebot-dbaaeb68bdf4143b9dca55b2baa86a2656a3249e.tar.gz
pokebot-dbaaeb68bdf4143b9dca55b2baa86a2656a3249e.zip
Add functions for BBCode formatting
Diffstat (limited to 'src/teamspeak')
-rw-r--r--src/teamspeak/bbcode.rs42
-rw-r--r--src/teamspeak/mod.rs232
2 files changed, 274 insertions, 0 deletions
diff --git a/src/teamspeak/bbcode.rs b/src/teamspeak/bbcode.rs
new file mode 100644
index 0000000..28be08a
--- /dev/null
+++ b/src/teamspeak/bbcode.rs
@@ -0,0 +1,42 @@
+use std::fmt::{Formatter, Display, Error};
+
+#[allow(dead_code)]
+pub enum BbCode<'a> {
+ Bold(&'a dyn Display),
+ Italic(&'a dyn Display),
+ Underline(&'a dyn Display),
+ Link(&'a dyn Display, &'a str),
+}
+
+impl<'a> Display for BbCode<'a> {
+ fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
+ match self {
+ BbCode::Bold(text) => fmt.write_fmt(format_args!("[B]{}[/B]", text))?,
+ BbCode::Italic(text) => fmt.write_fmt(format_args!("[I]{}[/I]", text))?,
+ BbCode::Underline(text) => fmt.write_fmt(format_args!("[U]{}[/U]", text))?,
+ BbCode::Link(text, url) => fmt.write_fmt(format_args!("[URL={}]{}[/URL]", url, text))?,
+ };
+
+ Ok(())
+ }
+}
+
+#[allow(dead_code)]
+pub fn bold(text: &dyn Display) -> BbCode {
+ BbCode::Bold(text)
+}
+
+#[allow(dead_code)]
+pub fn italic(text: &dyn Display) -> BbCode {
+ BbCode::Italic(text)
+}
+
+#[allow(dead_code)]
+pub fn underline(text: &dyn Display) -> BbCode {
+ BbCode::Underline(text)
+}
+
+#[allow(dead_code)]
+pub fn link<'a>(text: &'a dyn Display, url: &'a str) -> BbCode<'a> {
+ BbCode::Link(text, url)
+}
diff --git a/src/teamspeak/mod.rs b/src/teamspeak/mod.rs
new file mode 100644
index 0000000..5ac0d44
--- /dev/null
+++ b/src/teamspeak/mod.rs
@@ -0,0 +1,232 @@
+use std::sync::{Arc, Mutex};
+use std::time::{Duration, Instant};
+
+use futures::compat::Future01CompatExt;
+use futures01::{future::Future, sink::Sink};
+use tokio02::sync::mpsc::UnboundedSender;
+
+use tsclientlib::Event::ConEvents;
+use tsclientlib::{
+ events::Event, ChannelId, ClientId, ConnectOptions, Connection, DisconnectOptions,
+ MessageTarget, Reason,
+};
+
+use log::error;
+
+use crate::bot::{Message, MusicBotMessage};
+
+mod bbcode;
+
+pub use bbcode::*;
+
+pub struct TeamSpeakConnection {
+ conn: Connection,
+}
+
+fn get_message<'a>(event: &Event) -> Option<MusicBotMessage> {
+ use tsclientlib::events::{PropertyId, PropertyValue};
+
+ match event {
+ Event::Message {
+ from: target,
+ invoker: sender,
+ message: msg,
+ } => Some(MusicBotMessage::TextMessage(Message {
+ target: *target,
+ invoker: sender.clone(),
+ text: msg.clone(),
+ })),
+ Event::PropertyChanged {
+ id: property,
+ old: from,
+ invoker: _,
+ } => match property {
+ PropertyId::ClientChannel(client) => {
+ if let PropertyValue::ChannelId(from) = from {
+ Some(MusicBotMessage::ClientChannel {
+ client: *client,
+ old_channel: *from,
+ })
+ } else {
+ None
+ }
+ }
+ _ => None,
+ },
+ Event::PropertyRemoved {
+ id: property,
+ old: client,
+ invoker: _,
+ } => match property {
+ PropertyId::Client(id) => {
+ if let PropertyValue::Client(client) = client {
+ Some(MusicBotMessage::ClientDisconnected {
+ id: *id,
+ client: client.clone(),
+ })
+ } else {
+ None
+ }
+ }
+ _ => None,
+ },
+ _ => None,
+ }
+}
+
+impl TeamSpeakConnection {
+ pub async fn new(
+ tx: Arc<Mutex<UnboundedSender<MusicBotMessage>>>,
+ options: ConnectOptions,
+ ) -> Result<TeamSpeakConnection, tsclientlib::Error> {
+ let conn = Connection::new(options).compat().await?;
+ let packet = conn.lock().server.set_subscribed(true);
+ conn.send_packet(packet).compat().await.unwrap();
+
+ conn.add_event_listener(
+ String::from("listener"),
+ Box::new(move |e| {
+ if let ConEvents(_conn, events) = e {
+ for event in *events {
+ if let Some(msg) = get_message(event) {
+ let tx = tx.lock().expect("Mutex was not poisoned");
+ // Ignore the result because the receiver might get dropped first.
+ let _ = tx.send(msg);
+ }
+ }
+ }
+ }),
+ );
+
+ Ok(TeamSpeakConnection { conn })
+ }
+
+ pub fn send_audio_packet(&self, samples: &[u8]) {
+ let packet =
+ tsproto_packets::packets::OutAudio::new(&tsproto_packets::packets::AudioData::C2S {
+ id: 0,
+ codec: tsproto_packets::packets::CodecType::OpusMusic,
+ data: samples,
+ });
+
+ let send_packet = self
+ .conn
+ .get_packet_sink()
+ .send(packet)
+ .map(|_| ())
+ .map_err(|_| error!("Failed to send voice packet"));
+
+ tokio::run(send_packet);
+ }
+
+ pub fn channel_of_user(&self, id: ClientId) -> Option<ChannelId> {
+ Some(self.conn.lock().clients.get(&id)?.channel)
+ }
+
+ pub fn channel_path_of_user(&self, id: ClientId) -> Option<String> {
+ let conn = self.conn.lock();
+
+ let channel_id = conn.clients.get(&id)?.channel;
+
+ let mut channel = conn
+ .channels
+ .get(&channel_id)
+ .expect("can find user channel");
+
+ let mut names = vec![&channel.name[..]];
+
+ // Channel 0 is the root channel
+ while channel.parent != ChannelId(0) {
+ names.push("/");
+ channel = conn
+ .channels
+ .get(&channel.parent)
+ .expect("can find user channel");
+ names.push(&channel.name);
+ }
+
+ let mut path = String::new();
+ while let Some(name) = names.pop() {
+ path.push_str(name);
+ }
+
+ Some(path)
+ }
+
+ pub fn my_channel(&self) -> ChannelId {
+ let conn = self.conn.lock();
+ conn.clients
+ .get(&conn.own_client)
+ .expect("can find myself")
+ .channel
+ }
+
+ pub fn user_count(&self, channel: ChannelId) -> u32 {
+ let conn = self.conn.lock();
+ let mut count = 0;
+ for (_, client) in &conn.clients {
+ if client.channel == channel {
+ count += 1;
+ }
+ }
+
+ count
+ }
+
+ pub fn set_nickname(&self, name: &str) {
+ tokio::spawn(
+ self.conn
+ .lock()
+ .to_mut()
+ .set_name(name)
+ .map_err(|e| error!("Failed to set nickname: {}", e)),
+ );
+ }
+
+ pub fn set_description(&self, desc: &str) {
+ tokio::spawn(
+ self.conn
+ .lock()
+ .to_mut()
+ .get_client(&self.conn.lock().own_client)
+ .expect("Can get myself")
+ .set_description(desc)
+ .map_err(|e| error!("Failed to change description: {}", e)),
+ );
+ }
+
+ pub fn send_message_to_channel(&self, text: &str) {
+ tokio::spawn(
+ self.conn
+ .lock()
+ .to_mut()
+ .send_message(MessageTarget::Channel, text)
+ .map_err(|e| error!("Failed to send message: {}", e)),
+ );
+ }
+
+ pub fn send_message_to_user(&self, id: ClientId, text: &str) {
+ tokio::spawn(
+ self.conn
+ .lock()
+ .to_mut()
+ .send_message(MessageTarget::Client(id), text)
+ .map_err(|e| error!("Failed to send message: {}", e)),
+ );
+ }
+
+ pub fn disconnect(&self, reason: &str) {
+ let opt = DisconnectOptions::new()
+ .reason(Reason::Clientdisconnect)
+ .message(reason);
+ tokio::spawn(
+ self.conn
+ .disconnect(opt)
+ .map_err(|e| error!("Failed to send message: {}", e)),
+ );
+ // Might or might not be required to keep tokio running while the bot disconnects
+ tokio::spawn(
+ tokio::timer::Delay::new(Instant::now() + Duration::from_secs(1)).map_err(|_| ()),
+ );
+ }
+}