[SNOLAB] I Heard Telegram Speaking

[SNOLAB] Speak latest telegram message With TTS technology just in your browser. 1. Speak latest message your received 2. Speak what you just send. 3. Send what you saying

Version vom 31.08.2022. Aktuellste Version

// ==UserScript==
// @name               [SNOLAB] I Heard Telegram Speaking
// @namespace          snomiao@gmail.com
// @author             snomiao@gmail.com
// @version            0.3.0
// @description        [SNOLAB] Speak latest telegram message With TTS technology just in your browser. 1. Speak latest message your received 2. Speak what you just send. 3. Send what you saying
// @match              https://*.telegram.org/z/
// @grant              none
// @run-at             document-start
// @license            GPL-3.0+
// @supportURL         https://github.com/snomiao/userscript.js/issues
// @contributionURL    https://snomiao.com/donate
// ==/UserScript==
/**
 * 1. Speak latest message your received
 * 2. Speak what you just send.
 * 3. Send what you saying
 */
main()

async function main() {
    if (!globalThis.speechSynthesis)
    return alert('unable to access speechSynthesis service, please update your browser')
    // polyfill and error detect
    globalThis.SpeechRecognition = globalThis.SpeechRecognition || globalThis.webkitSpeechRecognition || globalThis.mozillaSpeechRecognition
    if(!globalThis.SpeechRecognition)
        return alert('unable to access speechSynthesis service, please update your browser');
    listeningLooper().then()
    speakingLooper().then();
    speakingMySelfLooper().then()
}
async function listeningLooper() {
    while(1) await tgSend( await messageSendingConfirmed(await heard(await userLangGet())))
}
async function speakingLooper() {
    const changed = edgeFilter('')
    while (1) {await speak(changed(latestMessage())); await delay1s()}
}
async function speakingMySelfLooper() {
    const changed = edgeFilter('')
    while (1) {await speak2nd(changed(latestMyMessage())); await delay1s()}
}
// async function titleLooper(){
//     const titleGet = ()=>document.querySelector('.selected h3').innerHTML
//     const titleUpdate = (t)=>t&&
//     while (1) {await titleUpdate(changed(titleGet())); await delay1s()}
// }

async function heard(lang = 'en-US') {
    globalThis.SpeechRecognition = globalThis.SpeechRecognition || globalThis.webkitSpeechRecognition || globalThis.mozillaSpeechRecognition;
    console.log("listening");
    const result = await new Promise((resolve, reject) => {
        const sr = new globalThis.SpeechRecognition();
        sr.continuous = true;
        sr.lang = lang;
        sr.onresult = e => {
            const result = e?.results?.[0]?.[0]?.transcript || '';
            resolve(result);
        };
        sr.onerror = () => resolve('')
        sr.start();
    });
    console.log("heard", result);
    await new Promise(r => setTimeout(r, 64));
    return result;
}

async function messageSendingConfirmed(s = ''){
    const r = (s.match(/^(?:那就是說|话说|話說|说起来|說起來|你看|呼叫呼叫|CQ CQ|もしもし)(?<msg>.*)$/i)?.groups?.msg)
    if(!r) return;
    await speak('发送 ' + r)
    return r
}


async function speak(s) {
    if (!s) return; // console.error('say empty msg')

    console.log('saying ' + s);
    await waitFor(voicesAvailiable);

    const utter = new SpeechSynthesisUtterance(s);
    utter.voice = await userVoiceGet();
    utter.rate = Math.min(Math.max(1, s.length / 60), 4);
    if(speechSynthesis.speaking) speechSynthesis.cancel()
    speechSynthesis.speak(utter);
};

async function speak2nd(s) {
    if (!s) return; // console.error('say empty msg')
    if (speechSynthesis.speaking) return; // low priority
    console.log('saying ' + s);
    await waitFor(voicesAvailiable);

    const utter = new SpeechSynthesisUtterance(s);
    utter.voice = await userVoiceGet2nd();
    utter.rate = Math.min(Math.max(1, s.length / 60), 4);
    if(speechSynthesis.speaking) speechSynthesis.cancel()
    speechSynthesis.speak(utter);
};

function voicesAvailiable() {
    return globalThis.speechSynthesis.getVoices().length !== 0;
}
function latestMessage() {
    return [...document.querySelectorAll('.Message:not(.own) .text-content')].map(e => e.textContent).reverse()[0];
}
function latestMyMessage() {
    return [...document.querySelectorAll('.Message.own .text-content')].map(e => e.textContent).reverse()[0];
}

function edgeFilter(init) {
    return (e) => e !== init ? (init = e) : undefined;
}

async function tgSend(s) {
    if(!s) return;
    document.querySelector('#editable-message-text').innerHTML = s;
    await new Promise(r => setTimeout(r, 1e3));
    document.querySelector('#editable-message-text').dispatchEvent(new InputEvent('input', { bubbles: true, cancellable: false, inputType: 'insertText', composed: true }));
    await new Promise(r => setTimeout(r, 1e3));
    document.querySelector('#editable-message-text').dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancellable: true, composed: true, key: "Enter", code: "Enter" }));
}

async function waitFor(cond) {
    while (!(cond()))
        await delay1s();
}

async function delay1s() {
    await new Promise(r => setTimeout(r, 1e3));
}

async function userLangGet() {
    return (await userVoiceGet()).lang;
}

async function userVoiceGet() {
    return (await voicesForUserLang())[0];
}

async function userVoiceGet2nd() {
    const ls = await voicesForUserLang();
    return ls[1] || ls[0];
}
async function voicesForUserLang() {
    await waitFor(voicesAvailiable);
    return navigator.languages.map(nlang=>globalThis.speechSynthesis.getVoices().filter(({lang})=>lang.startsWith(nlang)).reverse()).flat()
    // return globalThis.speechSynthesis.getVoices().filter(({ lang }) => navigator.languages.find(nlang => lang.startsWith(nlang))).reverse();
}