GPT语音助手

通Hook fetch函数,直接调用tts接口。兼容性很强。

目前為 2023-07-17 提交的版本,檢視 最新版本

// ==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);
    });
}
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址