WM Host Object

This is the host object which is created under the WM version 4.x script

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/6908/28422/WM%20Host%20Object.js

// ==UserScript==
// @name          	WM Host Object
// @namespace       MerricksdadWMHostObject
// @description	This is the host object which is created under the WM version 4.x script
// @license		http://creativecommons.org/licenses/by-nc-nd/3.0/us/
// @version        	4.0.0.4
// @copyright      	Charlie Ewing except where noted
// ==/UserScript==

//this script requires some functions in the WM Common Library
//this script needs access to a pre-defined JSON object


(function(){

	//*****short text versions for WM.config menu options*****

	var checkBox=function(l,d,c,n){return ({type:"checkbox",label:l,"default":d||false,kids:c,newitem:n});}
	var hidden=function(l,d,c){return ({type:"hidden",label:l,"default":d,kids:c});}
	var optionBlock=function(l,c,hideSelectAll,n){hideSelectAll=hideSelectAll||false;return ({type:"optionblock",label:l,kids:c,hideSelectAll:hideSelectAll,newitem:n});}
	var separator=function(l,s,c,hideSelectAll){hideSelectAll=hideSelectAll||false; return ({type:"separator",label:l,section:s,kids:c}); }
	var section=function(l,c){return ({type:"section",label:l,kids:c});}
	var tabSection=function(l,c){return ({type:"tab",label:l,kids:c});}
	var inputBox=function(l,d,n){return ({type:"float",label:l,"default":(d||0),newitem:n});}
	var textArea=function(l,d,n){return ({type:"textarea",label:l,"default":(d||0),newitem:n});}
	var colorBox=function(l,d,n){return ({type:"colorbox",label:l,"default":(d||"black"),newitem:n});}
	var button=function(l,s){return ({type:"button",label:l,script:s});}
	var anchor=function(l,u,t){return ({type:"link",label:l,href:u,title:(t||'')});}

	this.WallManager={	
		paused : false,
		fetchPaused : false,
		requestsOpen : 0,
		reqTO : 30000, //request timeout default
		newSidekicks : [],

		accDefaultText : "Got this!",
		failText : "Oh no! Sorry pardner!",
		overLimitText : "Limit reached!",

		version:"4.0.0.4",
		currentUser:{
			id:"",
			profile:"",
			alias:""
		},
		resources:{
			iconsURL:GM_getResourceURL("IconSheet")
		},
		apps:{},
		posts:{},
		history:{},
		config:null,
		opts:{},
		quickOpts:{},
		displayGroups:{},
		likeQueue:[],
		switches:{
			manualAuthToken:true
		},

		statusText : {
			"20":"Sidekick returned force accept",
			"3":"Marked as accepted by user",
			"2":"Responseless Collection",
			"1":"Accepted",
			"0":"Unknown",
			"-1":"Failed",
			"-2":"None Left",
			"-3":"Over Limit (App)",
			"-4":"Over Limit, Sent One Anyway",
			"-5":"Server Error",
			"-6":"Already Got",
			"-7":"Server Down For Repairs",
			"-8":"Problem Getting Passback Link",
			"-9":"Final Request Returned Null Page",
			"-10":"Final Request Failure",
			"-11":"Expired",
			"-12":"Not a Neighbor",
			"-13":"Requirements not met",
			"-14":"Timeout",
			"-15":"Unrecognized Response",
			"-16":"Passback Link is missing",
			"-17":"Window Missing",
			"-18":"Marked as failed by user",
			"-20":"Sidekick returned force fail",
			
			"-19":"Over Limit (Bonus Type)",
			"-21":"Cancelled mid-process by user",
			
		},
		
		sortGroups : function(params){
			params=params||{};
			params.direction=(WM.quickOpts.groupDirection=(params.direction||WM.quickOpts.groupDirection||"desc")); //default descending to keep time ordered posts in order newest to oldest
			WM.saveQuickOpts();			
			
			//reorder the groups
			var groupsArray=[];
			for (var g in WM.displayGroups) {
				groupsArray.push({id:g,node:WM.displayGroups[g].parentNode,box:WM.displayGroups[g]});
			}
			
			if (["asc","ascending"].inArray(params.direction.toLowerCase())) groupsArray.sort(function(a,b){return a.id>b.id;});
			else if (["desc","descending"].inArray(params.direction.toLowerCase())) groupsArray.sort(function(a,b){return a.id<b.id;});
			
			WM.displayGroups={};
			for (var g=0; g<groupsArray.length; g++) {
				WM.displayGroups[groupsArray[g].id]=groupsArray[g].box;
				WM.console.feedNode.appendChild(groupsArray[g].node);
			}
		},

		newGroup : function(params){
			params=params||{};

			//prevent duplicates
			if (WM.displayGroups[params.by]||null) return WM.displayGroups[params.by];
			
			//create the nodes
			var box;
			var group=createElement("div",{className:"listItem"},[
				createElement("div",{className:"line", onclick:function(){
					//toggle rollout
					with (this.nextSibling) className=className.swapWordB((className.containsWord("collapsed")),"expanded","collapsed");
					with (this.firstChild.firstChild) className=className.swapWordB((className.containsWord("treeCollapse"+WM.opts.littleButtonSize)),"treeExpand"+WM.opts.littleButtonSize,"treeCollapse"+WM.opts.littleButtonSize);
				}},[
					createElement("div",{className:"littleButton",title:"Toggle Content"},[
						createElement("img",{className:"resourceIcon treeCollapse"+WM.opts.littleButtonSize}),
					]),
					createElement("label",{textContent:params.label||params.by})
				]),
				box=createElement("div",{className:"subsection rollout expanded"}),
			]);
						
			//add it to our group list
			WM.displayGroups[params.by]=box;
			
			WM.sortGroups();
			
			return box;
		},
		
		pauseCollecting : function(doPause){
			var isPaused;
			if (exists(doPause)) isPaused = (WM.paused = doPause);
			else isPaused=(WM.paused = !WM.paused);
			var btn=WM.console.pauseCollectButton;
			btn.className = btn.className.swapWordB(isPaused,"oddGreen","oddOrange");
			btn.title = (isPaused)?"Start Automatic Collection":"Pause Automatic Collection";
			var img = btn.childNodes[0];
			img.className = img.className.swapWordB(isPaused,"playRight24","stop24");
		},

		pauseFetching : function(doPause){
			var isPaused;
			if (exists(doPause)) isPaused = (WM.fetchPaused = doPause);
			else isPaused=(WM.fetchPaused = !WM.fetchPaused);
			var btn=WM.console.pauseFetchButton;
			btn.className = btn.className.swapWordB(isPaused,"oddGreen","oddOrange");
			btn.title = (isPaused)?"Start Automatic Fetching":"Pause Automatic Fetching";
		},

		clearGroups : function(params){
			//destroy previous groups
			for (var g in WM.displayGroups){
				remove(WM.displayGroups[g].parentNode); //kill the node
				delete WM.displayGroups[g]; //remove from list
			}
		},

		clearPosts : function(){
			//remove all post nodes from the collector panel
			for (var p in WM.posts){
				if (WM.posts[p].node) remove(WM.posts[p].node);
			}
		},
		
		constructGroups : function(params){
			params=params||{};
			//this specifically allows a null so we can remove grouping
			var by=exists(params.by)?params.by:WM.quickOpts.groupBy;
			//if nothing changed, just cancel
			if (by==WM.quickOpts.groupBy) return;
			
			//set the new group order
			WM.quickOpts.groupBy=by;
			WM.saveQuickOpts();
			
			WM.clearGroups();
		},
		
		sortPosts : function(params){
			params=params||{};
			params.direction=(WM.quickOpts.sortDirection=(params.direction||WM.quickOpts.sortDirection||"desc")); //default descending to keep time ordered posts in order newest to oldest
			params.by=(WM.quickOpts.sortBy=(exists(params.by)?params.by:(WM.quickOpts.sortBy||"created_time"))); //default by date
			WM.saveQuickOpts();

			//convert to array
			var postsArray=methodsToArray(WM.posts);

			//sort
			postsArray.sort(function(a,b){
				if (["ascending","asc"].inArray(params.direction.toLowerCase())) return a[params.by]>b[params.by];
				if (["descending","desc"].inArray(params.direction.toLowerCase())) return a[params.by]<b[params.by];
			});

			//convert back to object
			WM.posts=arrayToMethods(postsArray);
		},

		doWhichTestTree : function(post, testList, testData, custom) {try{
			//match post to an app
			var app=post.app;
			var synApp=app.synApp, w=null;

			for (var i=0,test;((test=testList[i]) && (w===null));i++) {

				//run only for tests that are not specifically disabled
				if (test.enabled===false) continue;
				
				//set find mode
				var findMode="auto";
			
				//finish constructing dynamic collection tests
				var ret = test.ret;
				if (custom) {
					if (!ret) ret = "dynamic"; //default to dynamic
					if (ret!="dynamic" && ret!="none" && ret!="exclude" && !ret.startsWith(synApp.appID)) ret=synApp.appID+ret; //add appID except to magic words
					findMode=test.findMode;
				}

			    //part to make dynamic collection tests work only if they are the correct appID
			    //also do not process disabled tests			    
				if (!custom || (custom && (!test.appID || (app.appID==test.appID)))){

					//if the test is not disabled (by test enabled both existing and being false)
					//OR if the test IS a dynamic test and the appID matches
					//OR if the test IS a dynamic test and no appID was supplied
					//then run the test

					//detect test type
					var testType=(test.search||null);
					var types=WM.grabber.methods;
					if (!testType) for (var tt=0,len=types.length; tt<len; tt++) {if (test[types[tt]]||"") {testType=types[tt];break;}}

					//select the type of data to use
					var src="";
					if (isArray(testType)){ //new search array format
						for (var t=0,tlen=testType.length;t<tlen;t++) src+=(testData[testType[t]]||"");
					}
					else src = (testData[testType]||""); //old test method like testType:text

					if (src){
						//begin processing this test
						var subTests=test.subTests, kids=test.kids, allowNone=false, subNumRange=test.subNumRange,text=(test.find||test[testType]||"");

						//process subtests array
						if (subTests && (findMode=="auto" || findMode=="subtests") && text) {
							for (var i2=0,subTest,found=false;((subTest=subTests[i2]) && (!found));i2++) {
								var testX = text.replace('{%1}',subTest).toLowerCase();

								//do a standard test with the replaced search string
								found=src.find(testX);

								//return a found value, replacing %1 with a lowercase no-space text equal to the subtest string
								w=(found)?ret.replace('{%1}',subTest.noSpaces().toLowerCase()):w;

								testX=null;
							}

						//process number array
						} else if (subNumRange && (findMode=="auto" || findMode=="subnumrange") && text){
							var start=parseInt(subNumRange.split(",")[0]), end=parseInt(subNumRange.split(",")[1]);
							for (var i2=start,found=false;((!found) && i2<=end);i2++)  {
								var testX = text.replace('{%1}',i2).toLowerCase();

								//do a standard test with the replaced search string
								found=src.find(testX);

								//return a found value, replacing %1 with a lowercase no-space text equal to the subtest string
								w=(found)?ret.replace('{%1}',i2):w;

								testX=null;
							}

						//process text array, process similar to subtests
						} else if (text && (findMode=="auto" || findMode=="basic") && (isArray(text))) {
							for (var i2=0,subTest,found=false;((subTest=text[i2]) && (!found));i2++) {
								var testX = subTest.toLowerCase();

								//do a standard test with the replaced search string
								found=src.find(testX);

								//return the same value no matter which element from the array is found
								w=(found)?ret:w;

								testX=null;
							}


						//process regex
						} else if (text && (test.regex||test.isRegex||null) ) {
							var mods = (test.mods||"gi");
							var testRegex = new RegExp(text,mods);
							var match=src.match(testRegex);
							if (match) match=match[0]; //always take the first match
							w=ret||match||w;



						//process single text
						} else if (text) {
							try{
								w=(src.find(text.toLowerCase() ))?ret:w;
							} catch(e){
								log("WM.doWhichTestTree:"+e);
								log("--app:"+app.appID);
								log("--test:"+JSON.stringify(test));
							}
						}

					}

					//see if test has type 2 subtests (child node tests based on parent test)
					w = ((kids && w)?WM.doWhichTestTree(post, kids, testData, custom):w) || w; //if kids return null, default to key found above

					//if this test tree returned "none", start over with next tree by replacing "none" with null
					//true "none" is handled in the which() function below
					if (w==="none") w=null;


				}//end custom checker
			}

			return w;
		}catch(e){log("WM.doWhichTestTree: "+e);}},

		which : function(post,params) {try{
			//prevent the rules manager from mistaking main as a post object
			if (!post) return;
			
			params=params||{};
			//match post to an app
			var w, app=post.app, synApp=app.synApp;

			//create various data for the tests to use
			if (!params.reid) post.testData = {
				title: (post.name||"undefined").toLowerCase(),
				msg: (post.message||"undefined").toLowerCase(),
				caption: (post.caption||"undefined").toLowerCase(),
				desc: (post.description||"undefined").toLowerCase(),
				link: (post.linkText||"undefined").toLowerCase(),
				url: Url.decode(post.linkHref).toLowerCase(),
				img: (post.picture||"undefined").toLowerCase(),
				fromName: post.fromName.toLowerCase(),
				fromID: post.fromID.toLowerCase(),
				targetName: "undefined", //","+post.getTargets("name").join(",").toLowerCase(),
				//targetID: "undefined", //","+post.getTargets("id").join(",").toLowerCase(),
				canvas: "undefined", //app.namespace.toLowerCase(),
				likeName: "undefined", //","+post.getLikes("name").join(",").toLowerCase(),
				likeID: "undefined", //","+post.getLikes("id").join(",").toLowerCase(),
				comments: "undefined", //post.getComments("message").join(" \n").toLowerCase(),
				commentorName: "undefined", //","+post.getComments("name").join(",").toLowerCase(),
				commentorID: "undefined", //","+post.getComments("id").join(",").toLowerCase(),
			};
			var testData=post.testData;

			//replacement for old options like body, either and html
			testData.body = testData.title+testData.caption+testData.desc;
			testData.either = testData.link+testData.body;
			testData.html = testData.fromID + testData.fromName + testData.targetID + testData.targetName + testData.message
				+ testData.href + testData.either + testData.img + testData.canvas + testData.likeID + testData.likeName
				+ testData.commentorID + testData.commentorName + testData.comments;

			var dynamicTests = WM.grabber.tests;

			//check user built dynamic tests first if enabled and told to run first
			if (WM.opts["dynamic"+app.appID] && WM.opts.dynamicFirst && dynamicTests) {
				w=WM.doWhichTestTree(post,dynamicTests, testData, true)||"none";
			}

			//process this game's tests if dynamic didn't already get one
			if (w=="none" || !w || w=="") {
				w=((tests=synApp.tests)?WM.doWhichTestTree(post,tests, testData):"none")||"none";
			}

			//check user built dynamic tests last if enabled and not told to run first
			if (w=="none" || !w || w=="") {
				if (WM.opts["dynamic"+app.appID] && !WM.opts.dynamicFirst && dynamicTests) {
					w=WM.doWhichTestTree(post,dynamicTests,testData, true)||"none";
				}
			}

			//switch to undefined collection if enabled
			w=(w==="none" && app.opts["doUnknown"])?"doUnknown":w;

			return w;
		}catch(e){log("WM.which: "+e);}},

		resetAccepted : function(params) {
			params=params||{};
			var ask=WM.opts.historyConfirmClear;
			if (params.noConfirm || !ask || (ask && confirm("Delete all history for this profile?"))){
				doAction(function(){
					WM.history={};
					setOpt('history_'+WM.currentUser.profile,'{}');
				});
			}
		},
		
		getIsConfigOpen : function(){
			if (document.evaluate("//iframe[@id='Config']",document,null,9,null).singleNodeValue) return true;
			return false;
		},

		onWindowResize : function(){
			WM.resizeConsole();
		},
		
		onHeartbeat : function(){
			if (WM.rulesManager.enabled) {

				//affect rules at the base level
				WM.rulesManager.doEvent("onHeartbeat",{});
				
				//affect rules at the app level
				if (WM.opts.heartbeatAffectsApps) {
					for (var a in WM.apps) {
						(function(){
							WM.rulesManager.doEvent("onHeartbeat",WM.apps[a]);
						})();
					}
				}

				//affect rules at the post level
				if (WM.opts.heartbeatAffectsPosts) {
					for (var p in WM.posts) if (!WM.posts[p].isGhost) {
						(function(){
							WM.rulesManager.doEvent("onHeartbeat",WM.posts[p]);
						})();
					}
				}

				//affect rules at the rule level
				if (WM.opts.heartbeatAffectsRules) {
					for (var r=0; r<WM.rulesManager.rules.length; r++) {
						(function(){
							WM.rulesManager.doEvent("onHeartbeat",WM.rulesManager.rules[r]);
						})();
					}
				}

				//affect rules at the feed and feed filter levels
				if (WM.opts.heartbeatAffectsFeeds || WM.opts.heartbeatAffectsFeedFilters) {
					var feeds=WM.feedManager.feeds;
					for (var f=0,len=feeds.length; f<len; f++) {
						//do the feed
						if (WM.opts.heartbeatAffectsFeeds) (function(){
							WM.rulesManager.doEvent("onHeartbeat",feeds[f]);
						})();
						
						//do the feed filters
						if (WM.opts.heartbeatAffectsFeedFilters) {
							for (var ff in feeds[f].filters){
								(function(){
									WM.rulesManager.doEvent("onHeartbeat",feeds[f].filters[ff]);
								})();
							}
						}
					}
				}
			}
			
			//check for new sidekick arrivals
			if (isArrayAndNotEmpty(WM.newSidekicks)) {
				while (WM.newSidekicks.length>0) {
					var app=WM.newSidekicks.shift();
					app.fetchPosts();
				}
			}
			
			//check for autolike queue contents
			var quePost = WM.checkAutoLikeQue();
			if (quePost) {
				//log([quePost.fn,quePost.post.id]);
				switch (quePost.fn) {
					case "like":quePost.post.like();break;
					case "comment":quePost.post.comment(quePost.say);break;
				}
			}
			
		},

		//this is for when the WM.config and globalConfig settings change
		onSave : function() {
			//recopy the settings array from WM.config
			WM.updateSettingsValues();

			//hide or show counters
			if (WM.opts.showcounters) WM.showCounters(); else WM.hideCounters();

			//update intervals
			WM.setIntervals();

			//set new user colors
			WM.setColors();
			
			//update config settings
			WM.changeConfigSettings();

			//update those settings we use as global variables
			WM.changeDebugSettings();
			
			//set console heights
			//WM.resizeConsole();
		},

		updateSettingsValues : function(){try{
			WM.opts = WM.config.values;
			
			//new: do this for each of the apps too
			for (var a in WM.apps) WM.apps[a].opts=WM.apps[a].config.values;
			
		}catch(e){"WM.updateSettingsValues: "+e}},

		getAccText: function(appID,w,past,status){
			var app=WM.apps[appID].synApp;
			//detect and use a status code message
			if (!(status>-1 || status==-4 || status==-6)) return WM.statusText[status];
			//or return a generic message based on post type
			else return (w=="dynamic")?"Dynamic Grab"+((past)?"bed":""):(((w.find("send")?"Sen"+((past)?"t":"d")+" ":w.find("wishlist")?"":"G"+((past?"o":"e"))+"t ") + (app.userDefinedTypes[w]||app.accText[w])) || ((past)?WM.accDefaultText:"Get Unknown") || ((w.startsWith(app.appID+"doUnknown"))?"Unknown":"") );
		},
		
		refreshBrowser : function(){
			/*
				make sure no collection process is running
				make sure the options menu is not open
			*/
			
			if (WM.requestsOpen>0 || $("Config")) {
				setTimeout(WM.refreshBrowser,5000);
			} else {
				window.location.reload();
			}
		},

		stopCollectionOf : function(w){
			for (var p in WM.posts) if (!WM.posts[p].isGhost && WM.posts[p].which==w) WM.posts[p].stopCollect();
		},

		startCollectionOf : function(w){
			for (var p in WM.posts) if (!WM.posts[p].isGhost && WM.posts[p].which==w) WM.posts[p].collect();
		},

		pauseByType : function(app,w){
			if (!isArray(w)) w=[w];
			//mark as paused all those posts not yet done
			for (var p in WM.posts) if (!WM.posts[p].isGhost && w.inArray(WM.posts[p].which)) WM.posts[p].pause();
			//store the paused type but dont save it
			var a=(app.parent||app);
			for (var i=0; i<w.length; i++) {
				var t=w[i];
				//add it to the array without making a duplicate
				if (!a.typesPaused.inArray(t)) {
					a.typesPaused.push(t);
					//add a visible node
					a.typesPausedNode.appendChild(
						a.pausedTypesListNodes[t]=createElement("div",{className:"line"},[
							createElement("span",{textContent:(a.userDefinedTypes[t]||a.accText[t])+" ("+t+") "}),
							createElement("div",{className:"littleButton oddGreen", title:"Unpause Type"},[
								createElement("img",{className:"resourceIcon playRight"+WM.opts.littleButtonSize,onclick:function(){
									WM.unPauseByType(a,t);
								}})
							])
						])
					);
				}
			}
		},

		unPauseByType : function(app,w){
			if (!isArray(w)) w=[w];
			//unpause all those posts not yet done
			for (var p in WM.posts) if (!WM.posts[p].isGhost && w.inArray(WM.posts[p].which)) WM.posts[p].unPause();
			//remove paused type from list but dont save it
			var a=(app.parent||app);
			for (var i=0; i<w.length; i++) {
				//remove the visible node
				remove (a.pausedTypesListNodes[w[i]]);
				//delete the visible node entry
				delete a.pausedTypesListNodes[w[i]];
				//remove it from the array
				a.typesPaused.removeByValue(w[i]);
			}
		},

		setAsAccepted : function(comment,status, post) {try{
			var app=post.app;
			var synApp=app.synApp;
			post.state="accepted";
			post.status=status;
			post.accept();

			WM.history[post.id]={status:status, date:timeStamp(), which:(post.which||"undefined").removePrefix(synApp.appID), appID:app.appID};
			setOptJSON('history_'+WM.currentUser.profile,WM.history);

			//do friend tracking
			if (WM.opts.useFriendTracker && WM.opts.trackAccepted){
				WM.friendTracker.trackStatus(post,true);
			}

			var postNode=post.node||$("post_"+post.id);
			if (postNode){
				var link=selectSingleNode(".//a[contains(@class,'linkText')]",{node:postNode});

				var text=WM.getAccText(synApp.appID,post.which,true,status);

				link.textContent = (comment || text || WM.statusText[status] || WM.accDefaultText);
				WM.updatePostStatus(post.id);
			}

			app.acceptCount++; 

			//perform the onAccepted event
			WM.rulesManager.doEvent("onAccepted",post);

			//try autolike
			try{
				if (WM.opts.useautolike && (WM.opts.autolikeall || WM.opts.autolikeaccepted || (WM.opts.autolikesent && (post.which||"undefined").startsWith("send")) )) {
					if (!WM.opts["nolike"+app.appID]){
						WM.queAutoLike(post);
						//post.like();
					}
				}
			} catch(e){log("setAsAccepted: autolike failed: "+e,{level:3});}
			//try autocomment
			
			try{
				if (WM.opts.useautocomment && (WM.opts.autolikeall || WM.opts.autolikeaccepted || (WM.opts.autolikesent && (post.which||"undefined").startsWith(synApp.appID+"send")) )) {
					if (!WM.opts["nolike"+app.appID]){
						//setTimeout(function(){post.comment();},100+(WM.opts.autolikedelay*1000));
						WM.queAutoComment(post,null);
					}
				}
			} catch(e){log("setAsAccepted: autocomment failed: "+e,{level:3});}
			
		}catch(e){log("WM.setAsAccepted: "+e);}},

		disableOpt : function(w,app){try{
			var targetConfig=(app||null)?app.config:WM.config;
			((app||null)?app.opts:WM.opts)[w]=false;
			targetConfig.set(w,false);
			targetConfig.save();
			debug.print([w,app,false]);
		}catch(e){log("WM.disableOpt: "+e);}},

		enableOpt : function(w,app){try{
			var targetConfig=(app||null)?app.config:WM.config;
			((app||null)?app.opts:WM.opts)[w]=true;
			targetConfig.set(w,true);
			targetConfig.save();
			debug.print([w,app,true]);
		}catch(e){log("WM.enableOpt: "+e);}},

		setOpt : function(w,v,app){try{
			var targetConfig=(app||null)?app.config:WM.config;
			((app||null)?app.opts:WM.opts)[w]=v;
			targetConfig.set(w,v);
			targetConfig.save();
			debug.print([w,app,v]);
		}catch(e){log("WM.setOpt: "+e);}},

		resetCounters : function(){try{
			for (var a in WM.apps) WM.apps[a].resetCounter();
		}catch(e){log("WM.resetCounters: "+e);}},

		setAsFailed : function(comment, status, post){try{
			var app=post.app;
			var synApp=app.synApp;
			var postNode=post.node||$("post_"+post.id);

			//special effects for timeout and cancelProcess
			if ((!WM.opts.failontimeout && status==-14) || status==-21) {
				post.state="timeout";
				post.timeout();
				if (status==-14) WM.rulesManager.doEvent("onTimeout",post);

			} else {
				post.state="failed";
				post.fail(); // don't pass true or it will loop here

				WM.history[post.id]={status:status, date:timeStamp(), which:(post.which||"undefined").removePrefix(synApp.appID), appID:app.appID};
				setOptJSON('history_'+WM.currentUser.profile,WM.history);
				
				WM.rulesManager.doEvent("onFailed",post);
			}
			post.status=status;

			//do friend tracking
			if (WM.opts.useFriendTracker && WM.opts.trackFailed){
				WM.friendTracker.trackStatus(post,false);
			}
			
			if (postNode) {
				var link=selectSingleNode(".//a[contains(@class,'linkText')]",{node:postNode});
				if (link) {
					//I can see no reason the link should be missing, but since its been proven to fail, here is a patch
					link.textContent = (comment || WM.statusText[status] || WM.failText);
				}
				WM.updatePostStatus(post.id);
			}

			app.failCount++; 
			
			//try autolike
			try{
				if (WM.opts.useautolike && WM.opts.autolikeall) {
					if (!WM.opts["nolike"+app.appID]){
						WM.queAutoLike(post);
						//post.like();
						//setTimeout(function(){post.like();},100+(WM.opts.autolikedelay*1000));
					}
				}
			} catch(e){log("setAsFailed: autolike failed: "+e,{level:3});}
			//try autocomment
			
			try{
				if (WM.opts.useautocomment && WM.opts.autolikeall) {
					if (!WM.opts["nolike"+app.appID]){
						//setTimeout(function(){post.comment();},100+(WM.opts.autolikedelay*1000));
						WM.queAutoComment(post,null);
					}
				}
			} catch(e){log("setAsFailed: autocomment failed: "+e,{level:3});}
			
		}catch(e){log("WM.setAsFailed: "+e);}},

		setPriority : function(){
			var postNode=selectSingleNode(".//ancestor::*[starts-with(@id,'post_')]",{node:this});
			var id=postNode.id.replace("post_","");
			WM.posts[id]["priority"]=this.getAttribute("name");
			remove(postNode);
			WM.posts[id].draw();
		},

		clearURL : function(tab){
			WM.collector.close(tab);
			WM.requestsOpen--;
		},
		
		//constantly update sidekick channel data
		skChannel : {},
		
		fetchSidekickData : function(){try{
			if (WM) {
				var node=selectSingleNode("./div",{node:$("wmDataDump")});
				while (node){
					log("WM.fetchSidekickData: found "+JSON.parse(node.getAttribute("data-ft")));
					WM.skChannel=mergeJSON(WM.skChannel,JSON.parse(node.getAttribute("data-ft")));
					remove(node);
					node=selectSingleNode("./div",{node:$("wmDataDump")});
				}
				setTimeout(WM.fetchSidekickData,1000);
			}
		}catch(e){log("WM.fetchSidekickData: "+e);}},		
		
		//this is WM3's method of handling conversations with sidekicks
		onFrameLoad3 : function(tab){try{
			log("onFrameLoad3(): tab="+tab.id);
						
			var postID=tab.postID||tab.id;
			var post=tab.post||WM.posts[postID];

			//detect if post process was cancelled by user
			if (post.processCancelled){
				//reset the cancel memory
				post.processCancelled = false;
				log("onFrameLoad3: process cancelled by user");
				//set the timeout flag even though its not timed out
				WM.setAsFailed(null,-21,post);
				WM.clearURL(tab);
				return;
			}

			//detect if valid WM.collector window still exists
			var windowExists=(tab.hwnd && !tab.hwnd.closed);
			/*try{
				var testUrl=tab.hwnd.location.toString();
			} catch(e) {
				windowExists=false; 
			}*/
			
			//make sure the post object still exists
			if (!(post||null)){
				log("onFrameLoad3: post is null");
				WM.clearURL(tab);
				return;
			}

			//check if window object is missing
			if (!windowExists) {
				log("windowExists = false");
				if (!tab.hwnd) log("onFrameLoad3: tab.hwnd is null");
				if (tab.hwnd.closed) log("onFrameLoad3: tab.hwnd is closed");
				WM.setAsFailed(null,-17,post);
				WM.clearURL(tab);
				return;
			}
			
			//check timer on this open post
			var openTime=tab.openTime;
			var nowTime=timeStamp();
			if ((WM.opts.reqtimeout*1000)<(nowTime-openTime)){
				log("onFrameLoad3: post timed out");
				WM.setAsFailed(null,-14,post);
				WM.clearURL(tab);
				return;
			}
			
			//create the retry function
			var retry=function(){setTimeout(function(){WM.onFrameLoad3(tab); return;},1000); return;};
			
			//look for status data
			var tabID = tab.id;
			var skData = WM.skChannel[tabID]||null;
			if (skData) {
				//data exists for this post
				if (skData.status) {
					//status is available
					delete WM.skChannel[tabID];
					
					//get useful post data
					var app=post.app; var synApp=app.parent||app;
					var postNode=post.node||$("post_"+post.id);
					var link=selectSingleNode(".//a[contains(@class,'linkText')]",{node:postNode});
					var w=post.which||"undefined";
					
					//confirm status
					var gotItem=((skData.status>0) || (skData.status==-6) || (skData.status==-4) || (skData.status==-15 && WM.opts.accepton15));
					var failedItem=(skData.status<0);
					
					if (gotItem){
						//build debug block
						switch(skData.status){
							case -6: case -4: case 1:
								// this bonus is available or we still have the ability to send something for no return
								//dont break before next
							case -15: case 2:
								if (!synApp.flags.requiresTwo){
									WM.setAsAccepted(null, skData.status, post);
								}
								break;

							default:
								//should not have come here for any reason, but if we did assume its a status code I didnt script for
								WM.setAsFailed(null, skData.status, post);
								log("onFrameLoad3: unexpected status code: "+skData.status,{level:2});
								break;
						}
					} else {
						WM.setAsFailed(null,skData.status,post);
					}
					
					// click "yes" to accept it, if we got this far we actually found an accept button
					if(synApp.flags.requiresTwo && gotItem) {
						if (skData.nopopLink) {
							var req; req=GM_xmlhttpRequest({
								method: "GET",
								url: skData.nopopLink,
								timeout: WM.opts.reqtimeout*1000,
								onload: function(response) {
									//search for error messages
									var test=response.responseText;
									if (test==""){
										//no text was found at requested href
										log("onFrameLoad3: final stage: null response",{level:2});
										WM.setAsFailed(null, -9,post);
									} else {
										//if no errors then we got it
										WM.setAsAccepted(null, skData.status,post);
									}
									WM.clearURL(tab);
									if(req)req=null;
								},

								onerror: function(response) {
									log("onFrameLoad3: final stage: error returned",{level:2});
									//if final request fails, drop the request for now
									WM.setAsFailed(null, -10,post);
									WM.clearURL(tab);
									if(req)req=null;
								},

								onabort: function(response) {
									log("onFrameLoad3: final stage: request aborted",{level:2});
									WM.setAsFailed(null, -10,post);
									WM.clearURL(tab);
									if(req)req=null;
								},
								ontimeout: function(response) {
									log("onFrameLoad3: final stage: request timeout",{level:2});
									WM.setAsFailed(null, -10,post);
									WM.clearURL(tab);
									if(req)req=null;
								},
							});

						} else {
							log("onFrameLoad3: skData.nopopLink is null and a string was expected",{level:3});
							WM.setAsFailed(null, -16,post);
							WM.clearURL(tab);
							return;
						}
					} else WM.clearURL(tab); //<- default page clearer, do not remove
				} else retry();
			} else {
				retry();
				//send the tab its init message (again)
				tab.hwnd.postMessage({
					channel:"WallManager",
					msg:1,
					tabID:tab.id,
				},"*");
				//log("useGM_openInTab: "+WM.opts.useGM_openInTab);
			}
			
		}catch(e){log("WM.onFrameLoad3: "+e);}},		
		
		//this is WM1-2's method of handling conversation with sidekicks
		//WM3 defaults to this if sidekick is not WM3 compatible
		onFrameLoad : function(tab,noDebug){try{
			//tab object contains {id,post,url}
			if (!noDebug) log("onFrameLoad()",{level:0});

			var id=tab.id; var post=tab.post||WM.posts[id];
			if (!(post||null)) {
				//resource deleted while post was out
				WM.clearURL(tab);
				return;
			}

			//detect if post process was cancelled by user
			if (post.processCancelled){
				//reset the cancel memory
				post.processCancelled = false;
				log("onFrameLoad3: process cancelled by user");
				//set the timeout flag even though its not timed out
				WM.setAsFailed(null,-21,post);
				WM.clearURL(tab);
				return;
			}

			var app=post.app;
			var synApp=app.parent||app;

			var httpsTrouble=synApp.flags.httpsTrouble;
			var responseLess=synApp.flags.skipResponse;

			var postNode=post.node||$("post_"+post.id);
			var link=selectSingleNode(".//a[contains(@class,'linkText')]",{node:postNode});
			var w=post.which||"undefined";

			tab.tries=(tab.tries||0)+1;
			if (tab.tries>WM.opts.reqtimeout) {
				log("onFrameLoad: request timeout",{level:3});
				WM.setAsFailed(null, -14, post);
				WM.clearURL(tab);
				return;
			}

			var retry=function(){setTimeout(function(e){WM.onFrameLoad(tab, true);}, 1000);};

			var failedItem=false, gotItem=false, nopopLink;

			//check if window object is missing
			var windowExists=(tab.hwnd && !tab.hwnd.closed);
			if (!windowExists) {WM.setAsFailed(null,-17,post); WM.clearURL(tab); return;}
			//check if window document does not yet exist
			//if (!(tab.hwnd.document||null)) {retry(); return;}

			//get sidekick return value
			var hashMsg="",hashStatus=0;
			try{
				//if error encountered, reload the page
				if (tab.hwnd.document.title==="Problem loading page"){
					log("processPosts: problem loading page",{level:1});
					tab.hwnd.location.reload();
					retry();
					return;
				}

				var temphash = tab.hwnd.location.hash; //capture a hash if we can
				hashMsg = ((temphash)?temphash.removePrefix("#"):null) || tab.hwnd.location.href.split("#")[1];
				hashStatus=(responseLess)?2:(hashMsg||null)?parseInt(hashMsg.split('status=')[1].split("&")[0]):0;
				gotItem=((hashStatus>0) || (hashStatus==-6) || (hashStatus==-4) || (hashStatus==-15 && WM.opts.accepton15));
				failedItem=(hashStatus<0);
				if (!gotItem && !failedItem) {retry(); return;}

			} catch(e){
				var errText=""+e;
				if (errText.contains("hashMsg is undefined")) {
					//this known issue occurs when a page is not yet fully loaded and the
					//WM script tries to read the page content
					retry();
					return;
				}
				else if (errText.contains("Permission denied to access property")) {
					//we've reached some known cross domain issue
					if (responseLess) {
						//if the sidekick creator has chosen to use responseless collection
						//simply assume the page has loaded and mark the item as collected

						gotItem=true;failedItem=false;hashStatus=2;
					} else {
						console.log("WM.onFrameLoad(before retry): "+e);
						retry();
						return;
					}
				}
				else if (errText.contains("NS_ERROR_INVALID_POINTER")
					|| errText.contains("tab.hwnd.document is null") ) {

					WM.setAsFailed(null,-17,post);
					WM.clearURL(tab);
					return;
				}
				else {
					log("onFrameLoad: "+e,{level:3});
					retry();
					return;
				}
			}

			//if gotItem then we have been offered the item so far
			if (gotItem){
				//build debug block
		    		switch(hashStatus){
					case -6: case -4: case 1:
						// this bonus is available or we still have the ability to send something for no return
						if (synApp.flags.requiresTwo){
							try{
								nopopLink=hashMsg.split("&link=[")[1].split("]")[0];
							}catch(e){
								//known rare issue where no link is passed back by pioneer trail
							}
						}
						//dont break before next
					case -15: case 2:
						if (!synApp.flags.requiresTwo){
							WM.setAsAccepted(null, hashStatus,post);
						}
						break;

					default:
						//should not have come here for any reason, but if we did assume its a status code I didnt script for
						WM.setAsFailed(null, hashStatus,post);
						log("onFrameLoad: unexpected status code: "+hashStatus,{level:2});
						break;
		    		}
			} else {
				WM.setAsFailed(null,hashStatus,post);
			}


			// click "yes" to accept it, if we got this far we actually found an accept button
			if(synApp.flags.requiresTwo && gotItem) {
				if (nopopLink) {
					var req; req=GM_xmlhttpRequest({
						method: "GET",
						url: nopopLink,
						timeout: WM.opts.reqtimeout*1000,
						onload: function(response) {
							//search for error messages
							var test=response.responseText;
							if (test==""){
								//no text was found at requested href
								log("onFrameLoad: final stage: null response",{level:2});
								WM.setAsFailed(null, -9,post);
							} else {
								//if no errors then we got it
								WM.setAsAccepted(null, hashStatus,post);
							}
							WM.clearURL(tab);
							if(req)req=null;
						},

						onerror: function(response) {
							log("onFrameLoad: final stage: error returned",{level:2});
							//if final request fails, drop the request for now
							WM.setAsFailed(null, -10,post);
							WM.clearURL(tab);
							if(req)req=null;
						},

						onabort: function(response) {
							log("onFrameLoad: final stage: request aborted",{level:2});
							WM.setAsFailed(null, -10,post);
							WM.clearURL(tab);
							if(req)req=null;
						},
						ontimeout: function(response) {
							log("onFrameLoad: final stage: request timeout",{level:2});
							WM.setAsFailed(null, -10,post);
							WM.clearURL(tab);
							if(req)req=null;
						},
					});

				} else {
					log("onFrameLoad: nopopLink is null and a string was expected",{level:3});
					WM.setAsFailed(null, -16,post);
					WM.clearURL(tab);
					return;
				}
			} else WM.clearURL(tab);

		}catch(e){log("WM.onFrameLoad: "+e);}},

		toggle : function(opt,app){
			var targetConfig=(app||null)?app.config:WM.config;
			var targetOpts=(app||null)?app.opts:WM.opts;
			if (targetOpts[opt]){
				targetConfig.set(opt, false);
				targetOpts[opt] = false;
			} else {
				targetConfig.set(opt, true);
				targetOpts[opt] = true;
			}
               targetConfig.save();
		},

		getAppDropDownList : function(selectedIndex,allowBlank){
			var retApps=[];
			//add the fake initial option
			retApps.push(createElement("option",{textContent:"select an app",value:""}));
			retApps.push(createElement("option",{textContent:"* All",value:""}));
			if (allowBlank) retApps.push(createElement("option",{textContent:"all apps",value:""}));

			for(var i in WM.apps){
				if (!WM.apps[i].parent) {
					var elem = createElement("option",{textContent:WM.apps[i].name,value:i});
					if ((selectedIndex||null) == i) elem.selected = true;
					retApps.push(elem);
				}
			}
			return retApps;
		},

		getBonusDropDownList : function(params){
			params=params||{};
			var selected = params.selected||"";
			var appID = params.appID||null;
			var dropID = params.dropID||false; //force the element value to drop its appID prefix

			var optsret=[], bonuses={};
			if (appID) bonuses = mergeJSON(WM.apps[appID].accText,WM.apps[appID].userDefinedTypes);
			bonuses["dynamic"]="* Dynamic: Just Grab It";
			bonuses["none"]="* None: Break Identification Circuit";
			bonuses["wishlist"]="* Flag as Wishlist";
			bonuses["exclude"]="* Exclude: Prevent Collection";
			bonuses["send"]="* Send Unknown";
			bonuses["doUnknown"]="* Get Unknown";

			//create option values and names;
			for (var i in bonuses) {
				var elem
				if (appID) elem = createElement("option",{textContent:((i.startsWith(appID+"send"))?"Send ":((bonuses[i].substring(0,1)=="*")?"":"Get "))+bonuses[i],value:((dropID)?i.removePrefix(appID):i)});
				else elem = createElement("option",{textContent:bonuses[i],value:i});

				if (appID) {if (selected==((dropID)?i.removePrefix(appID):i) ) elem.selected = true;}
				else {if (selected==i) elem.selected=true;}

				optsret.push(elem);
			}

			return optsret;
		},

		reIDAll : function(){
			for (var p in WM.posts) {
				if (!WM.posts[p].isGhost && WM.posts[p].identify({reid:true}))
					WM.rulesManager.doEvent("onIdentify",WM.posts[p]);
			}
			WM.sortPosts(); //in this case sorting may cancel movetotop and movetobottom
			WM.clearGroups();
			WM.redrawPosts({postRedraw:true});
		},

		updatePostStatus : function(id){
			var status = WM.posts[id].status;
			var statusNode = selectSingleNode(".//*[contains(@class,'status')]",{node:$("post_"+id)});
			if (statusNode) statusNode.textContent="Status: "+(status||"0") + " " + (WM.statusText[status||"0"]);
			status=null; statusNode=null;
		},

		onLikePost : function(post){
			post.isLiked=true;
			return;
			
			//pre beta 40 stuff
			var postID=tab.id;
			var post=tab.post||WM.posts[postID];

			//detect if post process was cancelled by user
			if (post.processCancelled){
				//reset the cancel memory
				post.processCancelled = false;
				log("onLikePost: feedback cancelled by user");
				WM.collector.close(tab);
				return;
			}

			//detect if valid WM.collector window still exists
			var windowExists=(tab.hwnd && !tab.hwnd.closed);

			//check if window object is missing
			if (!windowExists) {
				log("onLikePost: tab.hwnd is null or closed");
				WM.collector.close(tab);
				return;
			}
			
			try{
				var like=tab.hwnd.location.hash.removePrefix("#").getUrlParam("status")=="1";
				if (like) {
					if (tab.post) {
						//tell the post it is liked
						tab.post.isLiked = true;
						//delete the post reference from the tab
						delete tab.post;
					}
					WM.collector.close(tab);
					return;
				}
			} catch (e){
				//log(""+e);
			}

			tab.tries=(tab.tries||0)+1;
			if (tab.tries<WM.opts.autoliketimeout) setTimeout(function(){WM.onLikePost(tab);}, 1000);
			else {
				log("onLikePost: unable to finish feedback",{level:3});
				doAction(function(){WM.collector.close(tab);});
			}
		},

		toggleSidekick : function(){
			var appID = this.id.split("master_")[1];
			var opt = !(WM.quickOpts["masterSwitch"][appID]||false); //toggle
			WM.quickOpts["masterSwitch"][appID]=opt;
			var className = this.parentNode.className;
			this.parentNode.className = ((opt)?className.removeWord("disabled"):className.addWord("disabled"));
			this.textContent=((opt)?"Disable":"Enable");
			WM.saveQuickOpts();
		},

		saveQuickOpts : function(){
			setOptJSON('quickopts_'+WM.currentUser.profile, WM.quickOpts);
		},

		setAppFilter : function(tab){
			WM.quickOpts.filterApp=tab.appFilter;
			WM.saveQuickOpts();
			WM.clearGroups();
			WM.redrawPosts({postRedraw:false,reorder:true});
			WM.rulesManager.doEvent("onSetAppFilter",WM.apps[tab.appFilter]);
			//debug.print(["Collection Tab Selected",WM.currentAppTab,WM.apps[tab.appFilter]]);
		},
		
		setDisplay : function(){
			var x=this.getAttribute("name");
			WM.quickOpts.displayMode=x;
			WM.saveQuickOpts();
			WM.redrawPosts({postRedraw:true,reorder:true});
			WM.setDisplayCols();
		},
		
		setDisplayCols : function(params){
			params=params||{};
			params.cols=params.cols||WM.quickOpts.displayCols;
			WM.quickOpts.displayCols=params.cols||1;
			WM.saveQuickOpts();
			with (WM.console.feedNode) {
				className=className
					.toggleWordB(params.cols==1,"singleCol")
					.toggleWordB(params.cols==2,"twoCol")
					.toggleWordB(params.cols==3,"threeCol")
					.toggleWordB(params.cols==4,"fourCol");
			}
		},

		redrawPosts : function(params){
			params=params||{};
			var feedNode=WM.console.feedNode;
			
			//set the proper display mode
			feedNode.className=feedNode.className
				.toggleWordB((WM.quickOpts.displayMode=="1" || WM.quickOpts.displayMode=="3"),"short");
			
			//avoid order issues by removing the posts from the panel
			WM.clearPosts();

			//redraw||reorder
			for (var p in WM.posts) {
				var post=WM.posts[p];
				if (!post.isGhost) {
					post.draw(params.postRedraw,params.reorder);
				}
			}
		},

		moveFloater : function(ev){
			if (isChrome) return;
			var img=this, offset=trueOffset(img), scrolled=trueScrollOffset(img),
				post=selectSingleNode(".//ancestor::div[starts-with(@id,'post')]",{node:img}),
				floater=$(post.id.replace("post","floater")), special={};

			//log( (scrolled.left) +","+ (scrolled.top) );

			special.x=(ev.clientX > (document.documentElement.clientWidth/2))?-(240+4+22):0; //width+overshot+BorderAndPadding
			special.y=(ev.clientY > (document.documentElement.clientHeight/2))?-(120+4+12):0;

			floater.style.left=(ev.clientX-(offset.left-scrolled.left))+(2+special.x)+"px";
			floater.style.top=(ev.clientY-(offset.top-scrolled.top))+(2+special.y)+"px";
		},
		
		//create a drip system for autolike, instead of an offset
		queAutoLike : function(post){
			var nowTime = timeStamp();
			var lastInQue = WM.likeQueue.last();
			var targetTime = nowTime + (1000*WM.opts.autolikedelay);
			if (lastInQue||null) {
				if (lastInQue.timer>nowTime) {
					targetTime = lastInQue.timer + (1000*WM.opts.autolikedelay);
				}
			}
			WM.likeQueue.push({post:post, timer:targetTime, fn:"like"});
			WM.console.likeQueueCounterNode.textContent = WM.likeQueue.length;
		},
		
		//create a drip system for autolike, instead of an offset
		queAutoComment : function(post,say){
			var nowTime = timeStamp();
			var lastInQue = WM.likeQueue.last();
			var targetTime = nowTime + (1000*WM.opts.autolikedelay);
			if (lastInQue||null) {
				if (lastInQue.timer>nowTime) {
					targetTime = lastInQue.timer + (1000*WM.opts.autolikedelay);
				}
			}
			WM.likeQueue.push({post:post, timer:targetTime, say:say, fn:"comment"});
			WM.console.likeQueueCounterNode.textContent = WM.likeQueue.length;
			//log(["autocomment added",say]);
		},		

		//dump the autolike queue
		emptyAutoLikeQue : function() {
			WM.likeQueue=[];
			WM.console.likeQueueCounterNode.textContent = 0;
		},
		
		//get the next ready autolike target
		checkAutoLikeQue : function() {
			if (WM.likeQueue.length<1) return null;
			var nowTime = timeStamp();
			if (WM.likeQueue[0].timer<=nowTime) {
				WM.console.likeQueueCounterNode.textContent = (WM.likeQueue.length-1);
				var t=nowTime;
				for (var i in WM.likeQueue) {
					i.timer = t;
					t+=(1000*WM.opts.autolikedelay);
				}
				return WM.likeQueue.shift(); // no longer returns the post, but the block of what to do with what post
			}
			return null;
		},

		processPosts : function(){
			//dont run if menu is open or if requests are still out or if the console is paused
			if($("Config") || (WM.requestsOpen >= WM.opts.maxrequests) || WM.paused) return;

			var postNode=selectSingleNode(".//div[starts-with(@id,'post_') and contains(@class,'collect') and not(contains(@class,'paused') or contains(@class,'working'))]",{node:WM.console.feedNode});
			if (postNode) {
				var post = WM.posts[postNode.id.replace('post_','')];
				if (post) post.open();
			}
		},

		olderPosts : function (params) {
			WM.fetch({older:true});
		},

		newerPosts : function (params) {
			WM.fetch({newer:true});
		},
		
		fetchRange : function (params) {
			WM.fetch({bypassPause:true, older:true, targetEdge:params.oldedge, currentEdge:params.newedge});
		},

		cleanPosts : function () {try{
			for (var p in WM.posts) if (!WM.posts[p].isGhost) {
				var post = WM.posts[p];
				with (post) if (!(
					isPinned || isCollect || isWorking ||
					(isTimeout && !WM.opts.cleanTimedOut)
				)) post.remove();
			}
		}catch(e){log("WM.cleanPosts(): "+e);}},
		
		setIntervals : function() {try{
			//setup the timer to try post collection
			if (procIntv) window.clearInterval(procIntv);
			procIntv=window.setInterval(WM.processPosts, 2000);

			//setup the timer to get new posts
			/*
			if (newIntv) window.clearInterval(newIntv);
			if(calcTime(WM.opts.newinterval)>0) newIntv=window.setInterval(WM.newerPosts, calcTime(WM.opts.newinterval));
			*/

			//setup the timer to get older posts
			/*
			if (oldIntv) window.clearInterval(oldIntv);
			if(calcTime(WM.opts.oldinterval)>0) oldIntv=window.setInterval(WM.olderPosts, calcTime(WM.opts.oldinterval)+2000);
			*/
			
			olderLimit=calcTime(WM.opts.maxinterval)||0;

			//setup the timer to refresh page, fetching newer content
			if (refreshIntv) window.clearInterval(refreshIntv);
			if(calcTime(WM.opts.autoRefresh)>0) refreshIntv=window.setInterval(WM.refreshPage, calcTime(WM.opts.autoRefresh));

			//setup the timer to clean up old posts from the feed
			if (cleanIntv) window.clearInterval(cleanIntv);
			if(calcTime(WM.opts.cleaninterval)>0) cleanIntv=window.setInterval(WM.cleanPosts, calcTime(WM.opts.cleaninterval)+250);

			//setup global heartbeat
			if (hbIntv) window.clearInterval(hbIntv);
			hbIntv=window.setInterval(WM.onHeartbeat, WM.opts.heartRate);
			
		}catch(e){log("WM.setIntervals: "+e);}},

		hideCounters : function(){try{
			hideNodes("//*[contains(@class,'accFailBlock')]");
		}catch(e){log("WM.hideCounters: "+e);}},

		showCounters : function(){try{
			showNodes("//*[contains(@class,'accFailBlock')]");
		}catch(e){log("WM.showCounters: "+e);}},

		validatePost : function(fbPost){try{
			//alert("validatepost");
			//validate required post fields
			/*if (!( exists(fbPost.application) && exists(fbPost.link) && fbPost.type=="link")) {
				return;
			}*/

			//accept only posts we have sidekicks for
			var app=WM.apps[fbPost.app_id];
			//debug.print(app);
			if (!exists(app)) return;
			//debug.print("app detected");
			
			//prevent redrawing same post in case one slips past the graph validator
			var postID=fbPost.source_id + "_" + fbPost.post_id;
			//debug.print([postID,fbPost.qid]);
			if (WM.posts[postID]||null) {
				//debug.print(WM.posts[postID].id);
				return;
			}
			//debug.print("no duplicate post detected");
			//fbPost.pageNode.className +=" notDuplicatePost";

			//accept only posts for which a sidekick is enabled
			if (!WM.quickOpts.masterSwitch[app.appID]) return;
			//debug.print("sidekick for this app is ready and willing");

			//create a Post object from the post data
			var post=(WM.posts[postID]=new WM.Post(fbPost));
			if (post) {
				var hasID=post.identify();
				WM.sortPosts(); //make sure new posts fit the current sort order and direction
				if (hasID) {
					WM.rulesManager.doEvent("onValidate",post);
					WM.rulesManager.doEvent("onIdentify",post);
					post.draw(true,true);
					//debug.print("post has been told to draw");
					
					//track the post
					if (WM.opts.useFriendTracker && !post.isMyPost){
						WM.friendTracker.track(post);
					}
				}
			} else {
				log("WM.validatePost: Unable to transform post data into a useful post object. (id:"+fbPost.source_id + "_" + fbPost.post_id+")");
			}
		}catch(e){log("WM.validatePost: "+e);}},

		handleEdges : function(params){
			/*
				apps
				friends
				edge:{newer,older}
			*/
			//console.log("handleEdges: "+JSON.stringify(params));
			
			if (params.friends||null) {
				//update user created feeds
				for (var f=0,l=WM.feedManager.feeds.length;f<l;f++){
					var feed = WM.feedManager.feeds[f];
					//if this feed is listed in those passed back...
					if (params.friends.contains(feed.id)){
						//update each app filter in this feed
						for (var c=0,l=params.apps.length;c<l;c++) {
							var appID=params.apps[c];
							filter = feed.filters["app_"+appID];
							if (!(filter||null)) {
								//this filter does not exist, create one
								filter=feed.addFilter({id:"app_"+appID});
							}
							if (params.edge.older) filter.oldedge = params.edge.older;
							if (params.edge.newer) filter.newedge = params.edge.newer;
							filter.oldedgeNode.textContent = filter.oldedge;
							filter.newedgeNode.textContent = filter.newedge;
							if (timeStamp()-(filter.oldedge*1000)>olderLimit) filter.olderLimitReached=true;
						}
					}
				}
			} else {
				//update base feed
				feed = WM.feedManager.feeds[0];
				for (var c=0,l=params.apps.length;c<l;c++) {
					var appID=params.apps[c];
					//update each app filter in this feed
					filter = feed.filters["app_"+appID];
					if (!(filter||null)) {
						//this filter does not exist, create one
						filter=feed.addFilter({id:"app_"+appID});
					}
					if (params.edge.older) filter.oldedge = params.edge.older;
					if (params.edge.newer) filter.newedge = params.edge.newer;
					filter.oldedgeNode.textContent = filter.oldedge;
					filter.newedgeNode.textContent = filter.newedge;
					if (timeStamp()-(filter.oldedge*1000)>olderLimit) filter.olderLimitReached=true;
				}					
			}
		},
				
		fetch : function(params) {try{
			/*
				older:bool
				newer:bool
				apps:[]
				feed:[]
				targetEdge:unixtime
				currentEdge:unixtime
				bypassPause:bool
				bypassFeedDisabled:bool
				bypassAppDisabled:bool
			*/
		
			params=params||{};
			if (WM.fetchPaused && !params.bypassPause) return;
			
			Graph.fetchPosts({bypassPause:true});

			//convert a single passed app to a single entry list
			/*
			if (exists(params.apps) && ((params.apps.objType||null)=="app")) {
				var ret={};
				ret[params.apps.appID]=params.apps;
				params.apps=ret;
			}
			
			var useApps = params.apps||WM.apps;

			//convert a single passed feed to an array
			if (exists(params.feeds) && ((params.feeds.objType||null)=="feed")) {
				params.feeds=[params.feeds];
			}
			params.currentEdge = params.currentEdge||null; //nullify undefined edge
						
			//for each feed individually
			var feeds=params.feeds||WM.feedManager.feeds;
			for (var f=0,len=feeds.length;f<len;f++) { 
				var feed=feeds[f];
				var friend=(feed.url!="https://graph.facebook.com/me/home")?[feed.id]:null;
				
				//ignore the old me feed because it is a duplicate of the wall feed
				if (feed.url!="https://graph.facebook.com/me/feed") if (feed.enabled || params.bypassFeedDisabled) {
								
					//for each app make a separate fetch call for the given feed
					//override this: no more by-app fetching
					
					if (false && !WM.opts.groupFetching && (useApps||null)) {
						for (var a in useApps) {
							var app=useApps[a];
							
							//only fetch for enabled apps
							//where we are fetching new
							//or if we are fetching old we are not at our older limit
							var feedFilter=feed.filters["app_"+a];
							if ((app.enabled || params.bypassAppDisabled) && (feedFilter.enabled || params.bypassFilterDisabled) && !(
									params.older && feedFilter.olderLimitReached
								)
							){
								var G=Graph.fetchPostsFQL_B({
									callback:WM.validatePost,
									direction:(params.newer?1:(params.older?-1:0)),
									limit:WM.opts.fetchQty,
									targetEdge:(params.targetEdge||null), //special for new rules manager actions
									friends:friend,
									apps:[app.appID],
									currentEdge:params.currentEdge||(params.newer?feedFilter.newedge:(params.older?feedFilter.oldedge:null)),
									edgeHandler:WM.handleEdges,
									noAppFiltering:WM.opts.noAppFiltering
									
								});								
							}
						}
						
						
					//join apps together before fetching a single time for the given feed
					} else {
						//get the keys of the apps collection
						var keys=Object.keys(useApps);
						//if any sidekicks are docked
						if (keys.length) {
							//get the values of the apps collection
							var appsToProcess=keys.map(function (key) {
								return useApps[key];
							//filter out which apps are able to be fetched for
							}).filter(function(o,i,p){
								//get the feed filter text
								var feedFilter=feed.filters["app_"+o.appID];
								//get if the app is enabled
								var isEnabled = (o.enabled || params.bypassAppDisabled);
								var isFilterEnabled=true,isOlderLimitReached=false; 
								if (feedFilter||null) {
									//get if the feed filter is enabled
									isFilterEnabled = (feedFilter.enabled || params.bypassFilterDisabled);
									//get if the feed filter has already reached its older edge limit
									isOlderLimitReached = (params.older && feedFilter.olderLimitReached);
								} else {
									//feed filter does not exist for this app
									//assume it was deleted by the user on purpose
									//and don't fetch for this app on this feed
									log("WM.fetch: could not find filter for " + o.appID + "in feed " + feed.id);
									return false;
								}
								if (isEnabled && isFilterEnabled && !isOlderLimitReached) return true;
								return false;
							//simply the array
							}).map(function(o,i,p){
								//just get the id's of apps to do, not the entire app object
								return o.appID;
							});
							
							//make sure we matched filters to process
							if (appsToProcess.length){
								//get the shared edges of the passed apps
								var edges = feed.getMergedEdges({apps:appsToProcess});
								//console.log("getMergedEdges returned: "+JSON.stringify(edges));
								
								var G=Graph.fetchPostsFQL_B({
									callback:WM.validatePost,
									direction:(params.newer?1:(params.older?-1:0)),
									limit:WM.opts.fetchQty,
									targetEdge:(params.targetEdge||null), //special for new rules manager actions
									friends:friend,
									apps:appsToProcess,
									currentEdge:params.currentEdge||(params.newer?edges.newedge:(params.older?edges.oldedge:null)),
									edgeHandler:WM.handleEdges,
									noAppFiltering:WM.opts.noAppFiltering
								});
							}
						}
					}
					
					
				}
			}
			*/
		}catch(e){log("WM.fetch: "+e);}},
		
		changeDebugSettings : function(){try{
			if (debug && debug.initialized) {
				debug.doDebug = WM.opts.debug;
				debug.debugLevel = parseInt(WM.opts.debugLevel);
				debug.debugMaxComments = WM.opts.debugMaxComments;
				debug.useScrollIntoView = WM.opts.debugScrollIntoView;
				debug.stackRepeats = WM.opts.debugStackRepeats;
			} else {
				if (debug) debug.init();
				setTimeout(WM.changeDebugSettings,1000);
			}
		}catch(e){log("WM.changeDebugSettings: "+e);}},
				
		changeConfigSettings : function(){try{
			WM.config.sectionsAsTabs=WM.opts.configSectionsAsTabs;
			WM.config.separatorsAsTabs=WM.opts.configSeparatorsAsTabs;
			WM.config.useScrollIntoView=WM.opts.configScrollIntoView;
			WM.config.confirms={
				save:WM.opts.configConfirmSave,
				cancel:WM.opts.configConfirmCancel,
				"import":WM.opts.configConfirmImport,
				restore:WM.opts.configConfirmRestore
			};
		}catch(e){log("WM.changeConfigSettings: "+e);}},

		resizeConsole : function(){try{
			//negotiate height with fb bluebar
			var node=$("pagelet_bluebar");
			var h=(node)?elementOuterHeight(node):0;
			
			with($("wmContent")){
				style.height=document.documentElement.offsetHeight-h+"px";
				style.width=document.documentElement.offsetWidth+"px";
			}
			WM.console.tabContainer.redraw();
			WM.console.collectTabControl.redraw();

		}catch(e){log("WM.resizeConsole: "+e);}},

		setColors : function(){try{
			var colors=["excluded","working","timeout","paused","nodef","failed","accepted","scam","pinned"];
			var css="";
			for (var c=0, color; (color=colors[c]); c++) {
				css+=("div."+color+"{background-color:"+WM.opts["colors"+color]+" !important;}\n");
			}
			//set the new transition delay timer
			css+=(".wm.post.short:hover .floater {-moz-transition-property: padding,border,width,height;-moz-transition-delay:"+WM.opts["transitiondelay"]+"s; width:240px; padding:5px 10px;border:1px solid;}\n");
			remove($("user_colors_css"));
			addGlobalStyle(css,"user_colors_css");
		}catch(e){log("WM.setColors: "+e);}},

		initConsole : function(){try{
			WM.console.loading=false;
			if (WM.console.initialized) log("WM Console Initialized");

			//show options menu button
			with (WM.console.configButton) {
				className = className.removeWord("jsfHidden");
			}

			//set console heights
			WM.resizeConsole();

			//load feed sources
			WM.feedManager.init();
			
			//import friend tracker data
			//and delete posts out of bounds with our "track for how many days"
			WM.friendTracker.init();
			WM.friendTracker.clean();

			//initialize user colors
			WM.setColors();
			
			//set up the priorities and limits object
			//and new rules manager
			WM.rulesManager.init();

			//decipher the dynamic tests
			WM.grabber.init();

			//show counters
			if (WM.opts.showcounters) WM.showCounters(); else WM.hideCounters();
			
			//set intervals
			WM.setIntervals();
			
			//set autopause
			if (WM.opts.autopausecollect) WM.pauseCollecting(true);
			if (WM.opts.autopausefetch) WM.pauseFetching(true);
			
			//open a channel for sidekick communication
			WM.fetchSidekickData();

			//add an entrypoint for sidekicks since we know FB gave us access
			var createDock = function(){
				document.body.appendChild(
					createElement('div',{id:'wmDock',style:'display:none;',onclick:function(){
						WM.dock.answerDockingDoor();
					}})
				);
				document.body.appendChild(
					createElement('div',{id:'wmDataDump',style:'display:none;'})
				);
			};
			
			createDock();			

		}catch(e){log("WM.initConsole: "+e);}},

		cleanHistory : function(params){try{
			log("Cleaning History");
			params=params||{};
			var ask=WM.opts.historyConfirmClean;
			if (params.noConfirm || !ask || (ask && confirm("Clean and pack history for this profile?"))){
				//history = getOptJSON("history_"+WM.currentUser.profile)||{};
				var ageDays=parseInt(WM.opts.itemage);
				var timeNow=timeStamp();
				for(var i in WM.history) {
					if( ( (timeNow-WM.history[i].date) /day) > ageDays) {
						delete WM.history[i];
					}
				}
				setOptJSON("history_"+WM.currentUser.profile, WM.history);
			}
		}catch(e){log("WM.cleanHistory: "+e);}},

		optionsSetup : function(){try{
			//debug.print("WM.optionsSetup:");
			
			//create the settings tree
			var configContent = {};
			configContent.storageName="settings_"+(WM.quickOpts.useGlobalSettings?"global":WM.currentUser.profile);
			configContent.onSave=WM.onSave;
			configContent.title="FB Wall Manager "+WM.version+(WM.quickOpts.useGlobalSettings?" (!! Global Settings !!)":"");
			configContent.logo=createElement("span",{}[
				createElement("img",{className:"logo",src:"",textContent:"v"+WM.version}),
				createElement("text","v"+WM.version)
			]);
			configContent.css=WM.console.dynamicIcons()+jsForms.globalStyle();
			
			//debug.print("starting settings section");
			configContent.settings={}; o=configContent.settings;
			
			o.btn_useGlobal={
				type:"button",
				label:"Use Global Settings", 
				title:"Switch to using a global storage for settings. Those settings can then be used by other accounts (not browser profiles).",
				script:function(){
					if (WM.quickOpts.useGlobalSettings||false) {
						//already using global settings
						return;
					}
					if (confirm("Switch to using global (shared) settings?")){
						WM.quickOpts.useGlobalSettings=true;
						WM.saveQuickOpts();
						WM.config.title = "FB Wall Manager "+WM.version+" (!! Global Settings !!))";
						WM.config.storageName = "settings_global";
						WM.config.values=WM.config.read();
						WM.config.configure();
						WM.config.reload();
					}
				},
			};
			o.btn_useOwnProfile={
				type:"button",
				label:"Use Profile Settings", 
				title:"Switch to using your own profile storage for settings.",
				script:function(){
					if (!(WM.quickOpts.useGlobalSettings||false)) {
						//already using profile settings
						return;
					}
					if (confirm("Switch to using your own profile settings?")){
						WM.quickOpts.useGlobalSettings=false;
						WM.saveQuickOpts();
						WM.config.title = "FB Wall Manager "+WM.version;
						WM.config.storageName = "settings_"+WM.currentUser.profile;
						WM.config.values=WM.config.read();
						WM.config.configure();
						WM.config.reload();
					}
				},
			};
			
			//debug.print("host options section start");
			o.wmtab_opts=tabSection("Host Options",{
			
				section_basicopts:section("Basics",{
					/*authTokenTools:optionBlock("Authorization",{
						devAuthToken:checkBox("Automatically check my developer tool app for my Auth Token"),									
					},true),*/
				
					intervals:optionBlock("Post Fetching",{								
						newinterval:{
							label:"Read Visible Posts Interval",
							type:"selecttime",
							title:"Read new posts from page after a set time (those on the page not yet read).",
							options:{
								"off":"Off",
								"tenth":"6 seconds",
								"sixth":"10 seconds",
								"half":"30 seconds",
								"one":"1 minute",
								"two":"2 minutes",
								"three":"3 minutes",
								"four":"4 minutes",
								"five":"5 minutes",
								"ten":"10 minutes",
							},
							"default":"t:30s"
						},

						autoRefresh:{
							label:"Refresh Page Interval",
							type:"selecttime",
							title:"Refresh page to look for newer posts.",
							options:{
								"off":"Off",
								"one":"1 minute",
								"two":"2 minutes",
								"three":"3 minutes",
								"four":"4 minutes",
								"five":"5 minutes",
								"ten":"10 minutes",
							},
							"default":"off"
						},

						/*fetchQty:{
							label:"Fetch how many? (subject to filtering)",
							type:"select",
							title:"Posts fetched per request. Higher numbers affect speed of fetching.",
							options:{
								"5":"5",
								"10":"10",
								"25":"25",
								"50":"50",
								"100":"100",
								"250":"250",
								"500":"500 (FB maximum)", //known maximum fetch as of 9/8/2013
							},
							"default":"25"
						},*/

						/*oldinterval:{
							label:"Get Older Posts Interval",
							type:"selecttime",
							title:"Fetch previous posts from facebook after a set time.",
							options:{
								"off":"Off",
								"tenth":"6 seconds",
								"sixth":"10 seconds",
								"half":"30 seconds",
								"one":"1 minute",
								"two":"2 minutes",
								"three":"3 minutes",
								"four":"4 minutes",
								"five":"5 minutes",
								"ten":"10 minutes",
							},
							"default":"off"
						},*/

						maxinterval:{
							label:"How old is too old?",
							type:"selecttime",
							title:"Tell WM what you think is a good max post age to fetch. Also affects which posts are considered 'stale'.",
							options:{
								"off":"Off/Infinite",
								"hour":"1",
								"2hour":"2",
								"3hour":"3",
								"4hour":"4",
								"8hour":"8",
								"12hour":"12",
								"18hour":"18",
								"24hour":"24",
								"32hour":"32",
								"48hour":"48",
							},
							"default":"t:1d"
						},

						//groupFetching:checkBox("All installed sidekicks in one request (default: one request per sidekick)",false,{},true),
						//noAppFiltering:checkBox("Have WM filter posts for you instead of having facebook do it (may prevent some empty data set issues)",false,{},true),
					},true),

					autoPauseBlock:optionBlock("Fetching/Collecting Autopause",{
						autopausefetch:checkBox("Pause Fetching after First Fetch"),
						autopausecollect:checkBox("Pause Collection on Startup"),
					},true),

					multiTaskBlock:optionBlock("Multi-task",{
						maxrequests:inputBox("Max requests simultaneously",1),
						recycletabs:inputBox("Recycle Windows/Tabs",1),
						recycletabsall:checkBox("Recycle All",true),
					},true),

					queBlock:optionBlock("Task-Queue",{
						queuetabs:checkBox("Force all posts and autolikes through one tab using a queue (overrides multi-task)",true),
					},true),

					timeoutBlock:optionBlock("Time-outs",{
						reqtimeout:inputBox("Item Acceptance Page Timeout (seconds)",30),
						failontimeout:checkBox("Mark Timeout as Failure (default: retry indefinitely)"),
					},true),
				}),

				section_access:section("Accessibility",{
					shortModeBlock:optionBlock("Short Mode",{
						thumbsize:{
							label:"Thumbnail Size",
							type:"select",
							title:"Size of bonus thumbnails in display mode: short and .",
							options:{
								"mosquito":"16px",
								"tiny":"24px",
								"small":"32px",
								"medium":"48px",
								"large":"64px",
								"xlarge":"96px",
							},
							"default":"medium"
						},
						transitiondelay:inputBox("Hover Box Delay (s)",1),
					},true),

					accessTweaksBlock:optionBlock("Tweaks",{
						debugrecog:checkBox("Show Identified Text (instead of original link text)",true),
						showcounters:checkBox("Show Accept/Fail Counts",true),
						showdynamictips:checkBox("Show Dynamic Console Tips",true),
						appsConfirmDeleteUDT:checkBox("Confirm Delete User Defined Types",true),
					},true),

					toolBoxBlock:optionBlock("Customize Post Toolbox",{
						showtoolbox:checkBox("Enable ToolBox", true),

						showopen:checkBox("Open Post",true),
						showmarkfailed:checkBox("Mark As Failed",true),
						showmarkaccepted:checkBox("Mark As Accepted",true),
						showlike:checkBox("Like Post",true),
						showreid:checkBox("Re-ID Post",true),
						showmovetop:checkBox("Move to Top",true),
						showmovebottom:checkBox("Move to Bottom",true),
						showpin:checkBox("Pin Post",true),
						showclean:checkBox("Clean Post",true),
						showpostsrc:checkBox("Show Post Source",true),

						showcancelprocess:checkBox("Cancel Process or Like",true),
						showrestartprocess:checkBox("Restart Process or Like",true),
						showpausetype:checkBox("Pause Bonus Type",true),
						showunpausetype:checkBox("Unpause Bonus Type",true),
						showaddfeed:checkBox("Add To Feeds",true),

						showmakerule:checkBox("Rule From Post",true),
						showoriginaldata:checkBox("Show Original Data",true),
						//showautocomment:checkBox("Auto Comment",true),
						
					},true),
					
					littleToolBoxBlock:optionBlock("Customize Mini Toolbox",{
						littleButtonSize:{
							label:"Mini Toolbutton Size (requires refresh to redraw)",
							type:"select",
							title:"Size of buttons on mini toolbars",
							options:{
								"16":"16px",
								"24":"24px",
								"32":"32px",
							},
							"default":"24",
						},
					},true),

					userColorsBlock:optionBlock("Colors",{
						colorsaccepted:colorBox("Accepted","limegreen"),
						colorsfailed:colorBox("Failed","red"),
						colorsworking:colorBox("Working","yellow"),
						colorsexcluded:colorBox("Excluded","gray"),
						colorspaused:colorBox("Paused","silver"),
						colorsnodef:colorBox("No Definition","deepskyblue"),
						colorsscam:colorBox("Potential Scam","purple"),
						colorspinned:colorBox("Pinned","black"),
						colorstimeout:colorBox("Timeout","orange"),
					},true),
					

				}),

				section_feedback:section("Feedback",{
					//publishwarning:{type:"message",title:"Autolike has changed",textContent:"As of WM beta 40 you must allow 'publish_actions' on the 'user data permissions' tab in your Graph API Explorer token builder.",newitem:true},
					//gotoapiexplorer:anchor("Visit API Explorer","http://developers.facebook.com/tools/explorer?&version=v1.0"),
					autoSetup:optionBlock("Setup",{
						//useautocomment:checkBox("Use Auto-comment (experimental)"),
						useautolike:checkBox("Use Auto-like"),
						//autoliketimeout:inputBox("Timeout (seconds)",30),
						autolikedelay:inputBox("Ban-Prevention Delay (seconds)",3),
					},true),
					autoLikeBlock:optionBlock("Perform Feedback For",{
						autolikeall:checkBox("All Posts"),
						autolikeaccepted:checkBox("Accepted Posts"),
						autolikesent:checkBox("Sent Posts"),
					},true),
					/*autoCommentListBlock:optionBlock("Comments (experimental)",{
						autocommentlist:textArea("Random Comments (One per line)","Thanks\nThank you\nthanks"),
					},true),*/
					blockautolikebygame:optionBlock("Block Feedback by Game",{},false),
				}),

				section_filters:section("Filters",{
					displayfilters:optionBlock("Remove Feed Parts (Classic Mode Only)",{
						hideimages:checkBox("Images (All)"),
						hideimagesunwanted:checkBox("Images (Unwanted Posts)"),
						hidebody:checkBox("Post Body Text"),
						hidevia:checkBox("Via App"),
						hidedate:checkBox("Date/Time"),
					},true),

					filters:optionBlock("Hide By Type",{
						hidemyposts:checkBox("My Posts"),
						hideunwanted:checkBox("Unwanted"),
						hideaccepted:checkBox("Accepted"),
						hidefailed:checkBox("Failed"),
						hidescams:checkBox("Scams"),
						hidestale:checkBox("Stale Posts"),
						hideexcluded:checkBox("Excluded"),
						hideliked:checkBox("Liked By Me"),
						hideunsupported:checkBox("Unsupported Apps"),

						donthidewishlists:checkBox("Don't Hide Known Wish Lists"),
					}),

					//allow hiding all posts by particular games
					filterapps:optionBlock("Hide By App",{}),

					//now added dynamically as appID+"dontsteal"
					dontstealBlock:optionBlock("Don't take W2W posts not for me",{}),

					skipopts:optionBlock("Skip By Type",{
						skipliked:checkBox("Liked By Me"),
						skipstale:checkBox("Day-Old Posts"),
					}),

					filterTweaksBlock:optionBlock("Tweaks",{
						accepton15:checkBox("Mark 'Unrecognized Response' As Accepted"),
						markliked:checkBox("Mark Liked As Accepted (must check 'Skip By Type: Liked By Me')"),
					},true),

					filterCleanupBlock:optionBlock("Cleanup",{
						cleaninterval:{
							label:"Cleanup Interval",
							type:"selecttime",
							title:"Remove unwanted posts from collection console after a set time.",
							options:{
								"off":"Off",
								"one":"1 minute",
								"two":"2 minutes",
								"five":"5 minutes",
								"ten":"10 minutes",
								"fifteen":"15 minutes",
								"thirty":"30 minutes",
								"hour":"1 hour",
							},
							"default":"off"
						},
						cleanTimedOut:checkBox("Clean timed out posts",true),
					},true),
				}),

				section_history:section("History",{
					itemage:inputBox("How long to keep tried items in memory (days)",2),
					oblock_historyConfirms:optionBlock("Confirm (Changes available on next config open)",{
						historyConfirmClear:{type:"checkbox",label:"Clear History",title:"Confirm before clearing history.","default":true},
					},true),
					reset:button("Clear History",
						WM.resetAccepted
					),
				}),

				section_feedopts:section("Feeds Manager",{
					oblock_feedsConfirms:optionBlock("Confirm",{
						feedsConfirmDeleteFeed:{type:"checkbox",label:"Delete Rule",title:"Require confirmation to delete a feed.","default":true},
					},true),
				}),

				section_dynamicopts:section("Dynamic Grabber",{
					dynamicopts:optionBlock("Dynamic Collection",{
						dynamicFirst:checkBox("Run Dynamics BEFORE Sidekicks",true),
					},true),
					enableDynamic:optionBlock("Enable Dynamics by Game",{}),
					oblock_dynamicConfirms:optionBlock("Confirm",{
						dynamicConfirmDeleteTest:{type:"checkbox",label:"Delete Rule",title:"Require confirmation to delete a test.","default":true},
					},true),
				}),

				section_friendtrackopts:section("Friend Tracker",{
					useFriendTracker:checkBox("Enable Friend Tracking",true),
					trackTime:inputBox("Track For How Many Days",2),
					trackeropts:optionBlock("Track Data",{
						trackCreated:checkBox("Post Creation Counts",true),
						trackLastKnownPost:checkBox("Last Known Post Time",true),
						trackAccepted:checkBox("Bonuses Accepted",true),
						trackFailed:checkBox("Bonuses Failed",true),
					},true),
					oblock_trackerConfirms:optionBlock("Confirm",{
						trackConfirmClearUser:{type:"checkbox",label:"Clear User Data",title:"Require confirmation to clear user data.","default":true},
					},true),
				}),
				
				section_rulesopts:section("Rules Manager",{
					oblock_rulesHeartbeat:optionBlock("Heartbeat",{
						heartRate:inputBox("Global Heartbeat Delay (ms)",1000),
						heartbeatAffectsApps:{type:"checkbox",label:"Affect Apps",title:"Heartbeat can be heard at app level on every rule at once. This can slow down your system."},
						heartbeatAffectsPosts:{type:"checkbox",label:"Affect Posts",title:"Heartbeat can be heard at post level on every rule at once. This can slow down your system."},
						heartbeatAffectsRules:{type:"checkbox",label:"Affect Rules",title:"Heartbeat can be heard at rule level on every rule at once. This can slow down your system."},
						heartbeatAffectsFeeds:{type:"checkbox",label:"Affect Feeds",title:"Heartbeat can be heard at feed level on every rule at once. This can slow down your system."},
						heartbeatAffectsFeedFilters:{type:"checkbox",label:"Affect Feed Filters",title:"Heartbeat can be heard at feed filter level on every rule at once. This can slow down your system."},
					},true),
					oblock_rulesConfirms:optionBlock("Confirm",{
						rulesConfirmDeleteValidator:{type:"checkbox",label:"Delete Validator",title:"Require confirmation to delete a rule's validator.","default":true},
						rulesConfirmDeleteAction:{type:"checkbox",label:"Delete Action",title:"Require confirmation to delete a rule's action.","default":true},
						rulesConfirmDeleteRule:{type:"checkbox",label:"Delete Rule",title:"Require confirmation to delete a rule.","default":true},
						rulesConfirmResetLimit:{type:"checkbox",label:"Reset Limit",title:"Require confirmation to reset individual limits.","default":true},
						rulesConfirmResetAllLimits:{type:"checkbox",label:"Reset All Limits",title:"Require confirmation to reset all limits.","default":true},
						rulesConfirmHatch:{type:"checkbox",label:"Hatch Eggs",title:"Require confirmation to hatch eggs.","default":true},
					},true),
					rulesJumpToNewRule:{type:"checkbox",label:"Jump To New Rules",title:"When new rules are created from tests or posts, select the rules manager tab and scroll the new rule into view.","default":true},
				}),

				section_dev:section("Debug",{
					oblock_debugTweaks:optionBlock("Tweaks",{
						pinundefined:checkBox("Pin Undefined Bonus Types"),
					},true),
					debugOpts:optionBlock("Debug",{
						debug:checkBox("Enable Debug",true),
						debugLevel:{
							label:"Debug Sensitivity",
							title:"Sets the level of errors and warnings to report. 0 is all, 5 shows only the worst.",
							type:"select",
							options:{
								"0":"Function calls",
								"1":"Function subsections & debug notes",
								"2":"Captured expected errors",
								"3":"Known open errors",
								"4":"Unexpected errors",
								"5":"Fatal errors",
							},
							"default":"0"
						},
						debugMaxComments:inputBox("Max debug lines (0 for no limit)",100),
						debugScrollIntoView:checkBox("Use scrollIntoView"),
						debugStackRepeats:checkBox("Stack Immediate Repeats"),

					},true),
					advDebugOpts:optionBlock("Advanced Debug",{
						devDebugFunctionSubsections:checkBox("Debug Function Subsections",false),
						devDebugGraphData:checkBox("Debug Graph Packets (not available for Chrome)",false),
					},true),
					GM_special:optionBlock("Script-runner Options",{
						useGM_openInTab:checkBox("Use GM_openInTab instead of window.open",false),
					},true),

				}),

				section_configopts:section("Config",{
					oblock_configConfirms:optionBlock("Confirm (Changes available on next config open)",{
						configConfirmSave:{type:"checkbox",label:"Save",title:"Confirm before saving settings.","default":true},
						configConfirmCancel:{type:"checkbox",label:"Cancel",title:"Confirm before closing settings without saving.","default":true},
						configConfirmImport:{type:"checkbox",label:"Import",title:"Confirm before importing settings.","default":true},
						configConfirmRestore:{type:"checkbox",label:"Restore Defaults",title:"Confirm before restoring defaults.","default":true},
					},true),
					oblock_configStyling:optionBlock("Styling (Changes available on next config open)",{
						configSectionsAsTabs:{type:"checkbox",label:"Display Sections as Tabs",title:"Converts top level roll-outs only. Display those rollouts as tabs on next open of config."},
						configSeparatorsAsTabs:{type:"checkbox",label:"Display Separators as Tabs",title:"Converts second level roll-outs only. Display those rollouts as tabs on next open of config. Removes select all/none buttons on top of the separator."},
					},true),
					oblock_configTweaks:optionBlock("Tweaks (Changes available on next config open)",{
						configScrollIntoView:{type:"checkbox",label:"Use scrollIntoView",title:"When tabs and sections are opened, use the scrollIntoView function to bring them more fully into view. This is jerky at best."},
					},true),
				}),
			});

			/*wmtab_games:tabSection("Sidekick Options",{
				skmovedwarning:{type:"message",title:"Sidekick options have moved",textContent:"Sidekick options have been moved to separate config windows. Access them by using the 'Manage Sidekicks' tab, where you can find new 'Options' buttons for each sidekick."},
			}),*/
			//debug.print("info section start");
			o.wmtab_info=tabSection("Info",{
				MainMessageCenter:separator("Documentation - Messages - Help",null,{
					//Mainupdate:anchor("Update Script","http://userscripts.org/scripts/source/86674.user.js"),
					donateWM:{type:"link",label:"Donate for FBWM via Paypal",href:"https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=merricksdad%40gmail%2ecom&lc=US&item_name=Charlie%20Ewing&item_number=FBWM&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"},
					Mainwikipage:anchor("Wiki Support Page","http://fbwm.wikia.com/wiki/Known_Issues"),
					Mainsetupinfo:anchor("Setup Info","http://fbwm.wikia.com/wiki/New_User_Setup"),
					Maindiscuss:anchor("Known Bugs","http://fbwm.wikia.com/wiki/Known_Issues"),
					Mainrevisionlog:anchor("Revision Log","http://fbwm.wikia.com/wiki/Revisions"),
				},true)
			});
			//debug.print("done config content");
				

			
			//debug.print(configContent);
			WM.config = new Config(configContent);
			
			//debug.print("host options menu created");
			
			// add options shortcut to user script commands
			GM_registerMenuCommand("Wall Manager "+WM.version+" Options", function(){WM.config.open();});
		}catch(e){log("WM.optionsSetup: "+e);}},

		init : function(){try{
			//capture user id/alias/name and make it global
			WM.currentUser.id = Graph.userID;
			WM.currentUser.alias = Graph.userAlias;
			WM.currentUser.profile = WM.currentUser.id||WM.currentUser.alias; //switch to id over alias preference, as alias may change

			debug.print("UserID:"+WM.currentUser.id+"; UserAlias:"+WM.currentUser.alias+"; WM is Using:"+WM.currentUser.profile);

			//get WM.quickOpts
			WM.quickOpts = getOptJSON('quickopts_'+WM.currentUser.profile)||{};
			WM.quickOpts["filterApp"]=(WM.quickOpts["filterApp"]||"All");
			WM.quickOpts["displayMode"]=(WM.quickOpts["displayMode"]||"0");
			WM.quickOpts["masterSwitch"]=(WM.quickOpts["masterSwitch"]||{});
			WM.quickOpts["useGlobalSettings"]=(WM.quickOpts["useGlobalSettings"]||false);

			//create the options menu
			//debug.print("starting options setup");
			WM.optionsSetup(); 
			

			//duplicate the options saved in WM.config
			//debug.print("starting update settings values");
			WM.updateSettingsValues();

			//set up the config with its internal special variables
			WM.changeConfigSettings();

			//setup debug beyond its defaults
			WM.changeDebugSettings();
			
			//clean history
			WM.history = getOptJSON('history_'+WM.currentUser.profile)||{};
			WM.cleanHistory();
						
			//prep the console now that we have an id and/or alias
			//and then carry on with our init
			WM.console.init({callback:WM.initConsole});
		}catch(e){log("WM.init: "+e);}},
		
		receiveSidekickMessage: function(event) {
			if (isObject(event.data)) {
				var data=event.data; //just shorten the typing
				if (data.channel=="WallManager"){
					//log(JSON.stringify(data));
					//$("WM_debugWindow").childNodes[1].lastChild.scrollIntoView();
					switch (data.msg){
						case 2: //getting a comOpen response from sidekick
							//WM.collector.tabs[data.tabID].comOpen=true;
							break;
							
						case 4: //getting a status package from sidekick
							switch (data.params.action){
								case "onFrameLoad":
									WM.onFrameLoad(data.params);
									break;
								case "onFrameLoad3":
									WM.onFrameLoad3(data.params);
									break;
							}
							break;
					}
				}
			}
		},		

		run : function() {try{
			// pre-load console images
			//for(var img in imgs) try{new Image().src = imgs[img];}catch(e){log("preload: "+e);}

			/* Removed for version 4

			//special about:config entry for disabling storage of fb auth token
			//should help multi account users
			//if (getOpt("disableSaveAuthToken")) 
			Graph.authToken=null;
			
			//patch 38 auth token stuff
			var flagManualAuthSuccessful=getOpt("flagManualAuthSuccessful")||false;					
			if (WallManager.switches.manualAuthToken && !flagManualAuthSuccessful) {
				var m="WM can no longer access your FB Access Token without your manual assistance.\nTo successfully fetch posts, please complete the following:\n\n*In a new browser window, visit: http://developers.facebook.com/tools/explorer\n\n*If required, allow that app access to your facebook information\n*Find the 'Get Access Token' button and click it\n*In the panel that appears, click 'extended permissions'\n*Be sure that 'read_stream' is selected or otherwise not blank\n*If you want to use autolike/autocomment also select 'publish_actions' from the 'user data permissions' tab*Click the 'Get Access Token' button\n*Now find the box that says 'Access Token' and select its value\n*Copy that value and paste it into the box on this promp\n\nNote: this token does not last forever, you may need to repeat this process";
				var manualToken = prompt(m,"paste token here");
				
				//validate manualToken at least somewhat
				
				//halt if manual token is not given
				if (manualToken=="" || manualToken==null || manualToken=="paste token here") {
					alert("manual token not accepted, please refresh and try again");
					return;
				}
				
				//pass the manual token along
				Graph.authToken=manualToken;
				
				//consider saving time by looking for auth tokens automatically from here out
				var m = "WM thinks your auth token setup is successful.\nIf you like, I can make it so WM just checks your dev tool for new auth tokens every time.\n\nPress Cancel to continue entering auth codes manually.\n\n*If you have multiple facebook accounts on this computer using WM, please make sure you set up the API explorer with every account.";
				var saveProgress = confirm(m);
				if (saveProgress) {
					setOpt("flagManualAuthSuccessful",true);					
				}
			} 

			var G=Graph.fetchUser({callback:WM.init});
			
			if (G){if (G.requestAlreadyOut) {
			} else if (G.initRequestSlow) {
			} else if (G.olderLimitReached) {
			} else if (G.getAuthTokenFailed) {
			}}
			*/
			WM.init();
		}catch(e){log("WM.run: "+e);}}	

	};

	this.WM=WallManager;		
	
	//allow certain options to be seen outside of the WallManager object
	//graph extension is external but still calls on WM options if they exist
	this.opts=WM.opts;
	this.quickOpts=WM.quickOpts;

})();