Vajehyab Assistan

Use the VajehYab.com website as a dictionary. Just double-click or select any text, and the results will appear as a smooth and light pop-up. It is a translator that you can enable/disable by using Ctrl + Alt + Q.

// ==UserScript==
// @name            Vajehyab Assistan
// @name:fa      	دستیار واژه‌یاب
// @version         0.11
// @author          Amir
// @description     Use the VajehYab.com website as a dictionary. Just double-click or select any text, and the results will appear as a smooth and light pop-up. It is a translator that you can enable/disable by using Ctrl + Alt + Q.
// @description:fa  کلمه انتخاب شده را در سایت واژه‌یاب جستجو و نمایش می‌دهد
// @namespace       amm1rr
// @match           https://twitter.com/*
// @include         *
// @homepage        https://github.com/Amm1rr/Vajehyab-Assistant
// @icon            https://vajehyab.com/assets/icons/180.png
// @source          https://github.com/Amm1rr/Vajehyab-Assistant
// @supportURL      https://github.com/Amm1rr/Vajehyab-Assistant/issues
// @require         https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @require         https://code.jquery.com/jquery-3.6.0.slim.min.js
// @resource        https://github.com/Amm1rr/Vajehyab-Assistant/raw/main/ref/style-minimal.min.css
// @grant           GM_xmlhttpRequest
// @grant           GM_getResourceText
// @connect         vajehyab.com
// @license         MIT
// ==/UserScript==

// window.getSelection().toString()

// window.document.body.addEventListener("mouseup", translate, false);
window.document.body.addEventListener("click", translate, false);
window.document.body.addEventListener("keyup", toggleVajehyab, false);
var toggle = true;

// Ctrl + Alt + Q => Toggle Enable\Disable Vajehyab Assistant
function toggleVajehyab(e) {
    if (e.which == 81 && e.altKey && e.ctrlKey) {
        if (toggle) {
            window.document.body.removeEventListener("click", translate, false);
            toggle = false;
        } else {
            window.document.body.addEventListener("click", translate, false);
            toggle = true;
        }
    }
}

