use crate::{
packet::{Opcode, Packet},
Error, Result,
};
use serde_json::Value;
use std::io::{Read, Write};
#[cfg(windows)]
use std::fs::{File, OpenOptions};
#[cfg(unix)]
use std::{net::Shutdown, os::unix::net::UnixStream};
#[cfg(windows)]
pub struct Pipe(pub Option<File>);
#[cfg(unix)]
pub struct Pipe(pub Option<UnixStream>);
impl Pipe {
pub fn open(&mut self) -> Result<()> {
#[cfg(windows)]
{
self.0 = (0..10).find_map(|i| {
let pipe = format!(r"\\.\pipe\discord-ipc-{}", i);
OpenOptions::new().read(true).write(true).open(&pipe).ok()
});
}
#[cfg(unix)]
{
let runtime_dir = std::env::var_os("XDG_RUNTIME_DIR")
.or(std::env::var_os("TMPDIR"))
.map(std::path::PathBuf::from)
.unwrap_or(std::env::temp_dir());
let possible_dirs = [
runtime_dir.clone(),
runtime_dir.join("app").join("com.discordapp.Discord"),
runtime_dir.join("snap.discord"),
];
self.0 = (0..10).find_map(|i| {
possible_dirs.iter().find_map(|dir| {
let socket_path = dir.join(format!("discord-ipc-{}", i));
UnixStream::connect(socket_path).ok().and_then(|stream| {
stream.set_nonblocking(true).ok()?;
Some(stream)
})
})
});
}
self.0.as_ref().ok_or(Error::NoAvailablePipes)?;
Ok(())
}
pub fn close(&mut self) -> Result<()> {
let mut pipe = self.0.take().ok_or(Error::NoPipe)?;
pipe.flush()?;
#[cfg(unix)]
pipe.shutdown(Shutdown::Both)?;
Ok(())
}
pub fn send(&mut self, packet: Packet) -> Result<()> {
let pipe = self.0.as_mut().ok_or(Error::NoPipe)?;
let payload = packet.payload.to_string();
let payload_len = payload.len() as u32;
let opcode = packet.opcode as u32;
let mut header = [0u8; 8];
header[..4].copy_from_slice(&opcode.to_le_bytes());
header[4..8].copy_from_slice(&payload_len.to_le_bytes());
pipe.write_all(&header)?;
pipe.write_all(payload.as_bytes())?;
pipe.flush()?;
Ok(())
}
pub fn try_receive(&mut self) -> Result<Packet> {
let mut header = [0u8; 8];
self.try_read_exact(&mut header)?;
let (opcode, payload_len) = Packet::decode_header(&header)?;
let mut payload = vec![0u8; payload_len as usize];
self.try_read_exact(&mut payload)?;
let payload_str = String::from_utf8(payload)
.map_err(|e| Error::DecodeError(format!("Invalid UTF-8 received: {}", e)))?;
let payload = serde_json::from_str::<Value>(&payload_str)
.map_err(|e| Error::DecodeError(format!("Invalid JSON received: {}", e)))?;
Ok(Packet::new(Opcode::try_from(opcode)?, payload))
}
fn try_read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
let pipe = self.0.as_mut().ok_or(Error::NoPipe)?;
#[cfg(windows)] if pipe.metadata()?.len() == 0 {
return Err(Error::IoError(std::io::Error::new(
std::io::ErrorKind::WouldBlock,
"No data available",
)));
};
pipe.read_exact(buf)?;
Ok(())
}
}