tts_external_api/tcp.rs
1//! The TCP connection used for communication between the external API and Tabletop Simulator
2
3use crate::messages::{Answer, Message};
4use std::fmt::Debug;
5use std::io::{self, Read, Write};
6use std::net::{TcpListener, TcpStream};
7
8/// A struct representing Tabletop Simulators [External Editor API](https://api.tabletopsimulator.com/externaleditorapi/).
9#[derive(Debug)]
10pub struct ExternalEditorApi {
11 /// TcpListener used for listening to incoming messages
12 pub listener: TcpListener,
13}
14
15impl ExternalEditorApi {
16 /// Creates a new ExternalEditorApi struct and binds the TcpListener to its socket address.
17 pub fn new() -> Self {
18 let listener = TcpListener::bind("127.0.0.1:39998").unwrap();
19 Self { listener }
20 }
21
22 /// Sends a [`Message`] in a TcpStream. If no connection to the game can be established, an [`io::Error`] gets returned.
23 pub fn send(&self, message: Message) -> io::Result<()> {
24 let mut stream = TcpStream::connect("127.0.0.1:39999")?;
25 let json_message = serde_json::to_string(&message).unwrap();
26 stream.write_all(json_message.as_bytes()).unwrap();
27 stream.flush().unwrap();
28 Ok(())
29 }
30
31 /// Accepts the next incoming [`Answer`] from the listener and deserializes it.
32 /// This function will block the calling thread until a new TCP connection is established and an answer gets received.
33 pub fn read(&self) -> Answer {
34 serde_json::from_str(&self.read_string()).unwrap()
35 }
36
37 /// Accepts the next incoming [`Answer`] from the listener as a String.
38 /// This function will block the calling thread until a new TCP connection is established and an answer gets received.
39 pub fn read_string(&self) -> String {
40 let (mut stream, _addr) = self.listener.accept().unwrap();
41 let mut buffer = String::new();
42 stream.read_to_string(&mut buffer).unwrap();
43 buffer
44 }
45
46 /// Reads incoming [`Answer`] messages until an answer matches the generic.
47 /// This function will block the calling thread until a new TCP connection is established and an answer gets received.
48 pub fn wait<T: TryFrom<Answer>>(&self) -> T {
49 loop {
50 if let Ok(answer) = T::try_from(self.read()) {
51 return answer;
52 }
53 }
54 }
55}
56
57/// Creates a new ExternalEditorApi struct and binds the TcpListener to its socket address.
58/// This is functionally the same as using `ExternalEditorApi::new()`.
59impl Default for ExternalEditorApi {
60 fn default() -> Self {
61 Self::new()
62 }
63}