import type { SourceRCONCommandRequest } from "../api/types"; const maxChatBytes = 1024; const maxCommandBytes = 4000; const steamID64 = /^[0-9]{17}$/; export interface SourceRCONChatDraft { chatType: number; message: string; targetSteamId?: string; } export function sourceRCONChatRequest(serverInstanceId: string, draft: SourceRCONChatDraft, sequence = Date.now()): SourceRCONCommandRequest { const message = validateSourceRCONText(draft.message, maxChatBytes, "聊天内容"); if (!Number.isInteger(draft.chatType) || draft.chatType < 0 || draft.chatType > 7) { throw new Error("聊天类型必须在 0 到 7 之间。"); } const targetSteamId = draft.targetSteamId?.trim() ?? ""; if (targetSteamId && !steamID64.test(targetSteamId)) { throw new Error("目标 SteamID64 必须为 17 位数字。"); } return { kind: "chat", chatType: draft.chatType, message, targetSteamId: targetSteamId || undefined, idempotencyKey: sourceRCONIdempotencyKey("chat", serverInstanceId, sequence) }; } export function sourceRCONRawCommandRequest(serverInstanceId: string, command: string, sequence = Date.now()): SourceRCONCommandRequest { return { kind: "command", command: validateSourceRCONText(command, maxCommandBytes, "原始指令"), idempotencyKey: sourceRCONIdempotencyKey("command", serverInstanceId, sequence) }; } function validateSourceRCONText(value: string, maxBytes: number, label: string): string { const normalized = value.trim(); if (!normalized || new TextEncoder().encode(normalized).byteLength > maxBytes || /[\u0000\r\n]/.test(normalized)) { throw new Error(`${label}必须是受限的单行文本。`); } return normalized; } function sourceRCONIdempotencyKey(kind: "chat" | "command", serverInstanceId: string, sequence: number): string { return `web:source-rcon:${kind}:${serverInstanceId.trim()}:${Math.max(0, Math.floor(sequence))}`; }