Initial commit
This commit is contained in:
commit
8fa566754e
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
|
.env
|
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "osu_v2"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Tracreed <davidalasow@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dotenv = "0.15"
|
||||||
|
thiserror = "1.0"
|
||||||
|
reqwest = { version = "0.11", features = ["json", "blocking"] }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
actix-rt = "*"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
async-trait = "0.1.42"
|
101
src/client.rs
Normal file
101
src/client.rs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
use super::user::*;
|
||||||
|
use super::Error;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct Client {
|
||||||
|
client_id: String,
|
||||||
|
client_secret: String,
|
||||||
|
client_token: String,
|
||||||
|
base_url: String,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct AccessToken {
|
||||||
|
token_type: String,
|
||||||
|
expires_in: u64,
|
||||||
|
access_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Client {
|
||||||
|
pub async fn new<CI: Into<String>, CS: Into<String>>(
|
||||||
|
client_id: CI,
|
||||||
|
client_secret: CS,
|
||||||
|
) -> Result<self::Client, Error> {
|
||||||
|
let mut client = Client {
|
||||||
|
client_id: client_id.into(),
|
||||||
|
client_secret: client_secret.into(),
|
||||||
|
base_url: "https://osu.ppy.sh/api/v2".to_string(),
|
||||||
|
client_token: "".to_string(),
|
||||||
|
};
|
||||||
|
client.refresh_token().await.unwrap();
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
async fn get<T>(&mut self, url: String, params: T) -> Result<reqwest::Response, Error>
|
||||||
|
where
|
||||||
|
T: Into<Option<&'a[(&'a str, &'a str)]>>
|
||||||
|
{
|
||||||
|
let cli = reqwest::Client::new();
|
||||||
|
let parms = params.into();
|
||||||
|
let resp = match cli.get(&format!("{}{}", &self.base_url, &url).to_string())
|
||||||
|
.bearer_auth(&self.client_token).query(&parms).send().await {
|
||||||
|
Ok(v) => {
|
||||||
|
if v.status().is_client_error() {
|
||||||
|
self.refresh_token().await.unwrap();
|
||||||
|
cli.get(&format!("{}{}", &self.base_url, &url).to_string())
|
||||||
|
.bearer_auth(&self.client_token).query(&parms).send().await.unwrap()
|
||||||
|
} else {
|
||||||
|
v
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(why) => {return Err(Error::NotAuthenticated(why.to_string()))},
|
||||||
|
};
|
||||||
|
Ok(resp)
|
||||||
|
}
|
||||||
|
async fn refresh_token(&mut self) -> Result<(), Error> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let mut data = HashMap::new();
|
||||||
|
data.insert("client_id", self.client_id.clone());
|
||||||
|
data.insert("client_secret", self.client_secret.clone());
|
||||||
|
data.insert("grant_type", "client_credentials".to_string());
|
||||||
|
data.insert("scope", "public".to_string());
|
||||||
|
let token = match client
|
||||||
|
.post("https://osu.ppy.sh/oauth/token")
|
||||||
|
.json(&data)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(why) => return Err(Error::NotAuthenticated(why.to_string())),
|
||||||
|
}
|
||||||
|
.json::<AccessToken>()
|
||||||
|
.await.unwrap();
|
||||||
|
self.client_token = token.access_token;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl UserMethods for Client {
|
||||||
|
async fn get_user<'a>(&'a mut self, id: u64, m: String) -> Result<Box<User>, Error> {
|
||||||
|
let resp = self
|
||||||
|
.get(format!("/users/{}/{}", id, m), None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let user = resp.json::<User>().await.unwrap();
|
||||||
|
Ok(Box::new(user))
|
||||||
|
//Ok(())
|
||||||
|
}
|
||||||
|
async fn search_user<'a>(&'a mut self, name: String) -> Result<Box<SearchResult>, Error> {
|
||||||
|
let paras = [("mode", "user"), ("query", &name)];
|
||||||
|
let resp = self
|
||||||
|
.get("/search".to_string(), ¶s[..])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let users = resp.json::<SearchResult>().await.unwrap();
|
||||||
|
Ok(Box::new(users))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fn get()
|
34
src/lib.rs
Normal file
34
src/lib.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
pub mod client;
|
||||||
|
pub mod user;
|
||||||
|
use thiserror::Error;
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Couldn't authenticate")]
|
||||||
|
NotAuthenticated(String),
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::client::*;
|
||||||
|
use std::env;
|
||||||
|
extern crate dotenv;
|
||||||
|
use crate::user::UserMethods;
|
||||||
|
use std::mem::size_of;
|
||||||
|
use crate::user::User;
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn it_works() {
|
||||||
|
assert_eq!(2 + 2, 4);
|
||||||
|
dotenv::dotenv().expect("Failed to load .env file");
|
||||||
|
let client_id = env::var("OSU_ID").expect("Expected a token in the environment");
|
||||||
|
let client_secret = env::var("OSU_SECRET").expect("Expected a token in the environment");
|
||||||
|
let mut client = Client::new(client_id, client_secret).await.unwrap();
|
||||||
|
let users = client.search_user("beetroot".to_string()).await.unwrap();
|
||||||
|
let user = client
|
||||||
|
.get_user(users.user.data[0].id, "osu".to_string())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
println!("{:?}", client);
|
||||||
|
println!("{:?}", users.user.data[0]);
|
||||||
|
println!("{:?}", user);
|
||||||
|
println!("{}", size_of::<User>())
|
||||||
|
}
|
||||||
|
}
|
0
src/types.rs
Normal file
0
src/types.rs
Normal file
171
src/user.rs
Normal file
171
src/user.rs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
use super::Error;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq)]
|
||||||
|
pub struct User {
|
||||||
|
pub id: i64,
|
||||||
|
pub username: String,
|
||||||
|
pub profile_colour: Option<String>,
|
||||||
|
pub avatar_url: String,
|
||||||
|
pub country_code: String,
|
||||||
|
pub default_group: String,
|
||||||
|
pub is_active: bool,
|
||||||
|
pub is_bot: bool,
|
||||||
|
pub is_deleted: bool,
|
||||||
|
pub is_online: bool,
|
||||||
|
pub is_supporter: bool,
|
||||||
|
pub last_visit: Option<String>,
|
||||||
|
pub pm_friends_only: bool,
|
||||||
|
pub cover_url: String,
|
||||||
|
pub discord: Option<String>,
|
||||||
|
pub has_supported: bool,
|
||||||
|
pub interests: Option<String>,
|
||||||
|
pub join_date: String,
|
||||||
|
pub kudosu: Kudosu,
|
||||||
|
pub location: Option<String>,
|
||||||
|
pub max_blocks: u64,
|
||||||
|
pub max_friends: u64,
|
||||||
|
pub occupation: Option<String>,
|
||||||
|
pub playmode: String,
|
||||||
|
pub playstyle: Option<Vec<String>>,
|
||||||
|
pub post_count: u64,
|
||||||
|
pub profile_order: Vec<String>,
|
||||||
|
pub skype: Option<String>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub twitter: Option<String>,
|
||||||
|
pub website: Option<String>,
|
||||||
|
pub country: Country,
|
||||||
|
pub cover: Cover,
|
||||||
|
pub is_admin: Option<bool>,
|
||||||
|
pub is_bng: Option<bool>,
|
||||||
|
pub is_full_bn: Option<bool>,
|
||||||
|
pub is_gmt: Option<bool>,
|
||||||
|
pub is_limited_bn: Option<bool>,
|
||||||
|
pub is_moderator: Option<bool>,
|
||||||
|
pub is_nat: Option<bool>,
|
||||||
|
pub is_restricted: Option<bool>,
|
||||||
|
pub is_silenced: Option<bool>,
|
||||||
|
pub statistics: UserStatistics,
|
||||||
|
pub rank_history: Option<RankHistory>,
|
||||||
|
pub user_achivements: Option<Vec<UserAchivement>>,
|
||||||
|
pub beatmap_playcounts_count: u64,
|
||||||
|
pub favourite_beatmapset_count: u64,
|
||||||
|
pub follower_count: u64,
|
||||||
|
pub graveyard_beatmapset_count: u64,
|
||||||
|
pub monthly_playcounts: Option<Vec<MonthlyPlayCount>>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct CompactUser {
|
||||||
|
pub avatar_url: String,
|
||||||
|
pub country_code: String,
|
||||||
|
pub default_group: String,
|
||||||
|
pub id: u64,
|
||||||
|
pub is_active: bool,
|
||||||
|
pub is_bot: bool,
|
||||||
|
pub is_deleted: bool,
|
||||||
|
pub is_online: bool,
|
||||||
|
pub is_supporter: bool,
|
||||||
|
pub last_visit: Option<String>,
|
||||||
|
pub pm_friends_only: bool,
|
||||||
|
pub profile_colour: Option<String>,
|
||||||
|
pub username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Kudosu {
|
||||||
|
pub total: u64,
|
||||||
|
pub available: u64,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct RankHistory {
|
||||||
|
pub mode: String,
|
||||||
|
pub data: Vec<u32>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct MonthlyPlayCount {
|
||||||
|
pub start_date: String,
|
||||||
|
pub count: u64,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct UserAchivement {
|
||||||
|
pub achieved_at: String,
|
||||||
|
pub achievement_id: u32,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct SearchResult {
|
||||||
|
pub user: SearchData,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct SearchData {
|
||||||
|
pub data: Vec<CompactUser>,
|
||||||
|
pub total: u32,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq)]
|
||||||
|
pub struct UserStatistics {
|
||||||
|
pub grade_counts: GradeCounts,
|
||||||
|
pub hit_accuracy: f32,
|
||||||
|
pub is_ranked: bool,
|
||||||
|
pub level: Level,
|
||||||
|
pub maximum_combo: u64,
|
||||||
|
pub play_count: u64,
|
||||||
|
pub play_time: Option<u64>,
|
||||||
|
pub pp: f32,
|
||||||
|
pub ranked_score: u64,
|
||||||
|
pub replays_watched_by_others: u64,
|
||||||
|
pub total_hits: u64,
|
||||||
|
pub total_score: u64,
|
||||||
|
pub rank: UserRank,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct UserRank {
|
||||||
|
pub global: Option<u64>,
|
||||||
|
pub country: Option<u64>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Level {
|
||||||
|
pub current: u8,
|
||||||
|
pub progress: u16,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct GradeCounts {
|
||||||
|
pub a: u64,
|
||||||
|
pub s: u64,
|
||||||
|
pub sh: u64,
|
||||||
|
pub ss: u64,
|
||||||
|
pub ssh: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Country {
|
||||||
|
pub code: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Cover {
|
||||||
|
custom_url: Option<String>,
|
||||||
|
url: String,
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(deserialize_with = "parse_to_int")]
|
||||||
|
id: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_to_int<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let stri: Option<String> = Option::deserialize(deserializer)?;
|
||||||
|
if let Some(s) = stri {
|
||||||
|
match s.parse::<u64>() {
|
||||||
|
Ok(v) => return Ok(Some(v)),
|
||||||
|
Err(_) => return Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait UserMethods {
|
||||||
|
async fn get_user<'a>(&'a mut self, id: u64, m: String) -> Result<Box<User>, Error>;
|
||||||
|
async fn search_user<'a>(&'a mut self, name: String) -> Result<Box<SearchResult>, Error>;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user