|
something something dark side
The purpose of this app is to demonstrate the use of a data saving add-on for Chaturbate apps and bots.
The add-on consists of two components:
a javascript module that adds a data save and restore capability to an app or bot, and
a javascript applet for broadcasters to control the save and restore process for any apps and bots they use that include the module and that have been configured to use it.
App and bot data can be saved to, and restored from, either
browser Local Storage, or
the broadcaster's local File System.
The advantage of using Local Storage is that data saves can be automated. The disadvantage of using local storage is that the saved data is less permanent than that saved to the File System. Local storage can be wiped automatically, if system resources are running low, or if cached data is cleared by the user. Indeed, if the browser is used in so-called Private or Incognito mode, local storage is wiped every time the browser window is closed.
CBS-enabled Tip Goal
This app is a copy of the popular sample app script Tip Goal by Admin that includes the CBS module and has been configured to use it.
CBS Save'N'Restore Applet
In order to access the added save and restore feature, a broadcaster must run the following javascript applet:
javascript:/*** CB app/bot data Save'n'restore bookmarklet v2.010 ***/window.__nativeST__=window.__nativeST__||window.setTimeout;window.__nativeSI__=window.__nativeSI__||window.setInterval;window.setTimeout=function(a,k){var g=this,u=Array.prototype.slice.call(arguments,2);return window.__nativeST__(a instanceof Function?function(){a.apply(g,u)}:a,k)};window.setInterval=function(a,k){var g=this,u=Array.prototype.slice.call(arguments,2);return window.__nativeSI__(a instanceof Function?function(){a.apply(g,u)}:a,k)};
(function(a,k,g,u){function f(b,c){c=c||0;b[c++](function(b,c){return function(){c<b.length&&a.setTimeout(function(){f(b,c)},1)}}(b,c))}a.CBS={version:"CBS::v2::CB app/bot data Save/restore::20171104.010::Release"};g=g||"1";u=u||function(a,b,c,d,g){setTimeout(g,1E4,a,b,c,d)};var b=k.getElementById("CBSv2Overlay"),n=[],c,q=!1,d,p=!1,E,w;/(camgasm|chaturbate)\.com$/i.test(k.location.hostname)?b||f([function(a){var c="<style>.CBSv2-no-overflow {overflow:hidden!important;outline:0;}${visibility:visible;position:fixed;top:0;right:0;left:0;bottom:0;overflow-y:auto;padding:0;z-index:10;background-color:rgba(0,0,0,0.05);}$ div {visibility:visible;width:300px;margin:100px auto;background-color:#fff;border:1px solid #000;padding:15px;text-align:center;}</style>".replace(/\$/g,
"#CBSv2Overlay");b=k.createElement("div");b.setAttribute("id","CBSv2Overlay");b.style.display="block";b.innerHTML=c+"<div>Starting...</div>";k.body.appendChild(b);k.body.classList.add("CBSv2-no-overflow");n.push(b);setTimeout(function(){a()},1E3)},function(d){(c=a.jQuery)&&g<=c.fn.jquery?d():(b=k.createElement("script"),b.type="text/javascript",b.src="//ajax.googleapis.com/ajax/libs/jquery/"+g+"/jquery.min.js",b.onload=b.onreadystatechange=function(){w=this.readyState;q||w&&"loaded"!==w&&"complete"!==
w||((c=a.jQuery).noConflict(q=!0),b.onload=b.onreadystatechange=null,d())},k.documentElement.childNodes[0].appendChild(b))},function(a){[["//cdn.jsdelivr.net/alertifyjs/1.4.1/css/alertify.min.css","alertify-notifier ajs-left","left","10px"],["//cdn.jsdelivr.net/alertifyjs/1.4.1/css/themes/default.min.css"],["//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css","fa","font-family","FontAwesome"]].forEach(function(a){(function(a,b,d,g){b=c("<div>").hide().css({height:0,width:0}).addClass(b||
"").appendTo("body");d=b.css(d||"")===g;b.remove();return c('link[rel="stylesheet"][href$="'+a.match(/(?:\/)[^\/]+$/)+'"]').length&&d}).apply(null,a)||(b=k.createElement("link"),b.setAttribute("rel","stylesheet"),b.setAttribute("href",a[0]),k.head.appendChild(b))});a()},function(c){(d=a.alertify)&&d.dialog?c():(b=k.createElement("script"),b.src="//cdn.jsdelivr.net/alertifyjs/1.4.1/alertify.min.js",b.onload=b.onreadystatechange=function(){w=this.readyState;p||w&&"loaded"!==w&&"complete"!==w||(E=a.alertify,
a.alertify=d,d=E,p=!0,b.onload=b.onreadystatechange=null,c())},k.documentElement.childNodes[0].appendChild(b))},function(a){setTimeout(function(){var a=k.querySelector("#CBSv2Overlay div");a&&(a.style.visibility="hidden")},3E3);a()},function(a){u(c,q,d,p,a)},function(a){p&&setTimeout(function(){Array.prototype.slice.call(k.querySelectorAll('div[class*="alertify-"],div[class*="ajs-"]')).forEach(function(a){a.parentElement.removeChild(a)})},2E3);a()},function(a){n.forEach(function(a){a.parentNode.removeChild(a)});
k.body.classList.remove("CBSv2-no-overflow");a()}]):a.alert("*chaturbate.com only bookmarklet")})(window,document,"1.6.4",function(a,k,g,u,f){function b(){return window.ws_handler&&window.ws_handler.connected||window.websocket_handler&&window.websocket_handler.connected||window.flash_handler&&window.flash_handler.connected||window.html_handler&&window.html_handler.connected}function n(e){v.empty().append(P,a("<p>"+e+"</p>"));z&&z.isOpen()&&z.setContent(v[0]).set("closable",!0)}function c(){z&&z.isOpen()&&
z.close().set("closable",!0)}function q(a){g.success(a).callback=c}function d(a){g.error(a).callback=c}function p(e){var b=L[window.CBSv2_ping.split("/").length],l;window.$message_sender.confirmed_send=!0;void 0!==(l=window.flash_handler)&&l.connected?(l.consolelog("User is sending message: "+e),e=a.toJSON({m:e,c:"",f:""}),l.consolelog(e),window.GetFlashObject("movie").SendRoomMsg(e)):void 0!==(l=window.html_handler)&&l.connected?(l.consolelog("User is sending message: "+e),e=a.toJSON({m:e,c:"",f:""}),
l.consolelog(e),a.ajax({url:l.post_address,dataType:"json",data:{room:l.room_owner_nick,message:e,username:l.user},type:"POST",success:function(a){if(""!==a&&a["X-Spam"])l.message_inbound.on_room_message(l.user,e)}})):void 0!==(l=window.websocket_handler)&&l.connected?l.connection.send(JSON.stringify({action:"msg",msg:e})):void 0!==(l=window.ws_handler)&&l.connected?(l.consolelog("User is sending message: "+e),e=a.toJSON({m:e,c:"",f:""}),l.consolelog(e),l.SendRoomMsg(e)):window.CBSv2_autosave_interval?
m.enm():n(b+" Failed. Chat may be disconnected.")}function E(){var e=a('.stop_link[name="/app/stop/'+this.idx+'/"]');this[this.idx]=e.length?e[0].parentNode.previousElementSibling.innerHTML.trim():"";this[this.idx].length&&b()?(window.CBSv2_ping=["","#"+this.idx+"CBSv2",G,""].join("/"),p(window.CBSv2_ping),A=setTimeout.call(this,function(){this[this.idx]="";this.enm()},5E3)):this.enm()}function w(){var a=L[window.CBSv2_ping.split("/").length];A=null;window.CBSv2_autosave_interval?m.enm():n(a+" Failed. Chat may be unresponsive.")}
function J(a,b){b=b||0;var e=y.slice(b,b+512);window.CBSv2_ping=["","#"+a+"CBSv2",G,b,e,e.length,""].join("/");p(window.CBSv2_ping);A=setTimeout(w,5E3)}function K(a,b){window.CBSv2_ping=["","#"+a+"CBSv2",G,b||0,""].join("/");p([window.CBSv2_ping,""].join("/"));A=setTimeout(w,5E3)}function U(){this[this.idx].length&&b()&&window.CBSv2_autosave_interval?K(this.idx):this.enm()}function x(e){function b(){x.a?--x.b?setTimeout(b,1E3):e.call(this):e.call(this)}window.CBSv2_autosave_interval?x.a?e&&(x.b=5,
setTimeout(b,1E3)):(x.a=!0,a("#CBSv2Overlay").length||m.enm(E,function(){G=(new Date).valueOf();m.enm(U,function(){x.a=!1;e&&e.call(this)})})):e&&e.call(this)}function O(){var a,b;r=[];for(b=0;b<t.length;b++){for(a=t.key(b).split("/");4<a.length;)a[1]=[a[1]].concat(a.splice(2,1)).join("/");4===a.length&&"CBSv2"===a[0]&&r.push(a.concat(t.key(b),[a[1],(new Date(parseInt(a[3],10))).toLocaleString(),B[parseInt(a[2],10)]].join(" ")))}}function V(a,b,l,d,h,M,C){a=parseInt(b[1],10);h?J(a,parseInt(d,10)+
parseInt(C,10)):c()}function W(a){document.body.removeChild(a.target)}function X(a){var b=document.createElement("input");b.type="file";b.onchange=function(b){var e=b.target.files[0],h=new window.FileReader;h.onload=function(b){b=JSON.parse(b.target.result);for(var h=(b.key||"").split("/");4<h.length;)h[1]=[h[1]].concat(h.splice(2,1)).join("/");4===h.length&&"CBSv2"===h[0]&&h[1]===m[a]?(y=b.value,J(a)):alert('"'+e.name+'" does not contain "'+m[a]+'" save data.')};h.readAsText(e,"UTF-8");b.target.parentNode&&
b.target.parentNode.removeChild(b.target)};null===window.webkitURL&&(b.style.display="none",document.body.appendChild(b));b.click()}function Y(a,b,l,g,h){function e(){var a=["CBSv2",m[D],D,l].join("/");if("false"!==t.CBSv2_use_local_storage)t[a]=y,window.CBSv2_autosave_interval?m.enm():q("Saved.");else{var b=new window.Blob([JSON.stringify({key:a,value:y})],{type:"application/json"}),e=document.createElement("a");e.download=a.split("/").slice(0,-1).join("_")+".json";e.innerHTML="Download File";null!==
window.webkitURL?e.href=window.webkitURL.createObjectURL(b):(e.href=window.URL.createObjectURL(b),e.onclick=W,e.style.display="none",document.body.appendChild(e));e.click();c()}}function C(){t.removeItem(r[f][4]);e()}var D=parseInt(b[1],10),f=-1;y="0"===g?h:y+h;if(h)K(D,y.length);else if(y.length)if("false"!==t.CBSv2_use_local_storage){window.CBSv2_autosave_interval&&O();for(a=0;a<r.length;a++)if(b[1]===r[a][2]&&m[D]===r[a][1]){f=a;break}-1<f?window.CBSv2_autosave_interval?C():F("Confirm Overwrite...",
'Overwrite existing "'+r[f][5]+'" saved data?',C,function(){d("Overwrite cancelled.")}):e()}else e();else window.CBSv2_autosave_interval?m.enm():n('No Data. "'+m[D]+'" did not respond with data to save.')}function H(a){A&&(clearTimeout(A),A=null);var b=L[window.CBSv2_ping.split("/").length];a=a.split("/");var e=parseInt(a[1][1],10);""===a[a.length-1]?4===a.length?(m[e]="",m.enm()):n(b+' Failed. "'+m[e]+'" did not respond to "'+b+'" command.'):4===a.length?m.enm():6===a.length?Y.apply(this,a):7===
a.length?V.apply(this,a):window.CBSv2_autosave_interval?m.enm():n(b+' Aborted. "'+m[e]+'" gave an unknown response to "'+b+'" command.')}function Z(a){function b(){d("Delete ALL cancelled.")}var e;z.set("closable",!1);a.html("Processing...");switch(a.attr("class")){case "da":F("Confirm Delete ALL...","Delete ALL saved data?",function(){F("Confirm Confirm Delete ALL...","Are you really sure you want to Delete ALL saved data?",function(){for(e=0;e<r.length;e++)t.removeItem(r[e][4]);q("ALL Deleted.")},
b)},b);break;case "ld":y=t[r[a.data("i")][4]];J(B.indexOf(a.data("s")));break;case "lf":X(B.indexOf(a.data("s")));c();break;case "rm":F("Confirm Delete...",'Delete "'+r[a.data("i")][5]+'" saved data?',function(){t.removeItem(a.data("k"));q("Deleted.")},function(){d("Delete cancelled.")});break;case "sv":K(B.indexOf(a.data("s")))}}function I(a,b){function e(a){var b=window;a=a.split(".");var e,h=a.length;for(e=0;e<h;e++){if(void 0===b||!b.hasOwnProperty(a[e]))return null;b=b[a[e]]}return b}var c=a.split("."),
h=c[c.length-1],d=e(c[0]);c=e(c.slice(0,-1).join("."));if(d&&c&&c.hasOwnProperty(h)&&"function"===typeof c[h]){var g=c[h].CBS_orig;"function"!==typeof g&&(g=c[h]);c[h]=b;c[h].CBS_orig=g;c[h].CBS_root=d}}function N(){a(".stop_link").unbind("click").click(function(){var b=this;x(function(){a.ajax({url:a(b).attr("name"),dataType:"text",data:"",type:"POST",success:function(){a.mydefchatconn("app_tab_refresh")}})})})}function R(a){return'<i class="'+a+'" style="vertical-align:middle; margin-right:20px;"></i>'}
var z,v=a("<div class=CBSv2_buttons></div>"),P=a("<style>.$_buttons{margin:0 auto;padding:10px 20px;}.$_buttons button{display:block;width:100%;margin:5px 0;}.$_buttons input[type=checkbox][disabled] + label{color: #ccc;}</style>".replace(/\$/g,"CBSv2")),B=["Active App","Bot #1","Bot #2","Bot #3"],m=["","","",""],L={4:"Query",6:"Save",7:"Restore"},t=window.localStorage,r,y,A=null,S=/^\/#[0-3]CBSv2\//;window.CBSv2_autosave_interval=window.CBSv2_autosave_interval||null;window.CBSv2_autosave_interval&&
window.clearInterval(window.CBSv2_autosave_interval);window.CBSv2_ping=window.CBSv2_ping||null;Array.prototype.enm=Array.prototype.enm||function(a,b){this.idx=++this.idx||0;arguments.length&&(this.fn=a||null,this.cb=b||null);this.idx<this.length?this.fn&&this.fn.call(this):(delete this.idx,this.cb&&this.cb.call(this))};g.set("notifier","delay",3);I("flash_handler.message_inbound.on_room_message",function Q(b,c){var h=Q.CBS_root,d=unescape(c);try{var g=a.parseJSON(d)}catch(D){g={m:d}}d=h.striphtml(g.m).replace(/\s*/g,
"");return 0===d.indexOf(window.CBSv2_ping)?(h.sanitize(b)===h.room&&H(d),!0):Q.CBS_orig.call(this,b,c)});I("html_handler.message_inbound.on_room_message",function M(a,b,h){var c=M.CBS_root,d=c.striphtml(b.m).replace(/\s*/g,"");return 0===d.indexOf(window.CBSv2_ping)?((c.sanitize(a)||c.sanitize(b.user))===c.room&&H(d),!0):S.test(d)?!0:M.CBS_orig.call(this,a,b,h)});I("websocket_handler.message_inbound.on_room_message",function h(a){if(void 0===a.m)return!0;var b=h.CBS_root,c=b.striphtml(a.m).replace(/\s*/g,
"");return 0===c.indexOf(window.CBSv2_ping)?(b.sanitize(a.user)===b.room&&H(c),!0):S.test(c)?!0:h.CBS_orig.call(this,a)});I("ws_handler.message_inbound.on_room_message",function C(b,c){var h=C.CBS_root,d=c;try{var g=a.parseJSON(d)}catch(aa){g={m:d}}d=h.striphtml(g.m).replace(/\s*/g,"");return 0===d.indexOf(window.CBSv2_ping)?(h.sanitize(b)===h.room&&H(d),!0):C.CBS_orig.call(this,b,c)});b()&&(a('.info-user a[data-tab="apps_and_bots"]').unbind("click").click(function(){var b=a(".info-user div.apps_and_bots"),
c=a(".info-user .buttons a[data-tab='apps_and_bots']").attr("href");b.show();b.html(window.gettext("loading . . ."));N?b.load(c,N):b.load(c)}),N());if(!F){g.dialog("CBSv2Confirm",function(){return{build:function(){this.setting("defaultFocus","cancel")},prepare:function(){this.setHeader("<span style=\"color:#dc5500; font-family: 'UbuntuBold', Arial, Helvetica, sans-serif;\">"+R("fa fa-exclamation-triangle fa-2x")+"CBSv2: "+this.get("title")+"</span>")}}},!0,"confirm");var F=g.CBSv2Confirm}if(!T){g.dialog("CBSv2Alert",
function(){return{build:function(){this.setHeader("<span style=\"color:#dc5500; font-family: 'UbuntuBold', Arial, Helvetica, sans-serif;\">"+R("fa fa-cog fa-2x")+"CBSv2: CB app/bot data Save'n'restore bookmarklet v2</span>")},setup:function(){return{buttons:[{text:"Close",key:27}],focus:{element:0},options:{maximizable:!1,resizable:!1,padding:!1}}},prepare:function(){var b=a(".CBSv2_buttons button");"false"!==t.CBSv2_use_local_storage?b.filter(".lf").hide():(a("#CBSv2_as").prop("disabled",!0),b.filter(".ld, .rm, .da").hide());
a('.chat-box ul.buttons li a[data-tab="autosave"]').length&&b.filter(".sv, .ld, .lf").prop("disabled",!0);b.click(function(c){c.preventDefault();b.prop("disabled",!0);Z(a(this))});a("#CBSv2_ls").change(function(){var c=a('.chat-box ul.buttons li a[data-tab="autosave"]').length;t.CBSv2_use_local_storage=a(this).is(":checked");"false"!==t.CBSv2_use_local_storage?(a("#CBSv2_as").prop("disabled",!1),b.filter(".ld").prop("disabled",c),b.filter(".ld, .rm, .da").show(),b.filter(".lf").hide()):(a("#CBSv2_as").prop("disabled",
!0),b.filter(".ld, .rm, .da").hide(),b.filter(".lf").prop("disabled",c),b.filter(".lf").show(),a("#CBSv2_as").removeProp("checked").change())});a("#CBSv2_as").change(function(){a(this).is(":checked")?(a('.chat-box ul.buttons li a[data-tab="settings"]').parent().before(a("<li></li>").html('<a href="#" data-tab="autosave" class="nooverlay">AutoSave</a>')),b.filter(".sv, .ld, .lf").prop("disabled",!0)):(a('.chat-box ul.buttons li a[data-tab="autosave"]').parent().remove(),b.filter(".sv, .ld, .lf").prop("disabled",
!1))})},hooks:{onclose:function(){a('.chat-box ul.buttons li a[data-tab="autosave"]').length?(window.CBSv2_autosave_interval=setInterval(x,18E4),setTimeout(x,6E3)):window.CBSv2_autosave_interval=null;a(".CBSv2_buttons").remove();f()}}}},null,"alert");var T=g.CBSv2Alert}var G=(new Date).valueOf();m.enm(E,function(){var c;var d=m.some(function(a){return a});var g=b();v.append(P);O();if(r.length||d&&g){for(c=0;c<m.length;c++)if(m[c]&&g){var f=a('<button data-s="^" data-a="$" class="sv">Save ^ "$" data</button>'.replace(/\^/g,
B[c]).replace(/\$/g,m[c]));v.append(f);f=a('<button data-s="^" data-a="$" class="lf">Restore ^ "$" saved data from file</button>'.replace(/\^/g,B[c]).replace(/\$/g,m[c]));v.append(f);for(d=0;d<r.length;d++)if(m[c]===r[d][1]){var k=r[d][4];f=a(('<button data-s="^" data-i="'+d+'" data-k="'+k+'" class="ld">Restore "'+r[d][5]+'" saved data into ^</button>').replace(/\^/g,B[c]));v.append(f)}}for(d=0;d<r.length;d++)f=a('<button data-i="'+d+'" data-k="'+k+'" class="rm">Delete "'+r[d][5]+'" saved data</button>'),
v.append(f);1<r.length&&(f=a('<button class="da">Delete ALL saved data</button>'),v.append(f));f=a('<input type="checkbox" id="CBSv2_ls"><label for="CBSv2_ls">Use browser Local Storage</label>');"false"!==t.CBSv2_use_local_storage&&f.prop("checked",!0);v.append(f);g&&(f=a('<input type="checkbox" id="CBSv2_as"><label for="CBSv2_as">AutoSave</label>'),a('.chat-box ul.buttons li a[data-tab="autosave"]').length&&f.prop("checked",!0),v.append(f))}else v.append(a("<p>Nothing to do!</p>"));z=T(v[0])})});
The most convenient way of running the applet is to copy and paste it into your browser's Bookmark Toolbar, so that it is available as a bookmark applet (bookmarklet*) to click on whenever you want to open the Save and Restore Menu.
*A WORD OF CAUTION: It is not normally advisable to use bookmarklets from unverified sources, as they can be dangerous to the health of your computer. Only use bookmarklets from sources that you trust, especially when the javascript has been optimized, as the above has been, and it is not obvious what the applet does. The unoptimized code is provided below for you to verify. But if you are in any doubt, DO NOT use it.
Save'N'Restore Menu
Once the bookmarklet has been added to your browser, you can open the Save'N'Restore menu whenever you're on the site.
The menu options available will depend on whether you are in-chat, whether there are any CBS-enabled scripts running, and whether you have chosen to use browser Local Storage.
A checkbox at the foot of the menu allows you to choose to use browser Local Storage.
If browser Local Storage is selected, a further checkbox allows you to choose to enable AutoSave mode.
AutoSave Mode
While AutoSave is enabled, an unselectable AutoSave tab is displayed in the chat panel, and the CBS applet will attempt to save the data from any active CBS-enabled scripts automatically every three minutes and when you deactivate a script.
However, an AutoSave can fail if a script crashes, or the site becomes unresponsive.
In addition, AutoSave will be disabled if you refresh or reload the page. It can be re-enabled via the menu.
Finally, in order to Restore, or Re-load, previously saved data, AutoSave mode must be temporarily disabled.
It is important to appreciate that there is no equivalent "AutoRestore". Previously saved data can only be Restored or Re-loaded manually via the Save'N'Restore Menu, either from file or from Local Storage, and AutoSave mode must be temporarily unselected, in order to do that.
CBS Module
In order to make an app or bot CBS-aware the following module must be added to the head of the script:
// startof CBSv2.010 module - not for re-compilation
(function(a,k){function g(a){this.message=a}g.prototype=Error();g.prototype.name="InvalidCharacterError";a.btoa||(a.btoa=function(a){a=String(a);for(var f,b,n=0,c=k,q="";a.charAt(n|0)||(c="=",n%1);q+=c.charAt(63&f>>8-n%1*8)){b=a.charCodeAt(n+=.75);if(255<b)throw new g('"btoa" failed: The string to be encoded contains characters outside of the Latin1 range.');f=f<<8|b}return q});a.atob||(a.atob=function(a){a=String(a).replace(/=+$/,"");if(1==a.length%4)throw new g('"atob" failed: The string to be decoded is not correctly encoded.');
for(var f=0,b,n,c=0,q="";n=a.charAt(c++);~n&&(b=f%4?64*b+n:n,f++%4)?q+=String.fromCharCode(255&b>>(-2*f&6)):0)n=k.indexOf(n);return q})})("undefined"===typeof exports?this:exports,"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");
(function(a){var k=cb.onMessage,g=null,u=null,f,b="#"+(cb.settings.hasOwnProperty("slot")?cb.settings.slot:"")+"CBSv2",n=/^\/#[0-3]CBSv2\//;cb.log("CBS::v2::CB app/bot data Save/restore::20171104.010::Release");cb.onMessage=function(c){if("function"!==typeof c)throw new TypeError(c+" is not a function");k(function(k){var d=k.m.replace(/\s*/g,"").split("/");if(3<d.length&&""===d[0]&&d[1]===b){if(g&&u&&k.user===cb.room_slug){var p=d[2];if(4===d.length)d[3]="?",k.m=d.join("/");else if(6===d.length){if(!(f||
{}).hasOwnProperty(p)){var q=g();f={};f[p]=a.btoa(a.unescape(a.encodeURIComponent(q)));q||cb.log("onSave returned no data.")}f.hasOwnProperty(p)&&(q=parseInt(d[3],10),p=f[p].slice(q,q+512),d[4]=p,d[5]=p.length,k.m=d.join("/"))}else 7===d.length&&("0"===d[3]&&(f={},f[p]=""),f.hasOwnProperty(p)&&(d[3]=f[p].length,d[6]=d[4].length,k.m=d.join("/"),d[4]?f[p]+=d[4]:(q=a.decodeURIComponent(a.escape(a.atob(f[p]))),u(q),cb.chatNotice("Previously Saved Data Restored.",cb.room_slug))))}k["X-Spam"]=!0}else n.test(k.m)&&
(k["X-Spam"]=!0);return c(k)});return c};cb.onRestore=function(a){if("function"!==typeof a)throw new TypeError(a+" is not a function");return u=a};cb.onSave=function(a){if("function"!==typeof a)throw new TypeError(a+" is not a function");return g=a};cb.onMessage(function(a){return a})})("undefined"===typeof exports?this:exports);
// endof CBSv2.010 module - not for re-compilation
In addition, in order to make an app or bot CBS-enabled, two new handler callback functions must be registered using cb.onSave(func); and cb.onRestore(func); statements.
Examples of both types of callback can be found in this app's source code.
Briefly, the CBS add-on saves and restores a piece of string data. So, if the app only needs to save a single string variable, the onSave callback just needs to return that string, and the onRestore callback just needs to set the variable to the value of its argument, which will be the previously saved string value.
The example callbacks in this app illustrate the more common situation, where more than a single piece of data is to be saved and restored. And, although in this demo all the app variables are saved and restored, exactly what pieces of data should be saved and restored is entirely a decision for the script author, as they feel is appropriate. For instance, in the CrazyTicket alternative, AutoStart Tip2View, only show ticket purchasers and unexpired preview ticket purchasers are saved and restored.
Using Save'N'Restore with Non-CBS-Aware Apps and Bots
The way that the Save'N'Restore bookmark applet works is that every time its menu is opened (and every time the AutoSave process runs, if it is enabled) each active app and bot is pinged with a message to test whether it is CBS-enabled. The applet makes sure that broadcasters won't see those message pings. However, if you're running cbscripts that aren't CBS-aware, that is those that don't include the CBS Save'N'Restore Module (which is most apps and bots), ping messages sent to those cbscripts will show up in the chat of visitors to your room. So you might get some people asking what the weird looking messages you keep posting are? They take the form of /#[slot number]CBSv[version number]/[time stamp]/ and are just the Save'N'Restore bookmarklet working behind the scenes, on your behalf ;)
Credits:
The bookmarklet menu system uses the excellent AlertifyJS library, created by Mohammad Younes, and the awesome FontAwesome iconic font and css toolkit, created by Dave Gandy.
The Base64 encoder/decoder module included in the CBS module is based on code originating from nignag and atk.
Also by the Same Author:
Too many to list. Just search for rubzombie
CBS Bookmarklet/Module Source Code:
The source code can be roughly divided into four sections:
a library, style, and code pre-loader,
the save/restorer controller,
the save/restorer cb object extender module, and
a Base64 encoder/decoder module.
// ==ClosureCompiler==
// @output_file_name default.js
// @compilation_level ADVANCED_OPTIMIZATIONS
// @externs_url https://raw.githubusercontent.com/google/closure-compiler/master/contrib/externs/jquery-1.6.js
// @js_externs var cbjs = { "arrayContains": function () {}, "arrayJoin": function () {}, "arrayRemove": function () {} }
// @js_externs var old_cb = { "changeRoomSubject": function () {}, "chatNotice": function () {}, "drawPanel": function () {}, "in_show": function () {}, "limitCam_addUsers": function () {}, "limitCam_allUsersWithAccess": function () {}, "limitCam_isRunning": function () {}, "limitCam_removeAllUsers": function () {}, "limitCam_removeUsers": function () {}, "limitCam_start": function () {}, "limitCam_stop": function () {}, "limitCam_userHasAccess": function () {}, "log": function () {}, "onDrawPanel": function () {}, "onEnter": function () {}, "onLeave": function () {}, "onMessage": function () {}, "onShowStatus": function () {}, "onTip": function () {}, "room_slug": {}, "sendNotice": function () {}, "setTimeout": function () {}, "settings": function () {}, "settings_choices": function () {}, "show_message": {}, "show_users": function () {}, "slot": function () {}, "tipOptions": function () {} }
// @js_externs var cb = { "app_id": {}, "cancelTimeout": function () {}, "changeRoomSubject": function () {}, "chatNotice": function () {}, "drawPanel": function () {}, "limitCam_addUsers": function () {}, "limitCam_allUsersWithAccess": function () {}, "limitCam_isRunning": function () {}, "limitCam_removeAllUsers": function () {}, "limitCam_removeUsers": function () {}, "limitCam_start": function () {}, "limitCam_stop": function () {}, "limitCam_userHasAccess": function () {}, "log": function () {}, "onDrawPanel": function () {}, "onEnter": function () {}, "onLeave": function () {}, "onMessage": function () {}, "onTip": function () {}, "panCam_controlsDisabled": {}, "panCam_controlsEnabled": {}, "panCam_isValidDirection": function () {}, "panCam_move": function () {}, "panCam_onPanelButtonClicked": function () {}, "room_slug": {}, "sendNotice": function () {}, "setTimeout": function () {}, "settings": { "slot": {} }, "settings_choices": {}, "slot": {}, "tipOptions": function () {} }
// ==/ClosureCompiler==
/*jslint ass: true, vars: true, sub: true, nomen: true, plusplus: true, regexp: true, indent: 2, white: true */
/*global window, document, console, alert, setInterval, clearInterval, setTimeout, clearTimeout, unescape, cb:false, exports:false */
/**
* @author rubzombie
*/
const app = "CBS";
const sDesc = "CB app/bot data Save/restore";
const sVer = "20171104.010";
const debug = false;
const persist = true; // persist element additions, i.e. don't reload the jquery/alertifyjs/fontawesome libraries each time
const ver = "v2";
const sSig = app + "::" + ver + "::" + sDesc + "::" + sVer + (debug ? "::Debug" : "::Release");
const magicnumber = app + ver;
const id = magicnumber + "Overlay";
const chunk_size = 512;
const yield_delay = 1;
/*javascript:*//*** CB app/bot data Save'n'restore bookmarklet v2 ***/(function () {
"use strict";
// Enable the passage of the 'this' object through the JavaScript timers
window["__nativeST__"] = window["__nativeST__"] || window["setTimeout"];
window["__nativeSI__"] = window["__nativeSI__"] || window["setInterval"];
window["setTimeout"] = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */ ) {
var oThis = this,
aArgs = Array.prototype.slice.call(arguments, 2);
return window["__nativeST__"](vCallback instanceof Function ? function () {
vCallback.apply(oThis, aArgs);
} : vCallback, nDelay);
};
window["setInterval"] = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */ ) {
var oThis = this,
aArgs = Array.prototype.slice.call(arguments, 2);
return window["__nativeSI__"](vCallback instanceof Function ? function () {
vCallback.apply(oThis, aArgs);
} : vCallback, nDelay);
};
}());
(function (window, document, version, app_callback) {
"use strict";
window[app] = { "version": sSig };
/*** jQuery/alertifyjs/font-awesome preloader v3, startof ***/
version = version || "1";
app_callback = app_callback || function (a, b, c, d, cont) {
setTimeout(cont, 10000, a, b, c, d);
};
const root = "//cdn.jsdelivr.net/alertifyjs/1.4.1/";
var element = document.getElementById(id), // NB. element is re-cycled after first use
cleanup = [],
$,
$_loaded = false,
ajs,
ajs_loaded = false,
alertify,
state;
//
/**
* @param {Array<Function>} tasks
* @param {number=} idx
*/
function run(tasks, idx) {
idx = idx || 0;
tasks[idx++]((function next(tasks, idx) {
return function () { // NB. this closure may be unnecessary, but better safe than sorry
if (idx < tasks.length) {
window.setTimeout(function () {
run(tasks, idx);
}, yield_delay);
}
};
}(tasks, idx)));
}
// retrict to *chaturbate.com and prevent multiple instances
if (!/(camgasm|chaturbate)\.com$/i.test(document.location.hostname)) {
window.alert("*chaturbate.com only bookmarklet");
} else if (!element) {
run([
function (cont) {
/**/
// splash screen
//var style = ("<style>." + magicnumber + "-no-overflow {overflow:hidden!important;outline:0;}${visibility:visible;position:fixed;top:0;left:0;height:100%;width:100%;z-index:10;background-color:rgba(0,0,0,0.05);}$ div {visibility:visible;width:300px;margin:100px auto;background-color:#fff;border:1px solid #000;padding:15px;text-align:center;}</style>").replace(/\$/g, "#" + id);
var style = ("<style>." + magicnumber + "-no-overflow {overflow:hidden!important;outline:0;}${visibility:visible;position:fixed;top:0;right:0;left:0;bottom:0;overflow-y:auto;padding:0;z-index:10;background-color:rgba(0,0,0,0.05);}$ div {visibility:visible;width:300px;margin:100px auto;background-color:#fff;border:1px solid #000;padding:15px;text-align:center;}</style>").replace(/\$/g, "#" + id);
//
if (debug) {
console.log(">do overlay and splash screen");
}
element = document.createElement("div");
element.setAttribute("id", id);
element.style.display = "block";
element.innerHTML = style + "<div>Starting...</div>";
document.body.appendChild(element);
document.body.classList.add(magicnumber + "-no-overflow");
cleanup.push(element);
setTimeout(function () {
cont();
}, 1000);
},
function (cont) {
if (debug) {
console.log(">do jquery load");
}
$ = window.jQuery;
if ($ && version <= $.fn.jquery) {
cont();
} else {
element = document.createElement("script");
element.type = "text/javascript";
element.src = "//ajax.googleapis.com/ajax/libs/jquery/" + version + "/jquery.min.js";
element.onload = element.onreadystatechange = function () {
state = this.readyState;
if (!$_loaded && (!state || state === "loaded" || state === "complete")) {
($ = window.jQuery).noConflict($_loaded = true);
if (debug) {
console.log("loaded: " + element.src);
}
// Handle memory leak in IE
element.onload = element.onreadystatechange = null;
cont();
}
};
//document.head.appendChild(element);
document.documentElement.childNodes[0].appendChild(element);
if (!persist) {
cleanup.push(element);
}
}
},
function (cont) {
if (debug) {
console.log(">do stylesheet load(s)");
}
// Loading style definitions
[
[root + "css/alertify.min.css", "alertify-notifier ajs-left", "left", "10px"],
[root + "css/themes/default.min.css"], // note no adequate test for theme stylesheet, so just test for file presence
["//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css", "fa", "font-family", "FontAwesome"]
].forEach(function (val) {
function testStyleRule(url, name, css, value) {
var dummy = $("<div>").hide().css({
height: 0,
width: 0
}).addClass(name || "").appendTo("body"),
res = dummy.css(css || "") === value; // true if name css and value are all undefined
//
dummy.remove();
return $("link[rel=\"stylesheet\"][href$=\"" + url.match(/(?:\/)[^\/]+$/) + "\"]").length && res;
}
if (!testStyleRule.apply(null, val)) {
element = document.createElement("link");
element.setAttribute("rel", "stylesheet");
element.setAttribute("href", val[0]);
document.head.appendChild(element);
if (debug) {
console.log("loaded: " + val[0]);
}
if (!persist) {
cleanup.push(element);
}
}
});
cont();
},
function (cont) {
if (debug) {
console.log(">do alertifyjs load");
}
// check for alertifyjs else Load the script and when it's ready run the cont callback.
ajs = window["alertify"];
if (ajs && ajs["dialog"]) {
cont();
} else {
// Load the script and when it's ready loading run the cont callback.
// BEWARE. recycling element and state var
element = document.createElement("script");
element.src = root + "alertify.min.js";
// Attach handlers for all browsers
element.onload = element.onreadystatechange = function () {
state = this.readyState;
if (!ajs_loaded && (!state || state === "loaded" || state === "complete")) {
alertify = window["alertify"];
window["alertify"] = ajs;
ajs = alertify;
ajs_loaded = true;
if (debug) {
console.log("loaded: " + element.src);
}
// Handle memory leak in IE
element.onload = element.onreadystatechange = null;
cont();
}
};
//document.head.appendChild(element);
document.documentElement.childNodes[0].appendChild(element);
if (!persist) {
cleanup.push(element);
}
}
},
function (cont) {
if (debug) {
console.log(">clear splash screen, after 3s delay");
}
setTimeout(function () {
var child = document.querySelector("#" + id + " div");
if (child) {
child.style.visibility = "hidden";
}
}, 3000);
cont();
},
function (cont) {
if (debug) {
console.log(">run app");
}
app_callback($, $_loaded, ajs, ajs_loaded, cont);
},
function (cont) {
if (debug) {
console.log(">schedule alertifyjs remnants removal");
}
// delay alertify remnants removal, so as not to interfere with alertifyjs's own cleanup process
if (ajs_loaded) {
setTimeout(function () {
Array.prototype.slice.call(document.querySelectorAll("div[class*=\"alertify-\"],div[class*=\"ajs-\"]")).forEach(function (node) {
node.parentElement.removeChild(node);
});
}, 2000);
}
cont();
},
function (cont) {
if (debug) {
console.log(">do final cleanup, remove any added script and link tags");
}
// final cleanup
cleanup.forEach(function (element) {
element.parentNode.removeChild(element);
});
document.body.classList.remove(magicnumber + "-no-overflow");
cont();
}
]);
}
/*** jQuery/alertifyjs/font-awesome preloader v3, endof ***/
}(window, document, "1.6.4", function ($, $L, alertify, alertifyL, complete) {
// CBS alertifyjs'd app/bot data combined save/restorer
"use strict";
if (debug) {
console.log("jQuery loaded: ", $L, " AlertifyJS loaded: ", alertifyL);
}
const post_timeout_delay = 5 * 1000; // msec
const autosave_interval_delay = (debug ? 1 : 3) * 60 * 1000; // msec
const retry_max = 5;
const retry_delay = 1000; // msec
const notify_delay = 3; // sec
var _alert,
confirm,
dialog,
div = $("<div class=" + magicnumber + "_buttons></div>"),
style = $("<style>.$_buttons{margin:0 auto;padding:10px 20px;}.$_buttons button{display:block;width:100%;margin:5px 0;}.$_buttons input[type=checkbox][disabled] + label{color: #ccc;}</style>".replace(/\$/g, magicnumber)),
slots = ["Active App", "Bot #1", "Bot #2", "Bot #3"];
/** @type {Object} */
var apps = ["", "", "", ""];
var ops = {
4: "Query",
6: "Save",
7: "Restore"
},
ls = window["localStorage"],
saves,
timestamp,
data,
post_timeout = null,
reTag = new RegExp("^\\/#[0-3]" + magicnumber + "\\/"); // NB. this must match reTag definition below
//
function inChat() {
return ((window["ws_handler"] && window["ws_handler"]["connected"]) || (window["websocket_handler"] && window["websocket_handler"]["connected"]) || (window["flash_handler"] && window["flash_handler"]["connected"]) || (window["html_handler"] && window["html_handler"]["connected"]));
}
function ts() {
var a = new Date(),
b = /(..)(:..)(:..)/.exec(a),
c = b[1] % 12 || 12; // NB.NB.NB. must manually insert space or convert to hex number format after ClosureCompiler optimization, as %12 will be interpreted as an escape character, in a bookmarklet! see "Uncaught SyntaxError: Unexpected token ILLEGAL"
return " " + (10 > c ? "0" + c : c) + b[2] + b[3] + " " + (12 > b[1] ? "A" : "P") + "M " + a.getTime().toString().slice(-3);
}
window[magicnumber + "_autosave_interval"] = window[magicnumber + "_autosave_interval"] || null;
if (window[magicnumber + "_autosave_interval"]) {
window.clearInterval(window[magicnumber + "_autosave_interval"]); // temporarily suspend autosave while CBS menu open
if (debug) {
console.log(ts() + ": AutoSave suspended");
}
}
window[magicnumber + "_ping"] = window[magicnumber + "_ping"] || null;
//apps["enm"] = function (fn, cb) { // enumerate function
/**
* @this {Object}
*/
Array.prototype["enm"] = Array.prototype["enm"] || function (fn, cb) { // enumerate function fn over an Array and call cb on completion
this["idx"] = ++this["idx"] || 0;
if (arguments.length) {
this["fn"] = fn || null;
this["cb"] = cb || null;
}
if (this["idx"] < this.length) {
if (this["fn"]) {
this["fn"]["call"](this);
}
} else {
delete this["idx"];
if (this["cb"]) {
this["cb"]["call"](this);
}
}
};
function setDialog(content) {
div.empty().append(style, $("<p>" + content + "</p>"));
if (dialog && dialog["isOpen"]()) {
dialog["setContent"](div[0])["set"]("closable", true);
}
}
function closeDialog() {
if (debug) {
console.log(ts() + ": closeDialog", dialog, dialog["isOpen"]());
}
if (dialog && dialog["isOpen"]()) {
dialog["close"]()["set"]("closable", true);
}
}
alertify.set("notifier", "delay", notify_delay);
//alertify.set("notifier", "callback", closeDialog); // NB. this doesn't work. see alertifyjs source code. only set position or delay. 'arse! so...
function success(message) {
alertify["success"](message)["callback"] = closeDialog;
}
function error(message) {
alertify["error"](message)["callback"] = closeDialog;
}
/*function notify(message) {
alertify["notify"](message)["callback"] = closeDialog;
}*/
function postChat(message) {
if (debug) {
console.log(ts() + "> " + JSON.stringify(window[magicnumber + "_ping"]) + " | " + JSON.stringify(message));
}
var op = ops[window[magicnumber + "_ping"].split("/").length],
handler;
window["$message_sender"]["confirmed_send"] = true; // avoid first-post hurdle
// @ref *_handler.message_outbound.send_room_message
if ((handler = window["flash_handler"]) !== undefined && handler["connected"]) {
handler["consolelog"]("User is sending message: " + message);
message = $.toJSON({
"m": message,
"c": "",
"f": ""
});
handler["consolelog"](message);
window["GetFlashObject"]("movie")["SendRoomMsg"](message);
} else if ((handler = window["html_handler"]) !== undefined && handler["connected"]) {
handler["consolelog"]("User is sending message: " + message);
message = $.toJSON({
"m": message,
"c": "",
"f": ""
});
handler["consolelog"](message);
// either
//handler["post_html_chat"](message);
// or, this if we don't want any spurious alert popups alarming the user ;)
$.ajax({
"url": handler["post_address"],
"dataType": "json",
"data": {
"room": handler["room_owner_nick"],
"message": message,
"username": handler["user"]
},
"type": "POST",
"success": function (response) {
if (response === "") {
if (debug) {
error("An error occurred");
}
return;
}
if (response["X-Spam"]) {
handler["message_inbound"]["on_room_message"](handler["user"], message);
}
}
});
} else if ((handler = window["websocket_handler"]) !== undefined && handler["connected"]) {
handler["connection"]["send"](JSON.stringify({
'action': 'msg',
'msg': message
}));
} else if ((handler = window["ws_handler"]) !== undefined && handler["connected"]) {
handler["consolelog"]("User is sending message: " + message);
var message = $.toJSON({
"m": message,
"c": "",
"f": ""
});
handler["consolelog"](message);
handler["SendRoomMsg"](message);
} else {
if (debug) {
console.log(ts() + ": \"" + op + "\" Failed. Not connected.");
}
if (window[magicnumber + "_autosave_interval"]) {
apps["enm"]();
} else {
setDialog(op + " Failed. Chat may be disconnected.");
}
}
//} // else if no handler, no message will be posted and ping will timeout
}
function qryApps() {
var self = this;
var $app = $(".stop_link[name=\"/app/stop/" + self["idx"] + "/\"]");
self[self["idx"]] = $app.length ? $app[0].parentNode.previousElementSibling.innerHTML.trim() : "";
if (self[self["idx"]].length && inChat()) {
window[magicnumber + "_ping"] = ["", "#" + self["idx"] + magicnumber, timestamp, ""].join("/");
postChat(window[magicnumber + "_ping"]);
post_timeout = setTimeout.call(self, function () {
// if ?? qry times out, app is most likely not CBS aware, so blank it and move on
this[this["idx"]] = "";
this["enm"]();
}, post_timeout_delay);
} else {
self["enm"]();
}
}
function onPostTimeout() {
var op = ops[window[magicnumber + "_ping"].split("/").length];
post_timeout = null;
if (debug) {
console.log(ts() + ": \"" + op + "\" Failed. Post timed-out."); // ie. the posted ping did not show up in chat yet
}
if (window[magicnumber + "_autosave_interval"]) {
apps["enm"]();
} else {
setDialog(op + " Failed. Chat may be unresponsive.");
}
}
/**
* @param {number} slot
* @param {number=} start
*/
function txData(slot, start) {
var chunk;
start = start || 0;
chunk = data.slice(start, start + chunk_size);
window[magicnumber + "_ping"] = ["", "#" + slot + magicnumber, timestamp, start, chunk, chunk.length, ""].join("/");
postChat(window[magicnumber + "_ping"]);
post_timeout = setTimeout(onPostTimeout, post_timeout_delay);
}
/**
* @param {number} slot
* @param {number=} start
*/
function rxData(slot, start) {
start = start || 0;
window[magicnumber + "_ping"] = ["", "#" + slot + magicnumber, timestamp, start, ""].join("/");
postChat([window[magicnumber + "_ping"], ""].join("/"));
post_timeout = setTimeout(onPostTimeout, post_timeout_delay);
}
function autosaveApps() {
var self = this;
if (self[self["idx"]].length && inChat() && window[magicnumber + "_autosave_interval"]) {
if (debug) {
console.log(ts() + ": " + "AutoSave \"" + slots[self["idx"]] + "\"");
}
rxData(self["idx"]);
} else {
self["enm"]();
}
}
/**
* @param {Function=} cb
*/
function onAutoSave(cb) {
function onAutoSaved() {
if (debug) {
console.log(ts() + ": AutoSave: onAutoSaved: retry: " + onAutoSave.retry);
}
if (!onAutoSave.saving) {
cb.call(this);
} else if (--onAutoSave.retry) {
setTimeout(onAutoSaved, retry_delay);
} else {
if (debug) {
console.log(ts() + ": AutoSave: onAutoSaved: Failed. retry timed-out.");
}
cb.call(this);
}
}
if (window[magicnumber + "_autosave_interval"]) {
if (!onAutoSave.saving) {
onAutoSave.saving = true;
if (!$("#" + id).length) { // don't autosave while overlay exists, ie. dialog is open
if (debug) {
console.log(ts() + ": AutoSave: " + JSON.stringify(apps));
}
apps["enm"](qryApps, function () {
timestamp = (new Date()).valueOf();
apps["enm"](autosaveApps, function () {
onAutoSave.saving = false;
if (cb) {
cb.call(this);
}
});
});
} else { // shouldn't be reachable, as onAutoSave shouldn't be being called while menu is up
if (debug) {
console.log(ts() + "! Missed AutoSave. CBS Menu open.");
}
}
} else if (cb) { // only need to wait if there is a callback
onAutoSave.retry = retry_max;
setTimeout(onAutoSaved, retry_delay);
}
} else if (cb) {
cb.call(this);
}
}
function getSaves() {
var a, i;
//
saves = [];
for (i = 0; i < ls.length; i++) {
a = ls.key(i).split("/");
while (a.length > 4) { // deal with appnames that include '/'
a[1] = [a[1]].concat(a.splice(2, 1)).join("/");
}
if (a.length === 4 && a[0] === magicnumber/* && apps.indexOf(a[1]) !== -1*/) {
saves.push(a.concat(ls.key(i), [
a[1], // appname
(new Date(parseInt(a[3], 10))).toLocaleString(), // timestamp
slots[parseInt(a[2], 10)] // slot
].join(" ")));
}
}
}
//
// v2.1
// obj.m transformations
// Qr /[_tag]/[timestamp]/ => /[_tag]/[timestamp]/?
// a.length === 4 && a[3] === "" => a.length === 4 && a[3] !== ""
// Sv /[_tag]/[timestamp]/[start]// => /[_tag]/[timestamp]/[start]/[sent]/[sent.length]
// a.length === 6 && a[5] === "" => a.length === 6 && a[5] !== ""
// Ld /[_tag]/[timestamp]/[start]/[sent]/[sent.length]/ => /[_tag]/[timestamp]/[start]/[sent]/[sent.length]/[received.length]
// a.length === 7 && a[6] === "" => a.length === 7 && a[6] !== ""
//
function onRestore(msg_hdr, msg_tag, msg_timestamp, msg_data_start, msg_data, msg_data_length, msg_data_received_length) {
if (debug) {
console.log(ts() + ": " + "onRestore: hdr, ts: ", msg_hdr, msg_timestamp);
}
var slot = parseInt(msg_tag[1], 10);
if (debug) {
console.log(ts() + ": onRestore: ", msg_data.length, msg_data_length, msg_data_received_length);
}
if (msg_data) {
txData(slot, parseInt(msg_data_start, 10) + parseInt(msg_data_received_length, 10));
} else {
//success("Restored."); // let CBS enabled app/bot report success
closeDialog();
}
}
function destroyClickedElement(event) {
document.body.removeChild(event.target);
}
function saveAsFile(key, data) {
var blob = new window["Blob"]([JSON.stringify({
"key": key,
"value": data
})], {
"type": "application/json"
});
var fileName = key.split("/").slice(0, -1).join("_") + ".json";
var downloadLink = document.createElement("a");
downloadLink.download = fileName;
downloadLink.innerHTML = "Download File";
if (window.webkitURL !== null) {
// Chrome allows the link to be clicked
// without actually adding it to the DOM.
downloadLink.href = window.webkitURL.createObjectURL(blob);
} else {
// Firefox requires the link to be added to the DOM
// before it can be clicked.
downloadLink.href = window.URL.createObjectURL(blob);
downloadLink.onclick = destroyClickedElement;
downloadLink.style.display = "none";
document.body.appendChild(downloadLink);
}
downloadLink.click();
}
function loadFromFile(target_slot) {
var fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.onchange = function (event) {
var file = event.target.files[0];
var reader = new window["FileReader"]();
reader.onload = function (fileLoadedEvent) {
var loaded = JSON["parse"](fileLoadedEvent.target.result);
var a = (loaded["key"] || "").split("/");
while (a.length > 4) { // deal with appnames that include '/'
a[1] = [a[1]].concat(a.splice(2, 1)).join("/");
}
if (a.length === 4 && a[0] === magicnumber && a[1] === apps[target_slot]) {
data = loaded["value"];
txData(target_slot);
} else {
//setDialog("\"" + file["name"] + "\" does not contain \"" + apps[target_slot] + "\" save data."); // can't use dialog, cos it's closed by now
alert("\"" + file["name"] + "\" does not contain \"" + apps[target_slot] + "\" save data.");
}
};
reader.readAsText(file, "UTF-8");
if (event.target.parentNode) {
event.target.parentNode.removeChild(event.target);
}
};
if (window.webkitURL !== null) { // yeah, it's an empty block, jslint. so what? it'll get compiled out.
// Chrome allows the elements to be clicked
// without actually adding them to the DOM.
} else {
// Firefox requires them to be added to the DOM
// before they can be clicked.
fileInput.style.display = "none";
document.body.appendChild(fileInput); // potential memory leak if user cancels file open dialog
}
fileInput.click();
}
function onSave(msg_hdr, msg_tag, msg_timestamp, msg_data_start, msg_data, msg_data_length) {
if (debug) {
console.log(ts() + ": " + "onSave: hdr, ts: ", msg_hdr, msg_timestamp);
}
var i,
slot = parseInt(msg_tag[1], 10),
found = -1;
function okSave() {
var key = [magicnumber, apps[slot], slot, msg_timestamp].join("/"); // magicnumber/appname/slot/timestamp
if (debug) {
console.log(ts() + ": onSave: " + (window[magicnumber + "_autosave_interval"] ? "Auto" : "") + "Saved.");
}
if (ls[magicnumber + "_use_local_storage"] !== "false") {
ls[key] = data;
if (window[magicnumber + "_autosave_interval"]) {
apps["enm"]();
} else {
success("Saved.");
}
} else {
saveAsFile(key, data);
closeDialog();
}
}
function okOverwrite() {
ls["removeItem"](saves[found][4]);
okSave();
}
if (debug) {
console.log(ts() + ": onSave: ", msg_data.length, parseInt(msg_data_length, 10));
}
if (msg_data_start === "0") {
data = msg_data;
} else {
data += msg_data;
}
if (msg_data) {
rxData(slot, data.length);
} else {
if (data.length) {
if (ls[magicnumber + "_use_local_storage"] !== "false") {
if (window[magicnumber + "_autosave_interval"]) {
getSaves();
}
for (i = 0; i < saves.length; i++) {
if (msg_tag[1] === saves[i][2] && apps[slot] === saves[i][1]) {
found = i;
break;
}
}
if (found > -1) {
if (window[magicnumber + "_autosave_interval"]) {
okOverwrite();
} else {
confirm("Confirm Overwrite...", "Overwrite existing \"" + saves[found][5] + "\" saved data?", okOverwrite, function () {
if (debug) {
console.log(ts() + ": Overwrite cancelled.");
}
error("Overwrite cancelled.");
});
}
} else {
okSave();
}
} else {
okSave();
}
} else {
if (debug) {
console.log(ts() + ": onSave: null data");
}
if (window[magicnumber + "_autosave_interval"]) {
apps["enm"]();
} else {
setDialog("No Data. \"" + apps[slot] + "\" did not respond with data to save.");
}
}
}
}
function onData(message) {
if (post_timeout) {
clearTimeout(post_timeout);
post_timeout = null;
}
if (debug) {
console.log(ts() + ": onData: ", JSON.stringify(message));
}
var op = ops[window[magicnumber + "_ping"].split("/").length],
a = message.split("/"),
slot = parseInt(a[1][1], 10);
//
if (a[a.length - 1] === "") {
if (a.length === 4) { // no response to Query means app is not CBS aware, so ignore it and move on
// either
apps[slot] = "";
// or
//apps[apps["idx"]] = ""; // in this case slot === apps[apps.idx]
apps["enm"]();
} else { // no response from any other ping type is an error
if (debug) {
console.log(ts() + ": \"" + op + "\" Failed. No Response");
}
setDialog(op + " Failed. \"" + apps[slot] + "\" did not respond to \"" + op + "\" command.");
}
} else if (a.length === 4) {
// Qr
apps["enm"](); // note apps.enm knows all about continue callback
} else if (a.length === 6) {
// Sv
onSave.apply(this, a);
} else if (a.length === 7) {
// Ld
onRestore.apply(this, a);
} else {
if (debug) {
console.log(ts() + ": Houston, we have a problem!"); // 'when the myth becomes legend, print the myth'
}
if (window[magicnumber + "_autosave_interval"]) {
if (debug) {
console.log(ts() + ": \"" + apps[slot] + "\" gave an unknown response to \"AutoSave\" command.");
}
apps["enm"]();
} else {
setDialog(op + " Aborted. \"" + apps[slot] + "\" gave an unknown response to \"" + op + "\" command.");
}
}
}
function handle($btn) {
var i;
function cancelDeleteALL() {
if (debug) {
console.log(ts() + ": " + "Delete ALL cancelled.");
}
error("Delete ALL cancelled.");
}
dialog["set"]("closable", false);
$btn.html("Processing...");
switch ($btn.attr("class")) {
case "da":
if (debug) {
console.log(ts() + ": " + "Delete ALL");
}
confirm("Confirm Delete ALL...", "Delete ALL saved data?", function () {
confirm("Confirm Confirm Delete ALL...", "Are you really sure you want to Delete ALL saved data?", function () {
for (i = 0; i < saves.length; i++) {
ls["removeItem"](saves[i][4]);
}
success("ALL Deleted.");
}, cancelDeleteALL);
}, cancelDeleteALL);
break;
case "ld":
if (debug) {
console.log(ts() + ": Restore \"" + $btn.data("k") + "\" to \"" + $btn.data("s") + "\"");
}
data = ls[saves[$btn.data("i")][4]];
txData(slots.indexOf($btn.data("s")));
break;
case "lf":
if (debug) {
console.log(ts() + ": Restore file to \"" + $btn.data("s") + "\"");
}
loadFromFile(slots.indexOf($btn.data("s")));
closeDialog();
break;
case "rm":
if (debug) {
console.log(ts() + ": Delete \"" + $btn.data("k") + "\"");
}
confirm("Confirm Delete...", "Delete \"" + saves[$btn.data("i")][5] + "\" saved data?", function () {
ls["removeItem"]($btn.data("k"));
success("Deleted.");
}, function () {
if (debug) {
console.log(ts() + ": Delete cancelled.");
}
error("Delete cancelled.");
});
break;
case "sv":
if (debug) {
console.log(ts() + ": Save \"" + $btn.data("s") + "\"");
}
rxData(slots.indexOf($btn.data("s")));
break;
}
}
//function subclass(ns_string, func) {
//function leaf(obj, ns_string) {
//var parts = ns_string.split("."),
//i,
//length = parts.length;
//for (i = 0; i < length; i++) {
//if (obj === undefined || !obj.hasOwnProperty(parts[i])) {
//return null;
//}
//obj = obj[parts[i]];
//}
//return obj;
//}
//var chain = ns_string.split("."),
//target = chain[chain.length - 1],
//handler = leaf(window, chain[0]),
//node = leaf(window, chain.slice(0, -1).join("."));
//if (handler && node && node.hasOwnProperty(target)) {
//if (!node.hasOwnProperty("orig_" + target)) {
//node["orig_" + target] = node[target];
//}
//node[target] = func;
//}
//}
const _orig = "_orig";
const _handler = "_root";
function subclass2(ns_string, func) {
function leaf(obj, ns_string) {
var parts = ns_string.split("."),
i,
length = parts.length;
for (i = 0; i < length; i++) {
if (obj === undefined || !obj.hasOwnProperty(parts[i])) {
return null;
}
obj = obj[parts[i]];
}
return obj;
}
var chain = ns_string.split("."),
target = chain[chain.length - 1],
handler = leaf(window, chain[0]),
node = leaf(window, chain.slice(0, -1).join("."));
if (handler && node && node.hasOwnProperty(target) && typeof node[target] === "function") {
var orig = node[target][app + _orig];
if (typeof orig !== "function") {
orig = node[target];
}
node[target] = func;
node[target][app + _orig] = orig;
node[target][app + _handler] = handler;
} else if (debug) {
console.error("failed to subclass: ", ns_string);
}
}
subclass2("flash_handler.message_inbound.on_room_message", function fn(nick, message) {
var handler = fn[app + _handler],
m = unescape(message),
msginfo;
try {
msginfo = $.parseJSON(m);
} catch (e) {
if (debug) {
console.error(e.message);
}
msginfo = {
"m": m
};
}
m = handler["striphtml"](msginfo["m"]).replace(/\s*/g, ""); // must strip space cos cb tends to add it to messages
if (debug) {
console.log(ts() + "< " + JSON.stringify(window[magicnumber + "_ping"]) + " | " + JSON.stringify(m));
}
if (m.indexOf(window[magicnumber + "_ping"]) === 0) {
if (handler["sanitize"](nick) === handler["room"]) {
onData(m);
}
return true;
}
return fn[app + _orig].call(this, nick, message);
});
subclass2("html_handler.message_inbound.on_room_message", function fn(nick, msginfo, index) {
var handler = fn[app + _handler],
m = handler["striphtml"](msginfo["m"]).replace(/\s*/g, ""); // must strip space cos cb tends to add it to messages
if (m.indexOf(window[magicnumber + "_ping"]) === 0) {
if (debug) {
console.log(ts() + "< " + JSON.stringify(window[magicnumber + "_ping"]) + " | " + JSON.stringify(m));
}
if ((handler["sanitize"](nick) || handler["sanitize"](msginfo["user"])) === handler["room"]) {
onData(m);
}
return true;
}
if (reTag.test(m)) {
if (debug) {
console.log(ts() + "? " + JSON.stringify(window[magicnumber + "_ping"]) + " | " + JSON.stringify(m));
}
return true;
}
if (debug) {
console.log(ts() + "< " + JSON.stringify(m));
}
return fn[app + _orig].call(this, nick, msginfo, index);
});
subclass2("websocket_handler.message_inbound.on_room_message", function fn(msginfo) {
if (msginfo["m"] === undefined) {
return true;
}
var handler = fn[app + _handler],
m = handler["striphtml"](msginfo["m"]).replace(/\s*/g, ""); // must strip space cos cb tends to add it to messages
if (m.indexOf(window[magicnumber + "_ping"]) === 0) {
if (debug) {
console.log(ts() + "< " + JSON.stringify(window[magicnumber + "_ping"]) + " | " + JSON.stringify(m));
}
if (handler["sanitize"](msginfo["user"]) === handler["room"]) {
onData(m);
}
return true;
}
if (reTag.test(m)) {
if (debug) {
console.log(ts() + "? " + JSON.stringify(window[magicnumber + "_ping"]) + " | " + JSON.stringify(m));
}
return true;
}
if (debug) {
console.log(ts() + "< " + JSON.stringify(m));
}
return fn[app + _orig].call(this, msginfo);
});
subclass2("ws_handler.message_inbound.on_room_message", function fn(nick, message) {
var handler = fn[app + _handler],
m = /*unescape*/(message),
msginfo;
try {
msginfo = $.parseJSON(m);
} catch (e) {
if (debug) {
console.error(e.message);
}
msginfo = {
"m": m
};
}
m = handler["striphtml"](msginfo["m"]).replace(/\s*/g, ""); // must strip space cos cb tends to add it to messages
if (debug) {
console.log(ts() + "< " + JSON.stringify(window[magicnumber + "_ping"]) + " | " + JSON.stringify(m));
}
if (m.indexOf(window[magicnumber + "_ping"]) === 0) {
if (handler["sanitize"](nick) === handler["room"]) {
onData(m);
}
return true;
}
return fn[app + _orig].call(this, nick, message);
});
if (debug) {
subclass2("jQuery.mydefchatconn", function fn(method) {
if (method === "app_tab_refresh" || method === "update_panel") {
console.log("$.mydefchatconn(\"" + method + "\")");
}
return fn[app + _orig].apply(this, arguments);
});
}
function setStopLink() {
$(".stop_link").unbind("click").click(function () {
var that = this;
onAutoSave(function () {
$.ajax({
url: $(that).attr('name'),
dataType: 'text',
data: '',
type: 'POST',
success: function (/*response*/) {
$["mydefchatconn"]('app_tab_refresh');
}
});
});
});
}
if (inChat()) {
$('.info-user a[data-tab="apps_and_bots"]').unbind("click").click(function () {
(function (tab, target, func) {
tab.show();
tab.html(window["gettext"]("loading . . ."));
if (func) {
tab.load(target, func);
} else {
tab.load(target);
}
}($(".info-user div.apps_and_bots"), $(".info-user .buttons a[data-tab='apps_and_bots']").attr('href'), setStopLink));
});
setStopLink();
}
var headerSpan = "<span style=\"color:#dc5500; font-family: 'UbuntuBold', Arial, Helvetica, sans-serif;\">";
function af(icon) {
return "<i class=\"" + icon + "\" style=\"vertical-align:middle; margin-right:20px;\"></i>";
}
// custom 'confirm' dialog
if (!confirm) {
alertify["dialog"](magicnumber + "Confirm", function () {
return {
"build": function () {
this["setting"]("defaultFocus", "cancel");
},
"prepare": function () {
this["setHeader"](headerSpan + af("fa fa-exclamation-triangle fa-2x") + magicnumber + ": " + this["get"]("title") + "</span>");
}
};
}, true, "confirm");
confirm = alertify[magicnumber + "Confirm"];
}
// custom 'alert' dialog
if (!_alert) {
alertify["dialog"](magicnumber + "Alert", function () {
return {
"build": function () {
this["setHeader"](headerSpan + af("fa fa-cog fa-2x") + magicnumber + ": CB app/bot data Save'n'restore bookmarklet " + ver + "</span>");
},
"setup": function () {
return {
"buttons": [{
"text": "Close",
"key": 27 /*Esc*/
}],
"focus": {
"element": 0
},
"options": {
"maximizable": false,
"resizable": false,
"padding": false
}
};
},
"prepare": function () {
var $btns = $("." + magicnumber + "_buttons button");
if (ls[magicnumber + "_use_local_storage"] !== "false") {
$btns.filter(".lf").hide();
} else {
$("#" + magicnumber + "_as").prop("disabled", true);
$btns.filter(".ld, .rm, .da").hide();
}
if ($(".chat-box ul.buttons li a[data-tab=\"autosave\"]").length) {
$btns.filter(".sv, .ld, .lf").prop("disabled", true);
}
$btns.click(function (evt) {
evt.preventDefault();
// either
//$btns.attr("disabled", true);
// or
$btns.prop("disabled", true);
handle($(this));
});
$("#" + magicnumber + "_ls").change(function (/*evt*/) {
var isAutoSave = $(".chat-box ul.buttons li a[data-tab=\"autosave\"]").length;
ls[magicnumber + "_use_local_storage"] = $(this).is(':checked'); //!(ls[magicnumber + "_use_local_storage"] === "true");
if (ls[magicnumber + "_use_local_storage"] !== "false") {
$("#" + magicnumber + "_as").prop("disabled", false);
$btns.filter(".ld").prop("disabled", isAutoSave);
$btns.filter(".ld, .rm, .da").show();
$btns.filter(".lf").hide();
} else {
$("#" + magicnumber + "_as").prop("disabled", true);
$btns.filter(".ld, .rm, .da").hide();
$btns.filter(".lf").prop("disabled", isAutoSave);
$btns.filter(".lf").show();
$("#" + magicnumber + "_as").removeProp("checked").change();
}
});
$("#" + magicnumber + "_as").change(function (/*evt*/) {
if ($(this).is(':checked')) {
if (debug) {
console.log(ts() + ": AutoSave On");
}
$(".chat-box ul.buttons li a[data-tab=\"settings\"]").parent().before($("<li></li>").html("<a href=\"#\" data-tab=\"autosave\" class=\"nooverlay\">AutoSave</a>"));
$btns.filter(".sv, .ld, .lf").prop("disabled", true);
// see dialog onclose hooks for actual autosave init
} else {
if (debug) {
console.log(ts() + ": AutoSave Off");
}
$(".chat-box ul.buttons li a[data-tab=\"autosave\"]").parent().remove();
$btns.filter(".sv, .ld, .lf").prop("disabled", false);
/*if (window[magicnumber + "_autosave_interval"]) {
clearInterval(window[magicnumber + "_autosave_interval"]);
}
window[magicnumber + "_autosave_interval"] = null;*/
}
});
},
"hooks": {
"onclose": function () {
// autosave
if ($(".chat-box ul.buttons li a[data-tab=\"autosave\"]").length) {
window[magicnumber + "_autosave_interval"] = setInterval(onAutoSave, autosave_interval_delay);
if (debug) {
console.log(ts() + ": AutoSave activated");
}
setTimeout(onAutoSave, notify_delay * 2 * 1000); // schedule an ~immediate autosave - won't work if menu is still open. fingers crossed!
} else {
window[magicnumber + "_autosave_interval"] = null;
}
// clear menu
$("." + magicnumber + "_buttons").remove();
complete();
}
}
};
}, null, "alert");
_alert = alertify[magicnumber + "Alert"];
}
function menu() {
var i, j, k, btn,
bSomeCBSApps = apps.some(function (v) {
return v;
}),
bInChat = inChat();
if (debug) {
console.log(ts() + ": apps: " + JSON.stringify(apps));
}
div.append(style);
getSaves();
if (saves.length || (bSomeCBSApps && bInChat)) {
// NB. while autosave is enabled, manual saves/restores are disabled
for (j = 0; j < apps.length; j++) {
if (apps[j] && bInChat) {
btn = $(("<button data-s=\"^\" data-a=\"$\" class=\"sv\">Save ^ \"$\" data</button>").replace(/\^/g, slots[j]).replace(/\$/g, apps[j]));
div.append(btn);
btn = $(("<button data-s=\"^\" data-a=\"$\" class=\"lf\">Restore ^ \"$\" saved data from file</button>").replace(/\^/g, slots[j]).replace(/\$/g, apps[j]));
div.append(btn);
for (i = 0; i < saves.length; i++) {
if (apps[j] === saves[i][1]) {
k = saves[i][4]; // key
btn = $(("<button data-s=\"^\" data-i=\"" + i + "\" data-k=\"" + k + "\" class=\"ld\">Restore \"" + saves[i][5] + "\" saved data into ^</button>").replace(/\^/g, slots[j]));
div.append(btn);
}
}
}
}
for (i = 0; i < saves.length; i++) {
btn = $("<button data-i=\"" + i + "\" data-k=\"" + k + "\" class=\"rm\">Delete \"" + saves[i][5] + "\" saved data</button>");
div.append(btn);
}
if (saves.length > 1) {
btn = $("<button class=\"da\">Delete ALL saved data</button>");
div.append(btn);
}
btn = $("<input type=\"checkbox\" id=\"" + magicnumber + "_ls\"><label for=\"" + magicnumber + "_ls\">Use browser Local Storage</label>");
if (ls[magicnumber + "_use_local_storage"] !== "false") {
btn.prop("checked", true);
}
div.append(btn);
if (/*bSomeCBSApps && */bInChat) {
btn = $("<input type=\"checkbox\" id=\"" + magicnumber + "_as\"><label for=\"" + magicnumber + "_as\">AutoSave</label>");
if ($(".chat-box ul.buttons li a[data-tab=\"autosave\"]").length) {
btn.prop("checked", true);
}
div.append(btn);
}
} else {
// alert: nothing to do
div.append($("<p>Nothing to do!</p>"));
if (debug) {
console.log(ts() + ": " + JSON.stringify({ "saves.length": saves.length, "bSomeCBSApps": bSomeCBSApps, "bInChat": bInChat}));
}
}
dialog = _alert(div[0]);
}
timestamp = (new Date()).valueOf();
apps["enm"](qryApps, menu); // once all active app/bots have been Query'd, create menu dialog to prompt for further action
}));
(function (exports) {
"use strict";
// startof CBS module
var _cb_onMessage = cb["onMessage"],
_onSave = null,
_onRestore = null,
_data, // object with single property: session timestamp
_tag = "#" + (cb["settings"].hasOwnProperty("slot") ? cb["settings"]["slot"] : "") + magicnumber, // #[slot][magicnumber]
reTag = new RegExp("^\\/#[0-3]" + magicnumber + "\\/"); // NB. this must match reTag definition above
//
// v2.1
// obj.m transformations
// Qr /[_tag]/[timestamp]/ => /[_tag]/[timestamp]/?
// a.length === 4 && a[3] === "" => a.length === 4 && a[3] !== ""
// Sv /[_tag]/[timestamp]/[start]// => /[_tag]/[timestamp]/[start]/[sent]/[sent.length]
// a.length === 6 && a[5] === "" => a.length === 6 && a[5] !== ""
// Ld /[_tag]/[timestamp]/[start]/[sent]/[sent.length]/ => /[_tag]/[timestamp]/[start]/[sent]/[sent.length]/[received.length]
// a.length === 7 && a[6] === "" => a.length === 7 && a[6] !== ""
//
function message(obj) {
var match = obj["m"].replace(/\s*/g, "").split("/"), // whitespace removal should be unnecessary. do it anyway
timestamp,
start,
chunk,
raw;
//
if (match.length > 3 && match[0] === "" && match[1] === _tag) {
if (_onSave && _onRestore && obj["user"] === cb["room_slug"]) {
if (debug) {
cb["log"](obj["user"] + ": " + obj["m"]);
}
timestamp = match[2];
if (match.length === 4 /* && !match[3].length */ ) {
match[3] = "?";
obj["m"] = match.join("/");
} else if (match.length === 6 /* && !match[4].length && !match[5].length */ ) {
// Sv
if (!(_data || {}).hasOwnProperty(timestamp)) {
// regenerate session data
/*try { // NB. is better if errors in onSave break the app/bot*/
raw = _onSave();
/*} catch (e) {
cb["log"](e.message);
raw = "";
}*/
if (debug) {
cb["log"](JSON["stringify"](raw));
}
_data = {};
_data[timestamp] = exports["btoa"](exports["unescape"](exports["encodeURIComponent"](raw))); // handle utf8 strings. see http://forums.enyojs.com/discussion/comment/9099/#Comment_9099
if (!raw) {
cb["log"]("onSave returned no data.");
}
}
if (_data.hasOwnProperty(timestamp)) { // sanity check
start = parseInt(match[3], 10);
chunk = _data[timestamp].slice(start, start + chunk_size);
match[4] = chunk;
match[5] = chunk.length;
obj["m"] = match.join("/");
}
} else if (match.length === 7 /* && match[5].length && parseInt(match[5], 10) === match[4].length && !match[6].length */ ) {
// Ld
if (debug) {
cb["log"](parseInt(match[5], 10) === match[4].length);
}
if (match[3] === "0") {
// reset session data
_data = {};
_data[timestamp] = "";
}
if (_data.hasOwnProperty(timestamp)) { // sanity check
match[3] = _data[timestamp].length;
match[6] = match[4].length;
obj["m"] = match.join("/");
if (match[4]) {
_data[timestamp] += match[4];
} else {
raw = exports["decodeURIComponent"](exports["escape"](exports["atob"](_data[timestamp]))); // handle utf8 strings. see http://forums.enyojs.com/discussion/comment/9099/#Comment_9099
if (debug) {
cb["log"](raw);
}
/*try { // NB. is better if errors in onRestore break the app/bot*/
_onRestore(raw);
/*} catch(e) {
cb["log"](e.message)
}*/
cb["chatNotice"]("Previously Saved Data Restored.", cb["room_slug"]);
}
} // else, if we don't alter obj.m, bookmarklet will detect restore fail
}
if (debug) {
cb["log"](obj["user"] + ": " + obj["m"]);
}
}
obj["X-Spam"] = true;
} else if (reTag.test(obj["m"])) { // any detected CBS pings are marked as spam
obj["X-Spam"] = true;
}
return obj;
}
cb["log"](sSig);
cb["onMessage"] = function (handler) {
if (typeof handler !== "function") {
throw new TypeError(handler + " is not a function");
}
_cb_onMessage(function (obj) {
return handler(message(obj));
});
return handler; // allow chaining
};
cb["onRestore"] = function (handler) {
if (typeof handler !== "function") {
throw new TypeError(handler + " is not a function");
}
_onRestore = handler;
return handler; // allow chaining
};
cb["onSave"] = function (handler) {
if (typeof handler !== "function") {
throw new TypeError(handler + " is not a function");
}
_onSave = handler;
return handler; // allow chaining
};
// set a default onMessage handler
cb["onMessage"](function (obj) {
return obj;
});
// endof CBS module
}(typeof exports === 'undefined' ? this : exports)); // ignore jslint warning. where this js is going, they don't like === undefined
// don't try to jslint beyond this point. here be dragons!
(function (exports, chars) {
"use strict";
// startof base64 module
/**
* @constructor
* @param {string} message
*/
function InvalidCharacterError(message) {
this["message"] = message;
}
InvalidCharacterError.prototype = new Error();
InvalidCharacterError.prototype["name"] = "InvalidCharacterError";
// encoder
// [https://gist.github.com/999166] by [https://github.com/nignag]
exports["btoa"] || (exports["btoa"] = function (input) {
var str = String(input);
for (
// initialize result and counter
var block, charCode, idx = 0, map = chars, output = "";
// if the next str index does not exist:
// change the mapping table to "="
// check if d has no fractional digits
str.charAt(idx | 0) || (map = "=", idx % 1);
// "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
output += map.charAt(63 & block >> 8 - idx % 1 * 8)) {
charCode = str.charCodeAt(idx += 3 / 4);
if (charCode > 0xFF) {
throw new InvalidCharacterError("\"btoa\" failed: The string to be encoded contains characters outside of the Latin1 range.");
}
block = block << 8 | charCode;
}
return output;
});
// decoder
// [https://gist.github.com/1020396] by [https://github.com/atk]
exports["atob"] || (exports["atob"] = function (input) {
var str = String(input).replace(/=+$/, "");
if (str.length % 4 == 1) {
throw new InvalidCharacterError("\"atob\" failed: The string to be decoded is not correctly encoded.");
}
for (
// initialize result and counters
var bc = 0, bs, buffer, idx = 0, output = "";
// get next character
buffer = str.charAt(idx++);
// character found in table? initialize bit storage and add its ascii value;
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
// and if not first of each 4 characters,
// convert the first 8 bits to one ascii character
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 // ignore ClosureCompiler warning
) {
// try to find character in table (0-63, not found => -1)
buffer = chars.indexOf(buffer);
}
return output;
});
// endof base64 module
}(typeof exports === 'undefined' ? this : exports, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="));
// NB NB NB *** after closure compiling (debug), remember to convert '%12' => '% 12' or '%0xc' *** ALSO *** re-order app modules to put base64 first ***
Closure Compiler Optimization Notes:
JSLint Tool Use Notes:
© Copyright Chaturbate 2011- 2024. All Rights Reserved.