ts_bookkeeping/
lib.rs

1//! `ts-bookkeeping` contains structs to store the state of a TeamSpeak server, with its clients and
2//! channels.
3//!
4//! The crate can be used to keep track of the state on a server by processing all incoming
5//! commands, which is why it is called “bookkeeping”. It also contains generated structs for all
6//! TeamSpeak commands.
7//!
8//! Incoming commands can be applied to the state and generate [`events`]. The main struct is
9//! [`data::Connection`].
10//!
11//! The structs have methods to create packets for various actions. The generated packets can be
12//! sent to a server.
13
14use std::fmt;
15use std::net::{IpAddr, SocketAddr};
16
17use serde::{Deserialize, Serialize};
18use thiserror::Error;
19
20pub mod data;
21pub mod events;
22pub mod messages;
23
24// Reexports
25pub use tsproto_types::errors::Error as TsError;
26pub use tsproto_types::versions::Version;
27pub use tsproto_types::{
28	ChannelGroupId, ChannelId, ChannelPermissionHint, ChannelType, ClientDbId, ClientId,
29	ClientPermissionHint, ClientType, Codec, CodecEncryptionMode, GroupNamingMode, GroupType,
30	HostBannerMode, HostMessageMode, IconId, Invoker, InvokerRef, LicenseType, LogLevel,
31	MaxClients, Permission, PermissionType, PluginTargetMode, Reason, ServerGroupId,
32	TalkPowerRequest, TextMessageTargetMode, TokenType, Uid, UidBuf,
33};
34
35type Result<T> = std::result::Result<T, Error>;
36
37#[derive(Error, Debug)]
38#[non_exhaustive]
39pub enum Error {
40	#[error("Target client id missing for a client text message")]
41	MessageWithoutTargetClientId,
42	#[error("Unknown TextMessageTargetMode")]
43	UnknownTextMessageTargetMode,
44	#[error("{0} {1} not found")]
45	NotFound(&'static str, String),
46	#[error("{0} should be removed but does not exist")]
47	RemoveNotFound(&'static str),
48	#[error("Failed to parse connection ip: {0}")]
49	InvalidConnectionIp(#[source] std::net::AddrParseError),
50}
51
52#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
53pub enum ServerAddress {
54	SocketAddr(SocketAddr),
55	Other(String),
56}
57
58impl From<SocketAddr> for ServerAddress {
59	fn from(addr: SocketAddr) -> Self { ServerAddress::SocketAddr(addr) }
60}
61
62impl From<String> for ServerAddress {
63	fn from(addr: String) -> Self { ServerAddress::Other(addr) }
64}
65
66impl<'a> From<&'a str> for ServerAddress {
67	fn from(addr: &'a str) -> Self { ServerAddress::Other(addr.to_string()) }
68}
69
70impl fmt::Display for ServerAddress {
71	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72		match self {
73			ServerAddress::SocketAddr(a) => fmt::Display::fmt(a, f),
74			ServerAddress::Other(a) => fmt::Display::fmt(a, f),
75		}
76	}
77}
78
79/// All possible targets to send messages.
80#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
81pub enum MessageTarget {
82	Server,
83	Channel,
84	Client(ClientId),
85	Poke(ClientId),
86}
87
88/// The configuration to create a new connection.
89#[derive(Deserialize, Serialize)]
90pub struct ConnectOptions {
91	address: ServerAddress,
92	local_address: Option<SocketAddr>,
93	name: String,
94	version: Version,
95	log_commands: bool,
96	log_packets: bool,
97	log_udp_packets: bool,
98}
99
100impl ConnectOptions {
101	/// Start creating the configuration of a new connection.
102	///
103	/// # Arguments
104	/// The address of the server has to be supplied. The address can be a
105	/// [`SocketAddr`](std::net::SocketAddr), a string or directly a [`ServerAddress`]. A string
106	/// will automatically be resolved from all formats supported by TeamSpeak.
107	/// For details, see [`resolver::resolve`].
108	#[inline]
109	pub fn new<A: Into<ServerAddress>>(address: A) -> Self {
110		Self {
111			address: address.into(),
112			local_address: None,
113			name: String::from("TeamSpeakUser"),
114			version: Version::Linux_3_2_1,
115			log_commands: false,
116			log_packets: false,
117			log_udp_packets: false,
118		}
119	}
120
121	/// The address for the socket of our client
122	///
123	/// # Default
124	/// The default is `0.0.0:0` when connecting to an IPv4 address and `[::]:0`
125	/// when connecting to an IPv6 address.
126	#[inline]
127	pub fn local_address(mut self, local_address: SocketAddr) -> Self {
128		self.local_address = Some(local_address);
129		self
130	}
131
132	/// The name of the user.
133	///
134	/// # Default
135	/// `TeamSpeakUser`
136	#[inline]
137	pub fn name(mut self, name: String) -> Self {
138		self.name = name;
139		self
140	}
141
142	/// The displayed version of the client.
143	///
144	/// # Default
145	/// `3.2.1 on Linux`
146	#[inline]
147	pub fn version(mut self, version: Version) -> Self {
148		self.version = version;
149		self
150	}
151
152	/// If the content of all commands should be written to the logger.
153	///
154	/// # Default
155	/// `false`
156	#[inline]
157	pub fn log_commands(mut self, log_commands: bool) -> Self {
158		self.log_commands = log_commands;
159		self
160	}
161
162	/// If the content of all packets in high-level form should be written to
163	/// the logger.
164	///
165	/// # Default
166	/// `false`
167	#[inline]
168	pub fn log_packets(mut self, log_packets: bool) -> Self {
169		self.log_packets = log_packets;
170		self
171	}
172
173	/// If the content of all udp packets in byte-array form should be written
174	/// to the logger.
175	///
176	/// # Default
177	/// `false`
178	#[inline]
179	pub fn log_udp_packets(mut self, log_udp_packets: bool) -> Self {
180		self.log_udp_packets = log_udp_packets;
181		self
182	}
183}
184
185impl fmt::Debug for ConnectOptions {
186	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187		// Error if attributes are added
188		let ConnectOptions {
189			address,
190			local_address,
191			name,
192			version,
193			log_commands,
194			log_packets,
195			log_udp_packets,
196		} = self;
197		write!(
198			f,
199			"ConnectOptions {{ address: {:?}, local_address: {:?}, name: {}, version: {}, \
200			 log_commands: {}, log_packets: {}, log_udp_packets: {} }}",
201			address, local_address, name, version, log_commands, log_packets, log_udp_packets,
202		)?;
203		Ok(())
204	}
205}
206
207#[derive(Clone, Debug, Deserialize, Serialize)]
208pub struct DisconnectOptions {
209	reason: Option<Reason>,
210	message: Option<String>,
211}
212
213impl Default for DisconnectOptions {
214	#[inline]
215	fn default() -> Self { Self { reason: None, message: None } }
216}
217
218impl DisconnectOptions {
219	#[inline]
220	pub fn new() -> Self { Self::default() }
221
222	/// Set the reason for leaving.
223	///
224	/// # Default
225	///
226	/// None
227	#[inline]
228	pub fn reason(mut self, reason: Reason) -> Self {
229		self.reason = Some(reason);
230		self
231	}
232
233	/// Set the leave message.
234	///
235	/// You also have to set the reason, otherwise the message will not be
236	/// displayed.
237	///
238	/// # Default
239	///
240	/// None
241	#[inline]
242	pub fn message<S: Into<String>>(mut self, message: S) -> Self {
243		self.message = Some(message.into());
244		self
245	}
246}