Add SCUM Source RCON transport

This commit is contained in:
npc0-hue
2026-07-23 10:55:31 +08:00
parent ec96c22e4c
commit 742f96ea02
33 changed files with 1297 additions and 19 deletions
+49
View File
@@ -0,0 +1,49 @@
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))}`;
}