Spawner / Reslator - LNMTL Plugin

// ==UserScript==
// @name        Reslator - LNMTL Plugin
// @author      Spawner
// @version     1.2.95
// @namespace   reader_translators
// @match       https://lnmtl.com/chapter/*
// @require     https://userscripts-mirror.org/scripts/source/107941.user.js#sha384=Q8t880BurrlGKTdpvYv2+da12PYnvljdiU8aJvakk1uE3QMbzb190ueXNpAUY98p
// @updateURL   https://openuserjs.org/meta/Spawner/Reslator_-_LNMTL_Plugin.meta.js
// @downloadURL https://openuserjs.org/install/Spawner/Reslator_-_LNMTL_Plugin.user.js
// @grant       GM_xmlhttpRequest
// @grant       GM_addStyle
// @grant       GM_setValue
// @grant       GM_getValue
// @license     MIT
// @run-at      document-start
// @connect     translate.alibaba.com
// @connect     aidatrans.com
// @connect     test.niutrans.com
// @connect     niutrans.com
// @connect     translate.googleapis.com
// @connect     translate.minganci.org
// @connect     https://www.kingsoftai-fanyi.com/aida_api/text/translate
// @connect     kingsoftai-fanyi.com/aida_api/text/translate
// @connect     kingsoftai-fanyi.com
// @require     http://code.jquery.com/jquery-3.4.1.min.js
// @description 6/1/2020, 1:51:50 PM
// ==/UserScript==

/*

        | Plugin      : Reslator
        | Version     : 1.2.941
        | Author      : Spawner

        | Description : Reader & Translator, which gives the user a better experience.


        CHANGELOG :

        1.0     -   Initial release

        1.1     -   Add Glossary support

        1.2     -   + Fixed : Word higlighter
                    + Raw on click delay

        1.2.1   -   Fixed comments section

        1.2.2   -   + Improve performance
                    + Fixing a small issue in the glossary

        1.2.3   -   + Performance improvement
                    + Add secondary theme switcher
                    + Save raw/original text state

        1.2.4   -   Improve Aideep speed + Fix translation display

        1.2.5   -   Fix niutrans API

        1.2.6   -   Added a new provider

        1.2.7   -   + Navigation between chapters has become much smoother.
                    + Changing the main font of the reader for a better a better one.
                    + Adding a font size controller.
                    + Fixed font delay problem.

        1.2.8   -   + Fixed niutrans
                    + Added deepL ( temporary )

        1.2.9   -   + Added Alibaba service

        1.2.91  -   + Deepl removed
                    + Alibaba model changed

        1.2.92  -   + Deepl fixed.
                    + Niutrans fixed.

        1.2.93  -   + Niutrans fixed (again).

        1.2.94  -   + Fixed some bugs on some providers.
                    + Added a new translation engine ( Aideep translation )

        1.2.95  -   + AIDeep fix
*/

let iframeHandler;

let uniqueWords = new Map();

let autoThemeState = GM_SuperValue.get("autoThemeState", false);
let autoReaderState = GM_SuperValue.get("autoReaderState", false);
let autoRawState = GM_SuperValue.get("autoRawState", false);

let providerOriginalState = GM_SuperValue.get("providerOriginalState", false);
let providerGoogleState = GM_SuperValue.get("providerGoogleState", false);
let providerAlibabaState = GM_SuperValue.get("providerAlibabaState", false);
let providerNiutransState = GM_SuperValue.get("providerNiutransState", false);
let providerAideepState = GM_SuperValue.get("providerAideepState", false);
let providerDeeplState = GM_SuperValue.get("providerDeeplState", false);

let secondaryTheme = GM_SuperValue.get("secondaryTheme", "");
let fontSizeValue = GM_SuperValue.get("fontSizeValue", "");
let fontSizeValueDefault = GM_SuperValue.get("fontSizeValueDefault", "");

const providers = ["Google", "Alibaba", "Niutrans", "Aideep", "Deepl"];

let isTranslated = {
    Google: [providerGoogleState, false],
    Niutrans: [providerNiutransState, false],
    Alibaba: [providerAlibabaState, false],
    Aideep: [providerAideepState, false],
    Deepl: [providerDeeplState, false],
};

const providerObject = {
    Google: {
        maxSize: 5000,
        timeout: 0,
        color: ["48, 175, 219", 0.04],
    },
    Niutrans: {
        maxSize: 4000,
        timeout: 400,
        color: ["235, 77, 75", 0.06],
    },
    Alibaba: {
        maxSize: 900,
        timeout: 1000,
        color: ["255, 164, 58", 0.06],
    },
    Aideep: {
        maxSize: 1500,
        timeout: 30,
        color: ["140, 122, 230", 0.1],
    },
    Deepl: {
        maxSize: 1000,
        timeout: 1000,
        color: ["52, 73, 94", 0.1],
    }
};

/**
 *   Show text
 *
 *   If no translation is enabled, show the original text
 *   otherwise hide it.
 */
if (
    !providerGoogleState &&
    !providerAlibabaState &&
    !providerNiutransState &&
    !providerAideepState
)
    providerOriginalState = true;

if (fontSizeValue === "" || fontSizeValueDefault === "") {
    fontSizeValue = "20";
    fontSizeValueDefault = "19";
}

/**
 *   Apply the theme properly.
 *
 *   The reason to set the display as 'none' and opacity as '0'
 *   is to avoid the iframe onload screen flash, we also need to hide the scrollbar
 *   since we will be using the iframe one.
 */
if (autoReaderState) {
    GM_addStyle(
        `
        html
        {
            background-color  : ${autoThemeState ? "#E9E9E9" : "#25282F"};
        }
        #app
        {
            display           : none;
            opacity           : 0;
            overflow-y        : hidden;
        }
      `
    );
}

/*
 *   Initializing our CSS Styles
 *
 *   The iframe ( #pageContainer ) needs to be created at document-start
 *   to avoid any performance issues.
 */
GM_addStyle(`
    #pageContainer, #pagePreload
    {
        font-family     : Open sans;
        position        : fixed;
        top             : 0px;
        left            : 0px;
        overflow-y      : scroll;
        width           : 100%;
        height          : 100%;
        display         : block;
        border-width    : 0px;
    }
`);

/*  *   Global variables for theme & settings Colors
 *
 *   Using the :root selector to easily
 *   switch between colors.
 */
let CSSRoot = `
    :root
    {
        --background-color          : #E9E9E9;
        --close-btn-back-color      : #fff;
        --close-btn-fore-color      : #000;
        --title-color               : #000;
        --chapter-color             : #000;
        --raw-color                 : #424242;
        --settings-t-clicked-color  : #E9E9E9;
        --settings-label-color      : rgba(51, 47, 53, 0.4);
        --settings-check-back-color : linear-gradient(to right, #000000, #434343);

        --checked-background        : #029992;
        --checked-background-rgb    : 2, 153, 146;
    }

    [data-theme="dark"]
    {
        --background-color          : #25282F;
        --close-btn-back-color      : #000;
        --close-btn-fore-color      : #fff;
        --title-color               : #fff;
        --chapter-color             : #E9E9E9;
        --raw-color                 : #424242;
        --settings-t-clicked-color  : #25282F;
        --settings-label-color      : #E9E9E9;
        --settings-check-back-color : #E9E9E847;

        --checked-background        : #029992;
        --checked-background-rgb    : 2, 153, 146;
    }

    [data-sec-theme="gold"]
    {
        --checked-background        : #F39B3A;
        --checked-background-rgb    : 243, 155, 58;
    }

    [data-sec-theme="moon"]
    {
        --checked-background        : #9c88ff;
        --checked-background-rgb    : 156, 136, 255;
    }

    [data-sec-theme="bomb"]
    {
        --checked-background        : #eb4d4b;
        --checked-background-rgb    : 235, 77, 75;
    }

    [data-sec-theme="default"]
    {
        --checked-background        : #029992;
        --checked-background-rgb    : 2, 153, 146;
    }
`;

/*  *  Our iframe body styles
 *
 *  Note : To avoid the scroll flickering issue we will
 *  be using will-change : transform as a temporary solution.
 */
