use crate::error::DiscordError; use crate::gateway::{GateMessage, GateUrl, OpCode}; use crate::route::Route; use crate::{request_url, GATEWAY_VERSION}; use log::info; use serde_json::Value; use std::time::Duration; use tungstenite::connect; use ureq::{Agent, AgentBuilder}; #[derive(Debug)] pub struct Client { http: Agent, secret: String, } impl Client { pub fn new(secret: impl AsRef) -> Self { let http = AgentBuilder::new().build(); let secret = secret.as_ref().to_string(); Client { http, secret } } pub fn run(self) -> Result<(), DiscordError> { let url = request_url(Route::Gateway); let url = format!("{}?v={}&encoding=json", url, GATEWAY_VERSION); let gateurl: GateUrl = self.http.get(&url).call()?.into_json()?; info!("Connecting to websocket stream."); let (mut socket, _response) = connect(&gateurl.url)?; match socket.get_mut() { tungstenite::stream::Stream::Plain(s) => { s.set_nonblocking(true)?; } tungstenite::stream::Stream::Tls(s) => { s.get_mut().set_nonblocking(true)?; } }; info!("Reading hello message."); let hello: GateMessage = serde_json::from_str(&socket.read_message()?.into_text()?)?; assert_eq!(hello.op, OpCode::Hello as u8); let interval = hello.d.get("heartbeat_interval").unwrap().as_u64().unwrap(); info!("Heartbeat interval is {} ms.", &interval); let heartbeat = GateMessage { op: OpCode::HeartBeat as u8, d: Value::default(), s: None, t: None, }; let mut delay = 0; loop { match socket.read_message() { Ok(m) => { let message: GateMessage = serde_json::from_str(&m.into_text()?)?; match message.op { 1 => { info!("Re-heartbeating."); socket.write_message(serde_json::to_string(&heartbeat)?.into())?; } 11 => info!("Heartbeat acknowledged."), _ => info!("Other message: {:?}", &message), } } Err(tungstenite::Error::Io(e)) if e.kind() == std::io::ErrorKind::WouldBlock => {} Err(e) => return Err(e.into()), }; std::thread::sleep(Duration::from_millis(100)); delay += 100; if delay >= interval { info!("Heartbeating."); socket.write_message(serde_json::to_string(&heartbeat)?.into())?; delay = 0; } } } }