您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Functions that any TagPro script could benefit from
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyforks.org/scripts/371240/620875/TagPro%20Userscript%20Library.js
// ==UserScript== // @name TagPro Userscript Library // @description Functions that any TagPro script could benefit from // @author Ko </u/Wilcooo> (https://greasyforks.org/users/152992) // @version 2.1 // @license MIT // @include *.koalabeast.com* // @include *.jukejuice.com* // @include *.newcompte.fr* // @downloadURL https://raw.githubusercontent.com/wilcooo/TagPro-UserscriptLibrary/master/tpul.lib.js // @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 // ==/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 <[email protected]> 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; })();