50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
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))}`;
|
|
}
|