let CSSContainer = `

    html
    {
        background-color    : var(--background-color);
    }

    @keyframes fadein
    {
        from
        {
            opacity        : 0;
        }
        to
        {
            opacity        : 1;
        }
    }

    .chapterBody
    {
        position          : relative    !important;
        font-family       : "Source Sans Pro" !important;
        text-align        : justify     !important;
        animation         : fadein 3s;
        will-change       : transform;
        overflow          : visible;
        color             : var(--chapter-color);
        max-width         : 72%;
        min-height        : 100%;
        margin            : 0 auto;
        padding-bottom    : 80px;
    }

    .pageContainerBackground
    {
        animation         : fadein 1s;
        min-height        : 100%;
    }

    .pageContainerSettings
    {
        transition        : all 0.35s ease-in-out;
        background-color  : transparent !important;
        margin            : auto;
    }

    .pageSplit
    {
        animation         : fadein 3s;
        border-bottom     : 1px solid rgba(var(--checked-background-rgb), 0.3)  !important;
        /*margin            : 0px 0px 10px;*/
    }

    h1.header-title
    {
        color             : var(--checked-background);
        animation         : fadein 3s;
        font-family       : Poppins;
        font-weight       : 300;
        font-size         : 2.25em;
        text-align        : center;
        padding-bottom    : .3em;
        line-height       : 1.2;
        margin            : 3em auto .2em;
        color             : var(--title-color);
        text-shadow       : 0 0 4px rgba(0, 0, 100,.5);
    }
`;

let CSSForm = `

    .settingsForm
    {
        will-change       : transform;
        position          : relative;
        justify-content   : center;
        margin-left       : -16;
        margin-top        : 20;
        flex-wrap         : wrap;
    }

    form
    {
        font-size         : 13px;
        letter-spacing    : .2em;
        font-family       : sans-serif;
        display           : flex;
        margin-top        : 0em;
    }

    label
    {
        user-select       : none;
        color             : var(--settings-label-color);
        text-transform    : uppercase;
        font-family       : sans-serif;
        display           : flex;
        font-weight       : bold;
        padding           : 7px 16px;
        /* margin-right   : -4px; */
    }

    input[type=checkbox],
    input[type=radio]
    {
        color             : #000;
        position          : normal;
        visibility        : hidden;
        display           : none;
    }

    input[type=checkbox][value=o-translator-p]:checked+label
    {
        background        : #fff;
        cursor            : pointer;
    }
    input[type=checkbox][value=o-translator-p]:hover+label
    {
        color             : #000;
        cursor            : pointer;
    }

    input[type=checkbox][value=g-translator-p]:checked+label
    {
        color             : #fff;
        background        : var(--settings-check-back-color);
        cursor            : pointer;
    }

    input[type=checkbox][value=s-translator-p]:checked+label
    {
        color             : #fff;
        background        : var(--settings-check-back-color);
        cursor            : pointer;
    }

    input[type=checkbox][value=n-translator-p]:checked+label
    {
        color             : #fff;
        background        : var(--settings-check-back-color);
        cursor            : pointer;
    }

    input[type=checkbox][value=b-translator-p]:checked+label
    {
        color             : #fff;
        background        : var(--settings-check-back-color);
        cursor            : pointer;
    }

    input[type=checkbox][value=y-translator-p]:checked+label
    {
        color             : #fff;
        background        : var(--settings-check-back-color);
        cursor            : pointer;
    }

    input[type=checkbox][name=g-translator-p]:checked+label,
    input[type=radio][name=g-translator-p]:checked+label
    {
        color             : #fff;
        background        : #000;
        cursor            : pointer;
    }

    input[type=checkbox]+label:hover,
    input[type=radio]+label:hover
    {
        /* transition     : all 0.25s ease-in-out; */
        color             : #fff;
        cursor            : pointer;
    }

    input[type=checkbox]+label,
    input[type=radio]+label
    {
        transition        : all 0.15s cubic-bezier(0.4, 0, 0.6, 1) 0s;
        background        : var(--settings-t-clicked-color);
        color             : #545454;
    }

    input[type=radio][value=raw]:checked+label
    {
        color             : #fff;
        background        : #7DD7FB;
        cursor            : default;
    }

    input[type=radio][value=mode-enabled]:checked+label
    {
        transition        : all 200ms ease;
        background        : var(--checked-background);
        color             : #fff;
        cursor            : default;
    }

    input[type=radio][value=mode-disabled]:checked+label
    {
        background        : #25282F;
        color             : #fff;
        cursor            : default;
    }

    input[type=radio][value=theme-enabled]:checked+label
    {
        background        : #fff;
        color             : #000;
        cursor            : default;
    }

    input[type=radio][value=theme-disabled]:checked+label
    {
        background        : #000;
        color             : #fff;
        cursor            : default;
    }

    .radio-group,
    .checkbox-group
    {
        font-size         : 9px;
        letter-spacing    : .2em;
        border            : solid 0px rgb(125, 214, 255);
        display           : flex;
        margin            : 0px 0px 30px;
        border-radius     : 20px;
        overflow          : hidden;
        box-shadow        : rgba(0, 0, 0, 0.3) 0px 8px 16px 0px;
        opacity           : 0.7;
    }

    .radio-group:hover,
    .checkbox-group
    {
        opacity           : 1;
        transition        : 1.0s;
    }
`;

let CSSChapterBody = `

    .sentence
    {
              animation         : fadein 2s;
    }
    .sentence.or
    {
        margin-block-end: -0.5em !important;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.google.default
    {
        padding           : 0px;
        background        : transparent;
        border-left-style : none;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.alibaba.default
    {
        padding           : 0px;
        background        : transparent;
        border-left-style : none;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.niutrans.default
    {
        padding           : 0px;
        background        : transparent;
        border-left-style : none;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.Aideep.default
    {
        padding           : 0px;
        background        : transparent;
        border-left-style : none;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.deepl
    {
        margin-block-end: -0.5em !important;
        word-spacing      : 1px;
        text-align        : initial;
        animation         : fadein 0.4s;
        line-height       : 1.7;
        font-size         : 17px;
        border-left-style : solid;
        border-color      : transparent transparent transparent rgb(${providerObject["Deepl"]["color"][0]});
        padding           : 10 10px;
        background        : rgba(${providerObject["Deepl"]["color"][0]}, ${providerObject["Deepl"]["color"][1]});
        margin            : 25 0;
    }

    .sentence.deepl.default
    {
        padding           : 0px;
        background        : transparent;
        border-left-style : none;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 300;
        line-height       : 1.7;
        margin-top        : 3.1em;
        font-size         : 20px;
    }

    .sentence.alibaba
    {
        margin-block-end: -0.5em !important;
        word-spacing      : 1px;
        text-align        : initial;
        animation         : fadein 1s;
        line-height       : 1.7;
        font-size         : 17px;
        border-left-style : solid;
        border-color      : transparent transparent transparent rgb(${providerObject["Alibaba"]["color"][0]});
        padding           : 10 10px;
        background        : rgba(${providerObject["Alibaba"]["color"][0]}, ${providerObject["Alibaba"]["color"][1]});
        margin            : 25 0;
    }

    .sentence.google
    {
        margin-block-end: -0.5em !important;
        word-spacing      : 1px;
        text-align        : initial;
        animation         : fadein 1s;
        line-height       : 1.7;
        font-size         : 18px;
        border-left-style : solid;
        border-color      : transparent transparent transparent rgb(${providerObject["Google"]["color"][0]});
        padding           : 10 10px;
        background        : rgba(${providerObject["Google"]["color"][0]}, ${providerObject["Google"]["color"][1]});
        margin            : 25 0;
    }

    .sentence.niutrans
    {
        margin-block-end: -0.5em !important;
        animation         : fadein 1s;
        line-height       : 1.7;
        font-size         : 18px;
        border-left-style : solid;
        border-color      : transparent transparent transparent rgb(${providerObject["Niutrans"]["color"][0]});
        padding           : 10 10px;
        background        : rgba(${providerObject["Niutrans"]["color"][0]}, ${providerObject["Niutrans"]["color"][1]});
        margin            : 25 0;
    }

    .sentence.Aideep
    {
        margin-block-end: -0.5em !important;
        word-spacing      : 1px;
        text-align        : initial;
        animation         : fadein 1s;
        line-height       : 1.7;
        font-size         : 18px;
        border-left-style : solid;
        border-color      : transparent transparent transparent rgb(${providerObject["Aideep"]["color"][0]});
        padding           : 10 10px;
        background        : rgba(${providerObject["Aideep"]["color"][0]}, ${providerObject["Aideep"]["color"][1]});
        margin            : 25 0;
    }

    .sentence.edit
    {
        overflow: hidden;
        background: none;
        outline: none;
        resize: none;
        margin-block-end: -0.5em !important;
        word-spacing: 1px;
        text-align: initial;
        animation: fadein 0.5s;
        line-height: 1.7;
        font-size: 18px;
        border-left-style: solid;
        padding: 10 20px;
        margin: 25 0;
        background-color: var(--background-color);
        box-shadow: 0 10px 10px 0 rgba(0, 0, 0, 0.1), 0 0 1px 0 rgba(0, 0, 0, 0.2);
        color: var(--title-color);
        border-radius: 13px;
        border-left-color: var(--checked-background);
    }

    .original
    {
        display           : inline;
        word-spacing      : 1px;
        text-align        : initial;
        font-weight       : 600;
        font-size         : 19px;
        /*margin-bottom     : 3.8em;*/
        color             : var(--checked-background);
    }

    .highlighter
    {
        font-weight       : 500;
        /*animation         : fadein 1s;*/
    }
`;

