// ==UserScript== // @name GPT语音助手 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 通Hook fetch函数,直接调用tts接口。兼容性很强。 // @author lsamchn // @match *://*/* // @grant GM_xmlhttpRequest // @grant unsafeWindow // @connect translate.volcengine.com // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; var oldFetch = "fetch" + Math.random() unsafeWindow[oldFetch] = unsafeWindow.fetch; unsafeWindow.fetch = HookFetch; /* 这个函数根据请求地址是否为api服务器,自动中间人读取数据包 */ function HookFetch(...args){ if(!/\/v1\/chat\/completions($|\?[\s\S]*?)/i.test(args[0])){ return unsafeWindow[oldFetch](...args) } return new Promise(async function(resolve,reject){ try{ var resp = await unsafeWindow[oldFetch](...args); }catch(e){reject(e)} var reader = resp.body.getReader(); var stream = (new ReadableStream({ start(controller) { // The following function handles each data chunk function push() { // "done" is a Boolean and value a "Uint8Array" reader.read().then(({ done, value }) => { // If there is no more data to read if (done) { //console.log('done', done); controller.close(); return; } // Get the data and send it to the browser via the controller controller.enqueue(value); try{ generalText(value)} catch(e) {console.error(e)} // Check chunks by logging to the console //console.log(done, value); push(); }); } push(); }, })) resolve(new Response(stream, { headers: resp.headers, ok: resp.ok, redirected: resp.redirected, status: resp.status, statusText: resp.statusText, type: resp.type, url: resp.url, bodyUsed: false })) }); } var utf8decoder = new TextDecoder(); var totalData = ""; var readIndex = 0; /* 这个函数用于提取响应JSON中的content值 */ function generalText(data){ totalData += utf8decoder.decode(data) for(let splitData = totalData.split(/(\n|^)data:/);readIndex{ waitFormuti = 0},1000) //或者等待3s,使语言更连续 var audio = document.createElement("audio"); if (!speakFuncRunning) { (async function() { while (true) { try { await sleep(100); if (audioQueueX.length < waitFormuti) continue; waitFormuti = 0; var audio_bloburl = await audioQueueX[0].blob; audioQueueX.shift() /*while (! (audio.duration > 0)) { await sleep(10) }*/ audio.src = audio_bloburl; audio.play() ; //console.log(audio.duration) var ms = await ( new Promise((resolve) => { audio.ontimeupdate=()=>{ if(!audio.duration) return; console.log(`currentTime: ${audio.currentTime} , duration: ${audio.duration}`);resolve(audio.duration - audio.currentTime) }})) console.log(ms) await sleep((1000 * ms - 100)) //await sleep(audio.duration * 1000 - 10) //await (()=>{return new Promise((resolve) => {audio.onended=resolve;audio.play();setTimeout(resolve,50000)})})() } catch(e) {} } })(); (async function() { while (true) { try { await sleep(300); if (audioQueue.length === 0) continue; while(audio = audioQueue.shift()){ if (!audio.blob) audio.blob = autoRefetch(audio.text).then(response =>{ // console.log("已加载:" + url); return "data:audio/wav;base64,"+response//response.blob() })/*.then(blob =>{ return URL.createObjectURL(blob); })*/ audioQueueX.push(audio) // //await (()=>{return new Promise((resolve) => {audio.onended=resolve;audio.play()})})() } } catch(e) {} } })() } speakFuncRunning = true; } /* 自动重试函数 */ function autoRefetch(speak_text,retries = 3) { return runAsync(speak_text). catch(error =>{ if (retries === 0) { throw error; } console.log(`Retrying $ { url }.$ { retries } retries left.`); return autoRefetch(speak_text, retries - 1); }); } function runAsync(speak_text) { //["zh_male_rap","zh_male_zhubo","zh_female_zhubo","tts.other.BV021_streaming","tts.other.BV026_streaming","tts.other.BV025_streaming","zh_female_sichuan","zh_male_xiaoming","zh_female_qingxin","zh_female_story"] var p = new Promise((resolve, reject)=> { GM_xmlhttpRequest({ method: "POST", url: "https://translate.volcengine.com/web/tts/v1/", headers: { "Content-Type": "application/json" }, data:JSON.stringify({"text":speak_text,"speaker":"tts.other.BV025_streaming","language":"zh"}), onload: function(response){ //console.log("请求成功"); //console.log(response.responseText); resolve(JSON.parse(response.responseText).audio.data); }, onerror: function(response){ //console.log("请求失败"); reject("请求失败"); } }); }) return p; } /* 经典sleep函数 */ function sleep(time) { return new Promise((resolve) =>{ setTimeout(() =>{ resolve(); }, time); }); } })();