// ==UserScript==
// @name        ADS扩展脚本
// @namespace
// @match       *://adsdesk.cn/*
// @match       *://www.adsdesk.cn/*
// @match       *://main.adsdesk.cn/*
// @grant       none
// @version     1.0
// @author      -
// @require     https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js
// @require     https://cdn.jsdelivr.net/npm/watermark-package
// @run-at      document-start
// @description 2022/09/08
// ==/UserScript==

(function () {
    'use strict';

    const debugMode = 0
    const OMS = "oms";
    const DOMAIN = '.adsdesk.cn'
    // 定时器ID
    let timerId
    let watermarkTimerId
    // 执行次数, 防止死循环, 执行到一定次数后终止
    let num = 0

    /**
     * 业务开始主函数
     */
    function start() {
        const pathname = location.pathname
        outLog("start() pathname: " + pathname)

        if (pathname === '/login') {
            const authToken = getQueryVariable('authToken');
            if (!authToken) {
                outLog('未找到authToken, 无需执行')
                cleanUnnecessary()
                return;
            }

            clearCookie('token')
            clearCookie('token', DOMAIN)

            const decodeResultArr = decryptAuthToken(authToken)
            outLog("decodeResultArr: " + decodeResultArr)

            // 前端登录限制, 有时候会重新导航到login页面且清空url参数, 所以先存到cookie
            setCookie(OMS + '_authToken', authToken, 1, DOMAIN)
            setCookie(OMS + '_uniqueCode', Date.now(), 1, DOMAIN)
            for (let i = 0; i < decodeResultArr.length; i++) {
                // setCookie(OMS + '_' + i, decodeResultArr[i], 1)
                setCookie(OMS + '_' + i, decodeResultArr[i], 1, DOMAIN)
            }
            timerId = setInterval(autoLoginFunc, 100)
            return
        }

        const optUser = getCookie(OMS + '_3')

        if (pathname === '/common-app/property/common/material-library-all' && optUser) {
            // 是否是真的重复登录, 还需要获取当前token用以判断角色, 优化师排除(这个角色的url就是library-all)
            const tokenJson = decryptToken(getCookie('token'))
            outLog(tokenJson)
            // 非设计师, 判断为重复登录
            if (!tokenJson || tokenJson['role'] !== 'DESIGNER') {
                outLog('重复登录')
                clearCookie('token')
                clearCookie('token', DOMAIN)
                timerId = setInterval(autoLoginFunc, 200)
            }
        }

        if (optUser) {
            outLog('模拟环境, 添加监控')
            timerId = setInterval(monitorFunc, 100)
            // 添加水印
            watermarkTimerId = setInterval(() => watermark({watermark_txt: '模拟环境[' + optUser + ']' + getNowTime()}), 1000)
        }

        // 1S之后添加监听, 否则会影响脚本执行效率
        setTimeout(addRequestListener, 1000)
    }

    //region 添加水印
    function watermark(settings) {
        // 默认设置
        let defaultSettings = {
            watermark_txt: "text",
            watermark_x: 0,//水印起始位置x轴坐标
            watermark_y: 40,//水印起始位置Y轴坐标
            watermark_rows: 100,//水印行数
            watermark_cols: 30,//水印列数
            watermark_x_space: 80,//水印x轴间隔
            watermark_y_space: 80,//水印y轴间隔
            watermark_color: '#aaa',//水印字体颜色
            watermark_alpha: 0.3,//水印透明度
            watermark_fontsize: '15px',//水印字体大小
            watermark_font: '微软雅黑',//水印字体
            watermark_width: 280,//水印宽度
            watermark_height: 80,//水印高度
            watermark_angle: 20//水印倾斜度数
        };
        Object.assign(defaultSettings, settings);
        let page_width = Math.max(document.documentElement.clientWidth, document.documentElement.scrollWidth)
        let page_height = Math.max(document.documentElement.clientHeight, document.documentElement.scrollHeight)
        // 如果将水印列数设置为0，或水印列数设置过大，超过页面最大宽度，则重新计算水印列数和水印x轴间隔
        if (defaultSettings.watermark_cols === 0 || (parseInt(defaultSettings.watermark_x + defaultSettings.watermark_width * defaultSettings.watermark_cols + defaultSettings.watermark_x_space * (defaultSettings.watermark_cols - 1)) > page_width)) {
            defaultSettings.watermark_cols = parseInt((page_width - defaultSettings.watermark_x + defaultSettings.watermark_x_space) / (defaultSettings.watermark_width + defaultSettings.watermark_x_space));
            // defaultSettings.watermark_x_space = parseInt((page_width - defaultSettings.watermark_x - defaultSettings.watermark_width * defaultSettings.watermark_cols) / (defaultSettings.watermark_cols - 1));
        }
        // 如果将水印行数设置为0，或水印行数设置过大，超过页面最大长度，则重新计算水印行数和水印y轴间隔
        if (defaultSettings.watermark_rows === 0 || (parseInt(defaultSettings.watermark_y + defaultSettings.watermark_height * defaultSettings.watermark_rows + defaultSettings.watermark_y_space * (defaultSettings.watermark_rows - 1)) > page_height)) {
            defaultSettings.watermark_rows = parseInt((defaultSettings.watermark_y_space + page_height - defaultSettings.watermark_y) / (defaultSettings.watermark_height + defaultSettings.watermark_y_space));
            // defaultSettings.watermark_y_space = parseInt(((page_height - defaultSettings.watermark_y) - defaultSettings.watermark_height * defaultSettings.watermark_rows) / (defaultSettings.watermark_rows - 1));
        }

        let oTemp = document.createElement('div');
        oTemp.id = 'oms_water_mark';
        let x;
        let y;
        for (let i = 0; i < defaultSettings.watermark_rows; i++) {
            y = defaultSettings.watermark_y + (defaultSettings.watermark_y_space + defaultSettings.watermark_height) * i;
            for (let j = 0; j < defaultSettings.watermark_cols; j++) {
                x = defaultSettings.watermark_x + (defaultSettings.watermark_width + defaultSettings.watermark_x_space) * j;
                let mask_div = document.createElement('div');
                mask_div.id = 'mask_div' + i + j;
                mask_div.className = 'mask_div';
                mask_div.appendChild(document.createTextNode(defaultSettings.watermark_txt));
                // 设置水印div倾斜显示
                mask_div.style.webkitTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                mask_div.style.MozTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                mask_div.style.msTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                mask_div.style.OTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                mask_div.style.transform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                mask_div.style.visibility = "";
                mask_div.style.position = "absolute";
                mask_div.style.left = x + 'px';
                mask_div.style.top = y + 'px';
                mask_div.style.overflow = "hidden";
                mask_div.style.zIndex = "9999";
                mask_div.style.pointerEvents = 'none';//pointer-events:none 让水印不遮挡页面的点击事件
                mask_div.style.opacity = defaultSettings.watermark_alpha;
                mask_div.style.fontSize = defaultSettings.watermark_fontsize;
                mask_div.style.fontFamily = defaultSettings.watermark_font;
                mask_div.style.color = defaultSettings.watermark_color;
                mask_div.style.textAlign = "center";
                mask_div.style.width = defaultSettings.watermark_width + 'px';
                mask_div.style.height = defaultSettings.watermark_height + 'px';
                mask_div.style.display = "block";
                oTemp.appendChild(mask_div);
            }
        }

        removeWatermark()
        document.body.appendChild(oTemp)
    }

    function removeWatermark() {
        const oldDiv = document.getElementById('oms_water_mark')
        if (oldDiv) {
            oldDiv.remove()
        }
    }

    //endregion

    //region 登出监听
    function monitorFunc() {
        startTimerWork('nav', monitorWork)
    }

    function monitorWork() {
        const logoutEl = findLogOutElement();
        if (!logoutEl) {
            outLog('未找到登出按钮')
            return false;
        }
        outLog('找到登出按钮, 添加监听事件')
        logoutEl.addEventListener('click', cleanUnnecessary, false);

        // const childDiv = document.createElement("div")
        // childDiv.setAttribute("id", "xbz-div")
        // childDiv.setAttribute("class", "nav-item")
        // childDiv.setAttribute("style", "position:fixed; bottom:0;left: 0%")
        // const innerContent = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;模拟登录环境, 请谨慎操作![' + getCookie(OMS + '_3') + ']&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'
        // childDiv.innerHTML = '<marquee><p style="color:red;font-size:30px">' + innerContent + '</p><marquee>'
        // childDiv.innerHTML = '<marquee><p style="color:red;font-size:30px">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;模拟登录环境, 请谨慎操作!<span style="color:blue;font-size:12px">退出模拟登录环境请在右上角退出登录</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p><marquee>'
        // elementArr[0].appendChild(childDiv)
        return true
    }

    function findLogOutElement() {
        const arr = document.getElementsByClassName('ivu-dropdown-item');
        for (let i = 0; i < arr.length; i++) {
            if ('退出登录'.indexOf(arr[i].innerText) >= 0) {
                return arr[i]
            }
        }
        return null
    }

    //endregion

    //region 自动登录
    function autoLoginFunc() {
        startTimerWork('ivu-input ivu-input-large', autoLoginWork)
    }

    function autoLoginWork(elementArr) {
        const inputVal1 = getCookie(OMS + '_' + 0) + '&&&' + getCookie(OMS + '_' + 1)
        const inputVal2 = getCookie(OMS + '_' + 2)
        outLog('autoLoginWork > inputVal1: ' + inputVal1 + "; inputVal2: " + inputVal2)


        elementArr[0].value = inputVal1
        // 直接设置value, vue不识别, 需要触发一下input事件
        elementArr[0].dispatchEvent(new Event('input'))
        elementArr[1].value = inputVal2
        elementArr[1].dispatchEvent(new Event('input'))

        outLog("点击登录")
        document.getElementsByClassName('login-btn')[0].click()
        return true
    }

    //endregion

    //region token解密相关
    function decryptToken(token) {
        if (!token) {
            return null
        }
        token = window.atob(token);
        const tokenStr = window.atob(token.split('.')[1])
        return JSON.parse(tokenStr)
    }

    function decryptAuthToken(authToken) {
        const sourceAuthToken = authToken
        authToken = urlDecode(authToken)
        outLog("urlDecode: " + sourceAuthToken + " ->\n" + authToken)
        const today = new Date()
        const randomIndex = randomNum(today.getFullYear() * 10000 + (today.getMonth() + 1) * 100 + today.getDate(), today.getDay())

        let result = ''
        const encodeStrArr = window.atob(authToken).split('')
        for (let i = 0; i < encodeStrArr.length; i++) {
            result += getByIndexLoop(encodeStrArr[i], -randomIndex)
        }
        return result.split('_|_')
    }

    const SALT = 11117

    function randomNum(daySeed, indexSeed) {
        let n = daySeed % SALT
        for (let i = 0; i < 10 + indexSeed; i++) {
            n = n * n
            n = n % SALT
        }
        return n
    }

    const LIST = "pihD|n+XK1G6^Z3q:F?a_y)eO5lTo4*jfb=PAktH;2RN<wrm&gcLW9zQ78x~v$,CS/ds0I(E.!M`V-%#U>JB@uY".split('')

    function getByIndexLoop(originStr, randomIndex) {
        let newIndex = (LIST.indexOf(originStr) + randomIndex) % LIST.length
        return LIST[newIndex < 0 ? LIST.length + newIndex : newIndex]
    }

    //endregion

    //region 工具函数封装
    function getQueryVariable(variable) {
        const query = window.location.search.substring(1)
        const vars = query.split("&")
        for (let i = 0; i < vars.length; i++) {
            const pair = vars[i].split("=")
            if (pair[0] === variable) {
                return pair[1]
            }
        }
        return false
    }


    function urlDecode(str) {
        return str.replace(/%20/g, '+')
            .replace(/%23/g, '#')
            .replace(/%25/g, '%')
            .replace(/%26/g, '&')
            .replace(/%27/g, '\'')
            .replace(/%2B/g, '+')
            .replace(/%2F/g, '/')
            .replace(/%3D/g, '=')
            .replace(/%3F/g, '?');
    }

    function getNowTime() {
        const now = new Date();
        let year = now.getFullYear(),
            month = now.getMonth() + 1,
            date = now.getDate(),
            h = now.getHours(),
            m = now.getMinutes()
        month = month < 10 ? '0' + month : month
        date = date < 10 ? '0' + date : date
        h = h < 10 ? '0' + h : h
        m = m < 10 ? '0' + m : m
        return year + "-" + month + "-" + date + " " + h + ":" + m;
    }

    function outLog(msg) {
        if (debugMode) {
            console.log(msg)
        }
    }

    /**
     * 定时执行指定任务
     */
    function startTimerWork(className, workFunc) {
        outLog("startTimerWork: " + className)

        if (num > 100) {
            outLog('10s后放弃...')
            clearInterval(timerId)
            return
        }

        const elementArr = document.getElementsByClassName(className)
        if (elementArr.length === 0) {
            num++
            outLog(className + '元素未就绪')
            return
        }

        outLog("元素已就绪, 开始执行函数")
        outLog(": " + workFunc)
        if (workFunc(elementArr)) {
            // 执行成功, 需要结束定时器
            clearInterval(timerId)
            outLog('执行成功, 结束定时器: ' + timerId)
        }
    }

    //endregion

    //region cookie相关操作
    function getCookie(cname) {
        const name = cname + "="
        const ca = document.cookie.split(';')
        for (let i = 0; i < ca.length; i++) {
            let c = ca[i]
            while (c.charAt(0) === ' ') c = c.substring(1)
            if (c.indexOf(name) !== -1) return c.substring(name.length, c.length)
        }
        return ""
    }

    function setCookie(cookieName, cookieValue, expiresDays, domain) {
        const d = new Date()
        d.setTime(d.getTime() + (expiresDays * 24 * 60 * 60 * 1000));
        const expires = "expires=" + d.toUTCString()
        document.cookie = cookieName + "=" + cookieValue + "; " + expires + ";path=/" + (domain ? (";domain=" + domain) : '')
    }

    function clearCookie(name, domain) {
        setCookie(name, "", -1, domain)
    }

    function cleanUnnecessary() {
        outLog('清除多余信息')
        for (let i = 0; i <= 3; i++) {
            clearCookie(OMS + '_' + i, DOMAIN)
        }
        clearInterval(watermarkTimerId)
        removeWatermark()
    }

    //endregion

    //region 监控HTTP请求

    function loadSimulationEnv(req, res) {
        // alert(res.code)
        // alert(req.username)
        // alert(getCookie(OMS + '_3'))
        if (res.code !== 200 || req.username.indexOf("&&&") <= 0) {
            cleanUnnecessary()
            return;
        }
        outLog('手动模拟登录, 添加操作人cookie和uniqueCode')
        setCookie(OMS + '_3', req.username.split("&&&")[0], 1, DOMAIN)
        setCookie(OMS + '_uniqueCode', Date.now(), 1, DOMAIN)
    }

    function addRequestListener() {
        window.addEventListener('ajaxReadyStateChange', function (e) {
            e.detail.onload = function () {
                const xhr = e.detail;
                let requestUrl = xhr.orignUrl.split("adsdesk.cn/adsdesk")[1];
                if (requestUrl === '/api/user/user/login') {
                    loadSimulationEnv(JSON.parse(xhr.body), JSON.parse(xhr.response))
                }
                const authToken = getCookie(OMS + '_authToken')
                const uniqueCode = getCookie(OMS + '_uniqueCode')
                const optUser = getCookie(OMS + '_3')
                if (optUser && validUrl(xhr.responseURL)) {
                    const param = {
                        "optUser": optUser,
                        "authToken": authToken,
                        "uniqueCode": uniqueCode,
                        "requestUrl": requestUrl,
                        "requestMethod": xhr.method,
                        "requestBody": cleanContent(xhr.body),
                        "responseCode": xhr.status,
                        "responseBody": cleanContent(xhr.response)
                    }
                    sendLog(param)
                }
            }
        })
    }

    const BLACK_URL_SUFFIX_LIST = ['search', 'list', 'listPage', 'opt_select', 'get', 'check', '/tree', 'query/dynamic_word', 'estimate', 'list_company_account', 'customer/permission',
        '/info', 'adver_account/num']
    const BLACK_URL_CONTAIN_LIST = ['/select', 'download_template', 'forward/adi', 'on_demand/package/get']

    function validUrl(url) {
        if (!url) {
            return false
        }
        if (!url.startsWith("https://api.adsdesk.cn/") && !url.startsWith("http://api.adsdesk.cn/")) {
            return false;
        }
        // 改为所有url都上传
        // for (let i = 0; i < BLACK_URL_SUFFIX_LIST.length; i++) {
        //     const blackUrlSuffix = BLACK_URL_SUFFIX_LIST[i]
        //     if (url.endsWith(blackUrlSuffix)) {
        //         return false;
        //     }
        // }
        // for (let i = 0; i < BLACK_URL_CONTAIN_LIST.length; i++) {
        //     const blackUrlContain = BLACK_URL_CONTAIN_LIST[i]
        //     if (url.indexOf(blackUrlContain) >= 0) {
        //         return false;
        //     }
        // }

        return true
    }

    function sendLog(param) {
        const httpRequest = new XMLHttpRequest();
        const sendLogUrl = (debugMode ? 'http://localhost/dev-api' : 'https://oms-api.adsdesk.cn') + '/openapi/adsdesk/request_log/save'
        httpRequest.open('POST', sendLogUrl, true);
        httpRequest.setRequestHeader("Content-type", "application/json");
        httpRequest.send(JSON.stringify(param));
    }

    function cleanContent(content) {
        if (!content) {
            return content;
        }
        try {
            return content.replaceAll("\n", "").replaceAll("\t", "")
        } catch (e) {
            return content
        }
    }

    //endregion

    //region 监听HTTP请求
    ;(function () {
        if (typeof window.CustomEvent === "function") return false;

        function CustomEvent(event, params) {
            params = params || {bubbles: false, cancelable: false, detail: undefined};
            const evt = document.createEvent('CustomEvent');
            evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
            return evt;
        }

        CustomEvent.prototype = window.Event.prototype;

        window.CustomEvent = CustomEvent;
    })();
    ;(function () {
        function ajaxEventTrigger(event) {
            const ajaxEvent = new CustomEvent(event, {detail: this});
            window.dispatchEvent(ajaxEvent);
        }

        const oldXHR = window.XMLHttpRequest;

        function newXHR() {
            const realXHR = new oldXHR();

            realXHR.addEventListener('abort', function () {
                ajaxEventTrigger.call(this, 'ajaxAbort');
            }, false);

            realXHR.addEventListener('error', function () {
                ajaxEventTrigger.call(this, 'ajaxError');
            }, false);

            realXHR.addEventListener('load', function () {
                ajaxEventTrigger.call(this, 'ajaxLoad');
            }, false);

            realXHR.addEventListener('loadstart', function () {
                ajaxEventTrigger.call(this, 'ajaxLoadStart');
            }, false);

            realXHR.addEventListener('progress', function () {
                ajaxEventTrigger.call(this, 'ajaxProgress');
            }, false);

            realXHR.addEventListener('timeout', function () {
                ajaxEventTrigger.call(this, 'ajaxTimeout');
            }, false);

            realXHR.addEventListener('loadend', function () {
                ajaxEventTrigger.call(this, 'ajaxLoadEnd');
            }, false);

            realXHR.addEventListener('readystatechange', function () {
                ajaxEventTrigger.call(this, 'ajaxReadyStateChange');
            }, false);


            let send = realXHR.send;
            realXHR.send = function (...arg) {
                send.apply(realXHR, arg);
                realXHR.body = arg[0];
                ajaxEventTrigger.call(realXHR, 'ajaxSend');
            }

            let open = realXHR.open;
            realXHR.open = function (...arg) {
                open.apply(realXHR, arg)
                realXHR.method = arg[0];
                realXHR.orignUrl = arg[1];
                realXHR.async = arg[2];
                ajaxEventTrigger.call(realXHR, 'ajaxOpen');
            }

            let setRequestHeader = realXHR.setRequestHeader;
            realXHR.requestHeader = {};
            realXHR.setRequestHeader = function (name, value) {
                realXHR.requestHeader[name] = value;
                setRequestHeader.call(realXHR, name, value)
            }
            return realXHR;
        }

        window.XMLHttpRequest = newXHR;

    })();
    //endregion

    start()
})()