let CSSTooltip = `
    .tooltip
    {
        position          : relative;
        display           : inline;
    }

    .tooltip .tooltip-text
    {
        visibility        : hidden;
        width             : 120px;
        background-color  : #555;
        color             : #fff;
        text-align        : center;
        /* border-radius  : 6px; */
        padding           : 3px 0;
        position          : absolute;
        z-index           : 1;
        opacity           : 0;
        transition        : opacity 0.3s;
    }

    /* Tooltip top content */
    .top .tooltip-text
    {
        bottom            : 100%;
        left              : 50%;
        margin-left       : -60px; /* 120/2 = 60 */
    }

    .tooltip .tooltip-text::after
    {
        content           : "";
        position          : absolute;
        border-width      : 5px;
        border-style      : solid;
    }

    /* Tooltip top arrow */
    .top .tooltip-text::after
    {
        margin-left       : -5px;
        left              : 50%;
        top               : 100%;
        border-color      : var(--checked-background) transparent transparent transparent;
    }

    .tooltip:hover .tooltip-text
    {
        visibility        : visible;
        opacity           : 1;
    }

    .highlighter-default
    {

        /* animation         : fadein 1s; */
        /* font-weight       : 600; */
    }
`;

let CSSNextPrevArrow = `

    .arrow
    {
        position          : fixed;
        top               : 90%;
        width             : 4vmin;
        height            : 4vmin;
        background        : transparent;
        border-top        : 1vmin solid gray;
        border-right      : 1vmin solid black;
        box-shadow        : 0 0 0 black;
        transition        : all 200ms ease;
        opacity           : 0.4;
    }

    .arrow.left
    {
        cursor            : pointer;
        /* left           : 55px; */
        right             : 3%;
        top               : 88%;
        transform         : translate3d(0, -50%, 0) rotate(-135deg);
    }

    .arrow.right
    {
        cursor            : pointer;
        right             : 4%;
        top               : 78%;
        transform         : translate3d(0,-50%,0) rotate(45deg);
    }

    .arrow:hover
    {
        border-color      : #2d3436;
        box-shadow        : 0.5vmin -0.5vmin 0 white;
        opacity           : 1;
    }

    .arrow: before
    {
        content           : '';
        position          : absolute;
        top               : 50%;
        left              : 50%;
        transform         : translate(-40%,-60%) rotate(45deg);
        width             : 200%;
        height            : 200%;
    }
`;

let CSSCloseBtn = `
    .close-button
    {
        box-shadow        : rgba(0, 0, 0, 0.4) 0px 8px 16px 0px;
        width             : 5vmin;
        height            : 5vmin;
        box-shadow        : 0px 10 10px 10px rgba(0, 0, 0, 0.25);
        border-radius     : 50%;
        background        : var(--close-btn-back-color);
        position          : fixed;
        right             : 3.6%;
        top               : 67%;
        display           : block;
        z-index           : 200;
        text-indent       : -9999px;
        cursor            : pointer;
    }

    .close-button:before,
    .close-button:after
    {
        content           : '';
        width             : 55%;
        height            : 2px;
        background        : var(--close-btn-fore-color);
        position          : absolute;
        top               : 48%;
        left              : 22%;
        -webkit-transform : rotate(-45deg);
        -moz-transform    : rotate(-45deg);
        -ms-transform     : rotate(-45deg);
        -o-transform      : rotate(-45deg);
        transform         : rotate(-45deg);
        -webkit-transition: all 0.3s ease-out;
        -moz-transition   : all 0.3s ease-out;
        -ms-transition    : all 0.3s ease-out;
        -o-transition     : all 0.3s ease-out;
        transition        : all 0.3s ease-out;
    }

    .close-button:after
    {
        -webkit-transform : rotate(45deg);
        -moz-transform    : rotate(45deg);
        -ms-transform     : rotate(45deg);
        -o-transform      : rotate(45deg);
        transform         : rotate(45deg);
        -webkit-transition: all 0.3s ease-out;
        -moz-transition   : all 0.3s ease-out;
        -ms-transition    : all 0.3s ease-out;
        -o-transition     : all 0.3s ease-out;
        transition        : all 0.3s ease-out;
    }

    .close-button:hover:before,
    .close-button:hover:after
    {
        -webkit-transform: rotate(180deg);
        -moz-transform   : rotate(180deg);
        -ms-transform    : rotate(180deg);
        -o-transform     : rotate(180deg);
        transform        : rotate(180deg);
    }
`;

let CSSBtnTheme = `
    .fa,
    .fab,
    .fal,
    .far,
    .fas
    {
        line-height: 0;
    }

    ul
    {
        animation: fadein 2s;
        padding: 0;
        position: relative;
        margin: 0 0 100 0;
        transform: scale(0.65);
        justify-content: start;
        display: inline-block;
        flex-direction: row;
        flex-wrap: nowrap;
        /* justify-content: end; */
        align-items: unsafe;
        /* align-content: end; */
        margin: 0 -5vmin;
        /* margin: 0 500px; */
        /* margin-bottom: 7.8em !important; */
        padding-inline-start: 0px !important;
        display: inline-flex;
        flex-direction: row;
        flex-wrap: nowrap;
        justify-content: flex-start;
        align-items: baseline;
        align-content: stretch;
    }


    ul li
    {
        animation    : fadein 1s;

        list-style   : none;
        margin       : 0 7px;
        display      : block;
    }

    ul li:after, .rr-font--update.rr-inc:after
    {
        pointer-events: none;
        content: "";
        position: absolute;
        top: -140px;
        bottom: -230px;
        right: 0;
        z-index: 10;
        /* padding-left: 320; */
        border-right: 1.0px solid rgba(var(--checked-background-rgb), 0.3);
        transform: rotate(30deg);
    }

    ul li a
    {
        position     : relative;
        display      : list-item;
        width        : 60px;
        height       : 60px;
        text-align   : center;
        line-height  : 63px;
        background   : var(--background-color);
        border-radius: 50%;
        font-size    : 35px;
        color        : darkgray;
        transition   : .5s;
    }

    ul li a:hover
    {
        cursor       : pointer;
    }

    ul li a::before
    {
        content      : '';
        position     : absolute;
        top          : 0;
        left         : 0;
        width        : 100%;
        height       : 100%;
        border-radius: 50%;
        background   : #009992;
        transition   : .5s;
        transform    : scale(.9);
        z-index      : -1;
    }

    ul li a:hover
    {
        color      : black;
    }

    ul li a:hover::before
    {
        transform : scale(1.1);
        background: #009992;
    }

    ul li:nth-child(1) a::before
    {
        background: #ffee10;
    }

    ul li:nth-child(1) a:hover::before
    {
        box-shadow: 0 0 10px #e67e22;
    }

    ul li:nth-child(1) a:hover
    {
        box-shadow : 0 0 5px #e67e22;
        text-shadow: 0 0 5px #e67e22;
    }

    ul li:nth-child(2) a::before
    {
        background: #95a5a6;
    }

    ul li:nth-child(2) a:hover::before
    {
        box-shadow: 0 0 10px #9b59b6;
    }

    ul li:nth-child(2) a:hover
    {
        box-shadow : 0 0 5px #9b59b6;
        text-shadow: 0 0 5px #9b59b6;
    }

    ul li:nth-child(3) a::before
    {
        background: #e74c3c;
    }

    ul li:nth-child(3) a:hover::before
    {
        box-shadow: 0 0 10px #e74c3c;
    }

    ul li:nth-child(3) a:hover
    {
        box-shadow : 0 0 5px #e74c3c;
        text-shadow: 0 0 5px #e74c3c;
    }

    ul li:nth-child(4) a:hover::before
    {
        box-shadow: 0 0 10px #029992;
    }

    ul li:nth-child(4) a:hover
    {
        box-shadow : 0 0 5px #029992;
        text-shadow: 0 0 5px #029992;
    }


    /*ul li a
    {
        color: transparent;
    }*/
`;

