分享码功能的实现,并修复了几个 bug

This commit is contained in:
eafonyang
2026-06-15 18:00:06 +08:00
parent b620805e13
commit b421fe1ca3
12 changed files with 756 additions and 222 deletions
+16 -7
View File
@@ -7,6 +7,7 @@ import {
LuxsinAPI,
PeqFilter,
FILTER_TYPE_LABELS,
buildPeqPresetBody,
dbToVolume,
volumeToDb,
} from "./luxsinApi";
@@ -34,6 +35,8 @@ export interface OptimizePeqPayload {
name?: string;
brand?: string;
model?: string;
target?: string;
form?: string;
preamp?: number;
canDel?: number;
autoPre?: number;
@@ -327,13 +330,19 @@ export async function executeFrontendTool(
case "set_peq": {
const p = block.input ?? {};
await api.upgradePeqChange({
peqChange: {
name: p.name,
filters: p.filters ?? [],
autoPre: p.autoPre,
preamp: p.preamp,
canDel: p.canDel,
},
peqChange: buildPeqPresetBody(
{
name: p.name,
brand: p.brand,
model: p.model,
target: p.target,
form: p.form,
autoPre: p.autoPre,
preamp: p.preamp,
canDel: p.canDel,
},
(p.filters ?? []) as PeqFilter[],
),
});
await api.refreshState();
return { ok: true, content: { ok: true, name: p.name } };
+140
View File
@@ -145,6 +145,7 @@ export interface PeqState {
canDel?: number;
brand?: string;
model?: string;
target?: string;
form?: string;
}>;
}
@@ -162,6 +163,38 @@ export interface PeqPresetBody {
form?: string;
}
/** PEQ preset metadata used when building peqChange / peqApply bodies. */
export type PeqPresetMeta = {
name: string;
autoPre?: number;
preamp?: number;
canDel?: number;
brand?: string;
model?: string;
target?: string;
form?: string;
};
/** Build device PEQ POST body; optional brand/model/target/form included only when non-empty. */
export function buildPeqPresetBody(peq: PeqPresetMeta, filters: PeqFilter[]): PeqPresetBody {
const body: PeqPresetBody = {
name: peq.name,
filters,
autoPre: peq.autoPre ?? 0,
preamp: peq.preamp ?? 0,
canDel: peq.canDel ?? 1,
};
const brand = peq.brand?.trim();
const model = peq.model?.trim();
const target = peq.target?.trim();
const form = peq.form?.trim();
if (brand) body.brand = brand;
if (model) body.model = model;
if (target) body.target = target;
if (form) body.form = form;
return body;
}
/** POST body for `/dev/info.cgi` — full peq preset update (custom base64 `json` field). */
export interface PeqChangePayload {
peqChange: PeqPresetBody;
@@ -635,3 +668,110 @@ export async function fetchLuxsinAudioCurve(
const body = (await res.text()).trim();
return decodeCustomBase64(body);
}
// ============================================================
// Share Code API — shareCreate / shareList / shareQuery / shareAccept
// ============================================================
export interface ShareCodeCreateResponse {
code: number;
msg: string;
share_code?: string;
expire_at?: string;
eq_data?: Record<string, unknown>;
}
export interface ShareCodeListItem {
share_code: string;
expire_at: string;
eq_data: Record<string, unknown>;
}
export interface ShareCodeListResponse {
code: number;
msg: string;
share_codes: ShareCodeListItem[];
}
export interface ShareCodeAcceptResponse {
code: number;
msg: string;
eq_data?: Record<string, unknown>;
}
export interface ShareCodeQueryResponse {
code: number;
msg: string;
eq_data?: Record<string, unknown>;
expire_at?: string;
}
/** Unwrap stringified PEQ filters (remove JSON escape layers) for API submit. */
export function normalizePeqFiltersForSubmit(filters: unknown): PeqFilter[] {
if (Array.isArray(filters)) return filters as PeqFilter[];
if (typeof filters !== "string" || !filters.trim()) return [];
let current: unknown = filters.trim();
for (let depth = 0; depth < 3; depth++) {
if (Array.isArray(current)) return current as PeqFilter[];
if (typeof current !== "string") return [];
try {
current = JSON.parse(current);
} catch {
return [];
}
}
return Array.isArray(current) ? (current as PeqFilter[]) : [];
}
/** POST `/audio/shareCreate` — 创建 EQ 分享码,返回 share_code + expire_at + eq_data */
export async function createShareCode(
mac: string,
eqData: Record<string, unknown>,
): Promise<ShareCodeCreateResponse> {
const payload = {
...eqData,
...(eqData.filters !== undefined
? { filters: normalizePeqFiltersForSubmit(eqData.filters) }
: {}),
};
const res = await fetch(buildLuxsinAudioUrl("shareCreate"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mac, eq_data: payload }),
});
if (!res.ok) throw new Error(`shareCreate HTTP ${res.status}`);
return (await res.json()) as ShareCodeCreateResponse;
}
/** GET `/audio/shareList?mac=...` — 查询某 MAC 尚未过期的分享码列表 */
export async function listShareCodes(mac: string): Promise<ShareCodeListResponse> {
const res = await fetch(
buildLuxsinAudioUrl(`shareList?mac=${encodeURIComponent(mac)}`),
);
if (!res.ok) throw new Error(`shareList HTTP ${res.status}`);
return (await res.json()) as ShareCodeListResponse;
}
/** GET `/audio/shareQuery?shareCode=...` — 预览分享码 EQ 数据(不记录导入) */
export async function queryShareCode(shareCode: string): Promise<ShareCodeQueryResponse> {
const res = await fetch(
buildLuxsinAudioUrl(`shareQuery?shareCode=${encodeURIComponent(shareCode)}`),
);
if (!res.ok) throw new Error(`shareQuery HTTP ${res.status}`);
return (await res.json()) as ShareCodeQueryResponse;
}
/** GET `/audio/shareAccept?mac=...&shareCode=...` — 确认导入分享码并记录流水 */
export async function acceptShareCode(
mac: string,
shareCode: string,
): Promise<ShareCodeAcceptResponse> {
const res = await fetch(
buildLuxsinAudioUrl(
`shareAccept?mac=${encodeURIComponent(mac)}&shareCode=${encodeURIComponent(shareCode)}`,
),
);
if (!res.ok) throw new Error(`shareAccept HTTP ${res.status}`);
return (await res.json()) as ShareCodeAcceptResponse;
}