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.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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