let CSSTagBtn = `

    .settings-container
    {
        animation: fadein 3s;
        overflow: hidden;
        border-bottom: 1px solid rgba(var(--checked-background-rgb), 0.3);
        margin-bottom: 150px;
          display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        justify-content: flex-start;
        align-items: center;
        align-content: stretch;
    }

    .raw-block
    {
        /* margin-bottom: 4.8em; */
        margin: 16px 0;
        /* display: -webkit-box; */
        align-items: center;
        margin-bottom: 4.5em;
    }

    .tag-num
    {
        user-select   : none;
        font-size     : 10px;
        line-height   : 18px;
        position      : relative;
        display       : inline-flex;
        height        : 18px;
        padding       : 0 6px;
        letter-spacing: .25px;
        color         : black;
        border-radius : 18px;
        background    : #c0c2cc;
        margin        : 0 7px;
    }

    .tag-num:hover
    {
        cursor      : pointer;
        transition  : all 400ms ease;
        background  : black;
        color       : white;
    }

    .rr-font--update.rr-dec {
        font-size: 15px;
        margin-left: 30px;
    }

    .rr-font--update.rr-inc
    {
        font-size: 22px;
        margin-right: 20px;
        position: relative;
        margin-left: 15px;
    }

    .rr-font--update.rr-inc:hover
    {
        /*transform: scale(1.3);*/
        color: var(--checked-background);
        transition: all 0.3s ease-out;
    }

    .rr-font--update.rr-dec:hover
    {
        color: var(--checked-background);
        transition: all 0.3s ease-out;
    }

    .rr-font--update
    {
        user-select : none;
        color       : darkgray;
        font-family : Open sans;
        width       : 30px;
        font-weight : 700;
        font-size   : 18px;
        cursor      : pointer;
    }

    .btn-custom
    {
        font-family: "Source sans Pro";
        outline: 0;
        border-radius: 16px;
        border: 1px solid var(--checked-background);
        background: transparent;
        color: var(--checked-background);
        width: 120px;
        height: 24px;
        cursor: pointer;
        text-transform: uppercase;
        margin-left: 30px;
    }

    .btn-custom:hover
    {
        transition: background 0.3s ease-in-out;
        background: white;
    }

    #font-indicator
    {
        color: var(--checked-background);
        font-family: Source sans pro;
        font-weight: 600;
    }
    .sentence.or,[class^="default"]
    {
        font-size: ${fontSizeValue};
    }

    [class^="sentence"]:not(.or)
    {
        font-weight: 300;
        font-size: ${fontSizeValueDefault};
    }
`;
/*
    | Plugin Mobile support.
*/
let CSSMediaQuery = `
    @media only screen and (max-width: 600px)
    {
        label
            { display: inline-block; font-size: 95%; }

        .radio-group, .checkbox-group
            { display: grid; }

        .chapterBody
            { margin: 7%;max-width: 82%; }

        .sentence.or
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .settingsForm
            { flex-wrap: wrap; display: contents; }

        .original
            { font-size: 87%; text-align: initial; font-weight: 500; }

        .sentence.google
            { font-size: 87%; text-align: initial; font-weight: 300; }
        .sentence.google.default
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .sentence.niutrans
            { font-size: 87%; text-align: initial; font-weight: 300;}
        .sentence.niutrans.default
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .sentence.aideep
            { font-size: 87%; text-align: initial; font-weight: 300; }
        .sentence.aideep.default
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .sentence.alibaba
            { font-size: 87%; text-align: initial; font-weight: 300; }
        .sentence.alibaba.default
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .sentence.deepl
            { font-size: 87%; text-align: initial; font-weight: 300; }
        .sentence.deepl.default
            { font-size: 92%; text-align: initial; font-weight: 300; }

        .close-button
            { max-width: 7vmin; max-height: 7vmin; min-width: 7vmin; min-height: 7vmin; right: 3%; top: 57%; background: #393A3D; border-radius: 50%; }

        .close-button:before, .close-button:after
            { background:white; }

        .arrow.left
            { top: 76%; }

        .arrow.right
            { top: 68%; right: 5%; }

        .tooltip
            { display: inline; }

        h1.header-title
            { font-size: 150% }

        .rr-font--update.rr-dec
            { font-size: 15px !important; margin-left: 35px !important; }

        .rr-font--update.rr-inc
            { margin-right: 15px !important; margin-left: 5px !important; }

        .btn-custom
            { height: 54px !important; text-align: center !important;}

        ul li
            { margin: 0 12px 4 !important; }

        ul li:after, .rr-font--update.rr-inc:after
            { border-right: 2px solid rgba(var(--checked-background-rgb), 1); }
        ul
            { animation:fadein 2s;padding:0!important;position:relative!important;margin:0 0 100!important;transform:scale(.65)!important;justify-content:center!important;display:flex!important;flex-direction:row!important;align-items:unsafe!important;margin:0 -10vm!important;margin:0 -10vmin!important;flex-wrap:wrap!important;justify-content:space-evenly!important;align-items:baseline!important;align-content:center!important}
    }
`;
window.onload = async function () {
    /*
          | Adding Glossary support
      */
    await new Promise((resolve) => setTimeout(resolve, 10));

    if (
        $('style[type="text/css"]').text().includes("noWordWrapping") &&
        !(
            window.sessionStorage.getItem("userjs_UGMTLComplete") &&
            window.sessionStorage.getItem("userjs_UGMTLComplete") >
            window.performance.timing.fetchStart
        )
    ) {
        await new Promise((resolve) =>
            document.addEventListener("userjs_UGMTLComplete", resolve)
        );
    } else {
        /*
                | Swaping Chinese/English words to have a better translation.
            */
        mainBodyWordsReplacer();
    }

    /*
          | Inject the iframe to the DOM.
      */
    $("#app")
        .get(0)
        .insertAdjacentHTML(
            "afterEnd",
            `<iframe id="pageContainer" style="display:${
        autoReaderState ? "block" : "none"
      }">`
        );

    /*
          | Get raw & original text from main page.
      */
    const originalSentences = $(".chapter-body")
        .find(".translated")
        .text()
        .replace(/(\xAD)/g, "")
        .trim()
        .split("\n");
    const originalRaw = $(".chapter-body")
        .find(".original")
        .text()
        .trim()
        .split("\n");
    const chapterTitle = $(".chapter-title")[0].textContent;
    /*
          | Add a button for the reader mode.
      */
    $(".js-toggle-original").after(
        '<button class="btn btn-enabled reader-mode">READER MODE</button>'
    );
    $(".btn.btn-enabled.reader-mode").css(
        "boxShadow",
        "rgb(236, 240, 241) 0px 0px 8px"
    );
    $(".btn.btn-enabled.reader-mode").css("color", "black");

    /*
          | Reader btn onClick event.
      */
    $(".reader-mode").click(function () {
        $("#app").css({
            display: "none",
            opacity: "0"
        });
        $("#pageContainer").show();
    });

    /*
            | We will need this for the tooltip & highlighting words.
            | The values produces non-ut8 characters so we must remove them
            | so that there will be no problem replacing them using regex.
      */
    $(".translated t").each(function (index) {
        let chineseValue = $(this).attr("data-title");
        let value = $(this).attr("data-title", $(this).text()).text().trimLeft();
        const replacedValue = value.replace(
            /[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/(\xAD)]/g,
            ""
        );

        if (replacedValue != "" || replacedValue.length != 0)
            uniqueWords.set(replacedValue, chineseValue);
    });

    /*
          | This what will be using to control the iframe DOM
      */
    iframeHandler = $("#pageContainer").contents();

    /*
          | Toggle theme state.
      */
    autoThemeState
        ?
        iframeHandler.find(":root").eq(0).attr("data-theme", "light") :
        iframeHandler.find(":root").eq(0).attr("data-theme", "dark");

    /*
          | Append the needed styles for the iframe.
      */
    addStyles();

    /*
          | Add iframe form.
      */
    addForm();

    /*
          | Apply secondary theme.
      */
    iframeHandler.find(":root").eq(0).attr("data-sec-theme", secondaryTheme);

    /*
          | Toggle secondary colors.
      */
    iframeHandler.find(".drag").click(function () {
        iframeHandler.find(":root").eq(0).attr("data-sec-theme", "gold");
        GM_SuperValue.set("secondaryTheme", "gold");
    });
    iframeHandler.find(".moon").click(function () {
        iframeHandler.find(":root").eq(0).attr("data-sec-theme", "moon");
        GM_SuperValue.set("secondaryTheme", "moon");
    });
    iframeHandler.find(".bomb").click(function () {
        iframeHandler.find(":root").eq(0).attr("data-sec-theme", "bomb");
        GM_SuperValue.set("secondaryTheme", "bomb");
    });
    iframeHandler.find(".norm").click(function () {
        iframeHandler.find(":root").eq(0).attr("data-sec-theme", "default");
        GM_SuperValue.set("secondaryTheme", "");
    });

    /*
          | If one provider is running then removed color/border translation identifier.
      */
    iframeHandler.find("input[name='items[]']").change(function (e) {
        onlyOneRunning();
    });

    for (let index in providers) {
        let provider = providers[index];

        if (eval("provider" + provider + "State")) {
            providerSelector(
                originalRaw,
                provider.toLowerCase(),
                providerObject[provider]["maxSize"]
            );
        }
    }

    iframeHandler.find(".close-button").click(function () {
        $("#app").css({
            display: "block",
            opacity: "1",
            "overflow-y": "scroll"
        });
        $("#pageContainer").hide();
    });

    /*
          | Hooking next/prev page on click
          | Replacing href results a bug, so I'll be using this approach.
      */
    iframeHandler.find(".arrow.right").click(function () {
        window.location.assign($(this).attr("value"));
    });
    iframeHandler.find(".arrow.left").click(function () {
        window.location.assign($(this).attr("value"));
    });

    /*
          | Add original and raw text ( with display:none )
          | Also there is a non-utf8 coming out of nowhere so we must clean that
      */
    for (let i in originalSentences) {
        for (const [key, value] of uniqueWords.entries()) {
            originalSentences[i] = originalSentences[i].replace(
                new RegExp("(?<![<>])(" + key + ")(?![<>])", "g"),
                `<span class="highlighter">$&</span>`
            );
        }

        iframeHandler
            .find(".chapterBody")
            .append(`<div class="sentence or">${originalSentences[i]}</div>`);
        iframeHandler
            .find(".chapterBody")
            .append(
                `<div class="raw-block"><p class="original">${originalRaw[i]}</p><a class="tag-num">${i}</a></div>`
            );
    }

    if (!autoRawState) iframeHandler.find(".raw-block").hide();

    if (!providerOriginalState) iframeHandler.find(".sentence.or").hide();

    // Should be optimized...
    iframeHandler.find(".rr-dec").click(function () {
        let currentFontSize =
            parseInt(
                iframeHandler.find('.sentence.or,[class^="default"]').css("font-size")
            ) - 1;
        let currentFontSizeDefault =
            parseInt(
                iframeHandler
                .find('[class^="sentence"]:not(.or,.default)')
                .css("font-size")
            ) - 1;

        if (currentFontSize <= 17) return;

        iframeHandler
            .find('[class^="sentence"]:not(.or)')
            .css("font-size", currentFontSizeDefault);
        iframeHandler
            .find('.sentence.or,[class^="default"]')
            .css("font-size", currentFontSize);

        iframeHandler.find("#font-indicator").text(currentFontSize);

        GM_SuperValue.set("fontSizeValueDefault", currentFontSizeDefault);
        GM_SuperValue.set("fontSizeValue", currentFontSize);
    });
    iframeHandler.find(".rr-inc").click(function () {
        let currentFontSize =
            parseInt(
                iframeHandler.find('.sentence.or,[class^="default"]').css("font-size")
            ) + 1;
        let currentFontSizeDefault =
            parseInt(
                iframeHandler
                .find('[class^="sentence"]:not(.or,.default)')
                .css("font-size")
            ) + 1;

        if (currentFontSize >= 30) return;

        iframeHandler
            .find('[class^="sentence"]:not(.or)')
            .css("font-size", currentFontSizeDefault);
        iframeHandler.find(".sentence.or").css("font-size", currentFontSize);

        iframeHandler.find("#font-indicator").text(currentFontSize);

        GM_SuperValue.set("fontSizeValueDefault", currentFontSizeDefault);
        GM_SuperValue.set("fontSizeValue", currentFontSize);
    });

    providerChangedEvent(originalRaw);
    settingsChangedEvent();

    iframeHandler.find(".btn-custom").on("click", function () {
        let self = $(this);
        let stringBuilder = `${chapterTitle}\n\n\n`;

        self.text("Copied ;-)");

        setTimeout(function () {
            self.text("Copy Edited Text");
        }, 600);

        let rawEdits = iframeHandler
            .find(".sentence.edit")
            .toArray()
            .map((p) => p.textContent);

        for (let index in rawEdits) {
            stringBuilder += `${rawEdits[index]}\n\n\n`;
        }

        window.navigator.clipboard.writeText(stringBuilder);
    });

    iframeHandler.find(".tag-num").on("click", function () {
        let index = parseInt(this.textContent);

        if ($(this).attr("value")) {
            iframeHandler
                .find(".tag-num")
                .eq(index)
                .next()
                .filter(".sentence.edit")
                .fadeToggle(200);
            return;
        }

        iframeHandler
            .find(".raw-block")
            .eq(index)
            .append(
                '<div class="sentence edit" contenteditable="plaintext-only" spellcheck="false"/>'
            );

        $(this).attr("value", "clicked");
    });
};

