Greasy Fork is available in English.

WM Host Object

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

لا ينبغي أن لا يتم تثبيت هذا السكريت مباشرة. هو مكتبة لسكبتات لتشمل مع التوجيه الفوقية // @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;

})();