nihongo_dict

毎日日本語を勉強しましょう

// ==UserScript==
// @name         nihongo_dict
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  毎日日本語を勉強しましょう
// @author       Ch4p1
// @match        *
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tv-asahi.co.jp
// @grant           GM_xmlhttpRequest
// @grant           GM.xmlHttpRequest
// @grant           GM_getResourceText
// @grant      GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @resource IMPORTED_CSS https://li1862-245.members.linode.com/audio/audio/tampermonkey.css
// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js
// ==/UserScript==
(function () {
    'use strict';
    const my_css = GM_getResourceText("IMPORTED_CSS");
    GM_addStyle(my_css);
    var player;
    var t = 0;
    var speed = 1;
    var touchL = false;
    var loopT = [];
    //next page
    const splitArr = window.location.href.split(/[\/,.=?]+/);
    var lastNumber = splitArr.findLast(v => parseInt(v) > 0);
    const selInd = window.location.href.lastIndexOf(lastNumber);
    //init ui
    let menu = document.createElement("div");
    menu.classList = "menu";
    let menuBtns = document.createElement("div");
    menuBtns.classList = "btns";
    menu.appendChild(menuBtns);
    createBtn('ㄆ', () => { oepnHelp() });
    createBtn('X', (e) => { e.target.parentElement.remove() });
    document.body.appendChild(menu);

    //get serifu
    var gotSerifu = false;
    //get serifu for kktv
    var serifuArr = {};
    if (window.location.hostname == "kktv.me" || window.location.hostname == "www.kktv.me") {
        XMLHttpRequest.prototype.realSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.send = function (value) {
            this.addEventListener("progress", function (e) {
                //zh-Hant.vtt ja.vtt
                var filename = e.target.responseURL.split('/').pop()
                if (filename == "zh-Hant.vtt" || filename == "ja.vtt") {
                    var matchArr = e.target.response.match(/\d+:\d+:\d+.\d+ --> \d+:\d+:\d+.\d+\n.+/gm, "");
                    matchArr.map(v => {
                        var tarr = v.split("-->");
                        var serifu = tarr[1].split("\n");
                        if (filename == "ja.vtt") {
                            if (!serifuArr[tarr[0].trim()]) serifuArr[tarr[0].trim()] = {}
                            serifuArr[tarr[0].trim()].endT = serifu[0].trim();
                            serifuArr[tarr[0].trim()].jp = serifu[1].trim();
                        }
                        else {
                            if (!serifuArr[tarr[0].trim()]) serifuArr[tarr[0].trim()] = {}
                            serifuArr[tarr[0].trim()].endT = serifu[0].trim();
                            serifuArr[tarr[0].trim()].tw = serifu[1].trim();
                        }
                    });
                    if (gotSerifu == false) {
                        tempAlert("字幕loading...", 2000);
                        createBtn('字幕', () => { copyKktv() });
                        gotSerifu = true;
                    }
                }

            }, false);
            this.realSend(value);
        };
    }
    /* function time2sec(s) {
        const sec = s.split(":").reduce((a, c, ind) => {
            var s = 1;
            if (ind == 0) s = 3600;
            else if (ind == 1) s = 60;
            return a + (parseFloat(c) * s)
        }, 0);
        return sec;
    }
    var responseTextArr = {};
    var temp_serifuArr = [];
    if (window.location.hostname == "www.netflix.com") {
        XMLHttpRequest.prototype.realSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.send = function (value) {
            this.addEventListener("progress", function (e) {
                //1 下載字幕 區分出中日
                try {
                    if ( e.target.responseText.substring(0, 6) == "WEBVTT") {
                        var lang = e.target.responseText.includes("c.japanese") ? "jp" : "tw";
                        console.log(">>>>",lang)
                        responseTextArr[lang] = e.target.responseText;
                        if (responseTextArr.jp && responseTextArr.tw) {
                            //process jp
                            var matchArr = responseTextArr.jp.match(/\d+:.+\n.+/gm, "");
                            matchArr.map(v => {
                                var teimArr = v.match(/\d+:\d+:\d+.\d+/gm, "");
                                var serifu = v.match(/(?<=bg_transparent>)(.+)(?=<\/c.bg)/gm);
                                if (serifu && serifu.length > 0)
                                    temp_serifuArr.push({ start: time2sec(teimArr[0]), end: time2sec(teimArr[1]), jp: serifu[0].trim(), tw: "" })
                                else
                                    console.log("~~~~~~~~~~", serifu, v);
                            });
                            console.log("~~~~")
                            //process tw
                            matchArr = responseTextArr.tw.match(/\d+:.+\n.+/gm, "");
                            matchArr.map(v => {
                                var teimArr = v.match(/\d+:\d+:\d+.\d+/gm, "");
                                var serifu = v.match(/(?<=bg_transparent>)(.+)(?=<\/c.bg)/gm);
                                if (serifu && serifu.length > 0) {
                                    var startSec = time2sec(teimArr[0]);
                                    var findObj = temp_serifuArr.find(vv => (startSec >= vv.start && startSec <= vv.end));
                                    if (findObj) {
                                        findObj.tw += serifu[0].trim();
                                    }
                                }
                                else {
                                    console.log("~~~~~~~~~~", serifu, v);
                                }
                            });
                            console.log("---------", temp_serifuArr);
                            tempAlert("字幕loading...", 2000);
                            createBtn('字幕', () => { copyKktv() });
                            gotSerifu = true;
                        }
                    }
                }
                catch (err) {
                    console.log(err);
                }
            }, false);
            this.realSend(value);
        };
    } */
    if (window.location.hostname == "ncode.syosetu.com") {
        var matchArr = document.querySelector(".novel_view").innerText.match(/.+\n/gm, "");

        if (matchArr.length > 0) {
            matchArr.map((v, k) => {
                serifuArr["f_" + k] = { jp: v.trim() };
            });
            tempAlert("字幕loading...", 2000);
            createBtn('字幕', () => { copyKktv() });
            gotSerifu = true;
        }
    }

    function getPlayer() {
        return document.querySelector("video") ? document.querySelector("video") : document.querySelector("audio");
    }

    setInterval(() => {
        if (!player) {
            player = getPlayer();
            if (player && (window.location.hostname == "kktv.me" || window.location.hostname == "www.kktv.me")) {
                const observer = new MutationObserver(function (mutations) {
                    const targetDiv = document.querySelector(".main-subtitle");
                    if (targetDiv) {
                        targetDiv.style.userSelect = "text";
                    }
                });
                const div = document.querySelector(".kktv-player");
                observer.observe(div, {
                    childList: true,
                    attributes: true,
                    characterData: true,
                });
            }
        }
        if (loopT.length == 2 && player.currentTime > loopT[1]) {
            player.currentTime = loopT[0];
            player.play();
        }
    }, 200);



    function createBtn(title, fn) {
        let btn = document.createElement("div");
        btn.classList = "menuBtn";
        btn.innerHTML = title;
        btn.onclick = fn
        menuBtns.appendChild(btn);
    }
    var hotkeyList = [
        { cmd: "mediaPlayStart", hotkey: "O", tip: "標記影片返回點" },
        { cmd: "mediaPlayEnd", hotkey: "P", tip: "按住回到O點並播放,放開停止" },
        { cmd: "mediaPause", hotkey: "SPACE", tip: "暫/播放" },
        { cmd: "mediaSetLoop", hotkey: "Q", tip: "設定循環播放範圍,連按2次清除" },
        { cmd: "mediaRepaet", hotkey: "‰", tip: "設定repeat" },
        { cmd: "nihongoHira", hotkey: "⁄", tip: "日文單字(若找的)顯示拼音" },
        { cmd: "nihongoDict", hotkey: "€", tip: "中日字典" },
        { cmd: "nihongoSakura", hotkey: "‹", tip: "日日字典sakura" },
        { cmd: "nihongoGoo", hotkey: "Í", tip: "日日字典goo" },
        { cmd: "nihongoSpeak", hotkey: "Å", tip: "日文發音" },
        { cmd: "enTranslate", hotkey: "Œ", tip: "英文字典google" },
        { cmd: "google", hotkey: "¸", tip: "google" },
        { cmd: "nextPage", hotkey: "˘", tip: "下一頁" },
        { cmd: "prevPage", hotkey: "¯", tip: "上一頁" },
        { cmd: "comm", hotkey: "-", tip: "↑↓速度←→前後" },
    ];
    hotkeyList.map(v => {
        const _key = GM_getValue(v.cmd);
        if (_key && _key != "")
            v.hotkey = _key;
    });

    function createInfo(obj) {
        let div = document.createElement("div");
        let hotkey = document.createElement("input");
        let tip = document.createElement("span");
        div.classList = "memo";
        if (obj.cmd != "comm") {
            hotkey.type = "text";
            hotkey.maxLength = "1";
            hotkey.value = obj.hotkey;
            hotkey.style = "width: 18px;";
            hotkey.onkeyup = (e) => {
                console.log("e.target.value", e.target.value, obj);
                obj.hotkey = e.target.value;
                GM_setValue(obj.cmd, obj.hotkey);
            };
            div.appendChild(hotkey);
        }
        tip.innerHTML = obj.tip;
        div.appendChild(tip);
        return div;
    }
    function tempAlert(msg, duration) {
        document.querySelectorAll(".tempAlert").forEach(el => {
            el.style.bottom = (parseInt(el.style.bottom) + 24) + "px";
        });
        var el = document.createElement("div");
        el.className = "tempAlert";
        el.setAttribute("style", "position:absolute;bottom:5px;left:1%;padding: 0 1rem;background-color:black;color: white;font-size: 20px;");
        el.innerHTML = msg;
        setTimeout(function () {
            el.parentNode.removeChild(el);
        }, duration);
        document.body.appendChild(el);
    }
    function copyKktv() {
        try {
            document.querySelector(".modalxx").remove();
        }
        catch (e) {
            /* var data = [];
            document.querySelectorAll(".kktv-player>div>:nth-child(2)>:nth-child(2)>button").forEach(button => {
                const divs = button.querySelectorAll("div");
                data.push({ id: button.id, jp: divs[0].innerText, tw: divs[1].innerText });
            }); */
            let headers = {
                "Content-Type": "application/json",
                "Accept": "application/json",
            }
            fetch('https://li1862-245.members.linode.com:3006/serifu', {
                method: "POST",
                headers: headers,
                body: JSON.stringify({ str: serifuArr })
            })
                .then((response) => {
                    return response.json();
                }).then((ret) => {
                    let canvas = document.createElement("div");
                    canvas.classList = "modalxx";
                    let img = document.createElement("img");
                    img.src = 'https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl=https://li1862-245.members.linode.com/kktvreader/build/?key=' + ret.key + '&choe=UTF-8';
                    img.onclick = (e) => {
                        window.open(
                            'https://li1862-245.members.linode.com/kktvreader/build/?key=' + ret.key, "_blank");
                    }
                    img.target = "_blank";
                    canvas.appendChild(img);
                    menu.appendChild(canvas);
                }).catch((err) => {
                    console.log('錯誤:', err);
                });
        }
    }
    function oepnHelp() {
        try {
            document.querySelector(".modalxx").remove();
        }
        catch (e) {
            let canvas = document.createElement("div");
            canvas.classList = "modalxx";
            hotkeyList.map(v => {
                canvas.appendChild(createInfo(v));
            });
            menu.appendChild(canvas);
        }
    }
    function addLoop() {
        if (loopT.length === 1) {
            loopT.push(player.currentTime);
            tempAlert("add loop end", 2000);
        }
        else if (loopT.length === 0) {
            loopT.push(player.currentTime);
            tempAlert("add loop start", 2000);
        }
    }
    function isRepeatKey(event) {
        return (event.key == lastKey.key && (performance.now() - lastKey.t) < 1000)
    }
    function clearAllLoop() {
        loopT = [];
    }
    function padLeft(str, len) {
        str = '' + str;
        if (str.length >= len) {
            return str;
        } else {
            return padLeft("0" + str, len);
        }
    }
    var lastKey = { key: "", t: 0 };

    document.body.addEventListener("keyup", function (event) {
        const hotkeyObj = hotkeyList.find(v => { return v.hotkey.toLowerCase() == event.key.toLowerCase() });
        if (player) {
            if (hotkeyObj.cmd === "mediaPlayEnd") {//L roolback
                console.log("keyup mediaPlayEnd")
                touchL = false;
                player.pause();
            }
        }
    });

    document.body.addEventListener("keydown", function (event) {
        var selectTxt = "";
        const hotkeyObj = hotkeyList.find(v => { return v.hotkey.toLowerCase() == event.key.toLowerCase() });
        if (player) {
            if (hotkeyObj.cmd === "mediaPlayEnd" && touchL == false) {//L roolback
                console.log("mediaPlayStart")
                touchL = true;
                player.currentTime = t;
                player.play();
            }
            else if ((hotkeyObj.cmd === "mediaPlayStart")) {//k set point
                if (isRepeatKey(event)) {
                    t -= 0.5;
                    player.currentTime = t;
                    player.play();
                }
                else {
                    t = player.currentTime;
                }
            }
            else if (hotkeyObj.cmd === "mediaSetLoop") {
                if (isRepeatKey(event)) {
                    clearAllLoop();
                    tempAlert("clear loop", 2000);
                }
                else {
                    addLoop();
                }
            }
            else if (hotkeyObj.cmd === "mediaRepaet") {//L roolback
                player.loop = !player.loop;
                tempAlert("repeat " + player.loop, 2000);
            }
            else if (event.key === "ArrowRight") {//back
                player.currentTime += 1;
            }
            else if (event.key === "ArrowLeft") {//font
                player.currentTime -= 1;
            }
            else if (event.key === "ArrowUp") {//up speed up
                speed += 0.1;
                if (speed >= 1) {
                    speed = 1;
                }
                player.playbackRate = speed;
            }
            else if (event.key === "ArrowDown") {//down speed down
                speed -= 0.1;
                if (speed <= 0.1) {
                    speed = 0.1;
                }
                player.playbackRate = speed;
            }
        }
        if (hotkeyObj.cmd === "nihongoHira") {
            //日文拼音
            if (window.getSelection) {
                selectTxt = window.getSelection();
            } else if (window.document.getSelection) {
                selectTxt = window.document.getSelection();
            } else if (window.document.selection) {
                selectTxt = window.document.selection.createRange().text;
            }
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://dict.asia/jc/" + selectTxt.toString(),
                headers: {
                    "User-Agent": "Mozilla/5.0",    // If not specified, navigator.userAgent will be used.
                    "Accept": "text/xml"            // If not specified, browser defaults will be used.
                },
                onload: function (ret) {
                    console.log(ret);
                    let parser = new DOMParser();
                    const document = parser.parseFromString(ret.responseText, "text/html");
                    var kijis = document.querySelectorAll("#jp_comment");
                    if (kijis != null && kijis.length > 0) {
                        const kiji = kijis[0];
                        var kana = kiji.querySelector(".trs_jp");
                        var kanaStr = "";
                        if (kana != null) {
                            kanaStr = kana.textContent.replaceAll(/【|】/g, '');
                            selectTxt.anchorNode.parentElement.innerHTML = selectTxt.anchorNode.parentElement.innerHTML.replaceAll(selectTxt.toString(), "<ruby>" + selectTxt.toString() + "<rt>" + kanaStr + "</rt></ruby>")
                        }
                    }
                    else {
                        tempAlert("can't find", 2000);
                    }
                }
            });
        }
        else if (hotkeyObj.cmd === "nihongoDict") {
            //日文字典 2
            if (window.getSelection) {
                selectTxt = window.getSelection();
            } else if (window.document.getSelection) {
                selectTxt = window.document.getSelection();
            } else if (window.document.selection) {
                selectTxt = window.document.selection.createRange().text;
            }
            window.open(
                "https://dict.asia/jc/" + selectTxt.toString(), "_blank");
        }
        else if (hotkeyObj.cmd === "nihongoSakura") {
            //日文字典 3
            if (window.getSelection) {
                selectTxt = window.getSelection();
            } else if (window.document.getSelection) {
                selectTxt = window.document.getSelection();
            } else if (window.document.selection) {
                selectTxt = window.document.selection.createRange().text;
            }
            window.open(
                "https://sakura-paris.org/dict/%E5%BA%83%E8%BE%9E%E8%8B%91/prefix/" + selectTxt.toString(), "_blank");
        }
        else if (hotkeyObj.cmd === "nihongoGoo") {
            //日文字典goo找前面一致 s
            if (window.getSelection) {
                selectTxt = window.getSelection();
            } else if (window.document.getSelection) {
                selectTxt = window.document.getSelection();
            } else if (window.document.selection) {
                selectTxt = window.document.selection.createRange().text;
            }
            window.open(
                "https://dictionary.goo.ne.jp/srch/jn/" + selectTxt.toString() + "/m0u/", "_blank");
        }
        else if (hotkeyObj.cmd === "nihongoSpeak") {
            //發音 a
            if (window.getSelection) {
                selectTxt = window.getSelection();
            } else if (window.document.getSelection) {
                selectTxt = window.document.getSelection();
            } else if (window.document.selection) {
                selectTxt = window.document.selection.createRange().text;
            }
            window.open(
                "https://ja.forvo.com/word/" + selectTxt.toString() + "/#ja", "_blank");
        }
        else if (hotkeyObj.cmd === "enTranslate") {
            //google translate q
            if (window.getSelection) {
                selectTxt = window.getSelection();
            } else if (window.document.getSelection) {
                selectTxt = window.document.getSelection();
            } else if (window.document.selection) {
                selectTxt = window.document.selection.createRange().text;
            }
            window.open(
                "https://translate.google.com.tw/?hl=zh-TW&tab=rT&sl=en&tl=zh-TW&text=" + selectTxt.toString() + "&op=translate", "_blank");
        }
        else if (hotkeyObj.cmd === "google") {
            //google Z
            if (window.getSelection) {
                selectTxt = window.getSelection();
            } else if (window.document.getSelection) {
                selectTxt = window.document.getSelection();
            } else if (window.document.selection) {
                selectTxt = window.document.selection.createRange().text;
            }
            window.open(
                "https://www.google.com/search?q=" + selectTxt.toString(), "_blank");
        }
        else if (hotkeyObj.cmd === "nextPage") {
            //next page >

            lastNumber = padLeft(Number(lastNumber) + 1, lastNumber.length);
            window.location.href = window.location.href.substring(0, selInd) + lastNumber + window.location.href.substring(selInd + lastNumber.length);
        }
        else if (hotkeyObj.cmd === "prevPage") {
            //prev page >
            lastNumber = padLeft(Number(lastNumber) - 1, lastNumber.length);
            window.location.href = window.location.href.substring(0, selInd) + lastNumber + window.location.href.substring(selInd + lastNumber.length);
        }
        lastKey = { key: event.key, t: performance.now() };
    });
})();