Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/workspace-september-updates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@sfmc-bds/sdk": patch
"@sfmc-bds/cli": patch
"@sfmc-bds/bds-tools": patch
"@sfmc-bds/db-server": patch
"@sfmc-bds/qq-bridge": patch
---

完善聊天频道来源与 QQ 消息转发、提交后事件通知和模块伴生服务加载;改进命令菜单与资源包展示,并支持安全注销系统消息处理器。
42 changes: 41 additions & 1 deletion _test_archive/cli/command-palette.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
resolvePaletteView,
} from "../../packages/cli/dist/command-palette.js";
import { visibleWidth } from "../../packages/cli/dist/logs.js";
import { listPaletteRoots } from "../../packages/cli/dist/command-surface.js";
import { listNextPaletteTokens, listPaletteRoots } from "../../packages/cli/dist/command-surface.js";

describe("clipPad 等宽", () => {
it("垫到 PANEL_WIDTH", () => {
Expand Down Expand Up @@ -80,6 +80,46 @@ describe("resolvePaletteView", () => {
it("叶命令 /start bds 到末尾立即关闭", () => {
assert.equal(resolvePaletteView("/start bds", [0], [0]).hidden, true);
});
it("/ui 展开 studio 与 stop", () => {
const v = resolvePaletteView("/ui ", [0], [0]);
assert.equal(v.hidden, false);
const tokens = v.columns.at(-1).items.map((n) => n.token);
assert.deepEqual(tokens, ["studio", "stop"]);
});
it("/packs doctor 展开实验性开关", () => {
const v = resolvePaletteView("/packs doctor ", [0], [0]);
assert.equal(v.hidden, false);
const tokens = v.columns.at(-1).items.map((n) => n.token);
assert.ok(tokens.includes("--fix"));
assert.ok(tokens.includes("--all-experiments"));
assert.ok(tokens.includes("--experiments="));
assert.ok(tokens.includes(""));
});
it("doctor 直接运行提交无参命令", () => {
const v = resolvePaletteView("/packs doctor ", [0], [0]);
const col = v.columns.at(-1);
const idx = col.items.findIndex((n) => n.token === "");
const view = resolvePaletteView("/packs doctor ", [0, 0, idx], [0, 0, 0]);
const acc = commitSelection("/packs doctor ", view);
assert.equal(acc.line, "/packs doctor");
assert.equal(acc.submit, true);
});
it("--experiments= 粘在同一 token 上且随后收起面板", () => {
const v0 = resolvePaletteView("/packs doctor ", [0], [0]);
const idx = v0.columns.at(-1).items.findIndex((n) => n.token === "--experiments=");
const view = resolvePaletteView("/packs doctor ", [0, 0, idx], [0, 0, 0]);
const acc = commitSelection("/packs doctor ", view);
assert.equal(acc.line, "/packs doctor --experiments=");
assert.equal(acc.submit, false);
assert.equal(resolvePaletteView("/packs doctor --experiments=beta,upcoming", [0], [0]).hidden, true);
});
it("灰字补全与面板共用 ui / doctor 的下一参", () => {
assert.deepEqual(listNextPaletteTokens(["ui"]), ["studio", "stop"]);
const doctor = listNextPaletteTokens(["packs", "doctor"]);
assert.ok(doctor?.includes("--all-experiments"));
assert.ok(doctor?.includes("--fix"));
assert.equal(doctor?.includes(""), false);
});
it("未到末尾 /start 仍显示", () => {
assert.equal(resolvePaletteView("/start", [0], [0]).hidden, false);
assert.ok(resolvePaletteView("/start", [0], [0]).columns.length >= 1);
Expand Down
2 changes: 1 addition & 1 deletion modules/sdk/@sfmc-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@
- efa6e73: 将 Minecraft Script API 类型与新模块模板更新到 BDS 1.26.51 使用的 server 2.11.0-beta、server-ui 2.3.0-beta,并同步 vanilla-data 版本。
- aadd2de: 将官方 QQ Webhook 的 SSH 反向隧道纳入 SFMC CLI 服务编排,提供配置、启停、断线重试、状态和日志。
- efa6e73: 官方 QQ 机器人可选择 Webhook 接收事件;官方与 LLBot 配置改为分组对象。Webhook 模式自动跳过游戏机的桥进程,并安全同步共享配置到云端。群全量事件中的 `<@…>` 机器人提及也能正确触发指令面板命令。官方回复不再附带 LLBot 风格的编号和“发送:”列表。
- 3465c7e: 更新 QQ 账号和服务器信息面板、频道来源控制及模块和世界包查询;移除 QQ 踢人指令,完善 CLI 服务窗口提示与模块配置行为。
- 3465c7e: 更新 QQ 账号和服务器信息面板、频道来源控制及模块和资源包查询;移除 QQ 踢人指令,完善 CLI 服务窗口提示与模块配置行为。
- efa6e73: 为 QQ 管理菜单增加可即时生效的服务器事件推送开关,并补充 BDS 正常停服通知。
- 1a4ddda: dropdown 的 options 支持从 load 数据源注入(source/as/label/value),与 each 对齐。
- 1a4ddda: 声明式 UI 危险确认支持可选校验文本 challenge(固定字或 {{path}}),未匹配前确认按钮禁用。
Expand Down
2 changes: 1 addition & 1 deletion modules/sdk/@sfmc-sdk/schemas/pack_sources.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/DogeLakeDev/ScriptsForMinecraftServer/schemas/pack_sources.schema.json",
"title": "SFMC pack-sources",
"description": "世界包远端源绑定清单,位于 packs/pack-sources.json(非 configs/)。键为行为包 UUID(小写);由探测安装 / `packs bind` / 更新流程写入。",
"description": "资源包远端源绑定清单,位于 packs/pack-sources.json(非 configs/)。键为行为包 UUID(小写);由探测安装 / `packs bind` / 更新流程写入。",
"type": "object",
"additionalProperties": false,
"properties": {
Expand Down
6 changes: 3 additions & 3 deletions modules/sdk/@sfmc-sdk/schemas/pack_update.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/DogeLakeDev/ScriptsForMinecraftServer/schemas/pack_update.schema.json",
"title": "SFMC pack-update",
"description": "世界包(addons)自动检查/更新。apiKey 来自 https://console.curseforge.com/(x-api-key);也可用环境变量 CURSEFORGE_API_KEY。Bedrock gameId=78022,Addons classId=4984。官方 /mods/search 若 403 会改用 searchBaseUrl。match 为源无关策略,置于顶层。defaultBindingEnabled=false 时新建绑定默认关闭。",
"description": "资源包(addons)自动检查/更新。apiKey 来自 https://console.curseforge.com/(x-api-key);也可用环境变量 CURSEFORGE_API_KEY。Bedrock gameId=78022,Addons classId=4984。官方 /mods/search 若 403 会改用 searchBaseUrl。match 为源无关策略,置于顶层。defaultBindingEnabled=false 时新建绑定默认关闭。",
"type": "object",
"additionalProperties": false,
"properties": {
Expand All @@ -28,7 +28,7 @@
},
"probeSourceAfterInstall": {
"type": "boolean",
"description": "安装世界包成功后是否探测 CurseForge 等远端源并尝试绑定"
"description": "安装资源包成功后是否探测 CurseForge 等远端源并尝试绑定"
},
"defaultBindingEnabled": {
"type": "boolean",
Expand Down Expand Up @@ -160,7 +160,7 @@
"properties": {
"recycleBin": {
"type": "boolean",
"description": "true=移入 trashRelativeDir;false=直接删除世界包目录。可用 CLI --purge 强制删除"
"description": "true=移入 trashRelativeDir;false=直接删除资源包目录。可用 CLI --purge 强制删除"
},
"trashRelativeDir": {
"type": "string",
Expand Down
11 changes: 3 additions & 8 deletions modules/sdk/@sfmc-sdk/src/sapi/runtime/menu-navigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ export class MenuNavigator {
private player: Player;
private form: CustomForm | null = null;
private titleObs: ObservableString | null = null;
private backVis: ObservableBoolean | null = null;
/** 跨页面共享的状态对象。 */
state: Record<string, any> = {};
private _confirmIdx = 0;
Expand All @@ -146,9 +145,8 @@ export class MenuNavigator {

constructor(player: Player) {
this.player = player;
if (ObservableStringCtor && ObservableBooleanCtor) {
if (ObservableStringCtor) {
this.titleObs = new ObservableStringCtor("");
this.backVis = new ObservableBooleanCtor(false);
}
}

Expand All @@ -157,7 +155,7 @@ export class MenuNavigator {
return this.history;
}

/** 原生「回到上一级」弹栈后回调,供声明式运行时同步 session。 */
/** `back()` 弹栈后回调,供声明式运行时同步 session。 */
setOnBack(handler: () => void): this {
this.onBack = handler;
return this;
Expand All @@ -177,7 +175,6 @@ export class MenuNavigator {
const token = ++this.sessionToken;
this.history = [sectionId];
this.applySection(sectionId);
this.backVis?.setData(false);
await this.buildAndShow(token);
}

Expand Down Expand Up @@ -367,11 +364,10 @@ export class MenuNavigator {
private async buildAndShow(token = this.sessionToken): Promise<void> {
if (token !== this.sessionToken) return;
if (this.form?.isShowing()) this.form.close();
if (!CustomFormCtor || !this.titleObs || !this.backVis) {
if (!CustomFormCtor || !this.titleObs || !ObservableBooleanCtor) {
throw new Error("当前环境下的 @minecraft/server-ui 不支持 CustomForm (DDUI),请在支持 DDUI 的 BDS 版本运行");
}
this.form = new CustomFormCtor(this.player, this.titleObs);
this.form.button("← 回到上一级", () => this.back(), { visible: this.backVis });
for (const [id, def] of this.sections) {
if (token !== this.sessionToken) return;
const vis = this.sectionVis.get(id);
Expand Down Expand Up @@ -408,7 +404,6 @@ export class MenuNavigator {

private applySection(sectionId: string): void {
for (const [id, vis] of this.sectionVis) vis.setData(id === sectionId);
this.backVis?.setData(this.history.length > 1);
this.updateTitle();
}

Expand Down
7 changes: 5 additions & 2 deletions modules/sdk/@sfmc-sdk/src/sapi/runtime/msg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ let _systemMsgHandler: ((player: Player, text: string) => void) | null = null;
*
* @param handler 接收目标玩家与消息文本的转发回调函数。
*/
export function registerSystemMsgHandler(handler: (player: Player, text: string) => void): void {
export function registerSystemMsgHandler(handler: (player: Player, text: string) => void): () => void {
_systemMsgHandler = handler;
const registered = handler;
return () => {
if (_systemMsgHandler === registered) _systemMsgHandler = null;
};
}

/**
Expand Down Expand Up @@ -82,4 +86,3 @@ export const Msg = {
_systemMsgHandler?.(player, msg);
},
};

2 changes: 1 addition & 1 deletion packages/bds-tools/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@

### Patch Changes

- 3714053: 发版:build-publishable 拓扑 + listPublishableBuildDeps(npm-publish 应急补发不再硬编码只 build SDK);push 缺失态 DRY 对齐 listUnpushedExistingVersionTags。世界包:readPackDirOccupancy DRY,去掉死不变式,occupancy 保留真实 kind(LSP)。
- 3714053: 发版:build-publishable 拓扑 + listPublishableBuildDeps(npm-publish 应急补发不再硬编码只 build SDK);push 缺失态 DRY 对齐 listUnpushedExistingVersionTags。资源包:readPackDirOccupancy DRY,去掉死不变式,occupancy 保留真实 kind(LSP)。
- 5ada90e: 修复 #80/#81 合并后 `scanDestOccupancy` 未赋值 `facts`、引用未声明标识符导致 tsc 构建失败;恢复经 `readPackDirOccupancy` 的 DRY 占用扫描。

## 0.2.0-beta.1
Expand Down
2 changes: 1 addition & 1 deletion packages/bds-tools/src/pack-update/bindings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* packs/pack-sources.json — 世界包远程源绑定
* packs/pack-sources.json — 资源包远程源绑定
*/
import { withConfigSchema } from "@sfmc-bds/sdk/node/config";
import fs from "node:fs";
Expand Down
2 changes: 1 addition & 1 deletion packages/bds-tools/src/pack-update/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export interface StartupUpdateConfig {
failMode: "continue" | "abort";
}

/** 世界包卸载策略(与 CF 更新同属 packs 生命周期配置) */
/** 资源包卸载策略(与 CF 更新同属 packs 生命周期配置) */
export interface PackUninstallConfig {
/** true:移入回收站;false:直接删除目录 */
recycleBin: boolean;
Expand Down
11 changes: 6 additions & 5 deletions packages/bds-tools/src/world-packs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ export function resolvePackLocalizedText(packDir: string, value: string): string
for (const file of languageFiles) {
if (!fs.existsSync(file)) continue;
try {
const lines = fs.readFileSync(file, "utf8").replace(/^\uFEFF/, "").split(/\r?\n/);
const lines = fs
.readFileSync(file, "utf8")
.replace(/^\uFEFF/, "")
.split(/\r?\n/);
for (const line of lines) {
const trimmed = line.trimStart();
if (!trimmed || trimmed.startsWith("#")) continue;
Expand Down Expand Up @@ -231,9 +234,7 @@ export function readPackManifestInfo(packDir: string): PackManifestInfo | null {
uuid,
version,
kind,
...(typeof description === "string"
? { description: resolvePackLocalizedText(packDir, description) }
: {}),
...(typeof description === "string" ? { description: resolvePackLocalizedText(packDir, description) } : {}),
};
} catch {
return null;
Expand Down Expand Up @@ -853,7 +854,7 @@ export type UninstallPackResult = { action: "trashed"; dest: string } | { action
export type UninstallPackAction = UninstallPackResult["action"];

/**
* 卸载已安装世界包:先 disable enable-list,再移入 trashDir 或直接删除目录。
* 卸载已安装资源包:先 disable enable-list,再移入 trashDir 或直接删除目录。
* trashDir 跨盘符时 fallback 为 copy + rm。
*/
export async function uninstallInstalledPack(opts: {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@
- efa6e73: QQ 查服改用游戏实时在线玩家及世界信息,CLI 托管的 BDS 启停和异常退出现在上报事件。
- aadd2de: 将官方 QQ Webhook 的 SSH 反向隧道纳入 SFMC CLI 服务编排,提供配置、启停、断线重试、状态和日志。
- efa6e73: 官方 QQ 机器人可选择 Webhook 接收事件;官方与 LLBot 配置改为分组对象。Webhook 模式自动跳过游戏机的桥进程,并安全同步共享配置到云端。群全量事件中的 `<@…>` 机器人提及也能正确触发指令面板命令。官方回复不再附带 LLBot 风格的编号和“发送:”列表。
- 3465c7e: 更新 QQ 账号和服务器信息面板、频道来源控制及模块和世界包查询;移除 QQ 踢人指令,完善 CLI 服务窗口提示与模块配置行为。
- 3465c7e: 更新 QQ 账号和服务器信息面板、频道来源控制及模块和资源包查询;移除 QQ 踢人指令,完善 CLI 服务窗口提示与模块配置行为。
- 4a9b066: 统一 QQ 双后端玩家服务菜单、权限核验、敏感操作确认及错误反馈,修复编号菜单与长名单发送,保留查服、版本和 ip 指令习惯。

新增 public_server 公开连接配置;版本优先使用 CLI 捕获并经状态接口核验的当前 BDS 启动版本。移除在线状态接口的 64 人上限,不改变入服业务状态与审批策略。
Expand Down
21 changes: 19 additions & 2 deletions packages/cli/src/command-palette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,15 @@ function findChild(parent: PaletteNode | undefined, token: string): PaletteNode
return parent?.children?.find((n) => n.token === token);
}

/**
* 当前 token 是否落在「`=` 结尾」的自由参数上(如 `--experiments=beta,upcoming`)。
* 这类值与前缀是同一个 token,面板应在写完前缀后收起。
*/
function matchesGluedFreeArg(parent: PaletteNode | undefined, token: string): boolean {
if (!parent?.children?.length || !token) return false;
return parent.children.some((n) => n.freeArgs && n.token.endsWith("=") && token.startsWith(n.token));
}

/**
* 是否已进入自由参数尾部(如 /send bds …)。
* 路径落在 freeArgs 叶上,或叶之后还有无法匹配的多余 token,均视为自由输入。
Expand Down Expand Up @@ -284,7 +293,7 @@ export function isCommandAtEnd(committed: string[], partial: string, trailingSpa
let node: PaletteNode | undefined;
for (let i = 0; i < path.length; i++) {
const next = i === 0 ? findRoot(path[i]!) : findChild(node, path[i]!);
if (!next) return false;
if (!next) return matchesGluedFreeArg(node, path[i]!);
node = next;
}
if (!node || (node.children?.length ?? 0) > 0) return false;
Expand Down Expand Up @@ -444,6 +453,12 @@ export function commitSelection(line: string, view: PaletteView): { line: string

const { committed, partial, trailingSpace, active } = view;

/* 空 token = 直接运行父命令,不把占位标签写进命令行 */
if (node.token === "") {
const base = committed.slice(0, active);
return { line: base.length ? `/${base.join(" ")}` : "/", submit: true };
}

/* 叶节点且输入已完整匹配 → 提交执行 */
const isLeaf = !node.children?.length && !node.freeArgs;
if (isLeaf && !trailingSpace) {
Expand All @@ -458,7 +473,9 @@ export function commitSelection(line: string, view: PaletteView): { line: string
const tokens = committed.slice(0, active);
tokens.push(node.token);
let next = "/" + tokens.join(" ");
if (node.children?.length || node.freeArgs) next += " ";
/* `=` 结尾的自由参数不补空格,避免把 `--experiments=beta` 拆成两个 token */
const glue = node.freeArgs && node.token.endsWith("=");
if ((node.children?.length || node.freeArgs) && !glue) next += " ";
return { line: next, submit: false };
}

Expand Down
Loading
Loading