[TS] Linx Amender

Generic tracking/redirection/open-in-new-tab removal; Amend page title; URL redirector; and more power functionality. Has rules for Pixiv, deviantArt, twitter, youtube, blogger, Batota etc.

当前为 2014-08-29 提交的版本,查看 最新版本

// ==UserScript==
// @name            [TS] Linx Amender
// @namespace       TimidScript
// @version         3.0.15b
// @description     Generic tracking/redirection/open-in-new-tab removal; Amend page title; URL redirector; and more power functionality. Has rules for Pixiv, deviantArt, twitter, youtube, blogger, Batota etc.
// @icon            https://i.imgur.com/WznrrlJ.png
// @author          TimidScript
// @homepageURL     https://openuserjs.org/users/TimidScript
// @copyright       © 2014 TimidScript, All Rights Reserved.
// @license         Creative Commons BY-NC-SA + Please notify me if distributing
// @include         *
// @require         https://openuserjs.org/src/libs/TimidScript/TSL_-_Generic.js
// @require         https://openuserjs.org/src/libs/TimidScript/TSL_-_Draggable_Table_Rows.js
// @require         https://openuserjs.org/src/libs/TimidScript/TSL_-_GM_Update.js
// @homeURL         https://openuserjs.org/scripts/TimidScript/[TS]_Linx_Amender
// @grant           GM_registerMenuCommand
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM_deleteValue
// @grant           GM_listValues
// @grant           GM_xmlhttpRequest
// @grant           GM_setClipboard
// @run-at          document-start
// ==/UserScript==

/* Information
********************************************************************************************
Copyright © 2014 TimidScript, All Rights Reserved.
Script's Homepage:              Check homepages below

TimidScript's Homepage:         https://openuserjs.org/users/TimidScript
                                https://gf.qytechs.cn/users/1455-timidscript
                                https://monkeyguts.com/author.php?un=timidscript
                                
                                http://userscripts.org/users/TimidScript
                                http://userscripts-mirror.org/users/100610/scripts


Order of rule execution
 - URL Rules (Does not run in iframes)
 - CSS Rules (Does not run in iframes)
 - Title Rules (Does not run in iframes) 
 - Script Rules
 - Attrib & Click in Order 

Hotkey F9       - Brings up settings window
Hotkey Alt+F9   - Brings up settings window + ForceUpdate

Onlien Rules URL Default:
https://github.com/TimidScript/GreasyMonkey/raw/master/data/LinxAmenderRules.txt

Default Online Rules can be set using GM_setValue
GM_setValue("OnlineRulesURL", "https://newlocation/LinxAmenderRules.txt");

------------------------------------
 Version History
------------------------------------
3.0.15 (2014-08-21) First public release
 - Replacement of the previous scripts "[TS] Direct Outgoing Links" and "[TS] Title Amender" 
 - Starts with version 3.#.#  
 - Ability to amend node attributes
 - Ability to amend page title
 - Ability to amend page URL (redirect)
 - Ability to imitate mouse clicks function
 - Ability to add simply javascripts
 - Ability to add CSS script
 - Import "[TS] Direct Outgoing Links" exports
 - Import "[TS] Title Amender" exports
Some of the script that I have that it replaces 
    - [TS] Youtube Secure
    - [TS] Direct Outgoing Links
    - [TS] Title Amender
    - [TS] Blogger NCR
    - [TS] Voltaire Network Language Picker    
 
********************************************************************************************/

if (window === window.top) console.info("[TS] Linx Amender: " + document.location.host);
else console.info("[TS] Linx Amender iframe: " + document.location.host);

/* Global Variables
==============================================================================================
  Global Variables
==============================================================================================*/
var iDocument = document;
var isOnline = false;

//#region TimidScript Library Functions
/* 
Copy and paste the commented out code underneath into your script for quick reference 
and auto-complete feature if available. 
*********************************************************************************/
var TSL = new Object();

//Remove node from document. Accepts id or node object
TSL.removeNode = function (node, doc) { TimidScriptLibrary.removeNode(node, doc); };

// Creates document element. Default doc value is the document.
TSL.createElement = function (tag, attributes, doc) { return TimidScriptLibrary.createElement(tag, attributes, doc) };

// Creates document element using html code. Default doc value is the document.
TSL.createElementHTML = function (html, doc) { return TimidScriptLibrary.createElementHTML(html, doc) };

//Add CSS styles to document header. Document can be left empty.
TSL.addStyle = function (id, CSS, doc) { TimidScriptLibrary.addSyle(id, CSS, doc); };

//General Functions
TSL.makeStruct = function (names) { return TimidScriptLibrary.makeStruct(names); };

// Checks if mouse event is within an elements client area
TSL.isMouseEventInClientArea = function (event, element) { return TimidScriptLibrary.isMouseEventInClientArea(event, element); };

//Returns the thickness of the scrollbar
TSL.getScrollBarThickness = function () { return TimidScriptLibrary.getScrollBarThickness(); };

//Array containing NTFS illegal characters alternatives
TSL.ALTNTFSChars = [["<", "〉"], [">", "〈"], [":", ":"], ['"', "‟"], ["/", "∕"], ["\\", ""], ["?", ""], ["*", "✳"], ];
TSL.replaceNTFSIllegals = function (str) { return TimidScriptLibrary.replaceNTFSIllegals(str); };

TSL.escapeRegExp = function (str) { return TimidScriptLibrary.escapeRegExp(str); };

//String Padding
String.prototype.lPad = function (chr, length) { return TimidScriptLibrary.paddingLeft(this, chr[0], length); };
String.prototype.rPad = function (chr, length) { return TimidScriptLibrary.paddingRight(this, chr[0], length); };
/*
*********************************************************************************/
//#endregion



/*
==================================================================================
==================================================================================*/
var DialogPaste =
{
    show: function ()
    {
        DialogMain.hideControlBar(true);

        var dialog = TSL.createElementHTML('<div id="LinxPasteDialog"><div style="background-color: #FFFFBC; border-top: 1px black solid; padding: 5px 5px; margin: 0 2px;">Copy the Linx Rules you want from another profile and then you can paste them here (ctrl-v) and press accept totransfer them onto this profile.</div><div style="text-align: center;"><textarea id="LinxPasteText" rows="8" wrap="off" style="width: 99%; resize: vertical;"></textarea></div><div class="solidBar"><input id="LinxPImport" type="button" value="Import" /><input id="LinxPCancel" type="button" value="Cancel" /></div></div>', iDocument);
        iDocument.body.appendChild(dialog);

        iDocument.getElementById("LinxPImport").onclick = DialogPaste.cbPasteImport;
        iDocument.getElementById("LinxPCancel").onclick = DialogPaste.cbPasteCancel;
    },

    cbPasteCancel: function (e)
    {
        TSL.removeNode("LinxPasteDialog", iDocument);
        DialogMain.hideControlBar(false);
    },

    importRules: function (data)
    {
        var rules = data.split("\n");

        for (var i = 0; i < rules.length; i++)
        {
            if (!rules[i].trim() || rules[i][0] == "/") continue;
            var rule = JSON.parse(rules[i]);
            if (!rule.id) continue;
            var id = rule.id;
            delete rule.id;

            if (GM_getValue(id) && !confirm("Rule with same ID already exists; would you like to override it"))
                id = id[0] + new Date().getTime();

            DialogEdit.saveRuleCompact(id, rule, true);
        }
    },

    cbPasteImport: function (e)
    {
        DialogPaste.importRules(iDocument.getElementById("LinxPasteText").value);

        DialogMain.hideControlBar(false);
        DialogMain.populateTable();
        TSL.removeNode("LinxPasteDialog", iDocument)
    }
};


