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<str>) -> 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;
}
}
}
}