您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Functions that any TagPro script could benefit from
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/371240/620873/TagPro%20Userscript%20Library.js
- // ==UserScript==
- // @name TagPro Userscript Library
- // @description Functions that any TagPro script could benefit from
- // @author Ko </u/Wilcooo> (https://greasyfork.org/users/152992)
- // @version 2.1
- // @license MIT
- // @include *.koalabeast.com*
- // @include *.jukejuice.com*
- // @include *.newcompte.fr*
- // @icon https://raw.githubusercontent.com/wilcooo/TagPro-UserscriptLibrary/master/icon.png
- // @supportURL https://www.reddit.com/message/compose/?to=Wilcooo
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_xmlhttpRequest
- // @connect koalabeast.com
- // @namespace https://greasyfork.org/users/152992
- // ==/UserScript==
- // ==UserLibrary==
- // @name TagPro Userscript Library
- // @description Functions that any TagPro script could benefit from
- // @version 1.0
- // @license MIT
- // ==/UserLibrary==
- var version = 1.4;
- console.log('Loading TPUL (TagPro Userscript Library) version '+version);
- ////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////
- // To use this library, include these 5 lines in your userscripts' metadata block:
- // @require https://github.com/wilcooo/TagPro-UserscriptLibrary/raw/master/tpul.lib.js
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_xmlhttpRequest
- // @connect koalabeast.com
- ////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////
- 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 call tpul.rollingChat.enable() to make the Arrow keys move your ball, even when typing text.
- this.node.addEventListener('focus', function(){tagpro.disableControls = true;});
- this.node.addEventListener('blur', function(){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);
- 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;
- }`);
- /*
- //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=====
- // =====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(location.port) SettingsFrame.classList.add('in-game');
- document.body.appendChild(SettingsFrame);
- // =====NOITCES MOD=====
- // =====LOGIC SECTION=====
- var GM_storage = typeof GM_setValue === 'function' && typeof GM_getValue === 'function',
- all_settings = [],
- profileId = null,
- last_opened = null,
- rollingChatEnabled = false;
- 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');
- last_opened = settings;
- if (this.events && typeof this.events.open == "function")
- this.events.open.call(this,...arguments);
- },
- close: function(){
- if(this.isOpen){}//TODO: Check whether unsaved?
- SettingsFrame.removeEventListener('scroll', settings.scroll);
- //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({...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({...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 (immediately && tagpro) {
- 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){
- // 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));
- 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();
- tagpro.rollingChat.enabled = Boolean(e);
- },
- },
- 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;
- }
- };
- if (!SettingsMenu.parentElement) tpul.settings.parent = null;
- // 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('scroll', function(wheel) {
- // Open when scrolling down (only in game)
- if (location.port && wheel.deltaY > 0 && last_opened && !last_opened.isOpen) last_opened.open();
- // 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);
- });
- // Section tabs
- SettingsFrame.addEventListener('click', function(click) {
- var tablist = click.target.parentElement;
- if (tablist.classList.contains('tab-list')) {
- 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');
- }
- });
- // Get settings from socket:
- if (tagpro && 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:{...settings.ui, stats: settings.stats}} );
- });
- }
- });
- }
- // Some helper functions
- 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){}
- }
- return tpul;
- })();