function addStyles() {
    iframeHandler.find("head").append(
        $(
            `
                <meta name="viewport" content="width=device-width, initial-scale=1">
                <link rel="preload" onload="this.rel = 'stylesheet'" as="style" href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600|Poppins|Source+Sans+Pro:300,400,500,600'>
                <link rel="preload" onload="this.rel = 'stylesheet'" as="style" href='https://use.fontawesome.com/releases/v5.6.3/css/all.css'>
                ${
                  $(".next > a").attr("href") != undefined
                    ? `<link rel="prefetch" href="${$(".next > a").attr(
                        "href"
                      )}">`
                    : ""
                }
                <style type='text/css'>
                          ${CSSRoot}
                          ${CSSContainer}
                          ${CSSCloseBtn}
                          ${CSSForm}
                          ${CSSChapterBody}
                          ${CSSTooltip}
                          ${CSSNextPrevArrow}
                          ${CSSMediaQuery}
                          ${CSSBtnTheme}
                          ${CSSTagBtn}
                </style>
            `
        )
    );
}

function addForm() {
    const chapterTitle = $(".chapter-title")[0].textContent.split(":");

    const readerState = autoReaderState ? "" : "checked";
    const themeState = autoThemeState ? "" : "checked";
    const rawState = autoRawState ? "" : "checked";

    const pageNext = $(".next > a").attr("href");
    const pagePrevious = $(".previous > a").attr("href");
    const nextChapterButton =
        pageNext != null ?
        '<a value="' + pageNext + '" class="arrow right"></a>' :
        "";
    const prevChapterButton =
        pagePrevious != null ?
        '<a value="' + pagePrevious + '" class="arrow left"></a>' :
        "";

    const isOriginalEnabled = providerOriginalState ? "checked" : "";
    const isGoogleEnabled = providerGoogleState ? "checked" : "";
    const isAlibabaEnabled = providerAlibabaState ? "checked" : "";
    const isNiutransEnabled = providerNiutransState ? "checked" : "";
    const isAideepEnabled = providerAideepState ? "checked" : "";
    const isDeeplEnabled = providerDeeplState ? "checked" : "";

    iframeHandler.find("body").append(
        `
              <div id="clean-reader">
              <div class="pageContainerBackground">
                    <a class="close-button">Close</a>
                    ${prevChapterButton}
                    ${nextChapterButton}
                    <form class="settingsForm">

                          <label>Provider</label>
                          <div class="radio-group">

                              <input type="checkbox" id="Original-translator" value="o-translator-p" name="items[]" ${isOriginalEnabled}>
                              <label for="Original-translator">Original</label>

                              <input type="checkbox" id="Google-translator" value="g-translator-p" name="items[]" ${isGoogleEnabled}>
                              <label for="Google-translator"><span style="color:rgb(${
                                providerObject["Google"]["color"][0]
                              });text-shadow: 0 0 3px #30AFDB, 0 0 5px #30AFDB;padding-right: 6">◼ </span> Google</label>

                              <input type="checkbox" id="Alibaba-translator" value="s-translator-p" name="items[]" ${isAlibabaEnabled}>
                              <label for="Alibaba-translator"><span style="color:rgb(${
                                providerObject["Alibaba"]["color"][0]
                              });text-shadow: 0 0 3px #e74c3c, 0 0 5px #e74c3c;padding-right: 6">◼ </span> Alibaba</label>

                              <input type="checkbox" id="Niutrans-translator" value="n-translator-p" name="items[]" ${isNiutransEnabled}>
                              <label for="Niutrans-translator"><span style="color:rgb(${
                                providerObject["Niutrans"]["color"][0]
                              });text-shadow: 0 0 3px #e74c3c, 0 0 5px #e74c3c;padding-right: 6">◼ </span> Niutrans</label>

                              <input type="checkbox" id="Aideep-translator" value="b-translator-p" name="items[]" ${isAideepEnabled}>
                              <label for="Aideep-translator"><span style="color:rgb(${
                                providerObject["Aideep"]["color"][0]
                              });text-shadow: 0 0 3px #6F68F2, 0 0 5px #6F68F2;padding-right: 6">◼ </span> Aideep</label>

                              <input type="checkbox" id="Deepl-translator" value="y-translator-p" name="items[]" ${isDeeplEnabled}>
                              <label for="Deepl-translator"><span style="color:rgb(${
                                providerObject["Deepl"]["color"][0]
                              });text-shadow: 0 0 3px #34495e, 0 0 5px #34495e;padding-right: 6">◼ </span> Deepl</label>

                          </div>

                          <label>Raw</label>
                          <div class="radio-group">
                              <input type="radio" id="option-one-a" name="raw" value="mode-enabled" checked>
                              <label for="option-one-a">Enable</label>

                              <input type="radio" id="option-two-2" name="raw" value="mode-disabled" ${rawState}>
                              <label for="option-two-2">Disable</label>
                          </div>

                          <label>Reader</label>
                          <div class="radio-group">
                              <input type="radio" id="option-1" name="mode" value="mode-enabled" data-theme="dark" checked>
                              <label for="option-1">Always</label>
                              <input type="radio" id="option-2" name="mode" value="mode-disabled" data-theme="light" ${readerState}>
                              <label for="option-2">Never</label>
                          </div>

                          <label>Theme</label>
                          <div class="radio-group theme">
                              <input type="radio" id="option-1-1" name="theme" value="theme-enabled" checked>
                              <label for="option-1-1">Light</label>
                              <input type="radio" id="option-2-2" name="theme" value="theme-disabled" ${themeState}>
                              <label for="option-2-2">Dark</label>
                          </div>

                    </form>
                    <h1 class="header-title">${
                      chapterTitle[1]
                    } - <span style="transition : all 200ms ease;color:var(--checked-background);font-weight:bold">${chapterTitle[0].replace(
      "#",
      ""
    )}</h1>
                    <div class="pageSplit"></div>
                    <div class="settings-container">
                    <ul>
                      <li>
                        <a class="drag"><i class="fab fa-d-and-d"></i></a>
                      </li>
                      <li>
                        <a class="moon"><i class="fas fa-bowling-ball"></i></a>
                        </li>
                      <li>
                        <a class="bomb"><i class="fas fa-bomb"></i></a>
                      </li>
                      <li>
                        <a class="norm"><i class="fas fa-tint"></i> </a>
                      </li>
                    </ul><span class="rr-font--update rr-dec">A-</span><div id="font-indicator">${fontSizeValue}</div><span class="rr-font--update rr-inc">A+</span>
                      <button class="btn-custom">Copy edited text</button></div>
                  <div class="chapterBody">
                  </div>
              </div>
            </div>
            `
    );
}

function providerChangedEvent(raws) {
    const addEventHandler = (iframe, provider) => {
        const capitalizeProvider =
            provider.charAt(0).toUpperCase() + provider.slice(1);

        iframe.find(`#${capitalizeProvider}-translator`).change(function () {
            GM_SuperValue.set(`provider${capitalizeProvider}State`, this.checked);

            this.checked && !isTranslated[capitalizeProvider][1] ?
                providerSelector(
                    raws,
                    provider,
                    providerObject[capitalizeProvider]["maxSize"]
                ) :
                iframe.find(`.sentence.${provider}`).fadeToggle(300);
        });
    };

    addEventHandler(iframeHandler, "google");
    addEventHandler(iframeHandler, "alibaba");
    addEventHandler(iframeHandler, "niutrans");
    addEventHandler(iframeHandler, "Aideep");
    addEventHandler(iframeHandler, "deepl");

    iframeHandler.find("#Original-translator").change(function () {
        GM_SuperValue.set("providerOriginalState", this.checked);
        iframeHandler.find(".sentence.or").delay(100).fadeToggle(100);
    });
}

