vrchat_box/
lib.rs

1//! Very simple VRChat OSC crate, only for the in-game chatbox (other osc endpoints coming 2050)
2//!
3//! Provides [Client] which can send "chatbox/input" or "chatbox/typing" messages to any address you set.
4//!
5
6use rosc::{encoder, OscMessage, OscPacket, OscType};
7use std::{
8    io,
9    net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
10};
11
12/// The address VRChat listens for OSC messages on if not changed in the VRChat config.json.
13pub const VRCHAT_OSC_ADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9000);
14
15/// Build a [Client] with the pattern everyone likes.
16#[derive(Copy, Clone, Debug)]
17pub struct ClientBuilder {
18    client: SocketAddr,
19    server: SocketAddr,
20}
21
22/// Contains the socket to connect to VRChat OSC to send messages.
23///
24/// Can either be created with a [ClientBuilder] or [Client::new]
25#[derive(Debug)]
26pub struct Client {
27    client_socket: UdpSocket,
28    server_address: SocketAddr,
29}
30
31impl Client {
32    /// Create a [Client] without [ClientBuilder].
33    /// client_socket.
34    pub fn new(server_address: SocketAddr, client_socket: UdpSocket) -> Self {
35        Client {
36            client_socket,
37            server_address,
38        }
39    }
40}
41
42impl ClientBuilder {
43    /// Create a new [ClientBuilder].
44    /// Without changes this will build as a [Client] with a socket, on a port chosen by the OS, connected to [VRCHAT_OSC_ADDR].
45    pub fn new() -> Self {
46        ClientBuilder {
47            client: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0),
48            server: VRCHAT_OSC_ADDR,
49        }
50    }
51
52    pub fn with_server_ip(mut self, ip: IpAddr) -> ClientBuilder {
53        self.server.set_ip(ip);
54        self
55    }
56
57    pub fn with_server_port(mut self, port: u16) -> ClientBuilder {
58        self.server.set_port(port);
59        self
60    }
61
62    /// Port is already chosen by the OS if this is not used.
63    pub fn with_client_port(mut self, port: u16) -> ClientBuilder {
64        self.client.set_port(port);
65        self
66    }
67
68    /// Build the [Client].
69    /// Err when it cannot bind to the set port or connect to the set server address.
70    pub fn build(&self) -> io::Result<Client> {
71        let socket = UdpSocket::bind(self.client)?;
72        socket.connect(self.server)?;
73        Ok(Client {
74            client_socket: socket,
75            server_address: self.server,
76        })
77    }
78}
79
80impl Client {
81    /// Change the address (ip and port) that the client sends OSC messages to.
82    ///
83    /// Err if the socket could not connect to the new address
84    pub fn change_server_address(&mut self, new_address: SocketAddr) -> io::Result<()> {
85        self.change_server_ip(new_address.ip())?;
86        self.change_server_port(new_address.port())?;
87        Ok(())
88    }
89
90    /// Change the ip that the client sends OSC messages to.
91    ///
92    /// Err if the socket could not connect to the new address.
93    pub fn change_server_ip(&mut self, new_ip: IpAddr) -> io::Result<()> {
94        self.server_address.set_ip(new_ip);
95        self.client_socket.connect(self.server_address)?;
96        Ok(())
97    }
98
99    /// Change the port that the client sends OSC messages to.
100    ///
101    /// Err if the socket could not connect to the new address.
102    pub fn change_server_port(&mut self, new_port: u16) -> io::Result<()> {
103        self.server_address.set_port(new_port);
104        self.client_socket.connect(self.server_address)?;
105        Ok(())
106    }
107
108    /// Set the visibility of the typing indicator.
109    ///
110    /// Err if the client is not connected to anything
111    ///
112    /// Panics if it could not encode an OscMessage (Should not happen, and if it does, not much a downstream user can do about it anyway)
113    pub fn typing_indicator(&self, toggle: bool) -> io::Result<()> {
114        match encoder::encode(&OscPacket::Message(OscMessage {
115            addr: String::from("/chatbox/typing"),
116            args: vec![OscType::Bool(toggle)],
117        })) {
118            Err(e) => {
119                panic!("vrchat-box error when encoding osc message: {e}")
120            }
121            Ok(msg_buf) => {
122                self.client_socket.send(&msg_buf)?;
123            }
124        }
125        Ok(())
126    }
127
128    /// Send a chatbox message.
129    ///
130    /// Err if the client is not connected to anything.
131    ///
132    /// Panics if it could not encode an OscMessage (Should not happen, and if it does, not much a downstream user can do about it anyway)
133    pub fn send_message(&self, msg: &str, keyboard: bool, sfx: bool) -> io::Result<()> {
134        match encoder::encode(&OscPacket::Message(OscMessage {
135            addr: String::from("/chatbox/input"),
136            args: vec![
137                OscType::String(msg.to_string()),
138                OscType::Bool(keyboard),
139                OscType::Bool(sfx),
140            ],
141        })) {
142            Err(e) => {
143                panic!("vrchat-box error when encoding osc message: {e}")
144            }
145            Ok(msg_buf) => {
146                self.client_socket.send(&msg_buf)?;
147            }
148        }
149        Ok(())
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn does_it_work() {
159        let vrc_client = ClientBuilder::new()
160            .with_server_ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)))
161            .with_server_port(9000)
162            .build();
163        assert!(vrc_client.is_ok());
164    }
165
166    #[test]
167    fn does_it_send() {
168        let server = UdpSocket::bind(SocketAddr::new(VRCHAT_OSC_ADDR.ip(), 0)).unwrap();
169        let server_addr = server.local_addr().unwrap();
170        server.set_nonblocking(true).unwrap();
171
172        let client = ClientBuilder::new()
173            .with_server_ip(server_addr.ip())
174            .with_server_port(server_addr.port())
175            .build()
176            .unwrap();
177
178        assert!(client
179            .send_message("helloooooOO!!!!!", false, false)
180            .is_ok());
181
182        let mut buf = [0; 8];
183        server.recv(&mut buf).unwrap();
184    }
185}