/*
==================================================================================
==================================================================================*/
var DialogEdit =
{
    show: function (rule, id)
    {
        DialogMain.hideControlBar(true);

        var dialog = TSL.createElementHTML('<div id="LinxEditDialog"><div class="solidBar" style="text-align: center;"><b>Linx Rule Editor</b></div><table id="LinxEdit"><tbody><tr><td style="min-width: 140px;">Name</td><td style="width: 99%;"><input id="RuleName" type="text" /></td></tr><tr><td>URLs <sup>1</sup></td><td><textarea id="RuleURLs" title="Regular expression for website URLs these rules apply to" style="resize: vertical; height: 50px;"></textarea></td></tr><tr id="RowScr"><td>Javascript Code <sup>4</sup></td><td><textarea id="RuleScript" title="Javascript code" style="resize: vertical; height: 100px;"></textarea></td></tr><tr id="RowVar"><td>Script Variables <sup>5</sup></td><td><input id="RuleScriptValues" type="text" /></td></tr><tr id="RowSel"><td>Node Selectors <sup>2</sup></td><td><input id="RuleSelectors" type="text" value="a" title="Value used in querySelector to get nodes" /><div id="nthNode" style="background-color: #FBE6A8; padding: 2px 5px; margin: 0 -1px; border-bottom: 1px solid black;">(n =<input id="RuleNth" type="text" value="0" style="width: 25px; text-align: right;" />) Apply rule on the nth node. "0" implies all the nodes.</div></td></tr><tr id="RowReg"><td title="Uses regular expression to change node attribute value">Attribute Rules <sup>3</sup></td><td><table id="RegexesTable" class="class3Columns"><tbody><tr><td><input type="text" title="Attribute name whose content you wish to change" /></td><td><div><input type="text" title="Regular expression search value." style="width: 47%" /><input type="text" title="Regular expression replace value." style="width: 47%" /></div></td><td><button>-</button><button>+</button>↓ Drag ↑</td></tr></tbody></table></td></tr><tr><td title="Sample Website URL">Sample Website</td><td><input id="RuleSampleURL" type="text" /></td></tr><tr id="RowDes"><td title="Rule Description">Description</td><td><textarea id="RuleDescription" title="Description of what the rule does" style="resize: vertical; height: 50px;"></textarea></td></tr><tr><td colspan="2" style="border-top: 1px black solid; background-color: #FFFFCE; padding: 5px; font-size: small;"><div onclick="OL.style.display = (OL.style.display) ? null :&quot;none&quot;;" style="text-align: center; margin: 0 5px; background-color: #FDFDA3"><b>Click to toggle guide</b></div><ol id="OL" style="padding: 0 20px; margin: 0; display: none;"><li>List of URLs, that the Linx Rule works on, separated by newlines. Takes&nbsp;<a href="http://www.w3schools.com/jsref/jsref_obj_regexp.asp">regular expression</a>.&nbsp;To perform a literal URL string search start the value with the escape character "@".To negate (exclude) a URL start it with the character "¬". A negate (exclude) URL should first have the "¬" character; example "¬@" and not "@¬".</li><li>Value used in "<a href="http://www.w3schools.com/jsref/met_element_queryselector.asp">querySelectorAll</a>" to get nodes.</li><li><ul style="padding: 0 10px;"><li>If the attribute name starts with "=" character it treats the text as object key. Example "=textContent" or "=innerHTML" to do search & replace on the elements content instead of attribute. Be careful when editing the node&quot;s "innerHTML" and especially the "outerHTML" properties, as bad edits can lead to infinite loop.</li><li>To remove an attribute start the name value with "-"; example: name="-target" & search="_blank" removes "target" attribute if the value is "_blank". Search="" implies remove the attribute without checking the attribute value.</li><li>To create an attribute start the name with the value "+". The search string will be ignored, while the replace string will be used to set the attribute value.</li><li>Search value takes regular expressions. Default search is case insensitive and global. To toggle them start search value with: "@1" = sensitive; "@2" = global off; "@3" = sensitive & global off.</li></ul></li><li>A JS script that is added to the document on load. NOT recommended except on rare occasions, for exceptionally short scripts that are not deserving, in their own right, a complete user script.</li><li>Defined <b>string</b> variables separated by semi-clone, that are accessed through the JS code using "LA_get". example <code>LA_get("lang");</code> in source code [4] and in Variable [5] it&quot;s <code>lang=en;</code>. Look at the "Online Rules" script rules for functioning examples.</li></ol></td></tr></tbody></table><div class="solidBar"><input id="LinxRuleA" type="button" value="Attribute Rule" ruletype="1" style="float: left;" /><input id="LinxRuleT" type="button" value="Title Rule" ruletype="2" style="float: left;" /><input id="LinxRuleU" type="button" value="URL Rule" ruletype="3" style="float: left;" /><input id="LinxRuleC" type="button" value="Click Rule" ruletype="4" style="float: left;" /><input id="LinxRuleS" type="button" value="Script Rule" ruletype="5" style="float: left;" /><input id="LinxRuleX" type="button" value="CSS Rule" ruletype="6" style="float: left;" /><input id="LinxEAccept" type="button" value="Accept" /><input id="LinxECancel" type="button" value="Cancel" /></div></div>', iDocument);
        iDocument.body.appendChild(dialog);

        TSRegisterTable(iDocument.getElementById("RegexesTable"), function ()
        {
            DialogEdit.adjustRuleREButtons();
        });
        DialogEdit.adjustRuleREButtons();

        var ipts = iDocument.getElementsByClassName("solidBar")[1].getElementsByTagName("input");
        for (var i = 0; i < ipts.length; i++) ipts[i].onclick = DialogEdit.adjustEditButtons;
        ipts[0].click();

        iDocument.getElementById("LinxEAccept").onclick = DialogEdit.cbAccept;
        iDocument.getElementById("LinxECancel").onclick = DialogEdit.cbCancel;

        if (!rule) return;

        //------------ Fill edit form ------------ //
        if (id) iDocument.getElementById("LinxEdit").ruleID = id;

        setValue(iDocument.getElementById("RuleName"), rule.name);
        setValue(iDocument.getElementById("RuleDescription"), rule.description);
        setValue(iDocument.getElementById("RuleURLs"), rule.URLs);


        setValue(iDocument.getElementById("RuleSampleURL"), rule.sampleURL);
        setValue(iDocument.getElementById("RuleNth"), rule.nthNode, 0);
        setValue(iDocument.getElementById("RuleScript"), rule.script, "");
        setValue(iDocument.getElementById("RuleScriptValues"), rule.scriptVariables);

        //Save all settings even though rule type may note require it
        setValue(iDocument.getElementById("RuleSelectors"), rule.selectors);

        ipts[rule.type - 1].click();

        addRegexes(rule.regexes);
        DialogEdit.adjustRuleREButtons();

        function setValue(el, value, defaultValue)
        {
            if (defaultValue == undefined) defaultValue = "";
            el.value = (value) ? value : defaultValue;
        }

        function addRegexes(rules)
        {
            var tb = iDocument.getElementById("RegexesTable");

            for (var i = 0; i < rule.regexes.length; i++)
            {
                var row = tb.rows[i];
                if (i > 0)
                {
                    row = tb.insertRow(-1);
                    row.innerHTML = tb.rows[0].innerHTML;
                }

                var ipts = row.getElementsByTagName("input");

                setValue(ipts[0], rule.regexes[i].name);
                setValue(ipts[1], rule.regexes[i].search);
                setValue(ipts[2], rule.regexes[i].replace);
            }
        }

    },

    adjustEditButtons: function (e)
    {
        var ruleType = this.getAttribute("ruletype");

        iDocument.getElementById("RegexesTable").className = (ruleType == 1) ? "class3Columns" : "class2Columns";

        iDocument.getElementById("RowReg").cells[0].innerHTML = this.value + "s <sup>3</sup>";
        iDocument.getElementById("RowScr").cells[0].innerHTML = (ruleType == 5) ? "Javascript Code <sup>4</sup>" : "CSS Code";

        iDocument.getElementById("RowSel").style.display = (ruleType == 1 || ruleType == 4) ? null : "none";
        iDocument.getElementById("RowReg").style.display = (ruleType < 4) ? null : "none";
        iDocument.getElementById("RowDes").style.display = (ruleType == 2) ? "none" : null;
        iDocument.getElementById("RowScr").style.display = (ruleType >= 5) ? null : "none";
        iDocument.getElementById("RowVar").style.display = (ruleType == 5) ? null : "none";


        var ipts = this.parentElement.getElementsByTagName("input");
        for (var i = 0; i < 6; i++) ipts[i].style.color = null;
        this.style.color = "#FF04FF";
    },


    //Adjust the regex buttons in the rules table
    adjustRuleREButtons: function ()
    {
        var tb = iDocument.getElementById("RegexesTable");
        var btns = tb.getElementsByTagName("button");
        for (var i = 0; i < btns.length; i++)
        {
            if ((i + 1) % 2 == 1)
            {
                btns[i].onclick = DialogEdit.cbRuleRemove;
                btns[i].removeAttribute("disabled");
            }
            else
            {
                btns[i].onclick = DialogEdit.cbRuleAdd;
                btns[i].style.visibility = (i + 1 < btns.length) ? "hidden" : null;
            }
        }
        if (tb.rows.length == 1) btns[0].disabled = true;
    },

    //Compacts rule before saving
    saveRuleCompact: function (id, rule, keepTimeStamp)
    {
        //Remove duplicate URLs and empty lines
        if (rule.URLs)
        {
            var urls = rule.URLs.split("\n");
            for (var i = 0; i < urls.length; i++)
            {
                if (urls[i].trim().length == 0)
                {
                    urls.splice(i, 1);
                    --i;
                }
                else for (var j = i + 1; j < urls.length; j++)
                    {
                    if (urls[i] == urls[j])
                    {
                        urls.splice(j, 1);
                        --j;
                    }
                }
            }
            rule.URLs = urls.join("\n");
        }

        //Remove properties with empty string
        //for (var key in rule) if (!rule[key]) delete rule[key];        


        var regexes = rule.regexes
        //Remove empty or unwanted regex values
        for (var i = 0; i < regexes.length; i++)
        {
            var regi = regexes[i];

            //Remove empty properties
            for (var key in regi)
            {
                if (!regi[key]) delete regi[key];
            }

            //Remove useless regexes
            if ((rule.type != 2 || rule.type != 3) //URL and Title
                && (!regi.name || regi.name.trim().length == 0)
                && (!regi.search || regi.search.trim().length == 0)
                && (!regi.replace || regi.replace.trim().length == 0))
            {
                regexes.splice(i, 1);
                --i;
            }
        }

        //Remove duplicate regexes        
        for (var i = 0; i < regexes.length; i++)
        {
            var regi = regexes[i];

            for (var j = i + 1; j < regexes.length; j++)
            {
                var regj = regexes[j];
                if (regi.name == regj.name && regi.search == regj.search && regi.replace == regj.replace)
                {
                    regexes.splice(j, 1);
                    --j;
                }
            }
        }

        if (!keepTimeStamp || !rule.timestamp) rule.timestamp = new Date().getTime();
        GM_setValue(id, JSON.stringify(rule));
    },

    cbCancel: function (e)
    {
        TSL.removeNode("LinxEditDialog", iDocument);
        DialogMain.hideControlBar(false);
    },

    cbAccept: function (e)
    {
        var rule = new Object();

        AddDataItem("name", iDocument.getElementById("RuleName").value);
        AddDataItem("URLs", iDocument.getElementById("RuleURLs").value);
        if (rule.type != 4) AddDataItem("description", iDocument.getElementById("RuleDescription").value);
        AddDataItem("sampleURL", iDocument.getElementById("RuleSampleURL").value);
        AddDataItem("nthNode", iDocument.getElementById("RuleNth").value);
        AddDataItem("script", iDocument.getElementById("RuleScript").value);
        AddDataItem("scriptVariables", iDocument.getElementById("RuleScriptValues").value);

        var ipts = iDocument.getElementsByClassName("solidBar")[1].getElementsByTagName("input");

        for (var i = 0; i < 6; i++)
            if (ipts[i].style.color) rule.type = i + 1;

        //Only need for Attribute Rule
        AddDataItem("selectors", iDocument.getElementById("RuleSelectors").value);


        //Fill the RegexesTable
        rule.regexes = new Array(0);
        var rows = iDocument.getElementById("RegexesTable").rows;
        for (var i = 0; i < rows.length; i++)
        {
            var ipts = rows[i].getElementsByTagName("input");

            var regex = new Object();
            if (ipts[0].value) regex.name = ipts[0].value;
            if (ipts[1].value) regex.search = ipts[1].value;
            if (ipts[2].value) regex.replace = ipts[2].value;

            rule.regexes.push(regex);
        }

        var name = iDocument.getElementById("LinxEdit").ruleID;
        if (name)
        {
            var r = GM_getValue(name);
            if (r)
            {
                r = JSON.parse(r)
                rule.enabled = r.enabled
            }
        }
        else
        {
            name = "L" + new Date().getTime();
            rule.enabled = true;
        }

        DialogEdit.saveRuleCompact(name, rule);

        DialogMain.hideControlBar(false);
        DialogMain.populateTable();
        DialogMain.saveOrder();

        TSL.removeNode("LinxEditDialog", iDocument);
        if (rule.type == 2) ParseNodes(true);

        function AddDataItem(name, val)
        {
            if (val && val.trim()) rule[name] = val;
        }
    },

    cbRuleAdd: function (e)
    {
        var tb = this.parentElement.parentElement.parentElement.parentElement;
        var row = tb.insertRow(-1);
        row.innerHTML = tb.rows[0].innerHTML;
        var ipts = row.getElementsByTagName("input");
        for (var i = 0; i < ipts.length; i++) ipts[i].value = "";

        DialogEdit.adjustRuleREButtons();
    },

    cbRuleRemove: function (e)
    {
        TSL.removeNode(this.parentElement.parentElement);
        DialogEdit.adjustRuleREButtons();
    }
};

