package backendclient import ( "context" "net/http" "net/url" ) // The Stage 8 response structs and client methods mirror the backend's social, // account and history JSON DTOs. The transcode layer maps them to FlatBuffers. // AccountRefResp is a referenced account with its display name resolved. type AccountRefResp struct { AccountID string `json:"account_id"` DisplayName string `json:"display_name"` } // FriendListResp is the caller's accepted friends. type FriendListResp struct { Friends []AccountRefResp `json:"friends"` } // IncomingListResp is the friend requests awaiting the caller. type IncomingListResp struct { Requests []AccountRefResp `json:"requests"` } // FriendCodeResp is a freshly issued one-time friend code. type FriendCodeResp struct { Code string `json:"code"` ExpiresAtUnix int64 `json:"expires_at_unix"` } // RedeemResultResp reports the friend gained by redeeming a code. type RedeemResultResp struct { Friend AccountRefResp `json:"friend"` } // BlockListResp is the accounts the caller has blocked. type BlockListResp struct { Blocked []AccountRefResp `json:"blocked"` } // StatsResp is a durable account's lifetime statistics. type StatsResp struct { Wins int `json:"wins"` Losses int `json:"losses"` Draws int `json:"draws"` MaxGamePoints int `json:"max_game_points"` MaxWordPoints int `json:"max_word_points"` } // InvitationInviteeResp is one invitee's seat and response with their name. type InvitationInviteeResp struct { AccountID string `json:"account_id"` DisplayName string `json:"display_name"` Seat int `json:"seat"` Response string `json:"response"` } // InvitationResp is a friend-game invitation with its settings and invitees. type InvitationResp struct { ID string `json:"id"` Inviter AccountRefResp `json:"inviter"` Invitees []InvitationInviteeResp `json:"invitees"` Variant string `json:"variant"` TurnTimeoutSecs int `json:"turn_timeout_secs"` HintsAllowed bool `json:"hints_allowed"` HintsPerPlayer int `json:"hints_per_player"` DropoutTiles string `json:"dropout_tiles"` Status string `json:"status"` GameID string `json:"game_id"` ExpiresAtUnix int64 `json:"expires_at_unix"` } // InvitationListResp is the caller's open invitations. type InvitationListResp struct { Invitations []InvitationResp `json:"invitations"` } // GcgResp is a finished game's GCG export. type GcgResp struct { GameID string `json:"game_id"` Filename string `json:"filename"` Content string `json:"content"` } // InvitationParams are the settings the inviter chooses for a friend game. type InvitationParams struct { InviteeIDs []string Variant string TurnTimeoutSecs int HintsAllowed bool HintsPerPlayer int DropoutTiles string } // --- friends --- // SendFriendRequest sends a friend request to a played opponent. func (c *Client) SendFriendRequest(ctx context.Context, userID, targetID string) error { return c.do(ctx, http.MethodPost, "/api/v1/user/friends/request", userID, "", map[string]string{"account_id": targetID}, nil) } // RespondFriendRequest accepts or declines an incoming request. func (c *Client) RespondFriendRequest(ctx context.Context, userID, requesterID string, accept bool) error { return c.do(ctx, http.MethodPost, "/api/v1/user/friends/respond", userID, "", map[string]any{"requester_id": requesterID, "accept": accept}, nil) } // CancelFriendRequest withdraws the caller's own pending request. func (c *Client) CancelFriendRequest(ctx context.Context, userID, targetID string) error { return c.do(ctx, http.MethodPost, "/api/v1/user/friends/cancel", userID, "", map[string]string{"account_id": targetID}, nil) } // Unfriend removes a friendship. func (c *Client) Unfriend(ctx context.Context, userID, targetID string) error { return c.do(ctx, http.MethodDelete, "/api/v1/user/friends/"+url.PathEscape(targetID), userID, "", nil, nil) } // ListFriends returns the caller's accepted friends. func (c *Client) ListFriends(ctx context.Context, userID string) (FriendListResp, error) { var out FriendListResp err := c.do(ctx, http.MethodGet, "/api/v1/user/friends", userID, "", nil, &out) return out, err } // ListIncoming returns the friend requests awaiting the caller. func (c *Client) ListIncoming(ctx context.Context, userID string) (IncomingListResp, error) { var out IncomingListResp err := c.do(ctx, http.MethodGet, "/api/v1/user/friends/incoming", userID, "", nil, &out) return out, err } // IssueFriendCode issues a one-time friend code for the caller. func (c *Client) IssueFriendCode(ctx context.Context, userID string) (FriendCodeResp, error) { var out FriendCodeResp err := c.do(ctx, http.MethodPost, "/api/v1/user/friends/code", userID, "", struct{}{}, &out) return out, err } // RedeemFriendCode redeems a friend code, befriending its issuer. func (c *Client) RedeemFriendCode(ctx context.Context, userID, code string) (RedeemResultResp, error) { var out RedeemResultResp err := c.do(ctx, http.MethodPost, "/api/v1/user/friends/code/redeem", userID, "", map[string]string{"code": code}, &out) return out, err } // --- blocks --- // Block blocks an account. func (c *Client) Block(ctx context.Context, userID, targetID string) error { return c.do(ctx, http.MethodPost, "/api/v1/user/blocks", userID, "", map[string]string{"account_id": targetID}, nil) } // Unblock removes a block. func (c *Client) Unblock(ctx context.Context, userID, targetID string) error { return c.do(ctx, http.MethodDelete, "/api/v1/user/blocks/"+url.PathEscape(targetID), userID, "", nil, nil) } // ListBlocks returns the accounts the caller has blocked. func (c *Client) ListBlocks(ctx context.Context, userID string) (BlockListResp, error) { var out BlockListResp err := c.do(ctx, http.MethodGet, "/api/v1/user/blocks", userID, "", nil, &out) return out, err } // --- invitations --- // CreateInvitation proposes a friend game to the named invitees. func (c *Client) CreateInvitation(ctx context.Context, userID string, p InvitationParams) (InvitationResp, error) { var out InvitationResp body := map[string]any{ "invitee_ids": p.InviteeIDs, "variant": p.Variant, "turn_timeout_secs": p.TurnTimeoutSecs, "hints_allowed": p.HintsAllowed, "hints_per_player": p.HintsPerPlayer, "dropout_tiles": p.DropoutTiles, } err := c.do(ctx, http.MethodPost, "/api/v1/user/invitations", userID, "", body, &out) return out, err } // RespondInvitation accepts or declines an invitation by id. func (c *Client) RespondInvitation(ctx context.Context, userID, invitationID string, accept bool) (InvitationResp, error) { var out InvitationResp action := "/decline" if accept { action = "/accept" } err := c.do(ctx, http.MethodPost, "/api/v1/user/invitations/"+url.PathEscape(invitationID)+action, userID, "", struct{}{}, &out) return out, err } // CancelInvitation withdraws the caller's own invitation. func (c *Client) CancelInvitation(ctx context.Context, userID, invitationID string) error { return c.do(ctx, http.MethodDelete, "/api/v1/user/invitations/"+url.PathEscape(invitationID), userID, "", nil, nil) } // ListInvitations returns the caller's open invitations. func (c *Client) ListInvitations(ctx context.Context, userID string) (InvitationListResp, error) { var out InvitationListResp err := c.do(ctx, http.MethodGet, "/api/v1/user/invitations", userID, "", nil, &out) return out, err } // --- profile, email, stats, gcg --- // UpdateProfile overwrites the caller's editable profile and returns it. func (c *Client) UpdateProfile(ctx context.Context, userID string, p ProfileResp) (ProfileResp, error) { var out ProfileResp body := map[string]any{ "display_name": p.DisplayName, "preferred_language": p.PreferredLanguage, "time_zone": p.TimeZone, "away_start": p.AwayStart, "away_end": p.AwayEnd, "block_chat": p.BlockChat, "block_friend_requests": p.BlockFriendRequests, } err := c.do(ctx, http.MethodPut, "/api/v1/user/profile", userID, "", body, &out) return out, err } // EmailBindRequest asks the backend to mail a confirm-code binding email. func (c *Client) EmailBindRequest(ctx context.Context, userID, email string) error { return c.do(ctx, http.MethodPost, "/api/v1/user/email/request", userID, "", map[string]string{"email": email}, nil) } // EmailBindConfirm verifies the code and binds the email, returning the profile. func (c *Client) EmailBindConfirm(ctx context.Context, userID, email, code string) (ProfileResp, error) { var out ProfileResp err := c.do(ctx, http.MethodPost, "/api/v1/user/email/confirm", userID, "", map[string]string{"email": email, "code": code}, &out) return out, err } // Stats returns the caller's lifetime statistics. func (c *Client) Stats(ctx context.Context, userID string) (StatsResp, error) { var out StatsResp err := c.do(ctx, http.MethodGet, "/api/v1/user/stats", userID, "", nil, &out) return out, err } // ExportGCG returns a finished game's GCG transcript. func (c *Client) ExportGCG(ctx context.Context, userID, gameID string) (GcgResp, error) { var out GcgResp err := c.do(ctx, http.MethodGet, c.gamePath(gameID, "/gcg"), userID, "", nil, &out) return out, err }