- // ==UserScript==
- // @name TagPro Userscript Library
- // @description Functions that any TagPro script could benefit from
- // @author Ko </u/Wilcooo> (https://greasyfork.org/users/152992)
- // @version 4.6
- // @license MIT
- // @match *://*.koalabeast.com/*
- // @match *://*.jukejuice.com/*
- // @match *://*.newcompte.fr/*
- // @downloadURL https://raw.githubusercontent.com/wilcooo/TagPro-UserscriptLibrary/master/tpul.lib.js
- // @supportURL https://www.reddit.com/message/compose/?to=Wilcooo
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_xmlhttpRequest
- // @connect koalabeast.com
- // ==/UserScript==
-
-
-
- // ==UserLibrary==
- // @name TagPro Userscript Library
- // @description Functions that any TagPro script could benefit from
- // @version 4.6
- // @license MIT
- // ==/UserLibrary==
-
-
- var version = 4.6;
- console.log('Loading TPUL (TagPro Userscript Library) version '+version);
-
-
-
- ////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////
-
- // To use this library, include these 5 lines in your userscripts' metadata block:
-
- // @require https://greasyfork.org/scripts/371240/code/TagPro%20Userscript%20Library.js
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_xmlhttpRequest
- // @connect koalabeast.com
-
- ////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////
-
-
-
-
-
-
- /* TODO
-
- compatibility with SWJ (done I think)
-
- Option to change the layout of the settings
-
- Notify "options cancld" when scrolling away
-
- margin beneath buttons on scoreboard
-
- option to disable notifications.
-
- ESC cancels, option to Save when canceld (scroll away, esc)
-
- */
-
-
-
-
-
-
-
- var GM_configStruct = (function(){
-
-
- ////////////////////////////////////////////////////////////////
- // START OF ORIGINAL GM_CONFIG //
- ////////////////////////////////////////////////////////////////
-
- /*
- Copyright 2009+, GM_config Contributors (https://github.com/sizzlemctwizzle/GM_config)
-
- GM_config Contributors:
- Mike Medley <medleymind@gmail.com>
- Joe Simmons
- Izzy Soft
- Marti Martz
-
- GM_config is distributed under the terms of the GNU Lesser General Public License.
-
- GM_config is free software: you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
- function GM_configStruct(){if(arguments.length){GM_configInit(this,arguments);this.onInit()}}
- 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; }",
- "#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; }",
- "#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!=
- "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=
- 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=
- 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}}
- 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",
- {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",
- 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+
- "_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||
- 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",
- 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();
- 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=
- 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||
- 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(","+
- 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";
- style.opacity="1"},remove:function(el){if(el&&el.parentNode)el.parentNode.removeChild(el)}};
- (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"?
- 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(){}})();
- 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}
- 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=
- customType.toNode;this.toValue=customType.toValue;this.reset=customType.reset}}
- 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",
- {}),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?
- 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);
- 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;
- 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=
- 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&&
- 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=
- 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==
- "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;
-
-
- ////////////////////////////////////////////////////////////////
- // END OF ORIGINAL GM_CONFIG //
- ////////////////////////////////////////////////////////////////
-
-
-
-
- // I'm going to edit GM_config slightly.
- // Mostly to get rid of the 'alerts' when something is wrong.
- // (alerts pause the window, which causes you to disconnect from a game)
-
-
- // This function will return true when no errors were found.
-
- GM_configStruct.prototype.valid = function() {
-
-
- for (var id in this.fields) {
-
- var node = this.fields[id].node;
-
- if (node.validity && !node.validity.valid) return false;
-
- /*
- var field = this.fields[id],
- type = field.settings.type,
- unsigned = false;
-
- if (type.indexOf('unsigned ') == 0) {
- type = type.substring(9);
- unsigned = true;
- }
-
- if (['int','integer','float','number'].includes(type)) {
-
- var num = Number(field.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 && num < 0)) {
- // Add a few ways for scripters to know that there is an error
- field.error = true;
- field.wrapper.classList.add('error');
- correct = false;
- }
-
- else if (typeof field.settings.min == "number" && num < field.settings.min) {
- // Add a few ways for scripters to know that there is an error
- field.error = true;
- field.wrapper.classList.add('error');
- correct = false;
- }
-
- else if (typeof field.settings.max == "number" && num > field.settings.max) {
- // Add a few ways for scripters to know that there is an error
- field.error = true;
- field.wrapper.classList.add('error');
- correct = false;
- }
-
- else {
- // Add a few ways for scripters to know that there is NO error
- field.error = false;
- field.wrapper.classList.remove('error');
- }
- }*/
- }
-
- return true;
- };
-
-
- // Change the field prototype
-
- var org_toNode = GM_configField.prototype.toNode;
-
- GM_configField.prototype.toNode = function(){
-
- var retNode = org_toNode.apply(this, ...arguments);
-
- var unsigned = false,
- type = this.settings.type;
-
- if (type.indexOf('unsigned ') === 0) {
- type = type.substring(9);
- unsigned = true;
- }
-
- if (this.node.validity) {
- // Validity checks will work for ANY input, not only numbers.
- // For example, if you want a text field to have at least 3 characters,
- // manually set the 'minLength' tag to 3 and the rest will be done
- // automagically.
-
- // Immediately show a validity report while typing / clicking
- this.node.addEventListener('input', this.node.reportValidity);
- this.node.addEventListener('click', this.node.reportValidity);
-
- // The autocomplete covers the validity report (at least in Chrome)
- this.node.autocomplete = 'off';
- }
-
- if (['int','integer','float','number'].includes(type)) {
-
- // By default, GM_config makes most inputs a text field, even numbers.
- // Lets fix that, to be able to check min and max values better.
-
- this.node.type = 'number';
-
- if (this.settings.min) this.node.min = this.settings.min;
- if (this.settings.max) this.node.max = this.settings.max;
-
- // unsigned means non-negative
- if (unsigned) this.node.min = Math.max(0,this.settings.min);
-
- // integers are only whole numbers
- if (type.substr(0, 3) == 'int') this.node.step = 1;
- }
-
- if (!['radio','select','checkbox','button','hidden'].includes(type)) {
- // Disable TagPro's controls when typing inside a field you can type in
- // You can set tpul.rollingChat.enable = true to make the Arrow keys move your ball, even when typing text.
- this.node.addEventListener('focus', function(){typeof tagpro != undefined && (tagpro.disableControls = true)});
- this.node.addEventListener('blur', function(){typeof tagpro != undefined && (tagpro.disableControls = false)});
- }
-
- return retNode;
- };
-
-
-
- return GM_configStruct;
- })();
-
-
-
-
-
-
-
-
- var tpul = (function(){
-
-
-
-
- // =====STYLE SECTION=====
-
-
-
- // Create our own stylesheet to define the styles in:
-
- var style = document.getElementById('tpul-style') || document.createElement('style');
- document.head.appendChild(style);
- style.id = 'tpul-style';
-
- // Remove all existing rules of any previous TPUL version.
-
- var styleSheet = style.sheet;
- Array.from(styleSheet.cssRules).forEach(rule => styleSheet.deleteRule(rule));
-
- // THE SETTINGS MENU BUTTONS
-
- // Container for settings buttons
- styleSheet.insertRule(` #tpul-settings-menu {
- text-align: center;
- margin: 0 10%;
- }`);
-
- // A settings button
- styleSheet.insertRule(` .tpul-settings-btn {
- position: relative;
- width: 64px;
- height: 64px;
- padding: 10px;
- margin: 20px;
- background-size: contain !important;
- background-origin: content-box !important;
- background-repeat: no-repeat !important;
- outline: none;
- }`);
-
- // Blue line around button when focussed
- styleSheet.insertRule(` .tpul-settings-btn:focus::after {
- content: "";
- position: absolute;
- width: 100%;
- height: 100%;
- border: 2px solid Highlight;
- top: 0;
- left: 0;
- }`);
-
- // Tooltip of button
- styleSheet.insertRule(` .tpul-settings-btn span {
- position: absolute;
- z-index: 1;
- border-radius: 10px;
- margin-top: 10px;
- padding: 10px;
- background: #0E8AE0;
- border: 1px solid #095C96;
- box-shadow: 0 3px #095C96;
- font-size: small;
- top: 100%;
- left: 50%;
- transform: translateX(-50%);
- width: max-content;
- min-width: 64px;
- max-width: 128px;
- overflow-wrap: break-word;
- word-wrap: break-word;
- pointer-events: none;
- opacity: 0;
- transition: opacity .3s;
- }`);
-
-
- // Arrow of tooltip
- styleSheet.insertRule(` .tpul-settings-btn span::after {
- content: "";
- position: absolute;
- left: 50%;
- bottom: 100%;
- margin-left: -20px;
- border-width: 20px;
- border-style: solid;
- border-color: transparent transparent #0E8AE0 transparent;
- }`);
-
- // Show tooltip when hovering/focussing
- styleSheet.insertRule(`.tpul-settings-btn:hover span, .tpul-settings-btn:focus span {
- opacity: 1;
- }`);
-
-
-
- // THE SETTINGS PANEL
-
- // The frame (gray, spans full page)
- styleSheet.insertRule(` .tpul-settings-frame {
- position: fixed;
- z-index: 1;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- overflow: auto;
- background-color: rgba(0,0,0,0.4);
- scroll-behavior: smooth;
-
- transition: opacity .5s;
- opacity: 0;
- pointer-events: none;
- }`);
-
- // The frame when shown
- styleSheet.insertRule(`.tpul-settings-shown .tpul-settings-frame {
- opacity: 1;
- pointer-events: auto;
- }`);
-
- // The settings window itself
- styleSheet.insertRule(` .tpul-settings-frame > div {
- width: 80%;
- max-width: 800px;
- margin: auto;
- margin-bottom: 10%;
-
- position: relative;
- padding: 20px;
-
- border: 1px solid #888;
- border-radius: 15px;
- background: #353535;
-
- font-size: 16px;
-
- top: 200%;
- transition: top .5s;
- }`);
-
- styleSheet.insertRule(`.tpul-settings-shown .tpul-settings-frame > div { top: 120%; }`);
- // In a game we want to have an 80% gap to be able to keep playing.
- styleSheet.insertRule(`.tpul-settings-shown .tpul-settings-frame.in-game > div { top: 180%; }`);
-
-
- styleSheet.insertRule(`.tpul-settings-frame .config_header {
- font-size: 2em;
- font-weight: bold;
- }`);
-
- styleSheet.insertRule(`.tpul-settings-frame .section_header {
- font-size: 1.5em;
- font-weight: bold;
- }`);
-
- styleSheet.insertRule(`.tpul-settings-frame .config_var {
- }`);
-
-
- // ERRORS in fields:
- styleSheet.insertRule(`.tpul-settings-frame .config_var input:invalid {
- box-shadow: inset 0 0 10px rgba(255,0,0,1), 0 0 10px rgba(255, 0, 0, 1);
- }`);
-
- /*styleSheet.insertRule(`.tpul-settings-frame .config_var.error:before {
- content: attr(data-min) ' - ' attr(data-max);
- display: block;
- text-align: right;
- margin: 5px 20px;
- color: #FFA9A2;
- font-style: italic;
- }`);*/
-
- styleSheet.insertRule(`.tpul-settings-frame .field_label {
- font-weight: bold;
- }`);
- styleSheet.insertRule(`.tpul-settings-frame .form-control {
- background: #212121;
- border-color: #5f5f5f;
- }`);
- styleSheet.insertRule(`.tpul-settings-frame .form-control[type="checkbox"] {
- width: auto;
- }`);
- styleSheet.insertRule(`.tpul-settings-frame .btn-default {
- border-color: #888888;
- }`);
- styleSheet.insertRule(`.tpul-settings-frame textarea.form-control {
- resize: vertical;
- }`);
-
- styleSheet.insertRule(`.tpul-settings-frame .btn-primary {
- margin-left: 10px;
- }`);
-
-
-
- styleSheet.insertRule(`.tpul-settings-frame .tab-list {
- border-bottom-color: #888888;
- }`);
- styleSheet.insertRule(`.tpul-settings-frame .tab-list li {
- cursor: pointer;
- color: #8BC34A;
- font-size: 1.5em;
- }`);
- styleSheet.insertRule(`.tpul-settings-frame .tab-list li:hover {
- color: #689F38;
- }`);
- styleSheet.insertRule(`.tpul-settings-frame .tab-list li.active {
- border-color: #888888;
- border-bottom-color: transparent;
- background-color: #353535;
- }`);
-
-
-
- // save/close/etc buttons
-
- styleSheet.insertRule(`.tpul-settings-frame-buttons-holder {
- height: 0;
- text-align: right;
- }`);
-
- styleSheet.insertRule(`.tpul-settings-frame-buttons-holder button {
- padding: 4px .5em;
- }`);
-
-
- styleSheet.insertRule(`@keyframes bounce {
- 0%, 20% {transform: translate(-50px,50%)scale(.06)}
- 10% {transform: translate(-50px,55%)scale(.06)}
- }`);
-
- styleSheet.insertRule(`.tpul-settings-scroll-down-arrow {
- position: fixed;
- width: 80%;
- max-width: 800px;
- left: 50%;
- transform: translate(-50px,50%)scale(.06);
- bottom: 30px;
- z-index: 1;
- transition: opacity .5s;
- animation-name: bounce;
- animation-delay: 1s;
- animation-duration: 3s;
- animation-iteration-count: infinite;
- animation-direction: alternate;
- transition: opacity .5s;
- cursor: pointer;
- }`);
-
- styleSheet.insertRule(`
- @media screen and (max-width: 1000) {
- .tpul-settings-scroll-down-arrow {
- right: calc(10% + 30px);
- width: 5px;
- }
- }`);
-
-
- /*
-
- //Bad design notice:
-
- styleSheet.insertRule(` .tpul-settings-frame > div::after {
- content: "Sorry for the bad design, I'm working on it!";
- font-style: italic;
- color: gray;
- }`);
-
- */
-
- // Stop the body from scrolling when the settings panel is shown
- styleSheet.insertRule(`body.tpul-settings-shown {
- overflow:hidden !important;
- }`);
-
-
-
-
- // Notifications
- styleSheet.insertRule(` .tpul-notification-success {
- border-color: #8BC34A;
- background: #4C6D25;
- color: black;
- }`);
-
- styleSheet.insertRule(` .tpul-notification-error {
- border-color: #BD0E0B;
- background: #6B2121;
- color: #FFA9A2;
- }`);
-
- styleSheet.insertRule(` .tpul-notification-warning {
- border-color: Olive;
- background: DarkKhaki;
- color: black;
- }`);
-
- styleSheet.insertRule(` .tpul-notification {
- position: fixed;
- bottom: 0px;
-
- padding: 10px;
-
- width: 100%;
-
- text-align: center;
-
- cursor: pointer;
- z-index: 2;
-
- border-top: 1px solid #404040;
- background: #353535;
- color: #fff;
-
- animation: slideUp 1s;
- transform: translateY(0);
- transition: transform 1s;
- }`);
-
- styleSheet.insertRule(` .tpul-notification.vanish {
- transform: translateY(100%);
- }`);
-
- styleSheet.insertRule(` @keyframes slideUp {
- 0% { transform: translateY(100%); }
- 100% { transform: translateY(0%); }
- }`);
-
-
-
- // =====NOITCES ELYTS=====
-
-
-
-
-
- // =====LOGIC SECTION=====
-
-
-
- var GM_storage = typeof GM_setValue === 'function' && typeof GM_getValue === 'function',
- all_settings = [],
- profileId = null,
- last_opened = null,
- rollingChatEnabled = false;
-
-
- // THE TPUL OBJECT!!
-
- var tpul = {
- get version(){return version},
-
- settings: {
- addSettings: function({id, title, fields, icon, tooltipText, buttonText}) {
-
- var config = arguments[0];
-
- 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.";
-
- 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)";
-
-
- if (arguments.length != 1 || typeof config != 'object')
- throw Error("addSettings() takes one object as an argument! Example: addSettings( {id:'MySettings', title:'Hello World'} )");
-
- // Create a new GM_config instance
- let settings = new GM_configStruct({
-
- frame: SettingsFrame,
-
- ...config,
-
- id: String(config.id) || 'defaultId',
-
- events: {
- ...(config.events||{}),
-
- open: function(){
-
- //Remove the default inline style of the GM_config frame
- this.frame.setAttribute('style', '');
-
- //Apply some TagPro/Bootstrap styles
- SettingsFrame.firstChild.classList.add('form-horizontal');
- for (let el of SettingsFrame.getElementsByClassName('config_header')) el.classList.add('header-title');
- for (let el of SettingsFrame.getElementsByClassName('config_var')) el.classList.add('form-group');
- for (let el of SettingsFrame.getElementsByClassName('field_label')) {
- el.classList.add('col-xs-4');
- el.classList.add('control-label');
- }
- for (let el of SettingsFrame.getElementsByClassName('radio_label')) el.classList.add('radio');
- for (let el of [...SettingsFrame.getElementsByTagName('input'),
- ...SettingsFrame.getElementsByTagName('select'),
- ...SettingsFrame.getElementsByTagName('textarea')]) {
-
- switch (el.type) {
- case 'radio':
- el.parentElement.classList.add('col-xs-8');
- el.parentElement.style.paddingLeft = '30px';
- el.nextElementSibling.prepend(el);
- continue;
- case 'button':
- el.classList.add('btn');
- el.classList.add('btn-default');
- break;
- default:
- el.classList.add('form-control');
-
- }
-
- var div = document.createElement('div');
- el.parentElement.appendChild(div);
- div.appendChild(el);
-
- div.classList.add('col-xs-8');
- div.classList.add('pull-right');
- }
-
- // The footer with the buttons:
-
- var buttonsHolder = SettingsFrame.firstElementChild.lastElementChild;
- buttonsHolder.classList.add('col-sm-12');
- buttonsHolder.classList.add('tpul-settings-frame-buttons-holder');
-
- // Place the "footer" on top
- buttonsHolder.parentElement.insertBefore(buttonsHolder, buttonsHolder.parentElement.firstElementChild);
-
- for (var btn of [...buttonsHolder.getElementsByClassName('saveclose_buttons'),
- ...buttonsHolder.getElementsByClassName('reset')]) {
- btn.classList.add('btn');
- btn.classList.add('btn-primary');
- }
-
- buttonsHolder.innerHTML = '';
-
- for (var type of this.buttons || ['ok','cancel','reset']) {
- var button = document.createElement('button');
- button.className = 'btn btn-primary';
- button.settings = settings;
- buttonsHolder.appendChild(button);
-
- switch(type.toLowerCase()) {
- case 'ok':
- button.onclick = function(){
- if(this.settings.valid()) {this.settings.save(); this.settings.close(); tpul.notify('Options saved!','success');}
- else {tpul.notify('Please fix any issues before saving', 'error');}
- };
- button.innerText = 'Ok';
- break;
- case 'cancel':
- button.onclick = function(){ this.settings.close(); tpul.notify('Options canceled','warning'); };
- button.innerText = 'Cancel';
- break;
- case 'reset':
- button.onclick = function(){ this.settings.reset(); tpul.notify('All options are reset to their defaults','');};
- button.innerText = 'Reset';
- break;
- case 'save':
- button.onclick = function(){
- if(this.settings.valid()) {this.settings.save(); tpul.notify('Options saved!','success');}
- else {tpul.notify('Please fix any issues before saving', 'error');}
- };
- button.innerText = 'Save';
- break;
- case 'close':
- button.onclick = function(){ this.settings.close(); tpul.notify('Options canceled','warning'); };
- button.innerText = 'Close';
- break;
- }
- }
-
-
- if (this.tabs) {
-
- var tablist = document.createElement('ul');
- tablist.classList.add('tab-list');
- SettingsFrame.firstElementChild.insertBefore(tablist, SettingsFrame.firstElementChild.lastElementChild);
-
- var tabcontent = document.createElement('div');
- tabcontent.classList.add('tab-content');
- SettingsFrame.firstElementChild.insertBefore(tabcontent, SettingsFrame.firstElementChild.lastElementChild);
-
- for (let el of [...SettingsFrame.getElementsByClassName('section_header_holder')]) {
-
- var header = el.getElementsByClassName('section_header')[0];
-
- tablist.innerHTML += '<li data-target="#'+el.id+'">' + header.innerText;
-
- tabcontent.appendChild(el);
- el.classList.add('tab-pane');
-
- el.removeChild(header);
- }
-
- tablist.firstElementChild.click();
-
- } else {
- for (let el of SettingsFrame.getElementsByClassName('section_header')) el.classList.add('header-title');
- }
-
- //Open the settings on our way (animated, blocking scroll of body etc.)
- this.frame.style.display = '';
- SettingsFrame.scrollTop = SettingsFrame.offsetHeight;
- document.body.classList.add('tpul-settings-shown');
-
- // Add an arrow, indicating the user to scroll down for more settings
- var arrow = document.createElement('img');
- arrow.classList.add("tpul-settings-scroll-down-arrow");
- arrow.src = "https://raw.githubusercontent.com/wilcooo/TagPro-UserscriptLibrary/master/arrow.png";
- this.frame.appendChild(arrow);
- arrow.onclick = function(){
- arrow.style.opacity = 0;
- SettingsFrame.scrollTo(0,SettingsFrame.scrollHeight)
- }
-
- last_opened = settings;
-
- // If the userscript adds an 'open' event as well, run it as well
- if (this.events && typeof this.events.open == "function")
- this.events.open.call(this,...arguments);
- },
-
- close: function(){
- if(this.isOpen){}//TODO: Check whether unsaved?
-
- //close the settings in our way (animated)
- this.frame.style.display = '';
- document.body.classList.remove('tpul-settings-shown');
-
- if (this.events && typeof this.events.close == "function")
- this.events.close.call(this,...arguments);
- },
- }
- });
-
- // Remove all other default styles of GM_config
- delete settings.css.basic;
-
- // Create a button using the function below
- var button = tpul.settings.addButton({
- onclick: ()=>settings.open(),
- icon: icon,
- tooltipText: tooltipText,
- buttonText: buttonText,
- });
-
- settings.button = button;
-
- for (let c in config) if(settings[c] === undefined) settings[c] = config[c];
-
- all_settings.push(settings);
-
- return settings;
-
- },
- addButton: function({onclick, icon, tooltipText, buttonText}) {
-
- if (!SettingsMenu) {
- console.error('TPUL: Could not find a place to add the settings button for '+name);
- return null;
- }
-
- var button = document.createElement('button');
- button.className = 'btn tpul-settings-btn';
-
- if (icon) {
- if (icon.search(/^url\((.*)\)$/) == -1) icon = 'url("'+icon+'")';
- button.style.backgroundImage = icon;
- button.innerHTML = ' ';
- } else button.innerText = buttonText || '?';
-
- var tooltip = document.createElement('span');
- tooltip.innerText = tooltipText || "Configure this script's settings" ;
- button.appendChild(tooltip);
-
- SettingsMenu.appendChild(button);
-
- button.addEventListener('click',function(click){
- button.blur();
- onclick(click);
- });
-
- return button;
- },
- get parent() {return SettingsMenu.parentElement;},
- set parent(container) {
-
- if (container) console.warn('You are repositioning the tpul settings menu. This will affect all settings buttons, not only for your script!');
-
- container = container ||
- document.getElementById('tpul-settings-container') || // Try to add it to a position pre-defined by another script (such as ModFather)
- document.getElementById('userscript-top') || // Try to add it on top of any page on the server
- document.getElementById('options'); // Try to add it to the scoreboard in-game
-
- if (container) {
- container.classList.remove('hidden');
- container.appendChild(SettingsMenu);
- } else console.error('Couldn\'t find a parent element.');
-
- return container;
- },
- get menu(){ return SettingsMenu; },
- set menu(_){ throw "You can't change the TPUL settings menu object. You might mean to change the tpul.settings.parent"; },
- },
-
- profile: {
- getId: function() {
-
- if (!tpul_promises.getProfileId) {
- tpul_promises.getProfileId = new Promise(function(resolve,reject) {
-
- GM_xmlhttpRequest({
- method: "GET",
- url: "http://"+document.location.hostname+"/",
- onload: function(){
- var match = this.responseText.match(/profile\/([0-9a-f]+)/i);
- if (match) {
- profileId = match[1];
- resolve(profileId);
- } else reject({error:"not logged in"});
- },
- onerror: ()=> reject({error:"request error", request:this}),
- });
- });
- }
-
- return tpul_promises.getProfileId;
- },
-
- getInfo: function() {
-
- if (!tpul_promises.getProfileInfo) {
-
- tpul_promises.getProfileInfo = new Promise(function(resolve,reject) {
-
- tpul.profile.getId().then( function(id){
-
- GM_xmlhttpRequest({
- method: "GET",
- url: "http://"+document.location.hostname+"/profiles/"+id,
- onload: function(r){
- // 'r' is the response that we get back from the TP server, lets do some error handling with it:
-
- var arr;
- try{ arr = JSON.parse(r.response); }
- catch(e){ reject({error:"/profiles/ responded invalid JSON", request:this}); }
-
- if(arr.error) reject(arr);
-
- if(Array.isArray( arr ) && arr.length == 1) {
- resolve(arr[0]);
- }
- else reject({error:"unknown error", response:arr, request:this});
- },
- onerror: ()=> reject({error:"request error", request:this}),
- });
- });
-
- tpul.profile.getId().catch( reject );
-
- });
- }
-
- return tpul_promises.getProfileInfo;
- },
-
- getPage: function() {
-
- if (!tpul_promises.getProfilePage) {
-
- tpul_promises.getProfilePage = new Promise(function(resolve,reject) {
-
- tpul.profile.getId().then( function(id){
-
- GM_xmlhttpRequest({
- method: "GET",
- url: "http://"+document.location.hostname+"/profile/"+id,
- onload: function(r){
- // 'r' is the response that we get back from the TP server, lets do some error handling with it:
- if(r.response.error) reject(r.response);
-
- var match,
- profile = {
- settings: {
- allChat: undefined,
- teamChat: undefined,
- groupChat: undefined,
- systemChat: undefined,
- tutorialChat: undefined,
-
- names: undefined,
- degrees: undefined,
- matchState: undefined,
- performanceInfo: undefined,
- spectatorInfo: undefined,
-
- stats: undefined,
- },
-
- flair: [],
- };
-
- // If the 'settings' div cannot be found, assume to not be logged in.
- if( !/<div(?: [^>]*)? id="settings"/i.test(this.responseText) ) return reject({error:"not logged in", request:this});
-
- // Get the global settings
- // (ball spin, respawn warnings and video settings are NOT stored on the TP server,
- // only in a cookie on your device)
- for (var setting in profile.settings) {
- match = RegExp('<input(?: [^>]*)? id="' +setting+ '"(?: [^>]*)? (checked)?', 'i').exec(this.responseText);
- if (match) {
- profile.settings[setting] = Boolean(match[1]);
- } else return reject({error:"unknown error", request:this});
- }
-
- // Get the 'Custom Team Names' setting (the only non-boolean setting)
- /*
- <select id="teamNames" name="teamNames" class="form-control">
- <option value="always" >Always</option>
- <option value="spectating" >When Spectating</option>
- <option value="never" selected>Never</option>
- </select>
- */
-
- var teamNamesOptions = /<select(?: [^>]*)? id="teamNames"(?: [^>]*)?>((?:\s*?.*?)*?)<\/select>/i.exec(this.responseText);
- if (teamNamesOptions) {
- var teamNamesOpt_rgx = /<option(?: [^>]*)? value="([^>]*)"(?: [^>]*)? (selected)?(?: [^>]*)?>/ig;
- while ( (match = teamNamesOpt_rgx.exec(teamNamesOptions[1])) ){
-
- if (match[2]) {
- profile.settings.teamNames = match[1];
- break;
- }
- }
- } else return reject({error:"unknown error", request:this});
-
- // Get both names
- for (var name of ['reservedName','displayedName']) {
- match = RegExp('<input(?: [^>]*)? id="' +name+ '"(?: [^>]*)? value="(.*?)"', 'i').exec(this.responseText);
- if (match) {
- profile[name] = match[1];
- } else return reject({error:"unknown error", request:this});
- }
-
- // Get your email
- match = /<span(?: [^>]*)? class="hidden-email"(?: [^>]*)?>[^<]*?\b([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})\b<\/span>/i.exec(this.responseText);
- if (match) {
- profile.email = match[1];
- } else return reject({error:"unknown error", request:this});
-
- // Get all flairs, and whether they are available, and which one is selected
- var flair_rgx = /<li class="(.*?)" data-flair="(.*?)">/ig;
- while ( (match = flair_rgx.exec(this.responseText)) ) {
- var i = profile.flair.push({
- id: match[2],
- selected: match[1].includes('selected'),
- available: match[1].includes('flair-available'),
- });
- if (profile.flair[i-1]) profile.selectedFlair = profile.flair[i-1];
- }
-
- // Remove duplicate flairs (because there are 3 tabs)
- var flair_ids = [];
- profile.flair = profile.flair.filter(flair => !flair_ids.includes(flair.id) && flair_ids.push(flair.id));
-
- resolve(profile);
-
- },
-
- onerror: ()=> reject({error:"request error", request:this}),
- });
- });
-
- tpul.profile.getId().catch( reject );
- });
- }
-
- return tpul_promises.getProfilePage;
- },
-
- getRolling: function() {
-
- if (!tpul_promises.getProfileRolling) {
-
- tpul_promises.getProfileRolling = new Promise(function(resolve,reject) {
-
- tpul.profile.getId().then( function(id){
-
- GM_xmlhttpRequest({
- method: "GET",
- url: "http://"+document.location.hostname+"/profile_rolling/"+id,
- onload: function(r){
- // 'r' is the response that we get back from the TP server, lets do some error handling with it:
- if(r.response.error) reject(r.response);
- if(Array.isArray( r.response )) {
- resolve(r.response);
- }
- else reject({error:"unknown error", request:this});
- },
- onerror: ()=> reject({error:"request error", request:this}),
- });
- });
-
- tpul.profile.getId().catch( reject );
-
- });
- }
-
- return tpul_promises.getProfileRolling;
- },
-
- getReservedName: function(fallbackTimeout=5e3) {
-
- /*
-
- Where to get the Reserved name from?
-
- - in-game when auth
- - getInfo /profiles/...
- - getPage /profile/...
-
- Logic:
-
- 1. if getInfo was called before: use that
- 2. if getPage was called before: use that
- 3. if in-game and auth: get it that way
- 4. call getInfo() to get the name
-
- */
-
- if (!tpul_promises.getReservedName) {
-
- tpul_promises.getReservedName = new Promise(function(resolve,reject) {
-
- // The fallback: get the reserved name using getInfo()
- var fallback = function(){
-
- done = true;
-
- tpul.profile.getInfo().then(function(profileInfo) {
-
- resolve(profileInfo.reservedName);
- });
-
- tpul.profile.getInfo().catch( reject );
- };
-
- if (tpul_promises.getProfileInfo) {
-
- tpul_promises.getProfileInfo.then(function(profileInfo){
- resolve(profileInfo.reservedName);
- });
-
- tpul_promises.getProfileInfo.catch( reject );
-
- } else if (tpul_promises.getProfilePage) {
-
- tpul_promises.getProfilePage.then(function(profilePage){
- resolve(profilePage.reservedName);
- });
-
- tpul_promises.getProfilePage.catch( reject );
-
- } else if (typeof tagpro != 'undefined' && tagpro.ready) {
- tagpro.ready(function(){
-
- if (tagpro.players) {
- if (tagpro.players[tagpro.playerId]) {
-
- if (tagpro.players[tagpro.playerId].auth) {
- resolve (tagpro.players[tagpro.playerId].name);
- } else fallback();
-
- } else {
-
- tagpro.socket.on('p',function(playerId) {
- if (tagpro.players[tagpro.playerId]) {
-
- if (tagpro.players[tagpro.playerId].auth) {
- resolve (tagpro.players[tagpro.playerId].name);
- } else fallback();
-
- }
- });
-
- }
- } else fallback();
- });
- } else fallback();
-
- var done = false;
- setTimeout(function(){
- tpul_promises.getReservedName.then(()=>done=true);
- });
-
- setTimeout( function() { if (!done) fallback(); }, fallbackTimeout );
-
- });
- }
-
- return tpul_promises.getReservedName;
- },
-
- getDisplayedName: function(fallbackTimeout = 5e3) {
-
- /*
-
- Where to get the Displayed name from?
-
- - in-game
- - getProfile /profile/...
-
- Logic:
-
- 1. if getPage was called before: use that
- 2. if in-game: get it that way
- 3. call getPage() to get the name
-
- */
-
- if (!tpul_promises.getDisplayedName) {
-
- tpul_promises.getDisplayedName = new Promise(function(resolve,reject) {
-
- // The fallback: get the displayed name using getPage()
- var fallback = function(){
-
- done = true;
-
- tpul.profile.getPage().then(function(profilePage) {
-
- resolve(profilePage.displayedName);
- });
-
- tpul.profile.getPage().catch( reject );
- };
-
- if (tpul_promises.getProfilePage) {
-
- tpul_promises.getProfilePage.then(function(profilePage){
- resolve(tpul_promises.getProfilePage.displayedName);
- });
-
- tpul_promises.getProfilePage.catch( reject );
-
- } else if (typeof tagpro != 'undefined' && tagpro.ready) {
- tagpro.ready(function(){
-
- if (tagpro.players) {
- if (tagpro.players[tagpro.playerId]) {
-
- resolve (tagpro.players[tagpro.playerId].name);
-
- } else {
-
- tagpro.socket.on('p',function(playerId) {
- if (tagpro.players[tagpro.playerId]) {
-
- resolve (tagpro.players[tagpro.playerId].name);
-
- }
- });
-
- }
- } else fallback();
- });
- } else fallback();
-
- var done = false;
- setTimeout(function(){
- tpul_promises.getDisplayedName.then(()=>done=true);
- });
-
- setTimeout( function() { if (!done) fallback(); }, fallbackTimeout );
-
- });
- }
-
- return tpul_promises.getDisplayedName;
-
- },
-
- getSettings: function(fallbackTimeout = 5e3) {
-
- /*
-
- Where to get the settings from?
-
- - in-game
- - getPage /profile/...
-
- Logic:
-
- 1. if in-game: get it that way
- 2. call getPage() to get the settings
-
- */
-
- var top_args = arguments;
-
- if (!tpul_promises.getProfileSettings) {
-
- tpul_promises.getProfileSettings = new Promise(function(resolve,reject) {
-
- var fallback = function(){
-
- done = true;
-
- tpul.profile.getPage().then(function(profilePage){
- resolve(profilePage.settings);
- });
-
- tpul.profile.getPage().catch( reject );
- };
-
- if (top_args[0] && top_args[0].__settings) {
- resolve(top_args[0].__settings);
- } else if (tpul_promises.getProfilePage) {
-
- tpul_promises.getProfilePage.then(function(profilePage){
- resolve(profilePage.settings);
- });
-
- tpul_promises.getProfilePage.catch( reject );
-
- } else if (typeof tagpro != 'undefined' && tagpro.ready) {
- tagpro.ready(function(){
-
- if (tagpro.socket && tagpro.socket.on) {
-
- tagpro.socket.on('settings', function(settings) {
- resolve(Object.assign(settings.ui, {stats:settings.stats}));
- });
-
- } else fallback();
- });
- } else fallback();
-
- var done = false;
- setTimeout(function(){
- tpul_promises.getProfileSettings.then(()=>done=true);
- });
-
- setTimeout( function() { if (!done) fallback(); }, fallbackTimeout );
-
- });
-
- }
-
- return tpul_promises.getProfileSettings;
-
- },
-
- setSettings: function(newSettings, persistent=true, immediately=false) {
-
- 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.");
-
- return new Promise(function(resolve, reject){
-
- // Step 1: set any local (cookie) settings
- // These don't have to be send to the server, easy!
-
- if (persistent) {
-
- for (let setting in newSettings) {
- if (['sound',
- 'music',
- 'volume',
-
- 'textures',
-
- 'disableBallSpin',
- 'tileRespawnWarnings',
- 'disableTutorialChat', // This cookie seems to be unused
- // Setting it anyway \(^.^)/
-
- 'disableParticles',
- 'forceCanvasRenderer',
- 'disableViewportScaling',
- ].includes(setting)) {
-
- var expires = new Date(Date.now() + 31536e8).toUTCString(); // A century from now (same as TagPro uses)
- document.cookie = setting + '=' + newSettings[setting] + '; expires='+expires+'; path=/; domain=.koalabeast.com';
- }
- }
-
- // Step 2: send any server-sided settings to the server
-
- if (['reservedName',
- 'displayedName',
-
- 'allChat',
- 'teamChat',
- 'groupChat',
- 'systemChat',
- 'tutorialChat',
-
- 'names',
- 'degrees',
- 'matchState',
- 'performanceInfo',
- 'spectatorInfo',
-
- 'teamNames',
- 'stats',
- ].some( s => s in newSettings ) ){
-
- // Call these to let them run in parallel
- tpul.profile.getSettings();
- tpul.profile.getReservedName();
- tpul.profile.getDisplayedName();
-
- tpul.profile.getSettings().then( function(settings){
- tpul.profile.getReservedName().then( function(reservedName){
- tpul.profile.getDisplayedName().then( function(displayedName){
-
- console.log(param({reservedName: reservedName, // Your reservedName
- displayedName: displayedName, // Your displayedName
- //...settings, // The current settings
- //...newSettings}));
- }));
- var req = GM_xmlhttpRequest({
- data: param(Object.assign({},
- settings, // The current settings
- {reservedName: reservedName, // Your reservedName
- displayedName: displayedName}, // Your displayedName
- newSettings // Overwrite with the settings that you want to edit.
- )),
- method: "POST",
- headers: {"Content-Type": "application/x-www-form-urlencoded"},
- url: "http://"+document.location.hostname+"/profile/update",
- onload: function(r){
- // 'r' is the response that we get back from the TP server, lets do some error handling with it:
-
- var arr;
- try{ arr = JSON.parse(r.response); }
- catch(e){ reject({error:"/profile/update responded invalid JSON", request:this}); }
-
- if(arr.error) reject(arr);
- else if(arr.success) {
- resolve(arr);
- } else reject({error:'unknown error',response: arr, request:this});
- },
- onerror: reject,
- });
-
- });
- });
- });
-
- }
-
- }
-
- // Step 3: In case we are in-game, let the settings go into effect immediately.
- // To update the reserved name, a refresh is required. TPUL won't do this!
-
- if (typeof tagpro != 'undefined' && immediately) {
- if (!tagpro.settings) tagpro.settings = {ui:{}};
- if (!tagpro.settings.ui) tagpro.settings.ui = {};
-
- for (let setting in newSettings) {
- if (['allChat',
- 'teamChat',
- 'groupChat',
- 'systemChat',
- 'tutorialChat',
- ].includes(setting)){
-
- tagpro.settings.ui[setting] = newSettings[setting];
- }
- }
-
- if (setting == 'tutorialChat') {
- var tutorialButton = document.getElementById('tutorialButton');
-
- if (tutorialButton) {
- var action = tutorialButton.innerText === "Enable Tips";
- if (newSettings[setting] == action) tutorialButton.click();
- }
- }
-
-
- }
-
- });
- }
-
- },
-
- rollingChat: {
-
- _init: function initRollingChat(enable = false){
-
- if (typeof tagpro == undefined) return console.error( "The `tagpro` object does not exist. Is this a no-script match?" )
-
- // In case you don't want to load the full TPUL library,
- // You can add RollingChat to your own script by copying this function
- // Usage:
- // initRollingChat(true);
-
- if (!tagpro.rollingChat) {
-
- tagpro.rollingChat = {
- enabled: false,
- get handler() {
- return function(event) {
-
- // Return if not enabled
- if (!tagpro.rollingChat.enabled) return;
-
- // Whether you are releasing instead of pressing the key:
- var releasing = event.type == 'keyup';
-
- // Check if any modifier keys where held down during a keyDown
- if (!releasing && (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey)) return;
-
- // The key that is pressed/released (undefined when it is any other key)
- var arrow = ['left','up','right','down'][[37,38,39,40].indexOf(event.keyCode)];
-
- // Only if the controls are disabled (usually while composing a message)
- // AND the key is indeed an arrow (not undefined)
- if (tagpro.disableControls && arrow) {
-
- // Prevent the 'default' thing to happen, which is the cursor moving through the message you are typing
- event.preventDefault();
-
- // Return if already pressed/released
- if (tagpro.players[tagpro.playerId].pressing[arrow] != releasing) return;
-
- // Send the key press/release to the server!
- tagpro.sendKeyPress(arrow, releasing);
-
- // Not necesarry, but useful for other scripts to 'hook onto'
- if (!releasing && tagpro.events.keyDown) tagpro.events.keyDown.forEach(f => f.keyDown(arrow));
- if (releasing && tagpro.events.keyUp) tagpro.events.keyUp.forEach(f => f.keyUp(arrow));
- if(tagpro.ping.avg)setTimeout(()=>(tagpro.players[tagpro.playerId][arrow]=!releasing),tagpro.ping.avg/2);
- }
- };
- }
- };
-
- // intercept all key presses and releases:
- document.addEventListener('keydown', tagpro.rollingChat.handler);
- document.addEventListener('keyup', tagpro.rollingChat.handler);
- }
-
- if (enable) tagpro.rollingChat.enabled = true;
- },
-
- get enabled(){
- tpul.rollingChat._init();
- return tagpro.rollingChat.enabled;
- },
-
- set enabled(e){
- tpul.rollingChat._init( Boolean(e) );
- if (!e) console.warn('Disabling Rolling Chat! This will disable Rolling Chat for all scripts, not only yours! Please enable it again asap to not get users confused.');
- },
- },
-
- notify: function(text, type="message", timeout=Math.max(4000, 50*text.length) ){
-
- // Accepted types: message, success, error, warning
- // ( white green red yellow )
- // For more types, the only thing you need to add is some CSS
-
- var notification = document.createElement('div');
- notification.className = 'tpul-notification tpul-notification-' + type;
- notification.innerText = text;
- document.body.appendChild(notification);
-
- // Hide after a while (timeout)
- setTimeout(function(notification){
- if(notification)notification.classList.add('vanish');
- }, timeout, notification);
-
- // Hide on click
- notification.onclick = function(){ this.classList.add('vanish'); };
-
- // Clear up the DOM once the notification is vanished
- notification.addEventListener('transitionend',function(){ this.remove(); });
-
- // Return the element, for scripters to "play" with
- return notification;
-
- },
-
- groupcomm: {
-
- emit: function ( script, command, ...args ) {
- // Example: tpul.groupcomm.emit('gropro', 'desc', 'welcome to my awesome group')
- if (typeof tagpro != undefined && tagpro.group && tagpro.group.socket && tagpro.group.socket.connected) {
-
- var full_command = "/" + [...arguments].map(a=>(a||"").replace(/([\^\/:;])/g,"^$1")).join("/") + ";";
-
- tagpro.group.socket.emit( "touch", full_command.substr( 0,12 ) );
- for (var i = 12; i < full_command.length; i += 11) {
- tagpro.group.socket.emit( "touch", ":" + full_command.substr( i,11 ) );
- }
-
- tagpro.group.socket.emit( "touch", tagpro.group.socket.playerLocation );
- }
- else throw "Not connected to a group";
- },
-
- oncommand: function oncommand( callback ) {
- if (typeof tagpro != undefined && tagpro.group && tagpro.group.socket && tagpro.group.socket.connected) {
- if (!tpul.groupcomm._active) tpul.groupcomm._init();
- tpul.groupcomm._callbacks.push(callback);
- }
- else throw "Not connected to a group";
- },
-
- _callbacks: [],
-
- _commands: {},
-
- _active: false,
-
- _init: function (){
-
- if (tpul.groupcomm._active) return;
-
- tpul.groupcomm._active = true;
-
- tagpro.group.socket.on( "member", function(member) {
-
- function handleCommand(command){
- var args = [...command
- .replace(/\^(.)/g, "$1^")
- .match(/\/(.*);(?=(?:\^\^)*(?!\^))/,1)[1]
- .split(/\/(?=(?:\^\^)*(?!\^))/)
- .map(a=>a.replace(/(.)\^(?=(?:\^\^)*(?!\^))/g, "$1"))
- ];
-
- for (var c in tpul.groupcomm._callbacks) {
- var callback = tpul.groupcomm._callbacks[c];
- try { callback({
- member: member,
- script: args.shift() || null,
- command: args.shift() || null,
- args: args,
- raw:command } ); }
- catch(e) {
- console.error("Unhandled GroupComm error. Mod makers, handle your errors!", e);
- tpul.groupcomm._callbacks.splice(c,1);
- }
- }
- }
-
- var raw = member.location,
- commands = tpul.groupcomm._commands;
-
- if (typeof raw !== "string") return;
-
- // A full one-line command: / ... ;
- if ( raw.match(/^\/.*[^^];/) ) {
- handleCommand( raw );
- delete commands[member.id];
- }
-
- // The start of a multi-line command: / ...
- else if ( raw.match(/^\//) ) {
- commands[member.id] = raw;
- }
-
- // The end of a multi-line command: : ... ;
- else if ( raw.match(/^:.*[^^];/) ) {
- if (!commands[member.id]) throw "Did not receive start of command.";
- var com = commands[member.id] + raw.slice(1);
- handleCommand( com );
- delete commands[member.id];
- }
-
- // A middle part of a multi-line command: : ...
- else if ( raw.match(/^:/) ) {
- if (!commands[member.id]) throw "Did not receive start of command.";
- commands[member.id] += raw.slice(1);
- }
-
- // Not a GroupComm command:
- else delete commands[member.id];
- });
- }
- },
-
- get playerLocation(){
-
- // This function seems to be pointless, but I'll make
- // sure it'll keep working even when the site architechture changes,
- // so that you don't have to update your script too often :)
- // (for example, when the SWJ got introduced)
-
- if ( location.pathname.startsWith('/games/find') ) return 'find';
- if ( location.pathname.match(/^\/groups\/[a-z]{8}$/) ) return 'group';
- var path = location.pathname.match(/\w+/);
- if (path) return path[0];
- if ( location.port ) return 'game';
- if ( location.pathname == '/' ) return 'home';
-
- throw 'Player location unknown';
- },
-
- /*events: {
- on: function(event, callback) {
-
- if( !tpul.events._listeners[event] ) tpul.events._listeners[event] = []
- tpul.events._listener.push(callback)
-
- //if (event in deepEvents) enableDeepEvents();
- },
-
- on: function(event, callback) {
-
- if (event == 'register') throw "You can't use 'register' as an event"
-
- if (typeof tagpro != undefined) {
- if ( !tagpro.events ) tagpro.events = {}
- if ( !tagpro.events[event] ) tagpro.events[event] = []
-
-
- if (!tagpro.events[event]) tagpro.events[event] = [];
-
- var eventFunc = {};
- eventFunc[event] = callback;
- tagpro.events[event].push(eventFunc);
-
- //if (event in deepEvents) enableDeepEvents();
- },
-
- emit: function(event, data) {
-
- if ( tpul.events._listeners[event] ) for (let callback of tpul.events._listeners[
- if (tagpro.events[event]) for (let listener of tagpro.events[event]) {
- try { listener[event](data); }
- catch (e) {
- console.error("Unhandled tagpro.events.on('"+event+"') error. Mod makers, handle your errors!");
- console.error(e);
- console.error( listener[event]);
- }
- }
- },
-
- _listeners: {}
- }*/
- };
-
-
-
-
-
-
- // =====DOM SECTION=====
-
-
-
- var SettingsMenu = document.getElementById('tpul-settings-menu') || document.createElement('div');
- SettingsMenu.id = 'tpul-settings-menu';
-
- var SettingsFrame = document.getElementsByClassName('tpul-settings-frame')[0] || document.createElement('div');
- SettingsFrame.className = 'tpul-settings-frame';
- if(tpul.playerLocation == 'game') SettingsFrame.classList.add('in-game');
- document.body.appendChild(SettingsFrame);
-
-
- if (!SettingsMenu.parentElement) tpul.settings.parent = null;
-
- // =====NOITCES MOD=====
-
-
-
-
-
- // OPENING AND CLOSING
-
- SettingsFrame.onclick = function(click) {
-
- // Close all settings when clicking outside the panel
- if (SettingsFrame == click.target) for (var settings of all_settings) settings.close();
-
- };
-
- SettingsFrame.addEventListener('wheel', function(wheel) {
-
- // Close all settings when scrolling up far enough
- setTimeout(function(){
- if (SettingsFrame.firstElementChild &&
- SettingsFrame.scrollTop + SettingsFrame.offsetHeight <= SettingsFrame.firstElementChild.offsetTop + 20)
- for (var settings of all_settings) settings.close();
- },200);
-
- // Open when scrolling down (only in game) DOESN'T WORK PROPERLY
- // if (tpul.playerLocation == 'game' && wheel.deltaY > 0 && last_opened && !last_opened.isOpen) last_opened.open();
-
- if (wheel.deltaY > 0) {
- // Hide the scrolldown arrow TODO
- for (var arrow of document.getElementsByClassName('tpul-settings-scroll-down-arrow')) {
- arrow.style.opacity = 0;
- }
- }
- })
-
-
-
-
- // Section tabs
-
- SettingsFrame.addEventListener('click', function(click) {
- var tablist = click.target.parentElement;
- if (tablist.classList.contains('tab-list')) {
-
- var scrollTop = SettingsFrame.scrollTop;
- console.log(scrollTop);
-
- for (let li of tablist.getElementsByTagName('li'))
- li.classList.remove('active');
- for (let pane of tablist.parentElement.getElementsByClassName('tab-pane'))
- pane.classList.remove('active');
- click.target.classList.add('active');
- document.querySelector(click.target.dataset.target).classList.add('active');
-
- SettingsFrame.scrollTop = scrollTop;
- }
- }, true);
-
-
-
- // Get settings from socket:
-
- if (typeof tagpro != undefined && tagpro.ready) {
- tagpro.ready(function(){
- if (tagpro.socket && tagpro.socket.on) {
- tagpro.socket.on('settings', function(settings) {
- // Don't try to tamper with this, or copy this in your own script.
- // It will affect all scripts using TPUL.
- tpul.profile.getSettings( {__settings:Object.assign(settings.ui, {stats: settings.stats})} );
- });
- }
- });
- }
-
- // Some helper function(s)
-
- function param(o){
-
- return Object.keys(o).map(function(k) {
- return encodeURIComponent(k) + '=' + encodeURIComponent(o[k.replace(' ','+')]);
- }).join('&').replace(/%20/g, '+');
- }
-
-
-
-
- // =====NOITCES CIGOL=====
-
-
-
-
-
- if (typeof tpul_promises == 'undefined') {
- try{
- window.tpul_promises = {};
- unsafeWindow.tpul_promises = window.tpul_promises;
- }catch(e){}
- }
-
- var tpul_common = {}
-
- if (typeof tpul_common == 'undefined') {
- if (window) window.tpul_common = tpul_common
- if (unsafeWindow) unsaeWindow.tpul_common = tpul_common
- }
-
-
- // If running independently (not @required by another script)
- // only good for modders or while debugging
- if (typeof GM_info == undefined || GM_info.script.name == 'TagPro Userscript Library') {
- if (typeof tagpro != undefined) tagpro.tpul = tpul;
- if (window) window.tpul = tpul;
- if (unsafeWindow) unsafeWindow.tpul = tpul;
- }
-
- return tpul;
- })();