您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
通Hook fetch函数,直接调用微软tts接口。兼容性很强。
// ==UserScript== // @name GPT语音助手 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 通Hook fetch函数,直接调用微软tts接口。兼容性很强。 // @author lsamchn // @match *://*/* // @grant none // @license MIT // ==/UserScript== // @grant GM_xmlhttpRequest // @grant unsafeWindow // @connect translate.volcengine.com // @connect southeastasia.api.speech.microsoft.com (function() { 'use strict'; if(typeof unsafeWindow === "undefined"){ var unsafeWindow = window; } 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(); generalWord("。") 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<splitData.length;readIndex++){ if(splitData[readIndex]){ try{ var json = JSON.parse(splitData[readIndex]) if(json.choices[0].delta.content) { //console.log(json.choices[0].delta.content) generalWord(json.choices[0].delta.content) } }catch(e){} } } } var totalText = "" var Words = []; /* 这个函数按照标点符号截断文本,以提取完整的句子,流式调用TTS */ function generalWord(text){ totalText += text; totalText = totalText.split(/。|!|?|\!|\?|,|,|、|:|:|\]|】/) for(let i=0;i<totalText.length-1;i++){ var word = totalText.shift().trim(); if(word) generalSound(word); } totalText = totalText.join(','); } var audioQueue = []; var audioQueueX = []; var speakFuncRunning = false; /* 这个函数用于给每个句子生成语音 */ function generalSound(word){ audioQueue.push({ text: word }) console.log(word) if(speakFuncRunning) return; var waitFormuti = 3; //等待积攒了三个语音再开始播放 setTimeout(() =>{ waitFormuti = 0},1000) //或者等待3s,使语言更连续 var audio = document.createElement("audio"); if (!speakFuncRunning) { (async function() { while (true) { try { if (audioQueueX.length <= waitFormuti) { await sleep(100); continue; } waitFormuti = 0; var audio_bloburl = await audioQueueX[0].blob; audioQueueX.shift() /*while (! (audio.duration > 0)) { await sleep(10) }*/ if(audio.src.indexOf("blob:")===0) { var oldsrc= audio.src; //console.log(audio.src) audio.src = audio_bloburl; URL.revokeObjectURL(oldsrc); }else{ 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}`);audio.ontimeupdate=null;resolve(audio.duration - audio.currentTime) }})) console.log(ms) await sleep((1000 * ms)) //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(400); if (audioQueue.length === 0) continue; var audio = audioQueue[0]; audioQueue.shift() if (!audio.blob) audio.blob = autoRefetch(audio.text).then(response =>{ // console.log("已加载:" + url); return 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 = 10) { return runAsync(speak_text). catch(error =>{ if (retries === 0) { throw error; } console.log(`Retrying ${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("data:audio/wav;base64,"+JSON.parse(response.responseText).audio.data); }, onerror: function(response){ //console.log("请求失败"); reject("请求失败"); } }); }) return p; }*/ async function runAsync(speak_text) { return tts(speak_text) } /* function runAsync(speak_text) { var p = new Promise((resolve, reject)=> { GM_xmlhttpRequest({ method: "POST", responseType: 'blob', url: "https://southeastasia.api.speech.microsoft.com/accfreetrial/texttospeech/acc/v3.0-beta1/vcg/speak", headers: { "Content-Type": "application/json", 'Origin': 'https://speech.microsoft.com' }, data:JSON.stringify({"ssml":"<!--ID=B7267351-473F-409D-9765-754A8EBCDE05;Version=1|{\"VoiceNameToIdMapItems\":[{\"Id\":\"5f55541d-c844-4e04-a7f8-1723ffbea4a9\",\"Name\":\"Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoxiaoNeural)\",\"ShortName\":\"zh-CN-XiaoxiaoNeural\",\"Locale\":\"zh-CN\",\"VoiceType\":\"StandardVoice\"},{\"Id\":\"26014551-90d7-4f55-a622-779b8263e006\",\"Name\":\"Microsoft Server Speech Text to Speech Voice (zh-CN, YunyeNeural)\",\"ShortName\":\"zh-CN-YunyeNeural\",\"Locale\":\"zh-CN\",\"VoiceType\":\"StandardVoice\"},{\"Id\":\"1011ca97-3e33-4e7c-8dda-a22dc244bafc\",\"Name\":\"Microsoft Server Speech Text to Speech Voice (zh-CN, YunxiNeural)\",\"ShortName\":\"zh-CN-YunxiNeural\",\"Locale\":\"zh-CN\",\"VoiceType\":\"StandardVoice\"}]}-->\n<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xmlns:mstts=\"http://www.w3.org/2001/mstts\" xmlns:emo=\"http://www.w3.org/2009/10/emotionml\" xml:lang=\"zh-CN\"><voice name=\"zh-CN-XiaoxiaoNeural\"><mstts:express-as style=\"chat\">"+JSON.stringify(speak_text)+"</mstts:express-as></voice></speak>","ttsAudioFormat":"audio-16khz-32kbitrate-mono-mp3","offsetInPlainText":0,"lengthInPlainText":131,"properties":{"SpeakTriggerSource":"AccTuningPagePlayButton"}}), onload: function(response){ //console.log("请求成功"); console.log(response); var blob_url = URL.createObjectURL(response.blob()) console.log(blob_url) resolve(blob_url); }, onerror: function(response){ console.log("请求失败"); reject("请求失败"); } }); }) return p; }*/ /* 经典sleep函数 */ function sleep(time) { return new Promise((resolve) =>{ setTimeout(() =>{ resolve(); }, time); }); } unsafeWindow.tts = tts; var ws_clients_num = 0; var ws_clients =[]; var ws_pool = []; var lookuprunning=false; async function lookup(){ var counts = 3; for(let i=0;i<counts;i++){ ws_pool.push(await newWS()); } while(true){ await sleep(200); if(ws_pool.length>counts) { ws_pool = ws_pool.slice(-counts) } while (ws_clients_num<counts && ws_clients.length>0){ console.log(666) var resolve = ws_clients.shift() if(resolve) resolve(); } } } function tts(speak_text) { if(lookuprunning === false){ lookuprunning = true; lookup() } return new Promise(async (resolve,reject)=>{ if (!("WebSocket" in window)) { reject("您的浏览器不支持 WebSocket!"); return } // 打开一个 web socket try{ await (new Promise((resolve111)=>{ ws_clients.push(resolve111) })) var ws ; for(ws=ws_pool.shift();!(ws&&ws.readyState === ws.OPEN); ws=ws_pool.shift()){ if(ws_pool.length===0){ ws_pool.push(await newWS()) }else{ newWS().then(e=>{ws_pool.push(e)}) } } ws_clients_num += 1; }catch(e){ reject(e) console.error(e) return } var _voice = "zh-CN-XiaoxiaoNeural" var _voiceLocale = "zh-CN" //<prosody pitch="+0Hz" rate="50" volume="80"></prosody> var d = unsafeWindow.document.createElement('div'); d.textContent= speak_text; speak_text = d.innerHTML; var requestSSML = `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="${_voiceLocale}"><voice name="${_voice}"><prosody pitch="+0Hz" rate="1.4" volume="80">${speak_text}</prosody></voice></speak>`; var requestId = (Math.random()+"8").substring(2,18)+(Math.random()+"8").substring(2,18); var request = `X-RequestId:${requestId}\r\nContent-Type:application/ssml+xml\r\nX-Timestamp:${Date.now()}Z\r\nPath:ssml\r\n\r\n` + requestSSML.trim(); ws.send(request) var mp3_blob = new Blob([],{type:"audio/mpeg"}); var blob_url =""; ws.onmessage = function (evt) { var received_msg = evt.data; if(typeof evt.data !== "string"){ mp3_blob = new Blob([mp3_blob,evt.data.slice(130)],{type:"audio/mpeg"}); //console.log(evt.data) }else{ //console.log(666,mp3_blob.size) if(mp3_blob.size>0){ blob_url = URL.createObjectURL(mp3_blob); //ws.close() ws_clients_num -= 1; ws_pool.push(ws) resolve(blob_url) //console.log(blob_url) } } // alert("数据已接收..."); }; ws.onerror = function(evt) { setTimeout(async()=>{ if(!blob_url){ console.error(evt); ws_clients_num -= 1; reject(evt); ws_pool.push(await newWS()) } },2000); }; ws.onclose = async function() { // 关闭 websocket //alert("连接已关闭..."); //ws_pool.push(await newWS()) setTimeout(async ()=>{ if(!blob_url){ console.error("连接意外关闭"); ws_clients_num -= 1; reject("连接意外关闭"); ws_pool.push(await newWS()) } },2000); }; } ); } function newWS(){ return new Promise((resolve)=>{ var ws = new WebSocket("wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4"); ws.onerror =async function(evt) { ws.onopen = ()=>{} await sleep(100) resolve( await newWS()) }; ws.onopen = function() { ws.send(`Content-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n{ "context": { "synthesis": { "audio": { "metadataoptions": { "sentenceBoundaryEnabled": false, "wordBoundaryEnabled": false }, "outputFormat": "audio-24khz-48kbitrate-mono-mp3" } } } }`); //alert("数据发送中..."); resolve(ws) }; }) } })();