#!/usr/bin/env node // Explicit, small real-provider smoke test. Never changes production vocabulary. import {readFileSync,mkdirSync,writeFileSync,existsSync,statSync} from 'node:fs'; import {homedir} from 'node:os'; import {resolve} from 'node:path'; import {createHash,webcrypto} from 'node:crypto'; import vm from 'node:vm'; const file=resolve(process.argv.find(x=>x.startsWith('--file='))?.slice(7)||homedir()+'/.config/xiaoshu/xfyun-word-audio.json'); if(statSync(file).mode&0o077)throw Error('请将配置文件权限设置为600'); const settings=JSON.parse(readFileSync(file,'utf8')); const ctx=vm.createContext({crypto:webcrypto,WebSocket,TextEncoder,URL,URLSearchParams,Uint8Array,btoa,atob,AbortSignal,setTimeout,clearTimeout,fetch,Parse:{Config:{get:async()=>({get:key=>settings[key]})}},fail:(_code,message)=>{throw Error(message);}}); vm.runInContext(readFileSync(new URL('./cloud/word-audio.js',import.meta.url),'utf8'),ctx); const config=await ctx.wordAudioConfig(); if(!config.appId||!config.apiKey||!config.apiSecret||!config.voice)throw Error('讯飞配置不完整'); if(!process.argv.includes('--generate')){console.log(JSON.stringify({configured:true,voice:config.voice,generated:false,message:'--generate 生成 apple 样音;--extended 追加四个代表性词句。'}));process.exit(0);} const output=resolve('tmp/word-audio-qa');mkdirSync(output,{recursive:true}); const words=process.argv.includes('--extended')?['apple','comfortable','read','NASA','Good morning']:['apple']; for(const word of words){ const key=createHash('sha256').update(JSON.stringify([word,config.voice,config.speed])).digest('hex').slice(0,16),path=output+'/'+key+'.mp3'; let cached=existsSync(path); if(!cached){const audio=await ctx.wordAudioSynthesize(word,config);writeFileSync(path,Buffer.from(audio,'base64'),{mode:0o600});} const bytes=readFileSync(path); console.log(JSON.stringify({word,voice:config.voice,path,bytes:bytes.length,cached,sha256:createHash('sha256').update(bytes).digest('hex')})); }