// ==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 :"none";" 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 <a href="http://www.w3schools.com/jsref/jsref_obj_regexp.asp">regular expression</a>. 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"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"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. 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 <b>"Linx Rules"</b> thread found on <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"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); };
})();