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}