1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//! The TCP connection used for communication between the external API and Tabletop Simulator

use crate::messages::{Answer, Message};
use std::fmt::Debug;
use std::io::{self, Read, Write};
use std::net::{TcpListener, TcpStream};

/// A struct representing Tabletop Simulators [External Editor API](https://api.tabletopsimulator.com/externaleditorapi/).
#[derive(Debug)]
pub struct ExternalEditorApi {
    listener: TcpListener,
}

impl ExternalEditorApi {
    /// Creates a new ExternalEditorApi struct and binds the TcpListener to its socket adress.
    pub fn new() -> Self {
        let listener = TcpListener::bind("127.0.0.1:39998").unwrap();
        Self { listener }
    }

    /// Sends a [`Message`] in a TcpStream. If no connection to the game can be established, an [`io::Error`] gets returned.
    pub fn send(&self, message: Message) -> io::Result<()> {
        let mut stream = TcpStream::connect("127.0.0.1:39999")?;
        let json_message = serde_json::to_string(&message).unwrap();
        stream.write_all(json_message.as_bytes()).unwrap();
        stream.flush().unwrap();
        Ok(())
    }

    /// Accepts the next incoming [`Answer`] from the listener.
    /// This function will block the calling thread until a new TCP connection is established and an answer gets recieved.
    pub fn read(&self) -> Answer {
        let (mut stream, _addr) = self.listener.accept().unwrap();
        let mut buffer = String::new();
        stream.read_to_string(&mut buffer).unwrap();
        serde_json::from_str(&buffer).unwrap()
    }

    /// Reads incoming [`Answer`] messages until an answer matches the generic.
    /// This function will block the calling thread until a new TCP connection is established and an answer gets recieved.
    pub fn wait<T: TryFrom<Answer>>(&self) -> T {
        loop {
            if let Ok(answer) = T::try_from(self.read()) {
                return answer;
            }
        }
    }
}

/// Creates a new ExternalEditorApi struct and binds the TcpListener to its socket adress.
/// This is functionally the same as using `ExternalEditorApi::new()`.
impl Default for ExternalEditorApi {
    fn default() -> Self {
        Self::new()
    }
}