Skip to content

Commit

Permalink
feat: add friends endpoint (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxOhn authored Oct 19, 2024
1 parent dad87b8 commit a231bd6
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 24 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ The following endpoints are currently supported:
- `comments`: Most recent comments and their replies up to two levels deep
- `events`: Collection of events in order of creation time
- `forums/topics/{topic_id}`: A forum topic and its posts
- `friends`: List of authenticated user's friends
- `matches`: List of currently open multiplayer lobbies
- `matches/{match_id}`: More specific data about a specific multiplayer lobby including participating players and occured events
- `me[/{mode}]`: Detailed info about the authenticated user [in the specified mode] (requires OAuth)
Expand Down
53 changes: 32 additions & 21 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,17 @@ impl Osu {
GetForumPosts::new(self, topic_id)
}

/// Get all friends of the authenticated user as a vec of [`User`](crate::model::user::User).
///
/// Note that the client has to be initialized with the `FriendsRead` scope
/// through the OAuth process in order for this endpoint to not return an error.
///
/// See [`OsuBuilder::with_authorization`](crate::OsuBuilder::with_authorization).
#[inline]
pub const fn friends(&self) -> GetFriends<'_> {
GetFriends::new(self)
}

/// Get the kudosu history of a user in form of a vec of
/// [`KudosuHistory`](crate::model::kudosu::KudosuHistory).
#[cfg(not(feature = "cache"))]
Expand Down Expand Up @@ -671,7 +682,7 @@ impl OsuRef {
let bytes = self.request_raw(req).await?;

// let text = String::from_utf8_lossy(&bytes);
// println!("Response:\n{}", text);
// println!("Response:\n{text}");

parse_bytes(&bytes)
}
Expand Down Expand Up @@ -715,31 +726,31 @@ impl OsuRef {
let url = Url::parse(&url).map_err(|source| OsuError::Url { source, url })?;
debug!("URL: {url}");

if let Some(ref token) = self.token.read().await.access {
let value = HeaderValue::from_str(token)
.map_err(|source| OsuError::CreatingTokenHeader { source })?;

let bytes = BodyBytes::from(body);
let Some(ref token) = self.token.read().await.access else {
return Err(OsuError::NoToken);
};

let mut req_builder = HyperRequest::builder()
.method(method)
.uri(url.as_str())
.header(AUTHORIZATION, value)
.header(USER_AGENT, MY_USER_AGENT)
.header(X_API_VERSION, api_version)
.header(ACCEPT, APPLICATION_JSON)
.header(CONTENT_LENGTH, bytes.len());
let value = HeaderValue::from_str(token)
.map_err(|source| OsuError::CreatingTokenHeader { source })?;

if !bytes.is_empty() {
req_builder = req_builder.header(CONTENT_TYPE, APPLICATION_JSON);
}
let bytes = BodyBytes::from(body);

let req = req_builder.body(bytes)?;
let mut req_builder = HyperRequest::builder()
.method(method)
.uri(url.as_str())
.header(AUTHORIZATION, value)
.header(USER_AGENT, MY_USER_AGENT)
.header(X_API_VERSION, api_version)
.header(ACCEPT, APPLICATION_JSON)
.header(CONTENT_LENGTH, bytes.len());

self.send_request(req).await
} else {
Err(OsuError::NoToken)
if !bytes.is_empty() {
req_builder = req_builder.header(CONTENT_TYPE, APPLICATION_JSON);
}

let req = req_builder.body(bytes)?;

self.send_request(req).await
}

async fn send_request(&self, req: HyperRequest<BodyBytes>) -> OsuResult<Response<HyperBody>> {
Expand Down
3 changes: 1 addition & 2 deletions src/client/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,8 @@ impl AuthorizationBuilder {
&response_type=code",
);

url.push_str("&scopes=%22");
url.push_str("&scope=");
scopes.format(&mut url, '+');
url.push_str("%22");

println!("Authorize yourself through the following url:\n{url}");
info!("Awaiting manual authorization...");
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
//! - `comments`: Most recent comments and their replies up to two levels deep
//! - `events`: Collection of events in order of creation time
//! - `forums/topics/{topic_id}`: A forum topic and its posts
//! - `friends`: List of authenticated user's friends
//! - `matches`: List of currently open multiplayer lobbies
//! - `matches/{match_id}`: More specific data about a specific multiplayer lobby including participating players and occured events
//! - `me[/{mode}]`: Detailed info about the authenticated user [in the specified mode] (requires OAuth)
Expand Down
38 changes: 37 additions & 1 deletion src/request/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl fmt::Display for UserId {

/// Get the [`UserExtended`](crate::model::user::UserExtended) of the authenticated user.
///
/// Note that the client has to be initialized with the `identify` scope
/// Note that the client has to be initialized with the `Identify` scope
/// through the OAuth process in order for this endpoint to not return an error.
///
/// See [`OsuBuilder::with_authorization`](crate::OsuBuilder::with_authorization).
Expand Down Expand Up @@ -124,6 +124,42 @@ impl<'a> GetOwnData<'a> {

poll_req!(GetOwnData => UserExtended);

/// Get all friends of the authenticated user as a vec of [`User`](crate::model::user::User).
///
/// Note that the client has to be initialized with the `FriendsRead` scope
/// through the OAuth process in order for this endpoint to not return an error.
///
/// See [`OsuBuilder::with_authorization`](crate::OsuBuilder::with_authorization).
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct GetFriends<'a> {
fut: Option<Pending<'a, Vec<User>>>,
osu: &'a Osu,
}

impl<'a> GetFriends<'a> {
#[inline]
pub(crate) const fn new(osu: &'a Osu) -> Self {
Self { fut: None, osu }
}

fn start(&mut self) -> Pending<'a, Vec<User>> {
let req = Request::new(Route::GetFriends);
let osu = self.osu;
let fut = osu.request::<Vec<User>>(req);

#[cfg(feature = "cache")]
let fut = fut.inspect_ok(move |users| {
for user in users.iter() {
osu.update_cache(user.user_id, &user.username);
}
});

Box::pin(fut)
}
}

poll_req!(GetFriends => Vec<User>);

/// Get a [`UserExtended`](crate::model::user::UserExtended) by their id.
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[derive(Serialize)]
Expand Down
3 changes: 3 additions & 0 deletions src/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub(crate) enum Route {
GetForumPosts {
topic_id: u64,
},
GetFriends,
GetMatch {
match_id: Option<u32>,
},
Expand Down Expand Up @@ -117,6 +118,7 @@ impl Route {
Self::GetForumPosts { topic_id } => {
(Method::GET, format!("forums/topics/{topic_id}").into())
}
Self::GetFriends => (Method::GET, "friends".into()),
Self::GetMatch { match_id } => {
let path = match match_id {
Some(id) => format!("matches/{id}").into(),
Expand Down Expand Up @@ -217,6 +219,7 @@ impl Route {
Self::GetComments => "GetComments",
Self::GetEvents => "GetEvents",
Self::GetForumPosts { .. } => "GetForumPosts",
Self::GetFriends => "GetFriends",
Self::GetMatch { match_id } => match match_id {
Some(_) => "GetMatch/match_id",
None => "GetMatch",
Expand Down

0 comments on commit a231bd6

Please sign in to comment.