/*
==================================================================================
==================================================================================*/
var DialogMain =
{
    show: function (forceUpdate)
    {
        if (document.getElementById("LinxFrame")) return;

        var iframe = TSL.createElement(
            "iframe",
                {
                    "id": "LinxFrame",
                    "style": "position: fixed; top: 0px; left: 0px; right: 0px; border: none; height: 100%; width: 100%; z-index: 9999999999999999999999999; background-color: rgba(128, 128, 128,0.25);"
                });

        iframe.onload = function ()
        {
            iDocument = iframe.contentDocument || iframe.contentWindow.document;

            TSL.addStyle(null, '#LinxMain{position: fixed;min-width: 800px;border: 1px solid black;background-color: #488AC7;left: 10px;right: 10px;top: 10px;bottom: 10px;z-index: 1;}.gradientBar{background-image: linear-gradient(bottom, rgb(72, 138, 199 ) 37%, rgb( 92, 179, 255 ) 69%);background-image: -o-linear-gradient(bottom, rgb(72, 138, 199 ) 37%, rgb( 92, 179, 255 ) 69%);background-image: -moz-linear-gradient(bottom, rgb(72, 138, 199 ) 37%, rgb( 92, 179, 255 ) 69%);background-image: -webkit-linear-gradient(bottom, rgb(72, 138, 199 ) 37%, rgb( 92, 179, 255 ) 69%);background-image: -ms-linear-gradient(bottom, rgb(72, 138, 199 ) 37%, rgb( 92, 179, 255 ) 69%);background-image: -webkit-gradient( linear, left bottom, left top, color-stop(0.37, rgb(82,106,179)), color-stop(0.69, rgb(157,117,217)));font-weight: bold;padding: 1px 5px;}.gradientBar input[type="button"]{width: 120px;}.solidBar{background-color: #E0F0FF;border: 1px black solid;padding: 5px 5px;text-align: right;}.solidBar input[type="button"]{width: 100px;}#LinxTable{border-spacing: 1px;background-color: gray;width: 100%;user-select: none;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;}#LinxTable td{padding: 0 5px;}#LinxTable td button:last-child{margin-right: 10px;}#LinxTable tr:hover{background-color: yellow !important;}#LinxTable.localRules tr:nth-child(odd){background-color: #E0F0FF;}#LinxTable.localRules tr:nth-child(even){background-color: #F0FFF0;}#LinxTable.onlineRules tr:nth-child(odd){background-color: #E9DBF7;}#LinxTable.onlineRules tr:nth-child(even){background-color: #F7DBF4;}.btnEnabled, .btnDisabled, .rowBtn, .btnCA, .btnCT, .btnCU, .btnCC, .btnCS, .btnCX{border-radius: 3px;font-size: 11px;margin: 2px 1px;display: inline-block;padding: 0 2px 0 2px;border: 1px groove;text-align: center;}.inputHolder{display: inline-block;width: 200px;}.inputHolder > button{display: inline-block;font-size: 12px;border-radius: 3px;padding: 0 2px 0 2px;border: 1px groove;text-align: center;width: 20px;margin: 0 0 0 2px;}.inputHolder > button:nth-child(1){margin-right: 6px;}.inputHolder > button:hover{color: black;}.inputHolder > input{margin-left: 35px;}.btnCA, .btnCT, .btnCU, .btnCC, .btnCS, .btnCX{width: 40px;margin: 2px 10px;}.btnCA{border-color: #895EB6;background-color: #D8C2EF;color: #895EB6;}.btnCT{border-color: #FF5572;background-color: #FDCFD7;color: #FF5572;}.btnCU{border-color: #3970CD;background-color: #96C5EF;color: #3970CD;}.btnCW{border-color: #A21C0F;background-color: #F51601;color: #A21C0F;}.btnCC{border-color: #C88517;background-color: #FBCD5A;color: #C88517;}.btnCS{border-color: #9C8F3F;background-color: #FDF797;color: #9C8F3F;}.btnCX{border-color: #0A9783;background-color: #10E7C9;color: #0A9783;}a .btnCA, a .btnCT, a .btnCU, a .btnCC, a .btnCS, a .btnCX{cursor: pointer;text-decoration: underline;}.btnEnabled, .btnCD{border-color: #015601;background-color: #BFFBBF;color: #015601;}.btnDisabled{border-color: #808080;background-color: #DBDADA;color: #808080;}.rowBtn{width: 40px;border-color: #08850A;background-color: #8EEB94;color: #08850A;}.rowBtn:hover, .btnEnabled:hover, .btnDisabled:hover{color: red;}td span.rowBtn:last-child{margin: 10px;}.rowSelected{background-color: yellow !important;}</style><style type="text/css">#LinxEditDialog, #LinxPasteDialog{position: fixed;width: 80%;min-width: 800px;border: 1px double #000000;left: 0;right: 0;margin: 50px auto;padding: 3px;background-color: #BABAB3;z-index: 100;}#LinxEdit{width: 100%;background-color: #FFFFBC;border: 1px solid black;padding: 5px 0 0 0;}#LinxEdit input, #LinxEdit textarea{width: 99%;}#LinxURLs{resize: vertical;}#RegexesTable{width: 100%;text-align: left;cursor: pointer;user-select: none;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;background-color: #E5DEAE;}#RegexesTable tr td:last-child{width: 140px;}.class2Columns tr td:first-child{display: none;}.class3Columns td:nth-child(1){width: 100px;}.class3Columns input:nth-child(1){width: auto;}.class3Columns tr td:nth-child(2){text-align: center;}code{background-color: #CDC6C6;}', iDocument);
            TSL.addStyle(null, '#LinxEditDialog, #LinxPasteDialog{position: fixed;width: 80%;min-width: 800px;border: 1px double #000000;left: 0;right: 0;margin: 50px auto;padding: 3px;background-color: #BABAB3;z-index: 100;}#LinxEdit{width: 100%;background-color: #FFFFBC;border: 1px solid black;padding: 5px 0 0 0;}#LinxEdit input, #LinxEdit textarea{width: 99%;}#LinxURLs{resize: vertical;}#RegexesTable{width: 100%;text-align: left;cursor: pointer;user-select: none;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;background-color: #E5DEAE;}#RegexesTable tr td:last-child{width: 140px;}.class2Columns tr td:first-child{display: none;}.class3Columns td:nth-child(1){width: 100px;}.class3Columns input:nth-child(1){width: auto;}.class3Columns tr td:nth-child(2){text-align: center;}code{background-color: #CDC6C6;}', iDocument);
            TSL.addStyle(null, '.descriptionBox {background-color:#EDF4FF; padding: 0 4px; margin: 0 0 2px 240px; }', iDocument);

            var dialog = TSL.createElementHTML('<div id="LinxMain"><div class="gradientBar"><img src="http://i.imgur.com/WznrrlJ.png?1" style="height: 24px; vertical-align: middle;" /><span>TimidScript - Linx Amender</span></div><div style="width: 100%; min-height: 200px; overflow-y: auto; background-color: #FFFFE8;"><table id="LinxTable" class="localRules"><tbody></tbody></table></div><div style="background-color: #F7EFC1; border-top: 1px black solid; padding: 0 5px; font-size: small;"><div id="ToggleUL" style="text-align: center; padding: 2px 0 3px 0; margin: 0; background-color: #FDFDA3; border-bottom: 1px black solid;"><b>Click to toggle guide</b></div><ul style="padding: 2px 20px; margin: 0; display: none;"><li>Rules are parsed first according to type and then table order. First (URL), then (Title), then (Script) and finally (Attribute & Click)</li><li>You can sort the rows by dragging them.</li><li>If you have some useful new <b>Linx Rules</b> or updates, please consider sharing them and make the "<b>Online Rules</b>" more comprehensive.&nbsp;Ctrl-Select the rules you want to share and press the "Copy Rules" button below and then paste the rule at the end of the&nbsp;<b>"Linx Rules"</b> thread found on&nbsp;<a href="https://github.com/TimidScript/GreasyMonkey/issues/1" target="_blank">GitHub</a>. If you do not have or wish to create a GitHub account then raise them on <a href="https://gf.qytechs.cn/forum/discussion/1209/linx-amender-rules-thread">Greasy Fork镜像</a>.<ul style="padding: 0 20px; margin: 0;"><li>Use descriptive names and if necessary fill out the rule description.</li><li>Title rules are not included in the online resource and should not be shared.</li><li>Third-party Script rules are most likely not going to be included as I would need to vouch for them</li><li>ID&quot;s of defunct rules should also be submitted.</li></ul></li><li>The copy button and the filter rule type buttons have alternative functionality when you press them with ctrl.</li><li>ctrl+m merges selected rules of the same type without removing the originals. Works only on Attribute, Title and URL rules.</li><li>F9 to bring up the this dialog</li></ul></div><div class="gradientBar"><span class="inputHolder"><button class="btnCD" title="Display Rule Description"><b>D</b></button><button class="btnCW" title="Current Website filter"><b>W</b></button><button class="btnCA" title="Attribute rule filter"><b>A</b></button><button class="btnCC" title="Click rule filter"><b>C</b></button><button class="btnCS" title="Script rule filter"><b>S</b></button><button class="btnCT" title="Title rule filter"><b>T</b></button><button class="btnCU" title="URL rule filter"><b>U</b></button><button class="btnCX" title="CSS rule filter"><b>X</b></button></span><input id="LinxSort" type="button" value="Sort Rules" /><input id="LinxSelectAll" type="button" value="Select All" /><input id="LinxSelectInverse" type="button" value="Inverse Selection" /><br /><span class="inputHolder"><input id="LinxNewRule" type="button" value="New Rule" /></span><input id="LinxOnlineRules" type="button" value="Online Rules" /><input id="LinxCopy" type="button" value="Copy Rules" /><input id="LinxPaste" type="button" value="Paste Rules" /><input id="LinxExit" type="button" value="Exit" style="margin-right: 5px; float: right; width: 100px;" /></div></div>', iDocument);
            iDocument.body.appendChild(dialog);

            var btns = iDocument.getElementsByClassName("gradientBar")[1].getElementsByTagName("input");
            for (var i = 0; i < btns.length; i++)
                btns[i].onclick = DialogMain.cbButtonClick;

            var btns = iDocument.getElementsByClassName("inputHolder")[0].getElementsByTagName("button");
            for (var i = 0; i < btns.length; i++)
                btns[i].onclick = DialogMain.cbFilter;

            DialogMain.populateTable();
            btns[1].click(); //Disable Website filter

            TSRegisterTable(iDocument.getElementById("LinxTable"),
                function (complete)
                {
                    if (complete) DialogMain.saveOrder();
                });

            DialogMain.onlineRulesUpdate(forceUpdate);

            DialogMain.cbWindowResized();
            window.onresize = DialogMain.cbWindowResized;

            iDocument.getElementById("ToggleUL").onclick = function (e)
            {
                this.nextElementSibling.style.display = (this.nextElementSibling.style.display) ? null : "none";
                DialogMain.cbWindowResized();
            };

            iDocument.onkeydown = DialogMain.cbKeyDown;
        }


        document.body.appendChild(iframe);
    },

    populateTable: function (sort)
    {
        var table = iDocument.getElementById("LinxTable");
        table.firstElementChild.innerHTML = "";

        var rules = GetOrderedRules();
        for (var i = 0; i < rules.length; i++)
        {
            if ((isOnline && rules[i].id[0] == "O") || (!isOnline && rules[i].id[0] == "L"))
                DialogMain.addRuleRow(rules[i], isOnline);
        }

        if (sort)
        {
            var rows = table.rows;

            for (var i = 0; i < rows.length - 1; i++)
            {
                for (var j = i + 1; j < rows.length; j++)
                {
                    if (rows[i].getAttribute("name").toLowerCase() > rows[j].getAttribute("name").toLowerCase())
                        table.firstElementChild.insertBefore(rows[j], rows[i]);
                }
            }

            DialogMain.saveOrder();
        }

        DialogMain.descriptionBoxDisplay();
        DialogMain.cbFilter();
    },

    addRuleRow: function (rule, online)
    {
        var row = iDocument.getElementById(name);
        if (!row)
        {
            row = iDocument.getElementById("LinxTable").insertRow(-1);
            row.id = rule.id;
        }

        name = (rule.name) ? rule.name : "";
        row.setAttribute("name", name);
        row.title = (rule.description) ? rule.description : name;

        var html = '<td>' + ((rule.timestamp) ? "<span style='float:right'>[" + rule.timestamp + "]</span>" : "") + '<button class="btnEnabled">Enabled</button>';

        //GM_setValue("AccessLevel", 2);
        if (online) html += '<button class="btnEnabled">Run First</button>' +
            ((GM_getValue("AccessLevel", 0) & 2)
            ? '<button class="rowBtn">Edit</button><button class="rowBtn">Copy</button><button class="rowBtn">Delete</button>'
            : '<button class="rowBtn">Copy</button>');
        else html += '<button class="rowBtn">Edit</button><button class="rowBtn">Copy</button><button class="rowBtn">Delete</button>';

        var url = (rule.sampleURL && rule.sampleURL.match(/^https?:\/\/[a-z0-9-\.]+\.[a-z]{2,4}\/?([^\s<>\#%"\,\{\}\\|\\\^\[\]`]+)?$/));

        var idC = ["btnCA", "btnCT", "btnCU", "btnCC", "btnCS", "btnCX"], idS = ["Attrib", "Title", "URL", "Click", "Script", "CSS"];

        html += ((url) ? '<a target="_blank" href="' + rule.sampleURL + '">' : '')
            + '<button class=' + idC[rule.type - 1] + ">" + idS[rule.type - 1];

        if (rule.description) rule.description = rule.description.replace("\n", "<br/>");

        html += '</button>' + ((url) ? "</a>" : "") + name
            + ((rule.description) ? '<div class="descriptionBox">' + rule.description + "<div>" : "")
            + '</td>';

        row.innerHTML = html;

        var btns = row.getElementsByTagName("button");
        btns[0].className = (rule.enabled) ? "btnEnabled" : "btnDisabled";
        if (online) btns[1].className = (rule.runFirst) ? "btnEnabled" : "btnDisabled";

        for (var i = 0; i < btns.length; i++)
            btns[i].onclick = DialogMain.cbRowButtonClick;

        row.onclick = DialogMain.cbRowClick;
    },

    hideControlBar: function (hide)
    {
        var btns = iDocument.getElementsByClassName("rowBtn");
        for (var i = 0; i < btns.length; i++) btns[i].disabled = (hide) ? "disabled" : null;

        var lm = iDocument.getElementById("LinxMain");
        var g = lm.getElementsByClassName("gradientBar")[1];

        g.style.visibility = (hide) ? "hidden" : null;
    },

    saveOrder: function ()
    {
        var rows = iDocument.getElementById("LinxTable").rows;

        var ids = new Array();
        for (var i = 0; i < rows.length; i++)
        {
            ids.push(rows[i].id);
        }

        GM_setValue(((isOnline) ? "RuleOrderO" : "RuleOrderL"), ids.join());
    },

    deselectRows: function (hiddenOnly)
    {
        var rows = iDocument.getElementById("LinxTable").rows;

        for (var i = 0; i < rows.length; i++)
        {
            if (!hiddenOnly || (hiddenOnly && rows[i].style.display == "none"))
                rows[i].className = rows[i].className.replace(/\s*rowSelected\s*/gi, "");
        }
    },

    descriptionBoxDisplay: function (hidden)
    {
        var name = (isOnline) ? "HideDescriptionO" : "HideDescriptionL";

        if (hidden === undefined)
        {
            hidden = GM_getValue(name, !isOnline);
            if (hidden) iDocument.getElementsByClassName("btnCD")[0].className = "btnCD btnDisabled";
            else iDocument.getElementsByClassName("btnCD")[0].className = "btnCD";
        }
        else GM_setValue(name, hidden);

        if (hidden) TSL.addStyle("dbHidden", ".descriptionBox { display: none; }", iDocument);
        else TSL.removeNode("dbHidden", iDocument);
    },

    cbWindowResized: function (e)
    {
        var lm = iDocument.getElementById("LinxMain");
        var total = lm.children[0].clientHeight + lm.children[3].clientHeight + lm.children[2].clientHeight;
        lm.children[1].style.height = (lm.clientHeight - total) + "px";
        lm.children[1].style.minHeight = lm.children[1].style.height + "px";
    },

    cbButtonClick: function (e)
    {
        switch (this.id)
        {
            case "LinxExit":
                TSL.removeNode("LinxFrame");
                break;
            case "LinxNewRule":
                DialogEdit.show();
                break;
            case "LinxSort":
                if (!confirm("Sorting will lose current order. Do you wish to continue?")) return;
                DialogMain.populateTable(true);
                break;
            case "LinxSelectAll":
                var rows = iDocument.getElementById("LinxTable").rows;

                for (var i = 0; i < rows.length; i++)
                {
                    if (rows[i].style.display != "none")
                        rows[i].className = rows[i].className.replace(/\s*rowSelected\s*/gi, "") + " rowSelected";
                }
                break;
            case "LinxSelectInverse":
                var rows = iDocument.getElementById("LinxTable").rows;

                for (var i = 0; i < rows.length; i++)
                {
                    if (rows[i].style.display != "none")
                        rows[i].className = (rows[i].className.match("rowSelected"))
                            ? rows[i].className.replace(/\s*rowSelected\s*/gi, "") : rows[i].className + " rowSelected";
                }
                break;
            case "LinxCopy":
                var rules = "";
                var rows = iDocument.getElementById("LinxTable").rows;
                var count = 0;
                for (var i = 0; i < rows.length; i++)
                {
                    if (!rows[i].className.match("rowSelected")) continue;

                    var rule = GM_getValue(rows[i].id, null);
                    if (!rule) continue;
                    rule = JSON.parse(rule);
                    delete rule.runFirst; //Remove the online attribute if it exists

                    //To place ID at the front.
                    var out = new Object();
                    out.id = rows[i].id;
                    if (e.ctrlKey && !isOnline)
                    {
                        rule.id = out.id.replace("L", "O");
                        delete rule.enabled;
                    }
                    else if (e.ctrlKey && isOnline) rule.id = out.id.replace("O", "L");

                    for (var key in rule) out[key] = rule[key];

                    rules += JSON.stringify(out) + "\r\n";
                    count++;
                }
                if (count == 0) return;
                rules = rules.replace(/\r\n$/, "");
                GM_setClipboard(rules);
                if (e.ctrlKey) alert("[" + count + "] Selected rules have been converted to online rules and copied into the clipboard.");
                else alert("[" + count + "] Selected rules have been copied into the clipboard.");

                break;
            case "LinxPaste":
                DialogPaste.show();
                break;
            case "LinxOnlineRules":
                var tb = iDocument.getElementById("LinxTable");

                if (this.style.color)
                {
                    isOnline = false;
                    this.style.color = null;

                    tb.className = "localRules";
                }
                else
                {
                    isOnline = true;
                    this.style.color = "#F0F";
                    tb.className = "onlineRules";
                }

                DialogMain.populateTable();
                break;
        }
    },

    cbFilter: function (e)
    {
        var btns = iDocument.getElementsByClassName("inputHolder")[0].getElementsByTagName("button");

        if (e && e.ctrlKey) for (var i = 2; i < btns.length; i++) btns[i].className = ((btns[i] == this) ? btns[i].className.replace(/\s*btnDisabled\s*/, "") : btns[i].className.replace(/(\s*btnDisabled\s*)?$/, " btnDisabled"));
        else if (e) this.className = (this.className.indexOf("btnDisabled") >= 0) ? this.className.replace(/\s*btnDisabled\s*/, "") : this.className + " btnDisabled";

        var flag = 0;
        for (var i = 0; i < btns.length; i++)
        {
            if (btns[i].className.indexOf("btnDisabled") < 0) flag += Math.pow(2, (i + 1));
        }

        var rules = GetSiteRules(true);
        var tb = iDocument.getElementById("LinxTable");

        DialogMain.descriptionBoxDisplay(!(flag & 2)); //Hide description box


        for (var i = 0; i < tb.rows.length; i++)
        {
            var row = tb.rows[i];

            var display = (flag & 4) ? "none" : null;

            if (flag & 4) //Website filter flag
            {
                for (var j = 0; j < rules.length; j++)
                    if (row.id == rules[j].id) display = null;
            }

            if (!(flag & 8) && row.getElementsByClassName("btnCA").length == 1) display = "none";
            if (!(flag & 16) && row.getElementsByClassName("btnCC").length == 1) display = "none";
            if (!(flag & 32) && row.getElementsByClassName("btnCS").length == 1) display = "none";
            if (!(flag & 64) && row.getElementsByClassName("btnCT").length == 1) display = "none";
            if (!(flag & 128) && row.getElementsByClassName("btnCU").length == 1) display = "none";
            if (!(flag & 256) && row.getElementsByClassName("btnCX").length == 1) display = "none";

            row.style.display = display;
        }

        DialogMain.deselectRows(true); //Deselect hidden rows
        DialogMain.cbWindowResized();
    },


    cbRowButtonClick: function (e)
    {
        e.stopPropagation();

        //Attrib, Title or URL button pressed
        if (this.className.indexOf("btnC") == 0) return true;

        var row = this.parentElement.parentElement;
        var rule = GM_getValue(row.id);

        if (!rule)
        {
            TSL.removeNode(row);
            return true;
        }

        rule = JSON.parse(rule);
        switch (this.textContent)
        {
            case "Edit":
                DialogEdit.show(rule, row.id);
                break;
            case "Copy":
                date = new Date();
                rule.name = ((rule.name) ? rule.name : "") + " [Copy-" + row.id + "]";
                if (isOnline)
                {
                    var id = row.id.replace("O", "L");

                    if (GM_getValue(id) && !confirm("Rule with same ID already exists; would you like to override it"))
                        id = id[0] + new Date().getTime();

                    DialogEdit.show(rule, id);
                }
                else DialogEdit.show(rule);
                break;
            case "Delete":
                //if (e.ctrlKey || confirm("Are you sure you wish to delete the rule \"" + rule.name + "\"   [" + row.id + "]"))
                if (confirm("Are you sure you wish to delete the rule \"" + rule.name + "\"   [" + row.id + "]"))
                {
                    TSL.removeNode(row, iDocument);
                    GM_deleteValue(row.id);
                }
                break;
            case "Run First":
                rule.runFirst = !(this.className == "btnEnabled");
                GM_setValue(row.id, JSON.stringify(rule));
                this.className = (rule.runFirst) ? "btnEnabled" : "btnDisabled";
                if (rule.type == 2) ParseNodes(true);
                break;
            default:
                rule.enabled = !(this.className == "btnEnabled");
                GM_setValue(row.id, JSON.stringify(rule));
                this.className = (rule.enabled) ? "btnEnabled" : "btnDisabled";
                if (rule.type == 2) ParseNodes(true);
                break;
        }

        return true;
    },

    /*
        Highlights row if clicked on.
    -------------------------------------------------------------------------*/
    cbRowClick: function (e)
    {
        if (!e.ctrlKey) DialogMain.deselectRows();

        if (e.ctrlKey && this.className.match(/\s*rowSelected\s*/)) this.className = this.className.replace(/\s*rowSelected\s*/, "");
        else this.className = this.className.replace(/rowSelected/, "") + " rowSelected";
    },

    cbKeyDown: function (e)
    {
        if (!e.ctrlKey || e.keyCode != 77 || iDocument.getElementById("LinxEditDialog") || iDocument.getElementById("LinxPasteDialog")) return;

        var rules = new Array();
        var rows = iDocument.getElementById("LinxTable").rows;

        for (var i = 0; i < rows.length; i++)
        {
            if (rows[i].className.indexOf("rowSelected") < 0) continue;

            var name = rows[i].id;
            var rule = GM_getValue(name);

            if (rule)
            {
                rule = JSON.parse(rule);
                if (rule.type > 3) continue;
                rule.id = name;
                rules.push(rule);
            }
        }

        if (rules.length == 0) return;
        var mergedRuleCount = 0;
        for (var i = 1; i < 4; i++)
        {
            var mergedRule = new Object();

            mergedRule.URLs = "";
            mergedRule.regexes = new Array();
            mergedRule.selectors = "";
            mergedRule.type = i;
            var count = 0;
            var ids = "";

            for (var j = 0; j < rules.length; j++)
            {
                var rule = rules[j];
                if (rule.type != mergedRule.type) continue;
                count++;
                ids += rule.id + " + ";

                if (!mergedRule.name) mergedRule.name = rule.name;
                if (!mergedRule.description) mergedRule.description = rule.description;
                if (rule.URLs) mergedRule.URLs += rule.URLs + "\n";
                if (rule.selectors) mergedRule.selectors += rule.selectors + " , ";
                if (!mergedRule.nthNode) mergedRule.nthNode = rule.nthNode;
                if (rule.regexes) mergedRule.regexes = mergedRule.regexes.concat(rule.regexes);
                if (!mergedRule.sampleURL) mergedRule.sampleURL = rule.sampleURL;
            }

            if (count > 1)
            {
                mergedRuleCount++;
                mergedRule.name = mergedRule.name.replace(/ \[Merged \d+ Rules\]$/i, "") + " [Merged " + count + " Rules]";
                mergedRule.selectors = mergedRule.selectors.replace(/" , "$/, "");

                DialogEdit.saveRuleCompact(("L" + new Date().getTime()), mergedRule);
                console.log("Merged rules ", ids.replace(/ \+ $/, ""));
            }
        }

        if (mergedRuleCount)
        {
            DialogMain.populateTable();
            alert(mergedRuleCount + " merged rule(s) created. Merging can cause unintended consequence, so check the created rules.");
        }
    },

    onlineRulesUpdate: function (forceUpdate)
    {
        //----------- Do Online Rules Update -----------//
        var date1 = GM_getValue("OnlineRulesTimestamp", 0);
        var date2 = new Date().getTime();

        var diff = (date2 - date1) / (1000 * 60 * 60 * 24);

        //Number of days before online checking again. 
        if (!forceUpdate && diff < 7) return;
        var url = GM_getValue("OnlineRulesURL", "https://github.com/TimidScript/GreasyMonkey/raw/master/data/LinxAmenderRules.txt");

        console.warn("Trying to download Linx Online Rules: " + url);
        GM_xmlhttpRequest({
            url: url,
            method: "GET",
            timeout: 5000,
            headers: { "User-agent": navigator.userAgent, "Accept": "text/html" },
            onload: function (response)
            {
                if (response.status == 200)
                {
                    console.log("Rules downloaded");
                    var rules = GetOrderedRules();

                    for (var i = 0; i < rules.length; i++)
                    {
                        if (rules[i].id[0] == "O")
                            GM_deleteValue(rules[i].id);
                    }

                    var strRules = response.responseText.replace(/("id":")L(\d+")/gi, "$1O$2"); //Change the rule type to Online if not already the case
                    strRules = strRules.replace(/,"enabled":true,/gi, ',"enabled":false,'); //Disable all rules                       
                    DialogPaste.importRules(strRules);

                    //Set the stored rule values for enabled and runFirst.
                    for (var i = 0; i < rules.length; i++)
                    {
                        if (rules[i].id[0] == "O" && (rules[i].enabled || rules[i].runFirst))
                        {
                            var rule = GM_getValue(rules[i].id);

                            if (!rule) continue; 
                            rule = JSON.parse(rule);

                            rule.enabled = rules[i].enabled;
                            rule.runFirst = rules[i].runFirst;

                            GM_setValue(rules[i].id, JSON.stringify(rule));
                        }
                    }


                    GM_setValue("OnlineRulesTimestamp", new Date().getTime());
                    console.info("Downloaded Online Rules");
                    iDocument.getElementsByClassName("gradientBar")[0].innerHTML += " <span style='color: yellow;'>(Online Rules Updated)<span>";
                    DialogMain.populateTable();
                }
                else { console.error("Unable to get online rules: ", url); console.warn(response); }
            },
            ontimeout: function (response) { console.error("Unable to get online rules due to timeout error. URL: " + url); console.warn(response);},
            onerror: function (response) { console.error("Error trying to get online rule. URL: " + url); console.warn(response); }
        });
        
    }
};

/*
==================================================================================  
 runFirstEnabled is only set when parsing through nodes. For listing in table you
 want the online rules to proceed the local rules.
==================================================================================*/
function GetOrderedRules(runFirstEnabled)
{
    var rules = new Array();

    var ruleOrder = GM_getValue("RuleOrderL", "");
    ruleOrder += "," + GM_getValue("RuleOrderO", "");


    var names = GM_listValues();
    for (var i = 0; i < names.length; i++)
    {
        var name = names[i];
        if (!name.match(/^[ol]\d+$/i)) continue;

        var rule = GM_getValue(name);
        if (!rule) continue;

        rule = JSON.parse(rule);
        rule.id = name;

        for (var j = 0; j < rules.length; j++) //Insert it in the correct place
        {
            if ((runFirstEnabled && rule.runFirst && !rules[j].runFirst) || //Online rule runs before offline rule               
               (ruleOrder.indexOf(rule.id) >= 0 && ruleOrder.indexOf(rule.id) < ruleOrder.indexOf(rules[j].id)))
            {
                rules.splice(j, 0, rule);
                rule = null;
                break;
            }
        }
        if (rule) rules.push(rule); //Hasn't been added so insert at the end
    }

    return rules;
}



function createRE(str, rule, regex, checkFlags)
{
    if (regex && regex.raised) return null;

    var re;
    try
    {
        var flags = "gi";
        if (checkFlags)
        {
            if (str.indexOf("@1") == 0) flags = "g";
            if (str.indexOf("@2") == 0) flags = "i";
            if (str.indexOf("@3") == 0) flags = "";

            if (flags != "gi") str = str.substr(2);
        }

        re = new RegExp(str, flags);
    }
    catch (err)
    {
        console.error(err);
        console.warn(rule.name + " [" + rule.id + "] : " + str);

        if (regex) regex.raised = true;
        return null;
    }

    return re;
}


/*
==================================================================================  
 Returns an ordered array containing all the enabled rules that are relevant to 
 the current site. Use forceEnable to enable it by force.
==================================================================================*/
function GetSiteRules(forceEnable)
{
    var rules = GetOrderedRules(true);
    var filterdRules = new Array();


    for (var i = 0; i < rules.length; i++)
    {
        var rule = rules[i];
        if (!forceEnable && (!rule.enabled || !rule.URLs)) continue;
        var URLs = rule.URLs.split("\n");
        var addRule = false;

        for (var j = 0; j < URLs.length; j++)
        {
            if (URLs[j].trim().length == 0) continue; //Skip it invalid URL 

            var str = URLs[j];

            var negate = (str[0] == "¬");
            var literal = (str.search(/^¬?\@/) >= 0);

            str = str.replace(/^¬?\@/, "");

            if (literal) str = TSL.escapeRegExp(str);


            var re = new createRE(str, rule);

            var found = (document.URL.search(re) >= 0);

            if (negate && found)
            {
                addRule = false;
                break; //Negate overrides all other positive search                        
            }
            else if (!negate && found)
            {
                addRule = true;
            }
        }

        if (addRule) filterdRules.push(rule);
    }

    return filterdRules;
}



var parsedNodes = new Array();
/* 
==================================================================================
   Parses through nodes and applies relevant rules.
==================================================================================*/
function ParseNodes(resetTitle)
{    
    /* Reparse title to take into account rule changes. For links you need to 
    refresh the page */
    if (resetTitle)
    {
        var title = document.head.getElementsByTagName("title")[0];
        if (title.hasAttribute("originalTitle"))
        {
            title.textContent = title.getAttribute("originalTitle");
            title.removeAttribute("amendedTitle");
        }
    }

    var rules = GetSiteRules();
    if (rules.length == 0) return;

    console.warn("Linx Amender Parsing Nodes");
    if (window === window.top)
    {
        amendURL(rules);
        if (document.readyState == "loading") return;
        appendCSS(rules);
        amendPageTitle(rules);        
    }
    else if (document.readyState == "loading") return;
    amendNodes(rules);



    /* Gets the rules regular expression parameter values
    ---------------------------------------------------------------------*/
    function getREParams(ruleRE)
    {
        var name = ((ruleRE.name) ? ruleRE.name : "").trim();
        var search = (ruleRE.search) ? ruleRE.search : "";
        var replace = (ruleRE.replace) ? ruleRE.replace : "";

        return { "name": name, "search": search, "replace": replace };
    }


    /* Apply rules to the document URL
    ---------------------------------------------------------------------*/
    function amendURL(rules)
    {
        var url = document.URL;
        for (var i = 0; i < rules.length ; i++)
        {
            var rule = rules[i];
            if (rule.type != 3) continue;

            for (var j = 0; j < rule.regexes.length; j++)
            {
                var reParms = getREParams(rule.regexes[j]);

                var re = createRE(reParms.search, rule, rule.regexes[i], true);
                if (re) url = url.replace(re, reParms.replace);
            }
        }

        if (url != document.URL) document.location = url;
    }

    /* Append CSS scripts to header
    ---------------------------------------------------------------------*/
    function appendCSS(rules)
    {
        if (document.head.appendedCSS) return;
        document.head.appendedCSS = true;
                
        for (var i = 0; i < rules.length ; i++)
        {            
            if (rules[i].type == 6) TSL.addStyle(null, rules[i].script);
        }
    }

    /* Apply rules to the document title
    ---------------------------------------------------------------------*/
    function amendPageTitle(rules)
    {
        var title = document.head.getElementsByTagName("title")[0];
        if (title && (title.getAttribute("amendedTitle") != title.textContent))
        {
            title.setAttribute("originalTitle", title.textContent);
            var newTitle = title.textContent;

            for (var i = 0; i < rules.length ; i++)
            {
                var rule = rules[i];
                if (rule.type != 2) continue;

                for (var j = 0; j < rule.regexes.length; j++)
                {
                    var reParms = getREParams(rule.regexes[j]);

                    var re = createRE(reParms.search, rule, rule.regexes[i], true);
                    if (re) newTitle = newTitle.replace(re, reParms.replace);
                }
            }

            title.setAttribute("amendedTitle", newTitle);
            title.textContent = newTitle;
        }
    }

    /* Apply rules to nodes (Attrib & Scripts)
    ---------------------------------------------------------------------*/
    function amendNodes(rules)
    {
        var addedNodes = new Array();
        for (var i = 0; i < rules.length ; i++)
        {
            var rule = rules[i];
            if (rule.type == 2 || rule.type == 3) continue; //Skip if Title or URL rule


            // ---- Script Rule
            if (rule.type == 5 && !document.getElementById(rule.id))
            {
                console.warn("Adding JS script: " + rule.name + " [" + rule.id + "]");

                //Apply script variables
                if (rule.scriptVariables)
                {
                    var vars = rule.scriptVariables.split(";");
                    for (var j = 0; j < vars.length; j++)
                    {
                        var v = vars[j].split("=");
                        if (v.length != 2) continue;

                        var s = "LA_get\\(['\"]" + TSL.escapeRegExp(v[0]) + "['\"]\\)";
                        var re = new RegExp(s);
                        rule.script = rule.script.replace(re, '"' + v[1] + '"');
                    }
                }

                var script = TSL.createElement("script", { "type": "text/javascript" });
                script.textContent = rule.script;
                script.id = rule.id;
                document.head.appendChild(script);
                continue;
            }

            var nodes = document.querySelectorAll(rule.selectors);
            //if (!nodes) continue;
            if (rule.nthNode != 0) nodes = [nodes[rule.nthNode - 1]]; //Shrink the array if only applying one node


            for (var n = 0; n < nodes.length; n++)
            {
                var node = nodes[n];
                if (!node) continue; //Can be an invalid node if the nthNode is out of range

                //Check if node has already been parsed, if so skip
                for (var j = 0; j < parsedNodes.length; j++)
                {
                    if (parsedNodes[j] == node) break;
                }
                if (parsedNodes[j] == node) continue;


                //Add the node to list so it can be added to the parseNodes at the end
                for (var j = 0; j < addedNodes.length; j++)
                {
                    if (addedNodes[j] == node) continue;
                }
                if (addedNodes[j] != node) addedNodes.push(node);

                if (rule.type == 4) node.click(); // ---- Click Rule
                else // ---- Attrib rule
                {
                    for (var j = 0; j < rule.regexes.length; j++)
                    {

                        var regex = rule.regexes[j];
                        var reParms = getREParams(regex);

                        if (regex.raised || reParms.name.length == 0 || (reParms.name[0] != "=" && reParms.name[0] != "+" && !node.hasAttribute(reParms.name.replace(/^-/, "")))) continue;


                        if (reParms.name[0] == "=")
                        {   //Operations done on node property 
                            var name = reParms.name.substr(1);

                            //if (node.hasOwnProperty(name))
                            if (!(name in node))
                            {
                                regex.raised = true;
                                console.error("Node object property \"" + name + "\" does not exist. Rule: " + rule.name + "[" + rule.id + "]");
                                continue;
                            }

                            var re = createRE(reParms.search, rule, regex, true);
                            if (!re) continue;

                            var v1 = node[name];
                            var v2 = v1.replace(re, reParms.replace);

                            if (v1 != v2) node[name] = v2;
                        }
                        else if (reParms.name[0] == "+") node.setAttribute(reParms.name.substr(1), reParms.replace);
                        else if (reParms.name[0] == "-") node.removeAttribute(reParms.name.substr(1));
                        else if (reParms.search[0] == "-")
                        {
                            var v1 = node.getAttribute(reParms.name);
                            var re = createRE(reParms.search.substr(1), rule, regex, true);
                            if (re && v1.match(re)) node.removeAttribute(reParms.name.substr(1));
                        }
                        else //Standard search and replace on attribute value
                        {
                            var re = createRE(reParms.search, rule, regex, true);
                            if (!re) continue;

                            var v1 = node.getAttribute(reParms.name);
                            var v2 = v1.replace(re, reParms.replace);
                            if (v1 != v2) node.setAttribute(reParms.name, v2);
                        }
                    }
                }
            }
        }

        parsedNodes = parsedNodes.concat(addedNodes);
    }
}


/*
=====================================================================
    MutationObserver
=====================================================================*/
var MO =
{
    busy: null,
    Observer: null,
    monitorChanges: function ()
    {
        if (!MO.Observer)
        {
            var mo = window.MutationObserver || window.MozMutationObserver || window.WebKitMutationObserver;
            if (mo) MO.Observer = new mo(MO.callback);
        }

        if (MO.Observer) MO.Observer.observe(document, { subtree: true, childList: true });
    },

    callback: function ()
    {
        MO.Observer.disconnect();

        //It crashes some pages on deviantArt if I do not do a delay.
        setTimeout(function ()
        {
            ParseNodes();
            MO.monitorChanges();
        }, 100);
    }
};


(function ()
{
    GM_registerMenuCommand("[TS] Linx Amender", DialogMain.show);

    ParseNodes();
    MO.monitorChanges();

    if (window === window.top)
        window.onkeypress = function (e) { if (e.keyCode == 120) DialogMain.show(e.altKey); };
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址