/** * Collabora Online Python 脚本调用工具 * * 职责: 统一封装所有 Python 脚本的调用逻辑和响应处理 * @encoding UTF-8 */ export interface ScriptResult { success: boolean; message: string; data?: { count?: number; action?: string; unit?: string; [key: string]: unknown; }; timestamp: number; } export interface CallScriptOptions { timeout?: number; verbose?: boolean; } interface PostMessageResponse { MessageId?: string; Values?: { commandName?: string; success?: boolean; result?: string | { type?: string; value?: string }; }; } export async function callPythonScript( iframeWindow: Window, scriptFile: string, functionName: string, args?: Record, options?: CallScriptOptions ): Promise { const timeout = options?.timeout || 10000; const verbose = options?.verbose ?? true; if (verbose) { console.log('[CallCustomScript] 调用 Python 脚本:', { scriptFile, functionName, args }); } return new Promise((resolve, reject) => { const cleanup = () => { clearTimeout(timeoutId); window.removeEventListener('message', handleMessage); }; const timeoutId: NodeJS.Timeout = setTimeout(() => { cleanup(); reject(new Error(`Python 脚本调用超时 (${timeout}ms): ${scriptFile}.${functionName}`)); }, timeout); const handleMessage = (event: MessageEvent) => { try { if (event.source !== iframeWindow) return; const data: PostMessageResponse = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; // 兼容两种 MessageId 格式: // - 'CallPythonScript_Resp' (预期格式) // - 'CallPythonScript-Result' (bundle.js 实际发送的格式) if (data.MessageId !== 'CallPythonScript-Result') return; cleanup(); if (verbose) { console.log('[CallCustomScript] 收到 Python 脚本响应:', data); } const result = parseScriptResponse(data, verbose); resolve(result); } catch (error) { cleanup(); reject(error); } }; window.addEventListener('message', handleMessage); const message = { MessageId: 'CallPythonScript', ScriptFile: scriptFile, Function: functionName, Values: args || {}, }; if (verbose) { console.log('[CallCustomScript] 发送 PostMessage:', message); } iframeWindow.postMessage(JSON.stringify(message), '*'); }); } function parseScriptResponse(data: PostMessageResponse, verbose: boolean): ScriptResult { const values = data.Values; if (!values || typeof values !== 'object') { throw new Error('响应格式错误: Values 字段缺失或格式不正确'); } let resultValue: string | undefined; if (typeof values.result === 'string') { resultValue = values.result; } else if (values.result && typeof values.result === 'object') { resultValue = (values.result as { value?: string }).value; } if (verbose) { console.log('[CallCustomScript] 解析结果:', { commandName: values.commandName, unoSuccess: values.success, resultRaw: values.result, resultExtracted: resultValue, }); } if (values.success === false) { return { success: false, message: resultValue || 'UNO 命令执行失败', timestamp: Date.now(), }; } if (typeof resultValue !== 'string') { throw new Error('Python 脚本返回值格式错误: result 不是字符串'); } return parseStandardResponse(resultValue); } function parseStandardResponse(message: string): ScriptResult { const timestamp = Date.now(); if (message.includes('Error:') || message.toLowerCase().includes('error')) { return { success: false, message, timestamp }; } const countMatch = message.match(/(\w+)\s+(\d+)\s+(regions?|instances?|items?)/i); if (countMatch) { const count = parseInt(countMatch[2], 10); return { success: true, message, data: { count, action: countMatch[1].toLowerCase(), unit: countMatch[3].toLowerCase(), }, timestamp, }; } return { success: true, message, timestamp }; } export async function callPythonScriptBatch( iframeWindow: Window, calls: Array<{ scriptFile: string; functionName: string; args?: Record; }>, options?: CallScriptOptions ): Promise { const results: ScriptResult[] = []; for (const call of calls) { try { const result = await callPythonScript( iframeWindow, call.scriptFile, call.functionName, call.args, options ); results.push(result); } catch (error) { results.push({ success: false, message: error instanceof Error ? error.message : String(error), timestamp: Date.now(), }); } } return results; }