You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
3.9 KiB
117 lines
3.9 KiB
mod serial;
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
use chrono::Utc;
|
|
use derive_more::From;
|
|
use reqwest::StatusCode;
|
|
use rocket::http::{Cookie, CookieJar};
|
|
use serial::*;
|
|
|
|
use serde::Deserialize;
|
|
|
|
use crate::cookies::{token::*, user::*};
|
|
|
|
#[derive(Default, Deserialize)]
|
|
pub struct User {
|
|
#[serde(deserialize_with = "deserialize_id")]
|
|
pub id: u64,
|
|
#[serde(rename = "username")]
|
|
pub name: String,
|
|
#[serde(deserialize_with = "deserialize_discriminator")]
|
|
pub discriminator: u16,
|
|
pub avatar: String,
|
|
}
|
|
|
|
impl User {
|
|
pub fn username(&self) -> String {
|
|
if self.discriminator == 0 {
|
|
return self.name.clone();
|
|
}
|
|
format!("{}#{:0>4}", self.name, self.discriminator)
|
|
}
|
|
|
|
pub async fn init(token: &str, cookies: &CookieJar<'_>) -> Result<Self, GetUserError> {
|
|
let (status, text) = {
|
|
let response = reqwest::Client::new()
|
|
.get("https://discord.com/api/users/@me")
|
|
.header("Authorization", format!("Bearer {token}"))
|
|
.send()
|
|
.await?;
|
|
(response.status(), response.text().await)
|
|
};
|
|
if !status.is_success() {
|
|
return Err(GetUserError::DiscordError {
|
|
status,
|
|
message: text.ok(),
|
|
});
|
|
}
|
|
let user: Self = serde_json::from_str(&text?)?;
|
|
cookies.add_private(Cookie::new(TOKEN_COOKIE, token.to_owned()));
|
|
cookies.add_private(Cookie::new(USER_ID_COOKIE, user.id.to_string()));
|
|
cookies.add_private(Cookie::new(USER_NAME_COOKIE, user.name.clone()));
|
|
cookies.add_private(Cookie::new(
|
|
USER_DISCRIMINATOR_COOKIE,
|
|
user.discriminator.to_string(),
|
|
));
|
|
cookies.add_private(Cookie::new(USER_AVATAR_COOKIE, user.avatar.clone()));
|
|
Ok(user)
|
|
}
|
|
|
|
pub fn purge(cookies: &CookieJar<'_>) {
|
|
cookies.remove_private(Cookie::named(TOKEN_COOKIE));
|
|
cookies.remove_private(Cookie::named(USER_ID_COOKIE));
|
|
cookies.remove_private(Cookie::named(USER_NAME_COOKIE));
|
|
cookies.remove_private(Cookie::named(USER_DISCRIMINATOR_COOKIE));
|
|
cookies.remove_private(Cookie::named(USER_AVATAR_COOKIE));
|
|
cookies.remove(Cookie::named(TOKEN_EXPIRE_COOKIE));
|
|
}
|
|
|
|
fn from_cookies(cookies: &CookieJar<'_>) -> Option<Self> {
|
|
Some(Self {
|
|
id: parse_cookie_value(cookies, USER_ID_COOKIE)?,
|
|
name: parse_cookie_value(cookies, USER_NAME_COOKIE)?,
|
|
discriminator: parse_cookie_value(cookies, USER_DISCRIMINATOR_COOKIE)?,
|
|
avatar: parse_cookie_value(cookies, USER_AVATAR_COOKIE)?,
|
|
})
|
|
}
|
|
|
|
pub async fn get(cookies: &CookieJar<'_>) -> Result<Option<Self>, GetUserError> {
|
|
let user = match Self::from_cookies(cookies) {
|
|
Some(user) => user,
|
|
None => return Ok(None),
|
|
};
|
|
if cookies
|
|
.get(TOKEN_EXPIRE_COOKIE)
|
|
.map(|expire| expire.value().parse::<i64>())
|
|
.and_then(Result::ok)
|
|
.map_or(true, |timestamp| Utc::now().timestamp() >= timestamp)
|
|
{
|
|
cookies.remove_private(Cookie::named(TOKEN_COOKIE));
|
|
cookies.remove_private(Cookie::named(USER_ID_COOKIE));
|
|
cookies.remove_private(Cookie::named(USER_NAME_COOKIE));
|
|
cookies.remove_private(Cookie::named(USER_DISCRIMINATOR_COOKIE));
|
|
cookies.remove_private(Cookie::named(USER_AVATAR_COOKIE));
|
|
cookies.remove(Cookie::named(TOKEN_EXPIRE_COOKIE));
|
|
return Ok(None);
|
|
}
|
|
Ok(Some(user))
|
|
}
|
|
}
|
|
|
|
#[derive(From, Debug)]
|
|
pub enum GetUserError {
|
|
ReqwestError(reqwest::Error),
|
|
DeserializeError(serde_json::Error),
|
|
#[allow(unused)]
|
|
DiscordError {
|
|
status: StatusCode,
|
|
message: Option<String>,
|
|
},
|
|
}
|
|
|
|
fn parse_cookie_value<T: std::str::FromStr>(cookies: &CookieJar<'_>, name: &str) -> Option<T> {
|
|
cookies.get_private(name)?.value().parse().ok()
|
|
}
|