fix(social): robot blocks
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 17s
CI / ui (pull_request) Successful in 52s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m21s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 17s
CI / ui (pull_request) Successful in 52s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m21s
Blocking an auto-match opponent who is secretly a pooled robot is recorded instead in a separate `robot_blocks` table. Now blocking behaves the same in that game (struck name, hidden composer) and lists the blocked opponent under the name you saw, but is recorded only against that game — the disguise holds, the shared robot is never globally blocked, and the matchmaker keeps pairing you with robots (so you can never block yourself out of opponents). - the shared robot account is never put in `blocks` - the matchmaker keeps it free and it is not blocked under its other per-game names - the blocked list and the in-game card still show it by joining that table; an unblock deletes the row
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
import type {
|
||||
AccountRef,
|
||||
BlockList,
|
||||
BlockStatus,
|
||||
ChatMessage,
|
||||
EvalResult,
|
||||
@@ -127,8 +128,8 @@ export interface GatewayClient {
|
||||
friendCodeRedeem(code: string): Promise<AccountRef>;
|
||||
|
||||
// --- blocks ---
|
||||
blocksList(): Promise<AccountRef[]>;
|
||||
block(accountId: string): Promise<void>;
|
||||
blocksList(): Promise<BlockList>;
|
||||
block(accountId: string, gameId?: string): Promise<void>;
|
||||
unblock(accountId: string): Promise<void>;
|
||||
|
||||
// --- invitations ---
|
||||
|
||||
+22
-5
@@ -9,6 +9,8 @@ import { indexForLetter, letterForIndex, setAlphabet, type AlphabetEntryWire } f
|
||||
import type { PlacedTile } from './client';
|
||||
import type {
|
||||
AccountRef,
|
||||
BlockList,
|
||||
RobotBlockEntry,
|
||||
Banner,
|
||||
BannerCampaign,
|
||||
BestMove,
|
||||
@@ -576,11 +578,15 @@ export function decodeEvent(kind: string, payload: Uint8Array): PushEvent | null
|
||||
|
||||
// --- social encoders ---
|
||||
|
||||
export function encodeTarget(accountId: string): Uint8Array {
|
||||
export function encodeTarget(accountId: string, gameId?: string): Uint8Array {
|
||||
const b = new Builder(64);
|
||||
const id = b.createString(accountId);
|
||||
// game_id is set only by an in-game block, so a disguised-robot opponent is recorded
|
||||
// per-game; every other target path omits it.
|
||||
const gid = gameId ? b.createString(gameId) : 0;
|
||||
fb.TargetRequest.startTargetRequest(b);
|
||||
fb.TargetRequest.addAccountId(b, id);
|
||||
if (gid) fb.TargetRequest.addGameId(b, gid);
|
||||
return finish(b, fb.TargetRequest.endTargetRequest(b));
|
||||
}
|
||||
|
||||
@@ -722,14 +728,25 @@ export function decodeOutgoingList(buf: Uint8Array): AccountRef[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function decodeBlockList(buf: Uint8Array): AccountRef[] {
|
||||
export function decodeBlockList(buf: Uint8Array): BlockList {
|
||||
const l = fb.BlockList.getRootAsBlockList(new ByteBuffer(buf));
|
||||
const out: AccountRef[] = [];
|
||||
const blocked: AccountRef[] = [];
|
||||
for (let i = 0; i < l.blockedLength(); i++) {
|
||||
const r = l.blocked(i);
|
||||
if (r) out.push(decodeAccountRef(r));
|
||||
if (r) blocked.push(decodeAccountRef(r));
|
||||
}
|
||||
return out;
|
||||
const robots: RobotBlockEntry[] = [];
|
||||
for (let i = 0; i < l.robotsLength(); i++) {
|
||||
const r = l.robots(i);
|
||||
if (r)
|
||||
robots.push({
|
||||
id: r.id() ?? '',
|
||||
displayName: r.displayName() ?? '',
|
||||
gameId: r.gameId() ?? '',
|
||||
seat: r.seat(),
|
||||
});
|
||||
}
|
||||
return { blocked, robots };
|
||||
}
|
||||
|
||||
export function decodeFriendCode(buf: Uint8Array): FriendCode {
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
import { GatewayError } from '../client';
|
||||
import type {
|
||||
AccountRef,
|
||||
BlockList,
|
||||
BlockStatus,
|
||||
ChatMessage,
|
||||
EvalResult,
|
||||
@@ -555,10 +556,13 @@ export class MockGateway implements GatewayClient {
|
||||
}
|
||||
|
||||
// --- blocks ---
|
||||
async blocksList(): Promise<AccountRef[]> {
|
||||
return this.blocks.map((b) => ({ ...b }));
|
||||
async blocksList(): Promise<BlockList> {
|
||||
// The mock models human blocks only (no disguised robots); robots stays empty.
|
||||
return { blocked: this.blocks.map((b) => ({ ...b })), robots: [] };
|
||||
}
|
||||
async block(accountId: string): Promise<void> {
|
||||
async block(accountId: string, _gameId?: string): Promise<void> {
|
||||
// A block hides the blocked person from the blocker's own friends list (the real
|
||||
// ListFriends filters them out), without deleting the underlying friendship.
|
||||
this.friends = this.friends.filter((f) => f.accountId !== accountId);
|
||||
if (!this.blocks.some((b) => b.accountId === accountId)) {
|
||||
this.blocks.push({ accountId, displayName: this.nameFor(accountId) });
|
||||
|
||||
@@ -205,6 +205,22 @@ export interface AccountRef {
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
// RobotBlockEntry is one blocked disguised-robot opponent: a per-game record (not a real
|
||||
// account) carrying the game name the player saw and the game/seat it was blocked in. Its id
|
||||
// is used to unblock it.
|
||||
export interface RobotBlockEntry {
|
||||
id: string;
|
||||
displayName: string;
|
||||
gameId: string;
|
||||
seat: number;
|
||||
}
|
||||
|
||||
// BlockList is the caller's blocked humans plus the per-game disguised-robot blocks.
|
||||
export interface BlockList {
|
||||
blocked: AccountRef[];
|
||||
robots: RobotBlockEntry[];
|
||||
}
|
||||
|
||||
/** A freshly issued one-time friend code (the plaintext is returned once). */
|
||||
export interface FriendCode {
|
||||
code: string;
|
||||
|
||||
@@ -187,8 +187,8 @@ export function createTransport(baseUrl: string): GatewayClient {
|
||||
async blocksList() {
|
||||
return codec.decodeBlockList(await exec('blocks.list', codec.empty()));
|
||||
},
|
||||
async block(accountId) {
|
||||
await exec('blocks.add', codec.encodeTarget(accountId));
|
||||
async block(accountId, gameId) {
|
||||
await exec('blocks.add', codec.encodeTarget(accountId, gameId));
|
||||
},
|
||||
async unblock(accountId) {
|
||||
await exec('blocks.remove', codec.encodeTarget(accountId));
|
||||
|
||||
Reference in New Issue
Block a user