function settingsChangedEvent() {
    /* READER CHANGING STATE */
    iframeHandler.find("input[type=radio][name=mode]").change(function () {
        autoReaderState = $(this).val().includes("enabled");
        GM_SuperValue.set("autoReaderState", autoReaderState);
    });

    /* RAW CHANGING STATE */
    iframeHandler.find("input[type=radio][name=raw]").click(function () {
        autoRawState = $(this).val().includes("enabled");
        GM_SuperValue.set("autoRawState", autoRawState);

        autoRawState
            ?
            iframeHandler.find(".raw-block").delay(100).fadeIn(300) :
            iframeHandler.find(".raw-block").fadeOut(200);
    });

    /* THEME CHANGING STATE */
    iframeHandler.find("input[type=radio][name=theme]").click(function () {
        autoThemeState = $(this).val().includes("enabled");
        GM_SuperValue.set("autoThemeState", autoThemeState);

        autoThemeState
            ?
            iframeHandler.find(":root").eq(0).attr("data-theme", "light") :
            iframeHandler.find(":root").eq(0).attr("data-theme", "dark");
    });
}

function onlyOneRunning() {
    let runningProviders = [];

    iframeHandler.find("input[name='items[]']:checked").each(function () {
        runningProviders.push(
            $(this).attr("id").replace("-translator", "").toLowerCase()
        );
    });

    if (runningProviders.length == 1) {
        iframeHandler
            .find(`.sentence.${runningProviders[0]}`)
            .attr("class", `sentence ${runningProviders[0]} default`);
        iframeHandler.find(".highlighter-default").css("font-weight", "500");
    } else {
        for (let id in runningProviders) {
            iframeHandler
                .find(`.sentence.${runningProviders[id]}.default`)
                .attr("class", `sentence ${runningProviders[id]}`);
            iframeHandler.find(".highlighter-default").css("font-weight", "500");
        }
    }

    return runningProviders.length;
}

const disableCheckbox = (elementId, status) =>
    iframeHandler.find(elementId).prop("disabled", status);

