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