TagPro Userscript Library

Functions that any TagPro script could benefit from

当前为 2018-08-16 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/371240/620873/TagPro%20Userscript%20Library.js

  1. // ==UserScript==
  2. // @name TagPro Userscript Library
  3. // @description Functions that any TagPro script could benefit from
  4. // @author Ko </u/Wilcooo> (https://greasyfork.org/users/152992)
  5. // @version 2.1
  6. // @license MIT
  7. // @include *.koalabeast.com*
  8. // @include *.jukejuice.com*
  9. // @include *.newcompte.fr*
  10. // @icon https://raw.githubusercontent.com/wilcooo/TagPro-UserscriptLibrary/master/icon.png
  11. // @supportURL https://www.reddit.com/message/compose/?to=Wilcooo
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @grant GM_xmlhttpRequest
  15. // @connect koalabeast.com
  16. // @namespace https://greasyfork.org/users/152992
  17. // ==/UserScript==
  18.  
  19.  
  20.  
  21. // ==UserLibrary==
  22. // @name TagPro Userscript Library
  23. // @description Functions that any TagPro script could benefit from
  24. // @version 1.0
  25. // @license MIT
  26. // ==/UserLibrary==
  27.  
  28.  
  29. var version = 1.4;
  30. console.log('Loading TPUL (TagPro Userscript Library) version '+version);
  31.  
  32.  
  33.  
  34. ////////////////////////////////////////////////////////////////
  35. ////////////////////////////////////////////////////////////////
  36. ////////////////////////////////////////////////////////////////
  37.  
  38. // To use this library, include these 5 lines in your userscripts' metadata block:
  39.  
  40. // @require https://github.com/wilcooo/TagPro-UserscriptLibrary/raw/master/tpul.lib.js
  41. // @grant GM_setValue
  42. // @grant GM_getValue
  43. // @grant GM_xmlhttpRequest
  44. // @connect koalabeast.com
  45.  
  46. ////////////////////////////////////////////////////////////////
  47. ////////////////////////////////////////////////////////////////
  48. ////////////////////////////////////////////////////////////////
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60. var GM_configStruct = (function(){
  61.  
  62.  
  63. ////////////////////////////////////////////////////////////////
  64. // START OF ORIGINAL GM_CONFIG //
  65. ////////////////////////////////////////////////////////////////
  66.  
  67. /*
  68. Copyright 2009+, GM_config Contributors (https://github.com/sizzlemctwizzle/GM_config)
  69.  
  70. GM_config Contributors:
  71. Mike Medley <medleymind@gmail.com>
  72. Joe Simmons
  73. Izzy Soft
  74. Marti Martz
  75.  
  76. GM_config is distributed under the terms of the GNU Lesser General Public License.
  77.  
  78. GM_config is free software: you can redistribute it and/or modify
  79. it under the terms of the GNU Lesser General Public License as published by
  80. the Free Software Foundation, either version 3 of the License, or
  81. (at your option) any later version.
  82.  
  83. This program is distributed in the hope that it will be useful,
  84. but WITHOUT ANY WARRANTY; without even the implied warranty of
  85. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  86. GNU Lesser General Public License for more details.
  87.  
  88. You should have received a copy of the GNU Lesser General Public License
  89. along with this program. If not, see <http://www.gnu.org/licenses/>.
  90. */
  91.  
  92. function GM_configStruct(){if(arguments.length){GM_configInit(this,arguments);this.onInit()}}
  93. function GM_configInit(config,args){if(typeof config.fields=="undefined"){config.fields={};config.onInit=config.onInit||function(){};config.onOpen=config.onOpen||function(){};config.onSave=config.onSave||function(){};config.onClose=config.onClose||function(){};config.onReset=config.onReset||function(){};config.isOpen=false;config.title="User Script Settings";config.css={basic:["#GM_config * { font-family: arial,tahoma,myriad pro,sans-serif; }","#GM_config { background: #FFF; }","#GM_config input[type='radio'] { margin-right: 8px; }",
  94. "#GM_config .indent40 { margin-left: 40%; }","#GM_config .field_label { font-size: 12px; font-weight: bold; margin-right: 6px; }","#GM_config .radio_label { font-size: 12px; }","#GM_config .block { display: block; }","#GM_config .saveclose_buttons { margin: 16px 10px 10px; padding: 2px 12px; }","#GM_config .reset, #GM_config .reset a,"+" #GM_config_buttons_holder { color: #000; text-align: right; }","#GM_config .config_header { font-size: 20pt; margin: 0; }","#GM_config .config_desc, #GM_config .section_desc, #GM_config .reset { font-size: 9pt; }",
  95. "#GM_config .center { text-align: center; }","#GM_config .section_header_holder { margin-top: 8px; }","#GM_config .config_var { margin: 0 0 4px; }","#GM_config .section_header { background: #414141; border: 1px solid #000; color: #FFF;"," font-size: 13pt; margin: 0; }","#GM_config .section_desc { background: #EFEFEF; border: 1px solid #CCC; color: #575757;"+" font-size: 9pt; margin: 0 0 6px; }"].join("\n")+"\n",basicPrefix:"GM_config",stylish:""}}if(args.length==1&&typeof args[0].id=="string"&&typeof args[0].appendChild!=
  96. "function")var settings=args[0];else{var settings={};for(var i=0,l=args.length,arg;i<l;++i){arg=args[i];if(typeof arg.appendChild=="function"){settings.frame=arg;continue}switch(typeof arg){case "object":for(var j in arg){if(typeof arg[j]!="function"){settings.fields=arg;break}if(!settings.events)settings.events={};settings.events[j]=arg[j]}break;case "function":settings.events={onOpen:arg};break;case "string":if(/\w+\s*\{\s*\w+\s*:\s*\w+[\s|\S]*\}/.test(arg))settings.css=arg;else settings.title=
  97. arg;break}}}if(settings.id)config.id=settings.id;else if(typeof config.id=="undefined")config.id="GM_config";if(settings.title)config.title=settings.title;if(settings.css)config.css.stylish=settings.css;if(settings.frame)config.frame=settings.frame;if(settings.events){var events=settings.events;for(var e in events)config["on"+e.charAt(0).toUpperCase()+e.slice(1)]=events[e]}if(settings.fields){var stored=config.read(),fields=settings.fields,customTypes=settings.types||{},configId=config.id;for(var id in fields){var field=
  98. fields[id];if(field)config.fields[id]=new GM_configField(field,stored[id],id,customTypes[field.type],configId);else if(config.fields[id])delete config.fields[id]}}if(config.id!=config.css.basicPrefix){config.css.basic=config.css.basic.replace(new RegExp("#"+config.css.basicPrefix,"gm"),"#"+config.id);config.css.basicPrefix=config.id}}
  99. GM_configStruct.prototype={init:function(){GM_configInit(this,arguments);this.onInit()},open:function(){var match=document.getElementById(this.id);if(match&&(match.tagName=="IFRAME"||match.childNodes.length>0))return;var config=this;function buildConfigWin(body,head){var create=config.create,fields=config.fields,configId=config.id,bodyWrapper=create("div",{id:configId+"_wrapper"});head.appendChild(create("style",{type:"text/css",textContent:config.css.basic+config.css.stylish}));bodyWrapper.appendChild(create("div",
  100. {id:configId+"_header",className:"config_header block center"},config.title));var section=bodyWrapper,secNum=0;for(var id in fields){var field=fields[id],settings=field.settings;if(settings.section){section=bodyWrapper.appendChild(create("div",{className:"section_header_holder",id:configId+"_section_"+secNum}));if(Object.prototype.toString.call(settings.section)!=="[object Array]")settings.section=[settings.section];if(settings.section[0])section.appendChild(create("div",{className:"section_header center",
  101. id:configId+"_section_header_"+secNum},settings.section[0]));if(settings.section[1])section.appendChild(create("p",{className:"section_desc center",id:configId+"_section_desc_"+secNum},settings.section[1]));++secNum}section.appendChild(field.wrapper=field.toNode())}bodyWrapper.appendChild(create("div",{id:configId+"_buttons_holder"},create("button",{id:configId+"_saveBtn",textContent:"Save",title:"Save settings",className:"saveclose_buttons",onclick:function(){config.save()}}),create("button",{id:configId+
  102. "_closeBtn",textContent:"Close",title:"Close window",className:"saveclose_buttons",onclick:function(){config.close()}}),create("div",{className:"reset_holder block"},create("a",{id:configId+"_resetLink",textContent:"Reset to defaults",href:"#",title:"Reset fields to default values",className:"reset",onclick:function(e){e.preventDefault();config.reset()}}))));body.appendChild(bodyWrapper);config.center();window.addEventListener("resize",config.center,false);config.onOpen(config.frame.contentDocument||
  103. config.frame.ownerDocument,config.frame.contentWindow||window,config.frame);window.addEventListener("beforeunload",function(){config.close()},false);config.frame.style.display="block";config.isOpen=true}var defaultStyle="bottom: auto; border: 1px solid #000; display: none; height: 75%;"+" left: 0; margin: 0; max-height: 95%; max-width: 95%; opacity: 0;"+" overflow: auto; padding: 0; position: fixed; right: auto; top: 0;"+" width: 75%; z-index: 9999;";if(this.frame){this.frame.id=this.id;this.frame.setAttribute("style",
  104. defaultStyle);buildConfigWin(this.frame,this.frame.ownerDocument.getElementsByTagName("head")[0])}else{document.body.appendChild(this.frame=this.create("iframe",{id:this.id,style:defaultStyle}));this.frame.src="about:blank";this.frame.addEventListener("load",function(e){var frame=config.frame;var body=frame.contentDocument.getElementsByTagName("body")[0];body.id=config.id;buildConfigWin(body,frame.contentDocument.getElementsByTagName("head")[0])},false)}},save:function(){var forgotten=this.write();
  105. this.onSave(forgotten)},close:function(){if(this.frame.contentDocument){this.remove(this.frame);this.frame=null}else{this.frame.innerHTML="";this.frame.style.display="none"}var fields=this.fields;for(var id in fields){var field=fields[id];field.wrapper=null;field.node=null}this.onClose();this.isOpen=false},set:function(name,val){this.fields[name].value=val;if(this.fields[name].node)this.fields[name].reload()},get:function(name,getLive){var field=this.fields[name],fieldVal=null;if(getLive&&field.node)fieldVal=
  106. field.toValue();return fieldVal!=null?fieldVal:field.value},write:function(store,obj){if(!obj){var values={},forgotten={},fields=this.fields;for(var id in fields){var field=fields[id];var value=field.toValue();if(field.save)if(value!=null){values[id]=value;field.value=value}else values[id]=field.value;else forgotten[id]=value}}try{this.setValue(store||this.id,this.stringify(obj||values))}catch(e){this.log("GM_config failed to save settings!")}return forgotten},read:function(store){try{var rval=this.parser(this.getValue(store||
  107. this.id,"{}"))}catch(e){this.log("GM_config failed to read saved settings!");var rval={}}return rval},reset:function(){var fields=this.fields;for(var id in fields)fields[id].reset();this.onReset()},create:function(){switch(arguments.length){case 1:var A=document.createTextNode(arguments[0]);break;default:var A=document.createElement(arguments[0]),B=arguments[1];for(var b in B)if(b.indexOf("on")==0)A.addEventListener(b.substring(2),B[b],false);else if(",style,accesskey,id,name,src,href,which,for".indexOf(","+
  108. b.toLowerCase())!=-1)A.setAttribute(b,B[b]);else A[b]=B[b];if(typeof arguments[2]=="string")A.innerHTML=arguments[2];else for(var i=2,len=arguments.length;i<len;++i)A.appendChild(arguments[i])}return A},center:function(){var node=this.frame;if(!node)return;var style=node.style,beforeOpacity=style.opacity;if(style.display=="none")style.opacity="0";style.display="";style.top=Math.floor(window.innerHeight/2-node.offsetHeight/2)+"px";style.left=Math.floor(window.innerWidth/2-node.offsetWidth/2)+"px";
  109. style.opacity="1"},remove:function(el){if(el&&el.parentNode)el.parentNode.removeChild(el)}};
  110. (function(){var isGM=typeof GM_getValue!="undefined"&&typeof GM_getValue("a","b")!="undefined",setValue,getValue,stringify,parser;if(!isGM){setValue=function(name,value){return localStorage.setItem(name,value)};getValue=function(name,def){var s=localStorage.getItem(name);return s==null?def:s};stringify=JSON.stringify;parser=JSON.parse}else{setValue=GM_setValue;getValue=GM_getValue;stringify=typeof JSON=="undefined"?function(obj){return obj.toSource()}:JSON.stringify;parser=typeof JSON=="undefined"?
  111. function(jsonData){return(new Function("return "+jsonData+";"))()}:JSON.parse}GM_configStruct.prototype.isGM=isGM;GM_configStruct.prototype.setValue=setValue;GM_configStruct.prototype.getValue=getValue;GM_configStruct.prototype.stringify=stringify;GM_configStruct.prototype.parser=parser;GM_configStruct.prototype.log=window.console?console.log:isGM&&typeof GM_log!="undefined"?GM_log:window.opera?opera.postError:function(){}})();
  112. function GM_configDefaultValue(type,options){var value;if(type.indexOf("unsigned ")==0)type=type.substring(9);switch(type){case "radio":case "select":value=options[0];break;case "checkbox":value=false;break;case "int":case "integer":case "float":case "number":value=0;break;default:value=""}return value}
  113. function GM_configField(settings,stored,id,customType,configId){this.settings=settings;this.id=id;this.configId=configId;this.node=null;this.wrapper=null;this.save=typeof settings.save=="undefined"?true:settings.save;if(settings.type=="button")this.save=false;this["default"]=typeof settings["default"]=="undefined"?customType?customType["default"]:GM_configDefaultValue(settings.type,settings.options):settings["default"];this.value=typeof stored=="undefined"?this["default"]:stored;if(customType){this.toNode=
  114. customType.toNode;this.toValue=customType.toValue;this.reset=customType.reset}}
  115. GM_configField.prototype={create:GM_configStruct.prototype.create,toNode:function(){var field=this.settings,value=this.value,options=field.options,type=field.type,id=this.id,configId=this.configId,labelPos=field.labelPos,create=this.create;function addLabel(pos,labelEl,parentNode,beforeEl){if(!beforeEl)beforeEl=parentNode.firstChild;switch(pos){case "right":case "below":if(pos=="below")parentNode.appendChild(create("br",{}));parentNode.appendChild(labelEl);break;default:if(pos=="above")parentNode.insertBefore(create("br",
  116. {}),beforeEl);parentNode.insertBefore(labelEl,beforeEl)}}var retNode=create("div",{className:"config_var",id:configId+"_"+id+"_var",title:field.title||""}),firstProp;for(var i in field){firstProp=i;break}var label=field.label&&type!="button"?create("label",{id:configId+"_"+id+"_field_label","for":configId+"_field_"+id,className:"field_label"},field.label):null;switch(type){case "textarea":retNode.appendChild(this.node=create("textarea",{innerHTML:value,id:configId+"_field_"+id,className:"block",cols:field.cols?
  117. field.cols:20,rows:field.rows?field.rows:2}));break;case "radio":var wrap=create("div",{id:configId+"_field_"+id});this.node=wrap;for(var i=0,len=options.length;i<len;++i){var radLabel=create("label",{className:"radio_label"},options[i]);var rad=wrap.appendChild(create("input",{value:options[i],type:"radio",name:id,checked:options[i]==value}));var radLabelPos=labelPos&&(labelPos=="left"||labelPos=="right")?labelPos:firstProp=="options"?"left":"right";addLabel(radLabelPos,radLabel,wrap,rad)}retNode.appendChild(wrap);
  118. break;case "select":var wrap=create("select",{id:configId+"_field_"+id});this.node=wrap;for(var i=0,len=options.length;i<len;++i){var option=options[i];wrap.appendChild(create("option",{value:option,selected:option==value},option))}retNode.appendChild(wrap);break;default:var props={id:configId+"_field_"+id,type:type,value:type=="button"?field.label:value};switch(type){case "checkbox":props.checked=value;break;case "button":props.size=field.size?field.size:25;if(field.script)field.click=field.script;
  119. if(field.click)props.onclick=field.click;break;case "hidden":break;default:props.type="text";props.size=field.size?field.size:25}retNode.appendChild(this.node=create("input",props))}if(label){if(!labelPos)labelPos=firstProp=="label"||type=="radio"?"left":"right";addLabel(labelPos,label,retNode)}return retNode},toValue:function(){var node=this.node,field=this.settings,type=field.type,unsigned=false,rval=null;if(!node)return rval;if(type.indexOf("unsigned ")==0){type=type.substring(9);unsigned=true}switch(type){case "checkbox":rval=
  120. node.checked;break;case "select":rval=node[node.selectedIndex].value;break;case "radio":var radios=node.getElementsByTagName("input");for(var i=0,len=radios.length;i<len;++i)if(radios[i].checked)rval=radios[i].value;break;case "button":break;case "int":case "integer":case "float":case "number":var num=Number(node.value);var warn='Field labeled "'+field.label+'" expects a'+(unsigned?" positive ":"n ")+"integer value";if(isNaN(num)||type.substr(0,3)=="int"&&Math.ceil(num)!=Math.floor(num)||unsigned&&
  121. num<0){alert(warn+".");return null}if(!this._checkNumberRange(num,warn))return null;rval=num;break;default:rval=node.value;break}return rval},reset:function(){var node=this.node,field=this.settings,type=field.type;if(!node)return;switch(type){case "checkbox":node.checked=this["default"];break;case "select":for(var i=0,len=node.options.length;i<len;++i)if(node.options[i].textContent==this["default"])node.selectedIndex=i;break;case "radio":var radios=node.getElementsByTagName("input");for(var i=0,len=
  122. radios.length;i<len;++i)if(radios[i].value==this["default"])radios[i].checked=true;break;case "button":break;default:node.value=this["default"];break}},remove:function(el){GM_configStruct.prototype.remove(el||this.wrapper);this.wrapper=null;this.node=null},reload:function(){var wrapper=this.wrapper;if(wrapper){var fieldParent=wrapper.parentNode;fieldParent.insertBefore(this.wrapper=this.toNode(),wrapper);this.remove(wrapper)}},_checkNumberRange:function(num,warn){var field=this.settings;if(typeof field.min==
  123. "number"&&num<field.min){alert(warn+" greater than or equal to "+field.min+".");return null}if(typeof field.max=="number"&&num>field.max){alert(warn+" less than or equal to "+field.max+".");return null}return true}};var GM_config=new GM_configStruct;
  124.  
  125.  
  126. ////////////////////////////////////////////////////////////////
  127. // END OF ORIGINAL GM_CONFIG //
  128. ////////////////////////////////////////////////////////////////
  129.  
  130.  
  131.  
  132.  
  133. // I'm going to edit GM_config slightly.
  134. // Mostly to get rid of the 'alerts' when something is wrong.
  135. // (alerts pause the window, which causes you to disconnect from a game)
  136.  
  137.  
  138. // This function will return true when no errors were found.
  139.  
  140. GM_configStruct.prototype.valid = function() {
  141.  
  142.  
  143. for (var id in this.fields) {
  144.  
  145. var node = this.fields[id].node;
  146.  
  147. if (node.validity && !node.validity.valid) return false;
  148.  
  149. /*
  150. var field = this.fields[id],
  151. type = field.settings.type,
  152. unsigned = false;
  153.  
  154. if (type.indexOf('unsigned ') == 0) {
  155. type = type.substring(9);
  156. unsigned = true;
  157. }
  158.  
  159. if (['int','integer','float','number'].includes(type)) {
  160.  
  161. var num = Number(field.node.value);
  162.  
  163. var warn = 'Field labeled "' + field.label + '" expects a' +
  164. (unsigned ? ' positive ' : 'n ') + 'integer value';
  165.  
  166. if (isNaN(num) ||
  167. (type.substr(0, 3) == 'int' && Math.ceil(num) != Math.floor(num)) ||
  168. (unsigned && num < 0)) {
  169. // Add a few ways for scripters to know that there is an error
  170. field.error = true;
  171. field.wrapper.classList.add('error');
  172. correct = false;
  173. }
  174.  
  175. else if (typeof field.settings.min == "number" && num < field.settings.min) {
  176. // Add a few ways for scripters to know that there is an error
  177. field.error = true;
  178. field.wrapper.classList.add('error');
  179. correct = false;
  180. }
  181.  
  182. else if (typeof field.settings.max == "number" && num > field.settings.max) {
  183. // Add a few ways for scripters to know that there is an error
  184. field.error = true;
  185. field.wrapper.classList.add('error');
  186. correct = false;
  187. }
  188.  
  189. else {
  190. // Add a few ways for scripters to know that there is NO error
  191. field.error = false;
  192. field.wrapper.classList.remove('error');
  193. }
  194. }*/
  195. }
  196.  
  197. return true;
  198. }
  199.  
  200.  
  201. // Change the field prototype
  202.  
  203. var org_toNode = GM_configField.prototype.toNode;
  204.  
  205. GM_configField.prototype.toNode = function(){
  206.  
  207. var retNode = org_toNode.apply(this, ...arguments);
  208.  
  209. var unsigned = false,
  210. type = this.settings.type;
  211.  
  212. if (type.indexOf('unsigned ') == 0) {
  213. type = type.substring(9);
  214. unsigned = true;
  215. }
  216.  
  217. if (this.node.validity) {
  218. // Validity checks will work for ANY input, not only numbers.
  219. // For example, if you want a text field to have at least 3 characters,
  220. // manually set the 'minLength' tag to 3 and the rest will be done
  221. // automagically.
  222.  
  223. // Immediately show a validity report while typing / clicking
  224. this.node.addEventListener('input', this.node.reportValidity);
  225. this.node.addEventListener('click', this.node.reportValidity);
  226.  
  227. // The autocomplete covers the validity report (at least in Chrome)
  228. this.node.autocomplete = 'off';
  229. }
  230.  
  231. if (['int','integer','float','number'].includes(type)) {
  232.  
  233. // By default, GM_config makes most inputs a text field, even numbers.
  234. // Lets fix that, to be able to check min and max values better.
  235.  
  236. this.node.type = 'number';
  237.  
  238. if (this.settings.min) this.node.min = this.settings.min;
  239. if (this.settings.max) this.node.max = this.settings.max;
  240.  
  241. // unsigned means non-negative
  242. if (unsigned) this.node.min = Math.max(0,this.settings.min);
  243.  
  244. // integers are only whole numbers
  245. if (type.substr(0, 3) == 'int') this.node.step = 1;
  246. }
  247.  
  248. if (!['radio','select','checkbox','button','hidden'].includes(type)) {
  249. // Disable TagPro's controls when typing inside a field you can type in
  250. // You can call tpul.rollingChat.enable() to make the Arrow keys move your ball, even when typing text.
  251. this.node.addEventListener('focus', function(){tagpro.disableControls = true;});
  252. this.node.addEventListener('blur', function(){tagpro.disableControls = false;});
  253. }
  254.  
  255. return retNode;
  256. }
  257.  
  258.  
  259.  
  260. return GM_configStruct;
  261. })();
  262.  
  263.  
  264.  
  265.  
  266.  
  267.  
  268.  
  269.  
  270. var tpul = (function(){
  271.  
  272.  
  273.  
  274.  
  275. // =====STYLE SECTION=====
  276.  
  277.  
  278.  
  279. // Create our own stylesheet to define the styles in:
  280.  
  281. var style = document.getElementById('tpul-style') || document.createElement('style');
  282. document.head.appendChild(style);
  283. style.id = 'tpul-style';
  284.  
  285. // Remove all existing rules of any previous TPUL version.
  286.  
  287. var styleSheet = style.sheet;
  288. Array.from(styleSheet.cssRules).forEach(rule => styleSheet.deleteRule(rule))
  289.  
  290. // THE SETTINGS MENU BUTTONS
  291.  
  292. // Container for settings buttons
  293. styleSheet.insertRule(` #tpul-settings-menu {
  294. text-align: center;
  295. margin: 0 10%;
  296. }`);
  297.  
  298. // A settings button
  299. styleSheet.insertRule(` .tpul-settings-btn {
  300. position: relative;
  301. width: 64px;
  302. height: 64px;
  303. padding: 10px;
  304. margin: 20px;
  305. background-size: contain !important;
  306. background-origin: content-box !important;
  307. background-repeat: no-repeat !important;
  308. outline: none;
  309. }`);
  310.  
  311. // Blue line around button when focussed
  312. styleSheet.insertRule(` .tpul-settings-btn:focus::after {
  313. content: "";
  314. position: absolute;
  315. width: 100%;
  316. height: 100%;
  317. border: 2px solid Highlight;
  318. top: 0;
  319. left: 0;
  320. }`);
  321.  
  322. // Tooltip of button
  323. styleSheet.insertRule(` .tpul-settings-btn span {
  324. position: absolute;
  325. z-index: 1;
  326. border-radius: 10px;
  327. margin-top: 10px;
  328. padding: 10px;
  329. background: #0E8AE0;
  330. border: 1px solid #095C96;
  331. box-shadow: 0 3px #095C96;
  332. font-size: small;
  333. top: 100%;
  334. left: 50%;
  335. transform: translateX(-50%);
  336. width: max-content;
  337. min-width: 64px;
  338. max-width: 128px;
  339. overflow-wrap: break-word;
  340. word-wrap: break-word;
  341. pointer-events: none;
  342. opacity: 0;
  343. transition: opacity .3s;
  344. }`);
  345.  
  346.  
  347. // Arrow of tooltip
  348. styleSheet.insertRule(` .tpul-settings-btn span::after {
  349. content: "";
  350. position: absolute;
  351. left: 50%;
  352. bottom: 100%;
  353. margin-left: -20px;
  354. border-width: 20px;
  355. border-style: solid;
  356. border-color: transparent transparent #0E8AE0 transparent;
  357. }`);
  358.  
  359. // Show tooltip when hovering/focussing
  360. styleSheet.insertRule(`.tpul-settings-btn:hover span, .tpul-settings-btn:focus span {
  361. opacity: 1;
  362. }`);
  363.  
  364.  
  365.  
  366. // THE SETTINGS PANEL
  367.  
  368. // The frame (gray, spans full page)
  369. styleSheet.insertRule(` .tpul-settings-frame {
  370. position: fixed;
  371. z-index: 1;
  372. left: 0;
  373. top: 0;
  374. width: 100%;
  375. height: 100%;
  376. overflow: auto;
  377. background-color: rgba(0,0,0,0.4);
  378.  
  379. transition: opacity .5s;
  380. opacity: 0;
  381. pointer-events: none;
  382. }`);
  383.  
  384. // The frame when shown
  385. styleSheet.insertRule(`.tpul-settings-shown .tpul-settings-frame {
  386. opacity: 1;
  387. pointer-events: auto;
  388. }`);
  389.  
  390. // The settings window itself
  391. styleSheet.insertRule(` .tpul-settings-frame > div {
  392. width: 80%;
  393. max-width: 800px;
  394. margin: auto;
  395. margin-bottom: 10%;
  396.  
  397. position: relative;
  398. padding: 20px;
  399.  
  400. border: 1px solid #888;
  401. border-radius: 15px;
  402. background: #353535;
  403.  
  404. font-size: 16px;
  405.  
  406. top: 200%;
  407. transition: top .5s;
  408. }`);
  409.  
  410. styleSheet.insertRule(`.tpul-settings-shown .tpul-settings-frame > div { top: 120%; }`);
  411. // In a game we want to have an 80% gap to be able to keep playing.
  412. styleSheet.insertRule(`.tpul-settings-shown .tpul-settings-frame.in-game > div { top: 180%; }`);
  413.  
  414.  
  415. styleSheet.insertRule(`.tpul-settings-frame .config_header {
  416. font-size: 2em;
  417. font-weight: bold;
  418. }`);
  419.  
  420. styleSheet.insertRule(`.tpul-settings-frame .section_header {
  421. font-size: 1.5em;
  422. font-weight: bold;
  423. }`);
  424.  
  425. styleSheet.insertRule(`.tpul-settings-frame .config_var {
  426. }`);
  427.  
  428.  
  429. // ERRORS in fields:
  430. styleSheet.insertRule(`.tpul-settings-frame .config_var input:invalid {
  431. box-shadow: inset 0 0 10px rgba(255,0,0,1), 0 0 10px rgba(255, 0, 0, 1);
  432. }`);
  433.  
  434. /*styleSheet.insertRule(`.tpul-settings-frame .config_var.error:before {
  435. content: attr(data-min) ' - ' attr(data-max);
  436. display: block;
  437. text-align: right;
  438. margin: 5px 20px;
  439. color: #FFA9A2;
  440. font-style: italic;
  441. }`);*/
  442.  
  443. styleSheet.insertRule(`.tpul-settings-frame .field_label {
  444. font-weight: bold;
  445. }`);
  446. styleSheet.insertRule(`.tpul-settings-frame .form-control {
  447. background: #212121;
  448. border-color: #5f5f5f;
  449. }`);
  450. styleSheet.insertRule(`.tpul-settings-frame .form-control[type="checkbox"] {
  451. width: auto;
  452. }`);
  453. styleSheet.insertRule(`.tpul-settings-frame .btn-default {
  454. border-color: #888888;
  455. }`);
  456. styleSheet.insertRule(`.tpul-settings-frame textarea.form-control {
  457. resize: vertical;
  458. }`);
  459.  
  460. styleSheet.insertRule(`.tpul-settings-frame .btn-primary {
  461. margin-left: 10px;
  462. }`);
  463.  
  464.  
  465.  
  466. styleSheet.insertRule(`.tpul-settings-frame .tab-list {
  467. border-bottom-color: #888888;
  468. }`);
  469. styleSheet.insertRule(`.tpul-settings-frame .tab-list li {
  470. cursor: pointer;
  471. color: #8BC34A;
  472. font-size: 1.5em;
  473. }`);
  474. styleSheet.insertRule(`.tpul-settings-frame .tab-list li:hover {
  475. color: #689F38;
  476. }`);
  477. styleSheet.insertRule(`.tpul-settings-frame .tab-list li.active {
  478. border-color: #888888;
  479. border-bottom-color: transparent;
  480. background-color: #353535;
  481. }`);
  482.  
  483.  
  484.  
  485. // save/close/etc buttons
  486.  
  487. styleSheet.insertRule(`.tpul-settings-frame-buttons-holder {
  488. height: 0;
  489. text-align: right;
  490. }`);
  491.  
  492. styleSheet.insertRule(`.tpul-settings-frame-buttons-holder button {
  493. padding: 4px .5em;
  494. }`);
  495.  
  496.  
  497. /*
  498. //Bad design notice:
  499.  
  500. styleSheet.insertRule(` .tpul-settings-frame > div::after {
  501. content: "Sorry for the bad design, I'm working on it!";
  502. font-style: italic;
  503. color: gray;
  504. }`);
  505.  
  506. */
  507.  
  508. // Stop the body from scrolling when the settings panel is shown
  509. styleSheet.insertRule(`body.tpul-settings-shown {
  510. overflow:hidden !important;
  511. }`);
  512.  
  513.  
  514.  
  515.  
  516. // Notifications
  517. styleSheet.insertRule(` .tpul-notification-success {
  518. border-color: #8BC34A;
  519. background: #4C6D25;
  520. color: black;
  521. }`);
  522.  
  523. styleSheet.insertRule(` .tpul-notification-error {
  524. border-color: #BD0E0B;
  525. background: #6B2121;
  526. color: #FFA9A2;
  527. }`);
  528.  
  529. styleSheet.insertRule(` .tpul-notification-warning {
  530. border-color: Olive;
  531. background: DarkKhaki;
  532. color: black;
  533. }`);
  534.  
  535. styleSheet.insertRule(` .tpul-notification {
  536. position: fixed;
  537. bottom: 0px;
  538.  
  539. padding: 10px;
  540.  
  541. width: 100%;
  542.  
  543. text-align: center;
  544.  
  545. cursor: pointer;
  546. z-index: 2;
  547.  
  548. border-top: 1px solid #404040;
  549. background: #353535;
  550. color: #fff;
  551.  
  552. animation: slideUp 1s;
  553. transform: translateY(0);
  554. transition: transform 1s;
  555. }`);
  556.  
  557. styleSheet.insertRule(` .tpul-notification.vanish {
  558. transform: translateY(100%);
  559. }`);
  560.  
  561. styleSheet.insertRule(` @keyframes slideUp {
  562. 0% { transform: translateY(100%); }
  563. 100% { transform: translateY(0%); }
  564. }`);
  565.  
  566.  
  567.  
  568. // =====NOITCES ELYTS=====
  569.  
  570.  
  571.  
  572.  
  573.  
  574. // =====DOM SECTION=====
  575.  
  576.  
  577.  
  578. var SettingsMenu = document.getElementById('tpul-settings-menu') || document.createElement('div');
  579. SettingsMenu.id = 'tpul-settings-menu';
  580.  
  581. var SettingsFrame = document.getElementsByClassName('tpul-settings-frame')[0] || document.createElement('div');
  582. SettingsFrame.className = 'tpul-settings-frame';
  583. if(location.port) SettingsFrame.classList.add('in-game');
  584. document.body.appendChild(SettingsFrame);
  585.  
  586.  
  587. // =====NOITCES MOD=====
  588.  
  589.  
  590.  
  591.  
  592.  
  593. // =====LOGIC SECTION=====
  594.  
  595.  
  596.  
  597. var GM_storage = typeof GM_setValue === 'function' && typeof GM_getValue === 'function',
  598. all_settings = [],
  599. profileId = null,
  600. last_opened = null,
  601. rollingChatEnabled = false;
  602.  
  603. var tpul = {
  604. get version(){return version},
  605.  
  606. settings: {
  607. addSettings: function({id, title, fields, icon, tooltipText, buttonText}) {
  608.  
  609. var config = arguments[0];
  610.  
  611. if (config.allowLocal && !id && !GM_storage) throw "TPUL: A unique id is required, because localStorage will be used! By the way; it is better to @grant GM_getValue and GM_setValue and set 'allowLocal:false' to use private storage instead.";
  612.  
  613. if (!config.allowLocal && !GM_storage) throw "TPUL: Please @grant GM_setValue and GM_getValue in your userscripts metadata (recommended) or use 'allowLocal:true' (not recommended)";
  614.  
  615.  
  616. if (arguments.length != 1 || typeof config != 'object')
  617. throw Error("addSettings() takes one object as an argument! Example: addSettings( {id:'MySettings', title:'Hello World'} )");
  618.  
  619. // Create a new GM_config instance
  620. let settings = new GM_configStruct({
  621.  
  622. frame: SettingsFrame,
  623.  
  624. ...config,
  625.  
  626. id: String(config.id) || 'defaultId',
  627.  
  628. events: {
  629. ...(config.events||{}),
  630.  
  631. open: function(){
  632.  
  633. //Remove the default inline style of the GM_config frame
  634. this.frame.setAttribute('style', '');
  635.  
  636. //Apply some TagPro/Bootstrap styles
  637. SettingsFrame.firstChild.classList.add('form-horizontal');
  638. for (let el of SettingsFrame.getElementsByClassName('config_header')) el.classList.add('header-title');
  639. for (let el of SettingsFrame.getElementsByClassName('config_var')) el.classList.add('form-group');
  640. for (let el of SettingsFrame.getElementsByClassName('field_label')) {
  641. el.classList.add('col-xs-4');
  642. el.classList.add('control-label');
  643. }
  644. for (let el of SettingsFrame.getElementsByClassName('radio_label')) el.classList.add('radio');
  645. for (let el of [...SettingsFrame.getElementsByTagName('input'),
  646. ...SettingsFrame.getElementsByTagName('select'),
  647. ...SettingsFrame.getElementsByTagName('textarea')]) {
  648.  
  649. switch (el.type) {
  650. case 'radio':
  651. el.parentElement.classList.add('col-xs-8');
  652. el.parentElement.style.paddingLeft = '30px';
  653. el.nextElementSibling.prepend(el);
  654. continue;
  655. case 'button':
  656. el.classList.add('btn');
  657. el.classList.add('btn-default');
  658. break;
  659. default:
  660. el.classList.add('form-control');
  661.  
  662. }
  663.  
  664. var div = document.createElement('div');
  665. el.parentElement.appendChild(div);
  666. div.appendChild(el);
  667.  
  668. div.classList.add('col-xs-8');
  669. div.classList.add('pull-right');
  670. }
  671.  
  672. // The footer with the buttons:
  673.  
  674. var buttonsHolder = SettingsFrame.firstElementChild.lastElementChild;
  675. buttonsHolder.classList.add('col-sm-12');
  676. buttonsHolder.classList.add('tpul-settings-frame-buttons-holder');
  677.  
  678. // Place the "footer" on top
  679. buttonsHolder.parentElement.insertBefore(buttonsHolder, buttonsHolder.parentElement.firstElementChild);
  680.  
  681. for (var btn of [...buttonsHolder.getElementsByClassName('saveclose_buttons'),
  682. ...buttonsHolder.getElementsByClassName('reset')]) {
  683. btn.classList.add('btn');
  684. btn.classList.add('btn-primary');
  685. }
  686.  
  687. buttonsHolder.innerHTML = '';
  688.  
  689. for (var type of this.buttons || ['ok','cancel','reset']) {
  690. var button = document.createElement('button');
  691. button.className = 'btn btn-primary';
  692. button.settings = settings;
  693. buttonsHolder.appendChild(button);
  694.  
  695. switch(type.toLowerCase()) {
  696. case 'ok':
  697. button.onclick = function(){
  698. if(this.settings.valid()) {this.settings.save(); this.settings.close(); tpul.notify('Options saved!','success');}
  699. else {tpul.notify('Please fix any issues before saving', 'error');}
  700. };
  701. button.innerText = 'Ok';
  702. break;
  703. case 'cancel':
  704. button.onclick = function(){ this.settings.close(); tpul.notify('Options canceled','warning'); };
  705. button.innerText = 'Cancel';
  706. break;
  707. case 'reset':
  708. button.onclick = function(){ this.settings.reset(); tpul.notify('All options are reset to their defaults','');};
  709. button.innerText = 'Reset';
  710. break;
  711. case 'save':
  712. button.onclick = function(){
  713. if(this.settings.valid()) {this.settings.save(); tpul.notify('Options saved!','success');}
  714. else {tpul.notify('Please fix any issues before saving', 'error');}
  715. };
  716. button.innerText = 'Save';
  717. break;
  718. case 'close':
  719. button.onclick = function(){ this.settings.close(); tpul.notify('Options canceled','warning'); };
  720. button.innerText = 'Close';
  721. break;
  722. }
  723. }
  724.  
  725.  
  726. if (this.tabs) {
  727.  
  728. var tablist = document.createElement('ul');
  729. tablist.classList.add('tab-list');
  730. SettingsFrame.firstElementChild.insertBefore(tablist, SettingsFrame.firstElementChild.lastElementChild);
  731.  
  732. var tabcontent = document.createElement('div');
  733. tabcontent.classList.add('tab-content');
  734. SettingsFrame.firstElementChild.insertBefore(tabcontent, SettingsFrame.firstElementChild.lastElementChild);
  735.  
  736. for (let el of [...SettingsFrame.getElementsByClassName('section_header_holder')]) {
  737.  
  738. var header = el.getElementsByClassName('section_header')[0];
  739.  
  740. tablist.innerHTML += '<li data-target="#'+el.id+'">' + header.innerText;
  741.  
  742. tabcontent.appendChild(el);
  743. el.classList.add('tab-pane');
  744.  
  745. el.removeChild(header);
  746. }
  747.  
  748. tablist.firstElementChild.click();
  749.  
  750. } else {
  751. for (let el of SettingsFrame.getElementsByClassName('section_header')) el.classList.add('header-title');
  752. }
  753.  
  754. //Open the settings on our way (animated, blocking scroll of body etc.)
  755. this.frame.style.display = '';
  756. SettingsFrame.scrollTop = SettingsFrame.offsetHeight;
  757. document.body.classList.add('tpul-settings-shown');
  758.  
  759. last_opened = settings;
  760.  
  761. if (this.events && typeof this.events.open == "function")
  762. this.events.open.call(this,...arguments);
  763. },
  764.  
  765. close: function(){
  766. if(this.isOpen){}//TODO: Check whether unsaved?
  767.  
  768. SettingsFrame.removeEventListener('scroll', settings.scroll);
  769.  
  770. //close the settings in our way (animated)
  771. this.frame.style.display = '';
  772. document.body.classList.remove('tpul-settings-shown')
  773.  
  774. if (this.events && typeof this.events.close == "function")
  775. this.events.close.call(this,...arguments);
  776. },
  777. }
  778. });
  779.  
  780. // Remove all other default styles of GM_config
  781. delete settings.css.basic;
  782.  
  783. // Create a button using the function below
  784. var button = tpul.settings.addButton({
  785. onclick: ()=>settings.open(),
  786. icon: icon,
  787. tooltipText: tooltipText,
  788. buttonText: buttonText,
  789. });
  790.  
  791. settings.button = button;
  792.  
  793. for (let c in config) if(settings[c] === undefined) settings[c] = config[c];
  794.  
  795. all_settings.push(settings);
  796.  
  797. return settings;
  798.  
  799. },
  800. addButton: function({onclick, icon, tooltipText, buttonText}) {
  801.  
  802. if (!SettingsMenu) {
  803. console.error('TPUL: Could not find a place to add the settings button for '+name);
  804. return null;
  805. }
  806.  
  807. var button = document.createElement('button');
  808. button.className = 'btn tpul-settings-btn';
  809.  
  810. if (icon) {
  811. if (icon.search(/^url\((.*)\)$/) == -1) icon = 'url("'+icon+'")';
  812. button.style.backgroundImage = icon;
  813. button.innerHTML = '&nbsp;';
  814. } else button.innerText = buttonText || '?';
  815.  
  816. var tooltip = document.createElement('span');
  817. tooltip.innerText = tooltipText || "Configure this script's settings" ;
  818. button.appendChild(tooltip);
  819.  
  820. SettingsMenu.appendChild(button);
  821.  
  822. button.addEventListener('click',function(click){
  823. button.blur();
  824. onclick(click);
  825. });
  826.  
  827. return button;
  828. },
  829. get parent() {return SettingsMenu.parentElement},
  830. set parent(container) {
  831.  
  832. if (container) console.warn('You are repositioning the tpul settings menu. This will affect all settings buttons, not only for your script!');
  833.  
  834. container = container ||
  835. document.getElementById('tpul-settings-container') || // Try to add it to a position pre-defined by another script (such as ModFather)
  836. document.getElementById('userscript-top') || // Try to add it on top of any page on the server
  837. document.getElementById('options'); // Try to add it to the scoreboard in-game
  838.  
  839. if (container) {
  840. container.classList.remove('hidden');
  841. container.appendChild(SettingsMenu);
  842. } else console.error('Couldn\'t find a parent element.');
  843.  
  844. return container;
  845. },
  846. get menu(){ return SettingsMenu; },
  847. set menu(_){ throw "You can't change the TPUL settings menu object. You might mean to change the tpul.settings.parent"; },
  848. },
  849.  
  850. profile: {
  851. getId: function() {
  852.  
  853. if (!tpul_promises.getProfileId) {
  854. tpul_promises.getProfileId = new Promise(function(resolve,reject) {
  855.  
  856. GM_xmlhttpRequest({
  857. method: "GET",
  858. url: "http://"+document.location.hostname+"/",
  859. onload: function(){
  860. var match = this.responseText.match(/profile\/([0-9a-f]+)/i);
  861. if (match) {
  862. profileId = match[1];
  863. resolve(profileId);
  864. } else reject({error:"not logged in"});
  865. },
  866. onerror: ()=> reject({error:"request error", request:this}),
  867. });
  868. });
  869. }
  870.  
  871. return tpul_promises.getProfileId;
  872. },
  873.  
  874. getInfo: function() {
  875.  
  876. if (!tpul_promises.getProfileInfo) {
  877.  
  878. tpul_promises.getProfileInfo = new Promise(function(resolve,reject) {
  879.  
  880. tpul.profile.getId().then( function(id){
  881.  
  882. GM_xmlhttpRequest({
  883. method: "GET",
  884. url: "http://"+document.location.hostname+"/profiles/"+id,
  885. onload: function(r){
  886. // 'r' is the response that we get back from the TP server, lets do some error handling with it:
  887.  
  888. var arr;
  889. try{ arr = JSON.parse(r.response); }
  890. catch(e){ reject({error:"/profiles/ responded invalid JSON", request:this}); }
  891.  
  892. if(arr.error) reject(arr);
  893.  
  894. if(Array.isArray( arr ) && arr.length == 1) {
  895. resolve(arr[0]);
  896. }
  897. else reject({error:"unknown error", response:arr, request:this});
  898. },
  899. onerror: ()=> reject({error:"request error", request:this}),
  900. });
  901. });
  902.  
  903. tpul.profile.getId().catch( reject );
  904.  
  905. });
  906. }
  907.  
  908. return tpul_promises.getProfileInfo;
  909. },
  910.  
  911. getPage: function() {
  912.  
  913. if (!tpul_promises.getProfilePage) {
  914.  
  915. tpul_promises.getProfilePage = new Promise(function(resolve,reject) {
  916.  
  917. tpul.profile.getId().then( function(id){
  918.  
  919. GM_xmlhttpRequest({
  920. method: "GET",
  921. url: "http://"+document.location.hostname+"/profile/"+id,
  922. onload: function(r){
  923. // 'r' is the response that we get back from the TP server, lets do some error handling with it:
  924. if(r.response.error) reject(r.response);
  925.  
  926. var match,
  927. profile = {
  928. settings: {
  929. allChat: undefined,
  930. teamChat: undefined,
  931. groupChat: undefined,
  932. systemChat: undefined,
  933. tutorialChat: undefined,
  934.  
  935. names: undefined,
  936. degrees: undefined,
  937. matchState: undefined,
  938. performanceInfo: undefined,
  939. spectatorInfo: undefined,
  940.  
  941. stats: undefined,
  942. },
  943.  
  944. flair: [],
  945. };
  946.  
  947. // If the 'settings' div cannot be found, assume to not be logged in.
  948. if( !/<div(?: [^>]*)? id="settings"/i.test(this.responseText) ) return reject({error:"not logged in", request:this});
  949.  
  950. // Get the global settings
  951. // (ball spin, respawn warnings and video settings are NOT stored on the TP server,
  952. // only in a cookie on your device)
  953. for (var setting in profile.settings) {
  954. match = RegExp('<input(?: [^>]*)? id="' +setting+ '"(?: [^>]*)? (checked)?', 'i').exec(this.responseText);;
  955. if (match) {
  956. profile.settings[setting] = Boolean(match[1]);
  957. } else return reject({error:"unknown error", request:this});
  958. }
  959.  
  960. // Get the 'Custom Team Names' setting (the only non-boolean setting)
  961. /*
  962. <select id="teamNames" name="teamNames" class="form-control">
  963. <option value="always" >Always</option>
  964. <option value="spectating" >When Spectating</option>
  965. <option value="never" selected>Never</option>
  966. </select>
  967. */
  968.  
  969. var teamNamesOptions = /<select(?: [^>]*)? id="teamNames"(?: [^>]*)?>((?:\s*?.*?)*?)<\/select>/i.exec(this.responseText);
  970. if (teamNamesOptions) {
  971. var teamNamesOpt_rgx = /<option(?: [^>]*)? value="([^>]*)"(?: [^>]*)? (selected)?(?: [^>]*)?>/ig;
  972. while (match = teamNamesOpt_rgx.exec(teamNamesOptions[1]) ){
  973.  
  974. if (match[2]) {
  975. profile.settings.teamNames = match[1];
  976. break;
  977. }
  978. }
  979. } else return reject({error:"unknown error", request:this});
  980.  
  981. // Get both names
  982. for (var name of ['reservedName','displayedName']) {
  983. match = RegExp('<input(?: [^>]*)? id="' +name+ '"(?: [^>]*)? value="(.*?)"', 'i').exec(this.responseText);;
  984. if (match) {
  985. profile[name] = match[1];
  986. } else return reject({error:"unknown error", request:this});
  987. }
  988.  
  989. // Get your email
  990. match = /<span(?: [^>]*)? class="hidden-email"(?: [^>]*)?>[^<]*?\b([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})\b<\/span>/i.exec(this.responseText);;
  991. if (match) {
  992. profile.email = match[1];
  993. } else return reject({error:"unknown error", request:this});
  994.  
  995. // Get all flairs, and whether they are available, and which one is selected
  996. var flair_rgx = /<li class="(.*?)" data-flair="(.*?)">/ig;
  997. while (match = flair_rgx.exec(this.responseText)) {
  998. var i = profile.flair.push({
  999. id: match[2],
  1000. selected: match[1].includes('selected'),
  1001. available: match[1].includes('flair-available'),
  1002. });
  1003. if (profile.flair[i-1]) profile.selectedFlair = profile.flair[i-1];
  1004. }
  1005.  
  1006. // Remove duplicate flairs (because there are 3 tabs)
  1007. var flair_ids = [];
  1008. profile.flair = profile.flair.filter(flair => !flair_ids.includes(flair.id) && flair_ids.push(flair.id));
  1009.  
  1010. resolve(profile);
  1011.  
  1012. },
  1013.  
  1014. onerror: ()=> reject({error:"request error", request:this}),
  1015. });
  1016. });
  1017.  
  1018. tpul.profile.getId().catch( reject );
  1019. });
  1020. }
  1021.  
  1022. return tpul_promises.getProfilePage;
  1023. },
  1024.  
  1025. getRolling: function() {
  1026.  
  1027. if (!tpul_promises.getProfileRolling) {
  1028.  
  1029. tpul_promises.getProfileRolling = new Promise(function(resolve,reject) {
  1030.  
  1031. tpul.profile.getId().then( function(id){
  1032.  
  1033. GM_xmlhttpRequest({
  1034. method: "GET",
  1035. url: "http://"+document.location.hostname+"/profile_rolling/"+id,
  1036. onload: function(r){
  1037. // 'r' is the response that we get back from the TP server, lets do some error handling with it:
  1038. if(r.response.error) reject(r.response);
  1039. if(Array.isArray( r.response )) {
  1040. resolve(r.response);
  1041. }
  1042. else reject({error:"unknown error", request:this});
  1043. },
  1044. onerror: ()=> reject({error:"request error", request:this}),
  1045. });
  1046. });
  1047.  
  1048. tpul.profile.getId().catch( reject );
  1049.  
  1050. });
  1051. }
  1052.  
  1053. return tpul_promises.getProfileRolling;
  1054. },
  1055.  
  1056. getReservedName: function(fallbackTimeout=5e3) {
  1057.  
  1058. /*
  1059.  
  1060. Where to get the Reserved name from?
  1061.  
  1062. - in-game when auth
  1063. - getInfo /profiles/...
  1064. - getPage /profile/...
  1065.  
  1066. Logic:
  1067.  
  1068. 1. if getInfo was called before: use that
  1069. 2. if getPage was called before: use that
  1070. 3. if in-game and auth: get it that way
  1071. 4. call getInfo() to get the name
  1072.  
  1073. */
  1074.  
  1075. if (!tpul_promises.getReservedName) {
  1076.  
  1077. tpul_promises.getReservedName = new Promise(function(resolve,reject) {
  1078.  
  1079. // The fallback: get the reserved name using getInfo()
  1080. var fallback = function(){
  1081.  
  1082. done = true;
  1083.  
  1084. tpul.profile.getInfo().then(function(profileInfo) {
  1085.  
  1086. resolve(profileInfo.reservedName);
  1087. });
  1088.  
  1089. tpul.profile.getInfo().catch( reject );
  1090. };
  1091.  
  1092. if (tpul_promises.getProfileInfo) {
  1093.  
  1094. tpul_promises.getProfileInfo.then(function(profileInfo){
  1095. resolve(profileInfo.reservedName);
  1096. });
  1097.  
  1098. tpul_promises.getProfileInfo.catch( reject );
  1099.  
  1100. } else if (tpul_promises.getProfilePage) {
  1101.  
  1102. tpul_promises.getProfilePage.then(function(profilePage){
  1103. resolve(profilePage.reservedName);
  1104. });
  1105.  
  1106. tpul_promises.getProfilePage.catch( reject );
  1107.  
  1108. } else if (typeof tagpro != 'undefined' && tagpro.ready) {
  1109. tagpro.ready(function(){
  1110.  
  1111. if (tagpro.players) {
  1112. if (tagpro.players[tagpro.playerId]) {
  1113.  
  1114. if (tagpro.players[tagpro.playerId].auth) {
  1115. resolve (tagpro.players[tagpro.playerId].name);
  1116. } else fallback();
  1117.  
  1118. } else {
  1119.  
  1120. tagpro.socket.on('p',function(playerId) {
  1121. if (tagpro.players[tagpro.playerId]) {
  1122.  
  1123. if (tagpro.players[tagpro.playerId].auth) {
  1124. resolve (tagpro.players[tagpro.playerId].name);
  1125. } else fallback();
  1126.  
  1127. };
  1128. });
  1129.  
  1130. }
  1131. } else fallback();
  1132. });
  1133. } else fallback();
  1134.  
  1135. var done = false;
  1136. setTimeout(function(){
  1137. tpul_promises.getReservedName.then(()=>done=true);
  1138. });
  1139.  
  1140. setTimeout( function() { if (!done) fallback(); }, fallbackTimeout );
  1141.  
  1142. });
  1143. }
  1144.  
  1145. return tpul_promises.getReservedName;
  1146. },
  1147.  
  1148. getDisplayedName: function(fallbackTimeout = 5e3) {
  1149.  
  1150. /*
  1151.  
  1152. Where to get the Displayed name from?
  1153.  
  1154. - in-game
  1155. - getProfile /profile/...
  1156.  
  1157. Logic:
  1158.  
  1159. 1. if getPage was called before: use that
  1160. 2. if in-game: get it that way
  1161. 3. call getPage() to get the name
  1162.  
  1163. */
  1164.  
  1165. if (!tpul_promises.getDisplayedName) {
  1166.  
  1167. tpul_promises.getDisplayedName = new Promise(function(resolve,reject) {
  1168.  
  1169. // The fallback: get the displayed name using getPage()
  1170. var fallback = function(){
  1171.  
  1172. done = true;
  1173.  
  1174. tpul.profile.getPage().then(function(profilePage) {
  1175.  
  1176. resolve(profilePage.displayedName);
  1177. });
  1178.  
  1179. tpul.profile.getPage().catch( reject );
  1180. };
  1181.  
  1182. if (tpul_promises.getProfilePage) {
  1183.  
  1184. tpul_promises.getProfilePage.then(function(profilePage){
  1185. resolve(tpul_promises.getProfilePage.displayedName);
  1186. });
  1187.  
  1188. tpul_promises.getProfilePage.catch( reject );
  1189.  
  1190. } else if (typeof tagpro != 'undefined' && tagpro.ready) {
  1191. tagpro.ready(function(){
  1192.  
  1193. if (tagpro.players) {
  1194. if (tagpro.players[tagpro.playerId]) {
  1195.  
  1196. resolve (tagpro.players[tagpro.playerId].name);
  1197.  
  1198. } else {
  1199.  
  1200. tagpro.socket.on('p',function(playerId) {
  1201. if (tagpro.players[tagpro.playerId]) {
  1202.  
  1203. resolve (tagpro.players[tagpro.playerId].name);
  1204.  
  1205. };
  1206. });
  1207.  
  1208. }
  1209. } else fallback();
  1210. });
  1211. } else fallback();
  1212.  
  1213. var done = false;
  1214. setTimeout(function(){
  1215. tpul_promises.getDisplayedName.then(()=>done=true);
  1216. });
  1217.  
  1218. setTimeout( function() { if (!done) fallback(); }, fallbackTimeout );
  1219.  
  1220. });
  1221. }
  1222.  
  1223. return tpul_promises.getDisplayedName;
  1224.  
  1225. },
  1226.  
  1227. getSettings: function(fallbackTimeout = 5e3) {
  1228.  
  1229. /*
  1230.  
  1231. Where to get the settings from?
  1232.  
  1233. - in-game
  1234. - getPage /profile/...
  1235.  
  1236. Logic:
  1237.  
  1238. 1. if in-game: get it that way
  1239. 2. call getPage() to get the settings
  1240.  
  1241. */
  1242.  
  1243. var top_args = arguments;
  1244.  
  1245. if (!tpul_promises.getProfileSettings) {
  1246.  
  1247. tpul_promises.getProfileSettings = new Promise(function(resolve,reject) {
  1248.  
  1249. var fallback = function(){
  1250.  
  1251. done = true;
  1252.  
  1253. tpul.profile.getPage().then(function(profilePage){
  1254. resolve(profilePage.settings);
  1255. });
  1256.  
  1257. tpul.profile.getPage().catch( reject );
  1258. };
  1259.  
  1260. if (top_args[0] && top_args[0].__settings) {
  1261. resolve(top_args[0].__settings);
  1262. } else if (tpul_promises.getProfilePage) {
  1263.  
  1264. tpul_promises.getProfilePage.then(function(profilePage){
  1265. resolve(profilePage.settings);
  1266. });
  1267.  
  1268. tpul_promises.getProfilePage.catch( reject );
  1269.  
  1270. } else if (typeof tagpro != 'undefined' && tagpro.ready) {
  1271. tagpro.ready(function(){
  1272.  
  1273. if (tagpro.socket && tagpro.socket.on) {
  1274.  
  1275. tagpro.socket.on('settings', function(settings) {
  1276. resolve({...settings.ui, stats: settings.stats});
  1277. });
  1278.  
  1279. } else fallback();
  1280. });
  1281. } else fallback();
  1282.  
  1283. var done = false;
  1284. setTimeout(function(){
  1285. tpul_promises.getProfileSettings.then(()=>done=true);
  1286. });
  1287.  
  1288. setTimeout( function() { if (!done) fallback(); }, fallbackTimeout );
  1289.  
  1290. });
  1291.  
  1292. }
  1293.  
  1294. return tpul_promises.getProfileSettings;
  1295.  
  1296. },
  1297.  
  1298. setSettings: function(newSettings, persistent=true, immediately=false) {
  1299.  
  1300. if (immediately) console.warn("Most settings will NOT take effect immediately, I might add this functionality in the future. Only chat settings work at the moment.");
  1301.  
  1302. return new Promise(function(resolve, reject){
  1303.  
  1304. // Step 1: set any local (cookie) settings
  1305. // These don't have to be send to the server, easy!
  1306.  
  1307. if (persistent) {
  1308.  
  1309. for (let setting in newSettings) {
  1310. if (['sound',
  1311. 'music',
  1312. 'volume',
  1313.  
  1314. 'textures',
  1315.  
  1316. 'disableBallSpin',
  1317. 'tileRespawnWarnings',
  1318. 'disableTutorialChat', // This cookie seems to be unused
  1319. // Setting it anyway \(^.^)/
  1320.  
  1321. 'disableParticles',
  1322. 'forceCanvasRenderer',
  1323. 'disableViewportScaling',
  1324. ].includes(setting)) {
  1325.  
  1326. var expires = new Date(Date.now() + 31536e8).toUTCString(); // A century from now (same as TagPro uses)
  1327. document.cookie = setting + '=' + newSettings[setting] + '; expires='+expires+'; path=/; domain=.koalabeast.com';
  1328. }
  1329. }
  1330.  
  1331. // Step 2: send any server-sided settings to the server
  1332.  
  1333. if (['reservedName',
  1334. 'displayedName',
  1335.  
  1336. 'allChat',
  1337. 'teamChat',
  1338. 'groupChat',
  1339. 'systemChat',
  1340. 'tutorialChat',
  1341.  
  1342. 'names',
  1343. 'degrees',
  1344. 'matchState',
  1345. 'performanceInfo',
  1346. 'spectatorInfo',
  1347.  
  1348. 'teamNames',
  1349. 'stats',
  1350. ].some( s => s in newSettings ) ){
  1351.  
  1352. // Call these to let them run in parallel
  1353. tpul.profile.getSettings();
  1354. tpul.profile.getReservedName();
  1355. tpul.profile.getDisplayedName();
  1356.  
  1357. tpul.profile.getSettings().then( function(settings){
  1358. tpul.profile.getReservedName().then( function(reservedName){
  1359. tpul.profile.getDisplayedName().then( function(displayedName){
  1360.  
  1361. console.log(param({reservedName: reservedName, // Your reservedName
  1362. displayedName: displayedName, // Your displayedName
  1363. //...settings, // The current settings
  1364. //...newSettings}));
  1365. }));
  1366. var req = GM_xmlhttpRequest({
  1367. data: param({...settings, // The current settings
  1368. reservedName: reservedName, // Your reservedName
  1369. displayedName: displayedName, // Your displayedName
  1370. ...newSettings}), // Overwrite with the settings that you want to edit.
  1371. method: "POST",
  1372. headers: {"Content-Type": "application/x-www-form-urlencoded"},
  1373. url: "http://"+document.location.hostname+"/profile/update",
  1374. onload: function(r){
  1375. // 'r' is the response that we get back from the TP server, lets do some error handling with it:
  1376.  
  1377. var arr;
  1378. try{ arr = JSON.parse(r.response); }
  1379. catch(e){ reject({error:"/profile/update responded invalid JSON", request:this}); }
  1380.  
  1381. if(arr.error) reject(arr);
  1382. else if(arr.success) {
  1383. resolve(arr);
  1384. } else reject({error:'unknown error',response: arr, request:this});
  1385. },
  1386. onerror: reject,
  1387. });
  1388.  
  1389. });
  1390. });
  1391. });
  1392.  
  1393. }
  1394.  
  1395. }
  1396.  
  1397. // Step 3: In case we are in-game, let the settings go into effect immediately.
  1398. // To update the reserved name, a refresh is required. TPUL won't do this!
  1399.  
  1400. if (immediately && tagpro) {
  1401. if (!tagpro.settings) tagpro.settings = {ui:{}};
  1402. if (!tagpro.settings.ui) tagpro.settings.ui = {};
  1403.  
  1404. for (let setting in newSettings) {
  1405. if (['allChat',
  1406. 'teamChat',
  1407. 'groupChat',
  1408. 'systemChat',
  1409. 'tutorialChat',
  1410. ].includes(setting)){
  1411.  
  1412. tagpro.settings.ui[setting] = newSettings[setting];
  1413. }
  1414. }
  1415.  
  1416. if (setting == 'tutorialChat') {
  1417. var tutorialButton = document.getElementById('tutorialButton');
  1418.  
  1419. if (tutorialButton) {
  1420. var action = tutorialButton.innerText === "Enable Tips";
  1421. if (newSettings[setting] == action) tutorialButton.click();
  1422. }
  1423. }
  1424.  
  1425.  
  1426. }
  1427.  
  1428. });
  1429. }
  1430.  
  1431. },
  1432.  
  1433. rollingChat: {
  1434.  
  1435. _init: function initRollingChat(enable = false){
  1436.  
  1437. // In case you don't want to load the full TPUL library,
  1438. // You can add RollingChat to your own script by copying this function
  1439. // Usage:
  1440. // initRollingChat(true);
  1441.  
  1442. if (!tagpro.rollingChat) {
  1443.  
  1444. tagpro.rollingChat = {
  1445. enabled: false,
  1446. get handler() {
  1447. return function(event) {
  1448.  
  1449. // Return if not enabled
  1450. if (!tagpro.rollingChat.enabled) return;
  1451.  
  1452. // Whether you are releasing instead of pressing the key:
  1453. var releasing = event.type == 'keyup';
  1454.  
  1455. // Check if any modifier keys where held down during a keyDown
  1456. if (!releasing && (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey)) return;
  1457.  
  1458. // The key that is pressed/released (undefined when it is any other key)
  1459. var arrow = ['left','up','right','down'][[37,38,39,40].indexOf(event.keyCode)]
  1460.  
  1461. // Only if the controls are disabled (usually while composing a message)
  1462. // AND the key is indeed an arrow (not undefined)
  1463. if (tagpro.disableControls && arrow) {
  1464.  
  1465. // Prevent the 'default' thing to happen, which is the cursor moving through the message you are typing
  1466. event.preventDefault();
  1467.  
  1468. // Return if already pressed/released
  1469. if (tagpro.players[tagpro.playerId].pressing[arrow] != releasing) return;
  1470.  
  1471. // Send the key press/release to the server!
  1472. tagpro.sendKeyPress(arrow, releasing);
  1473.  
  1474. // Not necesarry, but useful for other scripts to 'hook onto'
  1475. if (!releasing && tagpro.events.keyDown) tagpro.events.keyDown.forEach(f => f.keyDown(arrow));
  1476. if (releasing && tagpro.events.keyUp) tagpro.events.keyUp.forEach(f => f.keyUp(arrow));
  1477. tagpro.ping.avg&&setTimeout(()=>(tagpro.players[tagpro.playerId][arrow]=!releasing),tagpro.ping.avg/2);
  1478. }
  1479. }
  1480. }
  1481. }
  1482.  
  1483. // intercept all key presses and releases:
  1484. document.addEventListener('keydown', tagpro.rollingChat.handler);
  1485. document.addEventListener('keyup', tagpro.rollingChat.handler);
  1486. }
  1487.  
  1488. if (enable) tagpro.rollingChat.enabled = true;
  1489. },
  1490.  
  1491. get enabled(){
  1492. tpul.rollingChat._init();
  1493. return tagpro.rollingChat.enabled;
  1494. },
  1495.  
  1496. set enabled(e){
  1497. tpul.rollingChat._init();
  1498. tagpro.rollingChat.enabled = Boolean(e);
  1499. },
  1500. },
  1501.  
  1502. notify: function(text, type="message", timeout=Math.max(4000, 50*text.length) ){
  1503.  
  1504. // Accepted types: message, success, error, warning
  1505. // ( white green red yellow )
  1506. // For more types, the only thing you need to add is some CSS
  1507.  
  1508. var notification = document.createElement('div');
  1509. notification.className = 'tpul-notification tpul-notification-' + type;
  1510. notification.innerText = text;
  1511. document.body.appendChild(notification);
  1512.  
  1513. // Hide after a while (timeout)
  1514. setTimeout(function(notification){
  1515. if(notification)notification.classList.add('vanish');
  1516. }, timeout, notification);
  1517.  
  1518. // Hide on click
  1519. notification.onclick = function(){ this.classList.add('vanish'); }
  1520.  
  1521. // Clear up the DOM once the notification is vanished
  1522. notification.addEventListener('transitionend',function(){ this.remove(); });
  1523.  
  1524. // Return the element, for scripters to "play" with
  1525. return notification;
  1526.  
  1527. }
  1528. };
  1529.  
  1530. if (!SettingsMenu.parentElement) tpul.settings.parent = null;
  1531.  
  1532.  
  1533.  
  1534.  
  1535.  
  1536. // OPENING AND CLOSING
  1537.  
  1538. SettingsFrame.onclick = function(click) {
  1539.  
  1540. // Close all settings when clicking outside the panel
  1541. if (SettingsFrame == click.target) for (var settings of all_settings) settings.close();
  1542.  
  1543. }
  1544.  
  1545. SettingsFrame.addEventListener('scroll', function(wheel) {
  1546.  
  1547. // Open when scrolling down (only in game)
  1548. if (location.port && wheel.deltaY > 0 && last_opened && !last_opened.isOpen) last_opened.open();
  1549.  
  1550. // Close all settings when scrolling up far enough
  1551. setTimeout(function(){
  1552. if (SettingsFrame.firstElementChild &&
  1553. SettingsFrame.scrollTop + SettingsFrame.offsetHeight <= SettingsFrame.firstElementChild.offsetTop + 20)
  1554. for (var settings of all_settings) settings.close()
  1555. },200);
  1556. });
  1557.  
  1558.  
  1559.  
  1560.  
  1561. // Section tabs
  1562.  
  1563. SettingsFrame.addEventListener('click', function(click) {
  1564. var tablist = click.target.parentElement;
  1565. if (tablist.classList.contains('tab-list')) {
  1566. for (let li of tablist.getElementsByTagName('li'))
  1567. li.classList.remove('active');
  1568. for (let pane of tablist.parentElement.getElementsByClassName('tab-pane'))
  1569. pane.classList.remove('active');
  1570. click.target.classList.add('active');
  1571. document.querySelector(click.target.dataset.target).classList.add('active');
  1572. }
  1573. });
  1574.  
  1575.  
  1576.  
  1577. // Get settings from socket:
  1578.  
  1579. if (tagpro && tagpro.ready) {
  1580. tagpro.ready(function(){
  1581. if (tagpro.socket && tagpro.socket.on) {
  1582. tagpro.socket.on('settings', function(settings) {
  1583. // Don't try to tamper with this, or copy this in your own script.
  1584. // It will affect all scripts using TPUL.
  1585. tpul.profile.getSettings( {__settings:{...settings.ui, stats: settings.stats}} );
  1586. });
  1587. }
  1588. });
  1589. }
  1590.  
  1591. // Some helper functions
  1592.  
  1593. function param(o){
  1594.  
  1595. return Object.keys(o).map(function(k) {
  1596. return encodeURIComponent(k) + '=' + encodeURIComponent(o[k.replace(' ','+')])
  1597. }).join('&').replace(/%20/g, '+');
  1598. }
  1599.  
  1600.  
  1601.  
  1602.  
  1603. // =====NOITCES CIGOL=====
  1604.  
  1605.  
  1606.  
  1607.  
  1608.  
  1609. if (typeof tpul_promises == 'undefined') {
  1610. try{
  1611. window.tpul_promises = {};
  1612. unsafeWindow.tpul_promises = window.tpul_promises;
  1613. }catch(e){}
  1614. }
  1615.  
  1616.  
  1617. return tpul;
  1618. })();
  1619.  
  1620.