async function providerSelector(raw, name, chunksize) {
    const taskDelay = (m) => new Promise((r) => setTimeout(r, m));

    let translatedChunks = [];
    let chunks = [];

    chunks = separateIntoChunks(raw, chunksize);
    chunks[0] = chunks[0].trimLeft();
    console.log(name);
    switch (name) {
        case "alibaba": {
            disableCheckbox("#Alibaba-translator", true);
            for (let id in chunks) {
                translatedChunks[id] = await alibabaTranslator(chunks[id]);
                console.log("test_2");
                await taskDelay(providerObject["Alibaba"]["timeout"]);
            }

            // Translation is finished.
            isTranslated["Alibaba"][1] = true;
            disableCheckbox("#Alibaba-translator", false);
        }
        break;
    case "google": {
        disableCheckbox("#Google-translator", true);

        for (let id in chunks) {
            translatedChunks[id] = await googleTranslator(chunks[id]);
            await taskDelay(providerObject["Google"]["timeout"]);
        }

        // Translation is finished.
        isTranslated["Google"][1] = true;
        disableCheckbox("#Google-translator", false);
    }
    break;
    case "niutrans": {
        disableCheckbox("#Niutrans-translator", true);

        let query = await niutransGetQuery();
        for (let id in chunks) {
            translatedChunks[id] = await niutransTranslator(chunks[id], query);
            await taskDelay(providerObject["Niutrans"]["timeout"]);
        }

        // Translation is finished.
        isTranslated["Niutrans"][1] = true;
        disableCheckbox("#Niutrans-translator", false);
    }
    break;
    case "aideep": {
        console.log('case ai deep');
        disableCheckbox("#Aideep-translator", true);

        let timer = -performance.now();

        //const tokens = await AideepReceiveTokens();
        for (let id in chunks) {
            translatedChunks[id] = await AideepTranslator_2(chunks[id]);
            await taskDelay(providerObject["Aideep"]["timeout"]);
        }

        timer += performance.now();
        console.log("Time: " + (timer / 1000).toFixed(5) + " sec.");

        // Translation is finished.
        isTranslated["Aideep"][1] = true;
        disableCheckbox("#Aideep-translator", false);
    }
    break;
    case "deepl": {
        console.log('case deepl');
        disableCheckbox("#Deepl-translator", true);

        for (let id in chunks) {
            translatedChunks[id] = await deeplTranslator(chunks[id]);
            await taskDelay(providerObject["Deepl"]["timeout"]);
        }

        // Translation is finished.
        isTranslated["Deepl"][1] = true;
        disableCheckbox("#Deepl-translator", false);
    }
    break;
    }

    let finalResult = seperateChunksIntoPars(translatedChunks);
    createSentence(finalResult, name, onlyOneRunning());
}

function mainBodyWordsReplacer() {
    $(".original t").each(function () {
        const textSpace = $(this).get(0).previousSibling.nodeName == "T" ? " " : "";
        const textValue = $(this).text();

        $(this).text(textSpace + $(this).attr("data-title"));
        $(this).attr("data-title", textValue);
    });
}

function createSentence(paragraphs, provider, runningProviders) {
    if (runningProviders == 1) {
        provider.concat(" default");
    }

    paragraphs.forEach((sentence, index) => {
        for (const [key, value] of uniqueWords.entries())
            sentence = sentence.replace(
                new RegExp("(?<![<>])(" + key + ")(?![<>])", "g"),
                `<span class="highlighter-default">$&</span>`
            );

        iframeHandler
            .find(".raw-block")
            .eq(index)
            .before(`<p class="sentence ${provider}">` + sentence + "</p>");
    });

    onlyOneRunning();
}

function googleTranslator(text) {
    return new Promise((resolve) =>
        $.ajax(
            "https://translate.googleapis.com/translate_a/single?client=gtx&sl=zh-CN&tl=en&dt=t", {
                method: "POST",
                data: {
                    q: text
                },
                dataType: "json",
            }
        )
        .done((t) => {
            let paragraph = "";

            for (let i = 0; i < t[0].length; i++) {
                paragraph += t[0][i][0];
            }

            resolve(paragraph);
        })
        .fail(function (xhr, textStatus, errorThrown) {
            GM_SuperValue.set("providerGoogleState", false);
            disableCheckbox("#Google-translator", false);
            iframeHandler.find("#Google-translator").prop("checked", false);
        })
    );
}

function alibabaTranslator(text) {
    return new Promise((resolve) => {
        GM_xmlhttpRequest({
            method: "GET",
            url: "https://translate.alibaba.com/translationopenseviceapp/trans/TranslateTextAddAlignment.do?srcLanguage=zh&tgtLanguage=en&bizType=general&srcText=" + encodeURI(text),
            headers: {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/74.0.3729.169 Safari/537.36",
            },
            onload: function (result) {
                try {
                    resolve(JSON.parse(result.response).listTargetText[0]);
                } catch (error) {
                    GM_SuperValue.set("providerAlibabaState", false);
                    disableCheckbox("#Alibaba-translator", false);
                    iframeHandler.find("#Alibaba-translator").prop("checked", false);
                }
            },
        });
    });
}

function niutransGetUrlHash() {
    return new Promise((resolve) => {
        GM_xmlhttpRequest({
            method: "GET",
            url: "https://niutrans.com/Trans?type=text",
            headers: {
                accept: "application/json, text/plain, */*"
            },
            onload: function (result) {
                try {
                    var webpackHash = /js\/app\.(.*)\.js/g.exec(result.response);
                    resolve(webpackHash[1]);
                } catch (error) {
                    console.log(error);
                }
            },
        });
    });
}

async function niutransGetQuery() {
    let hash = await niutransGetUrlHash();

    return new Promise((resolve) => {
        GM_xmlhttpRequest({
            method: "GET",
            url: "https://niutrans.com/static/js/app." + hash + ".js",
            headers: {
                accept: "application/json, text/plain, */*"
            },
            onload: function (result) {
                try {
                    var webpackHash = /testtrans\?query=(.*?)"/g.exec(result.response);
                    resolve(webpackHash[1]);
                } catch (error) {
                    console.log(error);
                }
            },
        });
    });
}

function niutransTranslator(text, query) {
    return new Promise((resolve) => {
        GM_xmlhttpRequest({
            method: "GET",
            url: "https://test.niutrans.com/NiuTransServer/testtrans?query=" + query + "&from=cht&to=en&src_text=" +
                encodeURIComponent(text) +
                "&source=text",
            headers: {
                accept: "application/json, text/plain, */*",
                "accept-language": "en-US,en;q=0.9,tr-TR;q=0.8,tr;q=0.7,zh-TW;q=0.6,zh;q=0.5",
                "sec-fetch-dest": "empty",
                "sec-fetch-mode": "cors",
                "sec-fetch-site": "same-site",
            },
            onload: function (result) {
                try {
                    var myResponse = JSON.parse(result.response).tgt_text;
                    var t = [...myResponse.split("\n \n")].join("\n\n");
                    resolve(t);
                } catch (error) {
                    console.log(error);
                    GM_SuperValue.set("providerNiutransState", false);
                    disableCheckbox("#Niutrans-translator", false);
                    iframeHandler.find("#Niutrans-translator").prop("checked", false);
                }
            },
        });
    });
}

async function AideepTranslator_2(text) {
    console.log('aideeeeeeep');
    var timestamp = Date.now();

    const formData = {
        source: "zh",
        target: "en",
        text: text,
        mark: timestamp,
        verify: md5(md5(text) + timestamp + "fpoTYKWlvhwhVaFG")
    };

    return new Promise((resolve) => {
        GM_xmlhttpRequest({
            method: 'POST',
            url: 'https://www.kingsoftai-fanyi.com/aida_api/text/translate',
            data: JSON.stringify(formData),
            headers: {
                'Content-Type': 'application/json',
                'Host': 'www.kingsoftai-fanyi.com',
                'Origin': 'https://www.kingsoftai-fanyi.com',
                'Referer': 'https://www.kingsoftai-fanyi.com/translate',
                'User-Agent': 'Mozilla/5.0 (Windows NT 8.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98 Safari/537.36'
            },
            onload: (result) => {
                try {
                    const cleanedResult = Array.from(JSON.parse(result.responseText).data.translateTexts.map(p => p.translation))[0].split("\n \n").join("\n\n");
                    resolve(cleanedResult);
                } catch (error) {
                    //console.log('error\n');
                    GM_SuperValue.set("providerAIDeepState", false);
                    disableCheckbox("#AIDeep-translator", false);
                    iframeHandler.find("#AIDeep-translator").prop("checked", false);
                }
            }
        })
    })
}

