From cd9d5be691412d605ec8eafb34e9b6f4d7aecc12 Mon Sep 17 00:00:00 2001 From: AlxcBTW <187763517+AlxcBTW@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:26:58 +0100 Subject: [PATCH] control: score_privacy and set_score_privacy commands (G12) The launcher needs to read and write the account's score audience. Both go to GET/PUT /v1/me/score-privacy and work while the leaderboard feature is off. --- crates/control/src/lib.rs | 19 +++++++++++++++++++ crates/net/src/cloud.rs | 17 ++++++++++++++--- crates/net/src/service.rs | 16 ++++++++++++++++ spec/control-protocol.md | 2 ++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/crates/control/src/lib.rs b/crates/control/src/lib.rs index 6385f7b..538a8fa 100644 --- a/crates/control/src/lib.rs +++ b/crates/control/src/lib.rs @@ -190,6 +190,12 @@ pub enum Command { SetPublicProfile { on: bool, }, + /// Who sees the player's scores on boards: `ScorePrivacy`. + ScorePrivacy, + /// `{scores}`: `friends`, `everyone` or `nobody`. + SetScorePrivacy { + scores: anyplay_api::Audience, + }, /// The people the player played with recently: `{remember, people}`. PlayedWith, /// Empties that list. @@ -1528,6 +1534,19 @@ mod tests { parse_command(r#"{"id":8,"do":"set_public_profile","on":true}"#), Ok((8, Command::SetPublicProfile { on: true })) ); + assert_eq!( + parse_command(r#"{"id":20,"do":"score_privacy"}"#), + Ok((20, Command::ScorePrivacy)) + ); + assert_eq!( + parse_command(r#"{"id":21,"do":"set_score_privacy","scores":"nobody"}"#), + Ok(( + 21, + Command::SetScorePrivacy { + scores: anyplay_api::Audience::Nobody + } + )) + ); assert_eq!( parse_command(r#"{"id":9,"do":"played_with"}"#), Ok((9, Command::PlayedWith)) diff --git a/crates/net/src/cloud.rs b/crates/net/src/cloud.rs index cbd4fd6..9dc403d 100644 --- a/crates/net/src/cloud.rs +++ b/crates/net/src/cloud.rs @@ -6,15 +6,15 @@ use std::time::Duration; use anyplay_api::client::{CLIENT_HEADER, ClientId}; use anyplay_api::{ AcceptShare, AccountId, AccountLink, AccountTrophies, AddToConversation, Added, ApiError, - AppealRequest, AppearOffline, BrowserTokenRequest, Cards, CodeAnswer, CodeRequest, + AppealRequest, AppearOffline, Audience, BrowserTokenRequest, Cards, CodeAnswer, CodeRequest, Conversation, Conversations, CreateConversation, CreateSession, CreateShare, Device, DiscordCodeExchange, DiscordRefresh, DiscordTokens, EmailAnswer, EmailRequest, ErrorCode, FriendLists, FriendRequest, FriendRequestAnswer, Info, Invite, Invited, JoinCode, JoinSession, MarkRead, Me, Message, Messages, MoveParty, Party, PartyEndpoint, PartyInvitations, PlayedWithList, PlaytimeAdded, PlaytimeEntry, PlaytimeUpload, PresenceRequest, Privacy, ProfilePage, PublicProfile, Rarity, ReactionRequest, Reactions, Redeemed, RefreshRequest, - Relays, RememberPlayedWith, Report, RunReport, RunSummary, SecretsChanged, SendMessage, - Session, Share, ShareInvitations, SignedIn, SignupRequest, Statement, Statements, + Relays, RememberPlayedWith, Report, RunReport, RunSummary, ScorePrivacy, SecretsChanged, + SendMessage, Session, Share, ShareInvitations, SignedIn, SignupRequest, Statement, Statements, TicketRequest, Tokens, TrophiesAdded, Trophy, TrophyCompare, TrophyNumbers, TrophyUpload, UpdateParty, UpdateSession, UpdateShare, path, }; @@ -750,6 +750,17 @@ impl Cloud { .await } + pub async fn score_privacy(&self) -> Result { + self.call::<(), _>(Method::GET, path::SCORE_PRIVACY, None) + .await + } + + pub async fn set_score_privacy(&self, scores: Audience) -> Result { + let body = ScorePrivacy { scores }; + self.call(Method::PUT, path::SCORE_PRIVACY, Some(&body)) + .await + } + pub async fn privacy(&self) -> Result { self.call::<(), _>(Method::GET, path::PRIVACY, None).await } diff --git a/crates/net/src/service.rs b/crates/net/src/service.rs index f157611..2803490 100644 --- a/crates/net/src/service.rs +++ b/crates/net/src/service.rs @@ -1041,6 +1041,22 @@ impl Service { |_, p| p, ); } + Command::ScorePrivacy => { + let Some(cloud) = self.signed_in_cloud(id) else { + return; + }; + self.request(id, async move { cloud.score_privacy().await }, |_, p| p); + } + Command::SetScorePrivacy { scores } => { + let Some(cloud) = self.signed_in_cloud(id) else { + return; + }; + self.request( + id, + async move { cloud.set_score_privacy(scores).await }, + |_, p| p, + ); + } Command::Privacy => { let Some(cloud) = self.signed_in_cloud(id) else { return; diff --git a/spec/control-protocol.md b/spec/control-protocol.md index 73108b6..54db060 100644 --- a/spec/control-protocol.md +++ b/spec/control-protocol.md @@ -108,6 +108,8 @@ When the cloud retires this launcher's version ([cloud API](cloud-api.md#convent | `played_with` | | `{remember, people}`: the people the player played with recently ([Played with recently](cloud-api.md#played-with-recently)) | | `clear_played_with` | | `{}`: empties that list | | `set_remember_played_with` | `on` | `{remember_played_with}`: the new setting | +| `score_privacy` | | `{scores}`: who sees the player's scores on boards (`friends`, `everyone` or `nobody`; `friends` for a new account), see [Leaderboards](cloud-api.md#leaderboards); works while the leaderboard feature is off | +| `set_score_privacy` | `scores` | `{scores}`: the new setting | | `privacy` | | the player's `Privacy` | | `set_privacy` | `privacy` | the new `Privacy` | | `profile` | `account` | `ProfilePage`: the `Profile` with the parts the player may see of that account, their trophy counts (null when hidden) and the connections they show (`links`) |