Greasy Fork is available in English.
通Hook fetch函数,直接调用tts接口。兼容性很强。
当前为
// ==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
// ==/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<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();
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 {
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}`);audio.ontimeupdate=null;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);
});
}
})();