async function AideepTranslator(text, _tokens) {
    const formData = {
        from: "zh",
        to: "en",
        query: text,
        transtype: "realtime",
        simple_means_flag: 3,
        sign: sign(text, _tokens[0]),
        token: _tokens[1],
        domain: "common",
    };

    return new Promise((resolve) => {
        GM_xmlhttpRequest({
            method: "POST",
            url: "https://fanyi.Aideep.com/v2transapi",
            data: $.param(formData),
            headers: {
                "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
                Accept: "application/json",
                Referer: "https://fanyi.Aideep.com",
                "Accept-Encoding": "gzip, deflate",
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
            },
            onload: function (result) {
                try {
                    const jsonObj = JSON.parse(result.responseText);
                    resolve(jsonObj.trans_result.data.map((p) => p.dst).join("\n\n"));
                } catch (error) {
                    GM_SuperValue.set("providerAideepState", false);
                    disableCheckbox("#Aideep-translator", false);
                    iframeHandler.find("#Aideep-translator").prop("checked", false);
                }
            },
        });
    });
}

function AideepReceiveTokens() {
    return new Promise((resolve) => {
        GM_xmlhttpRequest({
            method: "GET",
            url: "https://fanyi.Aideep.com/",
            headers: {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
            },
            onload: function (result) {
                try {
                    const windowToken = result.responseText.match(
                        /window\.gtk = '(.*?)'/
                    )[1];
                    const commonToken = result.responseText.match(/token: '(.*?)',/)[1];

                    resolve([windowToken, commonToken]);
                } catch (error) {
                    disableCheckbox("#Aideep-translator", false);
                    iframeHandler.find("#Aideep-translator").prop("checked", false);
                }
            },
        });
    });
}

async function deeplTranslator(text) {

    console.log('deepl clicked');
    return new Promise((resolve) => {
        GM_xmlhttpRequest({
            method: "GET",
            url: "https://translate.minganci.org/deepl?text=" + encodeURI(text) + "&source_lang=ZH&target_lang=EN",
            headers: {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
            },
            onload: function (result) {
                try {
                    let jsonObj = JSON.parse(result.response);
                    console.log(jsonObj.translations[0].text);
                    resolve(jsonObj.translations[0].text);
                } catch (error) {
                    //console.log(error);
                    GM_SuperValue.set("providerDeeplState", false);
                    disableCheckbox("#Deepl-translator", false);
                    iframeHandler.find("#Deepl-translator").prop("checked", false);
                }
            },
        });
    });
}

/*
    | Utilities
*/

function setCookie(a, val, url, c, name) {
    return (
        (a = [a, "=", val]),
        url && a.push(";expires=", url),
        c && a.push(";domain=", c),
        name && a.push(";path=", name),
        (document.cookie = a.join("")),
        a.join("")
    );
}

function separateIntoChunks(paragraphs, size) {
    let chunks = [];
    let currentchunk = "";
    for (let i = 0; i < paragraphs.length; i++) {
        if ((currentchunk + paragraphs[i]).length >= size) {
            chunks.push(currentchunk);
            currentchunk = paragraphs[i];
        } else {
            currentchunk = currentchunk + "\n\n" + paragraphs[i];
        }
    }
    if (paragraphs.length != 0) {
        chunks.push(currentchunk);
    }
    return chunks;
}

function seperateChunksIntoPars(chunks, splitby = "\n\n") {
    let pars = [];
    chunks.forEach((chunk) =>
        chunk.split(splitby).forEach((par) => pars.push(par))
    );
    return pars;
}

function a(r) {
    if (Array.isArray(r)) {
        for (var o = 0, t = Array(r.length); o < r.length; o++) t[o] = r[o];
        return t;
    }
    return Array.from(r);
}

function n(a, o) {
    var s = 0;
    for (; s < o.length - 2; s = s + 3) {
        var d = o.charAt(s + 2);
        d = d >= "a" ? d.charCodeAt(0) - 87 : Number(d);
        d = "+" === o.charAt(s + 1) ? a >>> d : a << d;
        a = "+" === o.charAt(s) ? (a + d) & 4294967295 : a ^ d;
    }
    return a;
}

function sign(r, gtk = 0) {
    var i = null;
    var o = r.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
    if (null === o) {
        var t = r.length;
        t > 30 &&
            (r =
                "" +
                r.substr(0, 10) +
                r.substr(Math.floor(t / 2) - 5, 10) +
                r.substr(-10, 10));
    } else {
        for (
            var e = r.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/),
                C = 0,
                h = e.length,
                f = []; h > C; C++
        )
            "" !== e[C] && f.push.apply(f, a(e[C].split(""))),
            C !== h - 1 && f.push(o[C]);
        var g = f.length;
        g > 30 &&
            (r =
                f.slice(0, 10).join("") +
                f.slice(Math.floor(g / 2) - 5, Math.floor(g / 2) + 5).join("") +
                f.slice(-10).join(""));
    }
    var u = void 0,
        l =
        "" +
        String.fromCharCode(103) +
        String.fromCharCode(116) +
        String.fromCharCode(107);
    u = null !== i ? i : (i = gtk || "") || "";
    for (
        var d = u.split("."),
            m = Number(d[0]) || 0,
            s = Number(d[1]) || 0,
            S = [],
            c = 0,
            v = 0; v < r.length; v++
    ) {
        var A = r.charCodeAt(v);
        128 > A ?
            (S[c++] = A) :
            (2048 > A ?
                (S[c++] = (A >> 6) | 192) :
                (55296 === (64512 & A) &&
                    v + 1 < r.length &&
                    56320 === (64512 & r.charCodeAt(v + 1)) ?
                    ((A = 65536 + ((1023 & A) << 10) + (1023 & r.charCodeAt(++v))),
                        (S[c++] = (A >> 18) | 240),
                        (S[c++] = ((A >> 12) & 63) | 128)) :
                    (S[c++] = (A >> 12) | 224),
                    (S[c++] = ((A >> 6) & 63) | 128)),
                (S[c++] = (63 & A) | 128));
    }
    for (
        var p = m,
            F =
            "" +
            String.fromCharCode(43) +
            String.fromCharCode(45) +
            String.fromCharCode(97) +
            ("" +
                String.fromCharCode(94) +
                String.fromCharCode(43) +
                String.fromCharCode(54)),
            D =
            "" +
            String.fromCharCode(43) +
            String.fromCharCode(45) +
            String.fromCharCode(51) +
            ("" +
                String.fromCharCode(94) +
                String.fromCharCode(43) +
                String.fromCharCode(98)) +
            ("" +
                String.fromCharCode(43) +
                String.fromCharCode(45) +
                String.fromCharCode(102)),
            b = 0; b < S.length; b++
    )
        (p += S[b]), (p = n(p, F));
    return (
        (p = n(p, D)),
        (p ^= s),
        0 > p && (p = (2147483647 & p) + 2147483648),
        (p %= 1e6),
        p.toString() + "." + (p ^ m)
    );
}

function md5(str) {
    var k = [],
        i = 0;

    for (i = 0; i < 64;) k[i] = 0 | (Math.abs(Math.sin(++i)) * 4294967296);

    var b,
        c,
        d,
        j,
        x = [],
        str2 = unescape(encodeURI(str)),
        a = str2.length,
        h = [(b = 1732584193), (c = -271733879), ~b, ~c];

    for (i = 0; i <= a;)
        x[i >> 2] |= (str2.charCodeAt(i) || 128) << (8 * (i++ % 4));

    x[(str = ((a + 8) >> 6) * 16 + 14)] = a * 8;
    i = 0;

    for (; i < str; i += 16) {
        a = h;
        j = 0;

        for (; j < 64;) {
            a = [
                (d = a[3]),
                (b = a[1] | 0) +
                (((d =
                            a[0] + [
                                (b & (c = a[2])) | (~b & d),
                                (d & b) | (~d & c),
                                b ^ c ^ d,
                                c ^ (b | ~d),
                            ][(a = j >> 4)] +
                            (k[j] + (x[([j, 5 * j + 1, 3 * j + 5, 7 * j][a] % 16) + i] | 0))) <<
                        (a = [7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21][
                            4 * a + (j++ % 4)
                        ])) |
                    (d >>> (32 - a))),
                b,
                c,
            ];
        }

        for (j = 4; j;) {
            h[--j] = h[j] + a[j];
        }
    }

    str = "";

    for (; j < 32;) {
        str += ((h[j >> 3] >> ((1 ^ (j++ & 7)) * 4)) & 15).toString(16);
    }

    return str;
}