function translate(e) {
    // remove previous .vajehPopup if exists
    PopupsRemover();
    // console.log("translate start");
    var selectObj = document.getSelection();

    // if #text node
    if (selectObj.anchorNode && selectObj.anchorNode.nodeType == 3) {
        //GM_log(selectObj.anchorNode.nodeType.toString());
        var word = selectObj.toString();
        if (word == "") {
            return;
        }
        // linebreak wordwrap, optimize for pdf.js
        word = word.replace("-\n", "");
        // multiline selection, optimize for pdf.js
        word = word.replace("\n", " ");
        //console.log("word:", word);
        var ts = new Date().getTime();
        //console.log("time: ", ts);
        var mx = e.clientX;
        var my = e.clientY;
        GetTranslate(word, ts);
    }

    function PopupsRemover() {
        // remove previous .vajehPopup if exists
        var previous = document.querySelector(".vajehPopup");
        while (previous) {
            document.body.removeChild(previous);
            previous = document.querySelector(".vajehPopup");
        }

        previous = document.querySelector("#vajehiframe");
        while (previous) {
            document.body.removeChild(previous);
            previous = document.querySelector("#vajehiframe");
        }
    }

    // function calculatePosition(x, y, popup) {
    //     const pos = {};
    //     const margin = 5;
    //     const anchor = 10;

    //     const outerWidth = $(popup).outerWidth();
    //     const outerHeight = $(popup).outerHeight();

    //     // show popup to the right of the word if it fits into window this way
    //     if (x + anchor + outerWidth + margin < $(window).width()) {
    //         pos.x = x + anchor;
    //     }
    //     // show popup to the left of the word if it fits into window this way
    //     else if (x - anchor - outerWidth - margin > 0) {
    //         pos.x = x - anchor - outerWidth;
    //     }
    //     // show popup at the very left if it is not wider than window
    //     else if (outerWidth + margin * 2 < $(window).width()) {
    //         pos.x = margin;
    //     }
    //     // resize popup width to fit into window and position it the very left of the window
    //     else {
    //         const non_content_x = outerWidth - $(popup).width();

    //         $(popup).width($(window).width() - margin * 2 - non_content_x);

    //         pos.x = margin;
    //     }

    //     // show popup above the word if it fits into window this way
    //     if (y - anchor - outerHeight - margin > 0) {
    //         pos.y = y - anchor - outerHeight;
    //     }
    //     // show popup below the word if it fits into window this way
    //     else if (y + anchor + outerHeight + margin < $(window).height()) {
    //         pos.y = y + anchor;
    //     }
    //     // show popup at the very top of the window
    //     else {
    //         pos.y = margin;
    //     }

    //     return pos;
    // }

    function calculatePosition(x, y, popup) {
        const pos = {};
        const margin = 5;
        const anchor = 10;

        const outerWidth = $(popup).outerWidth();
        const outerHeight = $(popup).outerHeight();

        // show popup to the right of the word if it fits into window this way
        if (x + anchor + outerWidth + margin < $(window).width()) {
            pos.x = x + anchor;
        }
        // show popup to the left of the word if it fits into window this way
        else if (x - anchor - outerWidth - margin > 0) {
            pos.x = x - anchor - outerWidth;
        }
        // show popup at the very left if it is not wider than window
        else if (outerWidth + margin * 2 < $(window).width()) {
            pos.x = margin;
        }
        // resize popup width to fit into window and position it the very left of the window
        else {
            const non_content_x = outerWidth - $(popup).width();

            $(popup).width($(window).width() - margin * 2 - non_content_x);

            pos.x = margin;
        }

        // show popup above the word if it fits into window this way
        if (y - anchor - outerHeight - margin > 0) {
            pos.y = y - anchor - outerHeight;
        }
        // show popup below the word if it fits into window this way
        else if (y + anchor + outerHeight + margin < $(window).height()) {
            pos.y = y + anchor;
        }
        // show popup at the very top of the window
        else {
            pos.y = margin;
        }

        const scrollBarWidth = $(window).width() - $(document).width();

        if (scrollBarWidth > 0) {
            margin += scrollBarWidth;
        }

        if (window.orientation === "landscape") {
            const temp = pos.x;
            pos.x = pos.y;
            pos.y = temp;
        }

        if (pos.x === x + anchor) {
            const scrollBarLeft = $(window).scrollLeft();

            if (scrollBarLeft > x) {
                pos.x -= scrollBarLeft;
            }
        }

        return pos;
    }

    function getParameter(jsonData, parameter, wrd) {
        let jsdata = JSON.parse(jsonData);
        let data;

        try {
            data = jsdata.props.pageProps.fallback[wrd + ":"].data;
        } catch (error) {
            return undefined;
        }

        if (typeof data === "undefined") {
            return undefined;
        }

        switch (parameter) {
            case "Query":
                return data.Query ? data.Query : undefined;
            case "Id":
                return data.Id ? data.Id : undefined;
            case "Exact": {
                const exact = [];

                if (data.WordBox) {
                    // console.log(data.WordBox);

                    let nullCount = 0;
                    let totalCount = 0;

                    for (const dictionaryName in data.WordBox) {
                        totalCount++;
                        if (data.WordBox.hasOwnProperty(dictionaryName)) {
                            const dictionary = data.WordBox[dictionaryName];
                            if (dictionary && dictionary.dictionary) {
                                const dicname = dictionary.dictionary;
                                // console.log(`Title for ${dictionaryName}: ${dicname}`);

                                const dataobjEntry = {
                                    dictionary: dicname,
                                    dictionary_id: dictionary.dictionary_id,
                                    id: dictionary.id,
                                    score: dictionary.score,
                                    slug: dictionary.slug,
                                    summary: dictionary.description,
                                    title: dictionary.title,
                                };

                                // Add dataobjEntry to eact array
                                exact.push(dataobjEntry);
                            } else {
                                //// Count null dictionary.id values
                                // if (dictionary.id === null) {
                                nullCount++;
                                // }
                                // Check if more than half of dictionary.id values are null
                            }

                            // switch (dictionaryName) {
                            //     // case "Query":
                            //     case "Amid":
                            //         exact.push();
                            //     // case "Motaradef":
                            //     // case "Sereh":
                            //     // case "En2fa":
                            //     // case "Fa2en":
                            // }
                        }
                    }
                    if (nullCount > totalCount / 2) {
                        if (data.Exact) {
                            for (const doc of data.Exact.Docs) {
                                exact.push(doc);
                            }
                        }

                        if (exact.length == 0) {
                            const similar = [];
                            if (data.Similar) {
                                for (const doc of data.Similar.Docs) {
                                    exact.push(doc);
                                }
                            }
                        }
                    }

                    return exact;
                } else {
                    if (data.Exact) {
                        for (const doc of data.Exact.Docs) {
                            exact.push(doc);
                        }
                    }

                    if (exact.length == 0) {
                        const similar = [];
                        if (data.Similar) {
                            for (const doc of data.Similar.Docs) {
                                exact.push(doc);
                            }
                        }
                    }
                    return exact;
                }
            }
            case "Similar":
                return data.Similar ? data.Similar : undefined;

            case "Text":
                return data.Text ? data.Text : undefined;
            case "Prefix":
                return data.Prefix ? data.Prefix : undefined;
            case "Mlt":
                return data.Mlt ? data.Mlt : undefined;
            case "Spell":
                return data.Spell ? data.Spell : undefined;
            case "Random":
                return data.Random ? data.Random : undefined;
            case "WordBox":
                return data.WordBox ? data.WordBox : undefined;
            default:
                return undefined;
        }
    }

    function popup(mx, my, result, wrd) {
        PopupsRemover();

        /* HTML Parse */
        const parser = new DOMParser();
        const page = parser.parseFromString(result, "text/html");
        const jsontag = page.querySelector("#__NEXT_DATA__");
        const json = jsontag.innerHTML;

        const res = getParameter(json, "Exact", wrd);

        if (
            typeof res === "undefined" ||
            !Array.isArray(res) ||
            res.length < 1 ||
            typeof res[0] === "undefined" ||
            res[0].length < 2
        ) {
            console.log("یافت نشد");
            return;
        }

        // // Move amid's dictionary first.
        // res.sort((a, b) => {
        //     if (a.dictionary === "amid") return -1;
        //     if (b.dictionary === "amid") return 1;
        //     return a.dictionary.localeCompare(b.dictionary);
        // });

        // Mapping of English dictionary names to Persian Names
        const dictionaryNames = {
            amid: "امید",
            dehkhoda: "دهخدا",
            moein: "معین",
            isfahani: "اصفهانی",
            mazani: "مازنی",
            fa2en: "انگلیسی",
            motaradef: "مترادف",
            fa2tr: "ترکی",
            fa2ar: "عربی",
            en2fa: "EN",
            ar2fa: "AR",
            sereh: "سِرِه",
            wiki: "ویکی",
        };

        // console.log(res);
        // console.log(res[0].title);

        /* HTML Parse */
        var iframe = document.createElement("iframe");
        iframe.src = "about:blank";
        iframe.id = "vajehiframe";

        // main window
        // first insert into dom then there is offsetHeight!IMPORTANT!
        // document.body.appendChild(vajehWindow);
        document.body.appendChild(iframe);

        var direc = "rtl";
        var html =
            "<html><head><title>واژه‌یاب فارسی</title>" +
            ' </head><body style="padding-bottom: 0px; direction:rtl; padding-right:15px;">';
        for (let i = 0; i < res.length; i++) {
            const persianDictionary =
                dictionaryNames[res[i].dictionary] || res[i].dictionary;

            if (persianDictionary == "انگلیسی" || persianDictionary == "ترکی") {
                direc = "ltr";
            } else {
                direc = "rtl";
            }

            html +=
                "<header><div style='float:right;color:rgb(158, 63, 26);'>" +
                res[i].title +
                "</div><div style='float:left;font-size:12px;color:green;'>" +
                persianDictionary +
                "</div></header><br><hr>";
            html +=
                "<div style='direction:" +
                direc +
                "; font-size:14px;'>" +
                res[i].summary +
                "</div><br>";
        }
        html += "</body></html>";

        iframe.srcdoc = html;
        let vajehWindow = iframe;
        vajehWindow.style.color = "blue";
        vajehWindow.style.textAlign = "right";
        vajehWindow.style.display = "block";
        vajehWindow.style.position = "fixed";
        vajehWindow.style.background = "lightblue";
        vajehWindow.style.borderRadius = "5px";
        vajehWindow.style.boxShadow = "0 0 5px 0";
        vajehWindow.style.opacity = "0.9";
        vajehWindow.style.width = "20%";
        vajehWindow.style.overflowWrap = "break-word";
        vajehWindow.style.left = mx - 40 + "px";
        // vajehWindow.style.dir = "rtl";

        var cssstyle = {
            "overflow-y": "scroll",
            height: "40%",
        };
        Object.assign(vajehWindow.style, cssstyle);

        var pos = calculatePosition(mx, my, vajehWindow);

        if (pos.x >= window.innerWidth || 500 >= window.innerWidth) {
            vajehWindow.style.left = "20px";
            vajehWindow.style.width = window.innerWidth - 55 + "px";
            vajehWindow.style.height = "25%";
        } else if (1000 >= window.innerWidth) {
            vajehWindow.style.width = "30%";
            vajehWindow.style.height = "25%";
        } else {
            vajehWindow.style.left = pos.x;
        }

        if (pos.y + vajehWindow.offsetHeight + 10 >= window.innerHeight) {
            vajehWindow.style.bottom = "10px";
        } else {
            vajehWindow.style.top = pos.y + 10 + "px";
        }

        if (my + vajehWindow.offsetHeight + 10 >= window.innerHeight) {
            //vajehWindow.style.bottom = "10px";
            //console.log("Height bottom");
        } else {
            vajehWindow.style.top = my + 15 + "px";
            //console.log("Height top");
        }

        if (pos.x + vajehWindow.offsetWidth + 55 >= window.innerWidth) {
            vajehWindow.style.left = window.innerWidth - vajehWindow.offsetWidth - 20 + "px";
        }

        vajehWindow.style.padding = "5px";
        vajehWindow.style.zIndex = "999999";
    }

    function GetTranslate(wrd, ts) {
        // var reqUrl = `https://www.vajehyab.com/?q=${word}&d=en&_=1617191412351&ts=${ts}`;
        // var reqUrl = `https://www.vajehyab.com/?q=${word}&d=en`;
        let trimWord = wrd.trim();
        let reqUrl = `https://vajehyab.com/?q=${trimWord}`;
        //console.log("request url: ", reqUrl);
        let ret = GM.xmlHttpRequest({
            method: "GET",
            url: reqUrl,
            headers: { Accept: "application/json" }, // can be omitted...
            onreadystatechange: function (res) {
                //console.log("Request state changed to: " + res.readyState);
            },
            onload: function (res) {
                let retContent = res.response;
                // console.log(retContent);
                // const parser = new DOMParser();
                // retContent = parser.parseFromString(retContent, 'text/html');
                popup(mx, my, retContent, trimWord);
            },
            onerror: function (res) {
                console.log("error");
            },
        });
    }
}