diff --git a/src/client/ipc.rs b/src/client/ipc.rs new file mode 100644 index 0000000..ebb6a20 --- /dev/null +++ b/src/client/ipc.rs @@ -0,0 +1,116 @@ +use std::os::unix::net::UnixStream; +use std::env::var; +use std::error::Error; +use std::io::{Read, Write}; +use std::path::PathBuf; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use log::debug; +use serde_json::{json, Value}; + +use crate::models::client::{payload::Payload, + payload::OpCode, + commands::Commands}; + +pub struct DiscordClient { + pub id: String, + socket: Option, +} + +impl DiscordClient { + pub fn new(id: &str) -> Self { + let mut client = Self { + id: id.to_string(), + socket: None, + }; + + client.connect().expect("Could not connect to client. Is Discord running ?"); + client + } + + pub fn send_payload(&mut self, payload: Payload) -> Result<(u32, Value), Box> { + let payload = json!({ + "cmd": Commands::SetActivity.as_string(), + "args": { + "pid": std::process::id(), + payload.event_name: payload.event_data, + }, + "nonce": uuid::Uuid::new_v4().to_string(), + }); + + Ok(self.send(payload, OpCode::MESSAGE as u8)?) + } + + fn socket(&mut self) -> &mut UnixStream { + self.socket.as_mut().unwrap() + } + + fn connect(&mut self) -> Result<(), Box> { + let path = self.fetch_process_pathbuf().join("discord-ipc-0"); + + match UnixStream::connect(&path) { + Ok(socket) => { + self.socket = Some(socket); + + self.handshake().expect("Could not handshake."); + } + Err(_) => panic!("Could not connect to client. Is Discord running ?"), + } + + Ok(()) + } + + fn fetch_process_pathbuf(&mut self) -> PathBuf { + let mut path = String::new(); + + for key in ["XDG_RUNTIME_DIR", "TMPDIR", "TMP"] { + match var(key) { + Ok(val) => { + path = val; + break; + } + _ => continue, + } + } + + PathBuf::from(path) + } + + fn handshake(&mut self) -> Result<(u32, Value), Box> { + let payload = json!({ "v": 1, "client_id": self.id}); + + Ok(self.send(payload, OpCode::HANDSHAKE as u8)?) + } + + fn send(&mut self, payload: Value, opcode: u8) -> Result<(u32, Value), Box> { + let payload = payload.to_string(); + let mut data: Vec = Vec::new(); + + data.write_u32::(opcode as u32)?; + data.write_u32::(payload.len() as u32)?; + data.write_all(payload.as_bytes())?; + + self.socket().write_all(&data)?; + Ok(self.recv()?) + } + + fn recv(&mut self) -> Result<(u32, Value), Box> { + let mut buf = [0; 2048]; + + let byte_count = self.socket().read(&mut buf)?; + let (op, payload) = self.extract_payload(&buf[..byte_count])?; + let json_data = serde_json::from_str::(&payload)?; + + debug!("{:?}", json_data); + + Ok((op, json_data)) + } + + fn extract_payload(&mut self, mut data: &[u8]) -> Result<(u32, String), Box> { + let opcode = data.read_u32::()?; + let payload_len = data.read_u32::()? as usize; + let mut payload = String::with_capacity(payload_len); + data.read_to_string(&mut payload)?; + + Ok((opcode, payload)) + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs new file mode 100644 index 0000000..1350c45 --- /dev/null +++ b/src/client/mod.rs @@ -0,0 +1 @@ +pub mod ipc; \ No newline at end of file