FB Wall Manager

Manages Wall Posts for Various FB Games

  1. // ==UserScript==
  2. // @name FB Wall Manager
  3. // @namespace MerricksdadWallManager
  4. // @description Manages Wall Posts for Various FB Games
  5. // @include http*://www.facebook.com/pages/FB-Wall-Manager/*
  6. // @license http://creativecommons.org/licenses/by-nc-nd/3.0/us/
  7. // @grant GM_setValue
  8. // @grant GM_getValue
  9. // @grant GM_xmlhttpRequest
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_addStyle
  12. // @grant GM_log
  13. // @grant GM_openInTab
  14. // @grant GM_getResourceURL
  15. // @version 3.1.10
  16. // @copyright Charlie Ewing except where noted
  17. // @require https://greasyfork.org/scripts/416-wm-common-library/code/WM%20Common%20Library.user.js
  18. // @require https://greasyfork.org/scripts/417-wm-debug-console/code/WM%20Debug%20Console.user.js
  19. // @require https://greasyfork.org/scripts/418-js-forms-library-b/code/JS%20Forms%20Library%20B.user.js
  20. // @require https://greasyfork.org/scripts/419-wm-config-interface/code/WM%20Config%20Interface.user.js
  21. // @require https://greasyfork.org/scripts/420-wm-graph-api-interface-beta-branch/code/WM%20Graph%20API%20Interface%20(Beta%20Branch).user.js
  22. // @resource IconSheet http://i.imgur.com/sLxzUA6.png
  23. // ==/UserScript==
  24.  
  25. // retired libraries
  26. // @resource IconSheet http://images.wikia.com/fbwm/images/c/c0/Images.png
  27. // @require http://userscripts.org/scripts/source/29910.user.js
  28. // @require http://userscripts.org/scripts/source/129006.user.js
  29. // @resource IconSheet http://i1181.photobucket.com/albums/x430/merricksdad/images.png
  30. // @require http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js
  31. // http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.js
  32.  
  33. //userscripts library locations
  34. // @require http://userscripts.org/scripts/source/123889.user.js
  35. // @require http://userscripts.org/scripts/source/128747.user.js
  36. // @require http://userscripts.org/scripts/source/150983.user.js
  37. // @require http://userscripts.org/scripts/source/152610.user.js
  38. // @require http://userscripts.org/scripts/source/150032.user.js
  39.  
  40. // for testing only
  41. // @include file:///C:/FB-Wall-Manager/*
  42.  
  43. // retired autolike functions
  44. // @include /^https?:\/\/www\.facebook\.com\/.*\/posts\/.*/
  45.  
  46. // Based on script built by Joe Simmons in Farmville Wall Manager
  47.  
  48.  
  49. (function() {
  50. //***************************************************************************************************************************************
  51. //***** Preload
  52. //***************************************************************************************************************************************
  53. //dont run in iframes
  54. try {
  55. //this does not mean we are using GM's unsafe window
  56. var unsafeWindow = unsafeWindow || window.wrappedJSObject || window;
  57. if (unsafeWindow.frameElement != null) return;
  58. } catch(e) {log("preload: "+e);}
  59.  
  60. //***************************************************************************************************************************************
  61. //***** Debug Object
  62. //***************************************************************************************************************************************
  63. if (debug) {
  64. debug.init();
  65. if (debug.initialized) log("Debug Console Initialized");
  66. }
  67.  
  68. //***************************************************************************************************************************************
  69. //***** Globals
  70. //***************************************************************************************************************************************
  71. this.WallManager={
  72. paused : false,
  73. fetchPaused : false,
  74. requestsOpen : 0,
  75. reqTO : 30000,
  76. newSidekicks : [],
  77.  
  78. accDefaultText : "Got this!",
  79. failText : "Oh no! Sorry pardner!",
  80. overLimitText : "Limit reached!",
  81.  
  82. version:"3.1.10",
  83. currentUser:{
  84. id:"",
  85. profile:"",
  86. alias:""
  87. },
  88. resources:{
  89. iconsURL:GM_getResourceURL("IconSheet")
  90. },
  91. apps:{},
  92. posts:{},
  93. history:{},
  94. config:null,
  95. opts:{},
  96. quickOpts:{},
  97. displayGroups:{},
  98. likeQueue:[],
  99. switches:{
  100. manualAuthToken:true
  101. },
  102.  
  103. statusText : {
  104. "20":"Sidekick returned force accept",
  105. "3":"Marked as accepted by user",
  106. "2":"Responseless Collection",
  107. "1":"Accepted",
  108. "0":"Unknown",
  109. "-1":"Failed",
  110. "-2":"None Left",
  111. "-3":"Over Limit (App)",
  112. "-4":"Over Limit, Sent One Anyway",
  113. "-5":"Server Error",
  114. "-6":"Already Got",
  115. "-7":"Server Down For Repairs",
  116. "-8":"Problem Getting Passback Link",
  117. "-9":"Final Request Returned Null Page",
  118. "-10":"Final Request Failure",
  119. "-11":"Expired",
  120. "-12":"Not a Neighbor",
  121. "-13":"Requirements not met",
  122. "-14":"Timeout",
  123. "-15":"Unrecognized Response",
  124. "-16":"Passback Link is missing",
  125. "-17":"Window Missing",
  126. "-18":"Marked as failed by user",
  127. "-20":"Sidekick returned force fail",
  128. "-19":"Over Limit (Bonus Type)",
  129. "-21":"Cancelled mid-process by user",
  130. },
  131. sortGroups : function(params){
  132. params=params||{};
  133. params.direction=(WM.quickOpts.groupDirection=(params.direction||WM.quickOpts.groupDirection||"desc")); //default descending to keep time ordered posts in order newest to oldest
  134. WM.saveQuickOpts();
  135. //reorder the groups
  136. var groupsArray=[];
  137. for (var g in WM.displayGroups) {
  138. groupsArray.push({id:g,node:WM.displayGroups[g].parentNode,box:WM.displayGroups[g]});
  139. }
  140. if (["asc","ascending"].inArray(params.direction.toLowerCase())) groupsArray.sort(function(a,b){return a.id>b.id;});
  141. else if (["desc","descending"].inArray(params.direction.toLowerCase())) groupsArray.sort(function(a,b){return a.id<b.id;});
  142. WM.displayGroups={};
  143. for (var g=0; g<groupsArray.length; g++) {
  144. WM.displayGroups[groupsArray[g].id]=groupsArray[g].box;
  145. WM.console.feedNode.appendChild(groupsArray[g].node);
  146. }
  147. },
  148.  
  149. newGroup : function(params){
  150. params=params||{};
  151.  
  152. //prevent duplicates
  153. if (WM.displayGroups[params.by]||null) return WM.displayGroups[params.by];
  154. //create the nodes
  155. var box;
  156. var group=createElement("div",{className:"listItem"},[
  157. createElement("div",{className:"line", onclick:function(){
  158. //toggle rollout
  159. with (this.nextSibling) className=className.swapWordB((className.containsWord("collapsed")),"expanded","collapsed");
  160. with (this.firstChild.firstChild) className=className.swapWordB((className.containsWord("treeCollapse"+WM.opts.littleButtonSize)),"treeExpand"+WM.opts.littleButtonSize,"treeCollapse"+WM.opts.littleButtonSize);
  161. }},[
  162. createElement("div",{className:"littleButton",title:"Toggle Content"},[
  163. createElement("img",{className:"resourceIcon treeCollapse"+WM.opts.littleButtonSize}),
  164. ]),
  165. createElement("label",{textContent:params.label||params.by})
  166. ]),
  167. box=createElement("div",{className:"subsection rollout expanded"}),
  168. ]);
  169. //add it to our group list
  170. WM.displayGroups[params.by]=box;
  171. WM.sortGroups();
  172. return box;
  173. },
  174. pauseCollecting : function(doPause){
  175. var isPaused;
  176. if (exists(doPause)) isPaused = (WM.paused = doPause);
  177. else isPaused=(WM.paused = !WM.paused);
  178. var btn=WM.console.pauseCollectButton;
  179. btn.className = btn.className.swapWordB(isPaused,"oddGreen","oddOrange");
  180. btn.title = (isPaused)?"Start Automatic Collection":"Pause Automatic Collection";
  181. var img = btn.childNodes[0];
  182. img.className = img.className.swapWordB(isPaused,"playRight24","stop24");
  183. },
  184.  
  185. pauseFetching : function(doPause){
  186. var isPaused;
  187. if (exists(doPause)) isPaused = (WM.fetchPaused = doPause);
  188. else isPaused=(WM.fetchPaused = !WM.fetchPaused);
  189. var btn=WM.console.pauseFetchButton;
  190. btn.className = btn.className.swapWordB(isPaused,"oddGreen","oddOrange");
  191. btn.title = (isPaused)?"Start Automatic Fetching":"Pause Automatic Fetching";
  192. },
  193.  
  194. clearGroups : function(params){
  195. //destroy previous groups
  196. for (var g in WM.displayGroups){
  197. remove(WM.displayGroups[g].parentNode); //kill the node
  198. delete WM.displayGroups[g]; //remove from list
  199. }
  200. },
  201.  
  202. clearPosts : function(){
  203. //remove all post nodes from the collector panel
  204. for (var p in WM.posts){
  205. if (WM.posts[p].node) remove(WM.posts[p].node);
  206. }
  207. },
  208. constructGroups : function(params){
  209. params=params||{};
  210. //this specifically allows a null so we can remove grouping
  211. var by=exists(params.by)?params.by:WM.quickOpts.groupBy;
  212. //if nothing changed, just cancel
  213. if (by==WM.quickOpts.groupBy) return;
  214. //set the new group order
  215. WM.quickOpts.groupBy=by;
  216. WM.saveQuickOpts();
  217. WM.clearGroups();
  218. },
  219. sortPosts : function(params){
  220. params=params||{};
  221. params.direction=(WM.quickOpts.sortDirection=(params.direction||WM.quickOpts.sortDirection||"desc")); //default descending to keep time ordered posts in order newest to oldest
  222. params.by=(WM.quickOpts.sortBy=(exists(params.by)?params.by:(WM.quickOpts.sortBy||"created_time"))); //default by date
  223. WM.saveQuickOpts();
  224.  
  225. //convert to array
  226. var postsArray=methodsToArray(WM.posts);
  227.  
  228. //sort
  229. postsArray.sort(function(a,b){
  230. if (["ascending","asc"].inArray(params.direction.toLowerCase())) return a[params.by]>b[params.by];
  231. if (["descending","desc"].inArray(params.direction.toLowerCase())) return a[params.by]<b[params.by];
  232. });
  233.  
  234. //convert back to object
  235. WM.posts=arrayToMethods(postsArray);
  236. },
  237.  
  238. doWhichTestTree : function(post, testList, testData, custom) {try{
  239. //match post to an app
  240. var app=post.app;
  241. var synApp=app.synApp, w=null;
  242.  
  243. for (var i=0,test;((test=testList[i]) && (w===null));i++) {
  244.  
  245. //run only for tests that are not specifically disabled
  246. if (test.enabled===false) continue;
  247. //set find mode
  248. var findMode="auto";
  249. //finish constructing dynamic collection tests
  250. var ret = test.ret;
  251. if (custom) {
  252. if (!ret) ret = "dynamic"; //default to dynamic
  253. if (ret!="dynamic" && ret!="none" && ret!="exclude" && !ret.startsWith(synApp.appID)) ret=synApp.appID+ret; //add appID except to magic words
  254. findMode=test.findMode;
  255. }
  256.  
  257. //part to make dynamic collection tests work only if they are the correct appID
  258. //also do not process disabled tests
  259. if (!custom || (custom && (!test.appID || (app.appID==test.appID)))){
  260.  
  261. //if the test is not disabled (by test enabled both existing and being false)
  262. //OR if the test IS a dynamic test and the appID matches
  263. //OR if the test IS a dynamic test and no appID was supplied
  264. //then run the test
  265.  
  266. //detect test type
  267. var testType=(test.search||null);
  268. var types=WM.grabber.methods;
  269. if (!testType) for (var tt=0,len=types.length; tt<len; tt++) {if (test[types[tt]]||"") {testType=types[tt];break;}}
  270.  
  271. //select the type of data to use
  272. var src="";
  273. if (isArray(testType)){ //new search array format
  274. for (var t=0,tlen=testType.length;t<tlen;t++) src+=(testData[testType[t]]||"");
  275. }
  276. else src = (testData[testType]||""); //old test method like testType:text
  277.  
  278. if (src){
  279. //begin processing this test
  280. var subTests=test.subTests, kids=test.kids, allowNone=false, subNumRange=test.subNumRange,text=(test.find||test[testType]||"");
  281.  
  282. //process subtests array
  283. if (subTests && (findMode=="auto" || findMode=="subtests") && text) {
  284. for (var i2=0,subTest,found=false;((subTest=subTests[i2]) && (!found));i2++) {
  285. var testX = text.replace('{%1}',subTest).toLowerCase();
  286.  
  287. //do a standard test with the replaced search string
  288. found=src.find(testX);
  289.  
  290. //return a found value, replacing %1 with a lowercase no-space text equal to the subtest string
  291. w=(found)?ret.replace('{%1}',subTest.noSpaces().toLowerCase()):w;
  292.  
  293. testX=null;
  294. }
  295.  
  296. //process number array
  297. } else if (subNumRange && (findMode=="auto" || findMode=="subnumrange") && text){
  298. var start=parseInt(subNumRange.split(",")[0]), end=parseInt(subNumRange.split(",")[1]);
  299. for (var i2=start,found=false;((!found) && i2<=end);i2++) {
  300. var testX = text.replace('{%1}',i2).toLowerCase();
  301.  
  302. //do a standard test with the replaced search string
  303. found=src.find(testX);
  304.  
  305. //return a found value, replacing %1 with a lowercase no-space text equal to the subtest string
  306. w=(found)?ret.replace('{%1}',i2):w;
  307.  
  308. testX=null;
  309. }
  310.  
  311. //process text array, process similar to subtests
  312. } else if (text && (findMode=="auto" || findMode=="basic") && (isArray(text))) {
  313. for (var i2=0,subTest,found=false;((subTest=text[i2]) && (!found));i2++) {
  314. var testX = subTest.toLowerCase();
  315.  
  316. //do a standard test with the replaced search string
  317. found=src.find(testX);
  318.  
  319. //return the same value no matter which element from the array is found
  320. w=(found)?ret:w;
  321.  
  322. testX=null;
  323. }
  324.  
  325.  
  326. //process regex
  327. } else if (text && (test.regex||test.isRegex||null) ) {
  328. var mods = (test.mods||"gi");
  329. var testRegex = new RegExp(text,mods);
  330. var match=src.match(testRegex);
  331. if (match) match=match[0]; //always take the first match
  332. w=ret||match||w;
  333.  
  334.  
  335.  
  336. //process single text
  337. } else if (text) {
  338. try{
  339. w=(src.find(text.toLowerCase() ))?ret:w;
  340. } catch(e){
  341. log("WM.doWhichTestTree:"+e);
  342. log("--app:"+app.appID);
  343. log("--test:"+JSON.stringify(test));
  344. }
  345. }
  346.  
  347. }
  348.  
  349. //see if test has type 2 subtests (child node tests based on parent test)
  350. w = ((kids && w)?WM.doWhichTestTree(post, kids, testData, custom):w) || w; //if kids return null, default to key found above
  351.  
  352. //if this test tree returned "none", start over with next tree by replacing "none" with null
  353. //true "none" is handled in the which() function below
  354. if (w==="none") w=null;
  355.  
  356.  
  357. }//end custom checker
  358. }
  359.  
  360. return w;
  361. }catch(e){log("WM.doWhichTestTree: "+e);}},
  362.  
  363. which : function(post,params) {try{
  364. //prevent the rules manager from mistaking main as a post object
  365. if (!post) return;
  366. params=params||{};
  367. //match post to an app
  368. var w, app=post.app, synApp=app.synApp;
  369.  
  370. //create various data for the tests to use
  371. if (!params.reid) post.testData = {
  372. title: (post.name||"undefined").toLowerCase(),
  373. msg: (post.message||"undefined").toLowerCase(),
  374. caption: (post.caption||"undefined").toLowerCase(),
  375. desc: (post.description||"undefined").toLowerCase(),
  376. link: (post.linkText||"undefined").toLowerCase(),
  377. url: Url.decode(post.linkHref).toLowerCase(),
  378. img: (post.picture||"undefined").toLowerCase(),
  379. fromName: post.fromName.toLowerCase(),
  380. fromID: post.fromID.toLowerCase(),
  381. targetName: "undefined", //","+post.getTargets("name").join(",").toLowerCase(),
  382. //targetID: "undefined", //","+post.getTargets("id").join(",").toLowerCase(),
  383. canvas: "undefined", //app.namespace.toLowerCase(),
  384. likeName: "undefined", //","+post.getLikes("name").join(",").toLowerCase(),
  385. likeID: "undefined", //","+post.getLikes("id").join(",").toLowerCase(),
  386. comments: "undefined", //post.getComments("message").join(" \n").toLowerCase(),
  387. commentorName: "undefined", //","+post.getComments("name").join(",").toLowerCase(),
  388. commentorID: "undefined", //","+post.getComments("id").join(",").toLowerCase(),
  389. };
  390. var testData=post.testData;
  391.  
  392. //replacement for old options like body, either and html
  393. testData.body = testData.title+testData.caption+testData.desc;
  394. testData.either = testData.link+testData.body;
  395. testData.html = testData.fromID + testData.fromName + testData.targetID + testData.targetName + testData.message
  396. + testData.href + testData.either + testData.img + testData.canvas + testData.likeID + testData.likeName
  397. + testData.commentorID + testData.commentorName + testData.comments;
  398.  
  399. var dynamicTests = WM.grabber.tests;
  400.  
  401. //check user built dynamic tests first if enabled and told to run first
  402. if (WM.opts["dynamic"+app.appID] && WM.opts.dynamicFirst && dynamicTests) {
  403. w=WM.doWhichTestTree(post,dynamicTests, testData, true)||"none";
  404. }
  405.  
  406. //process this game's tests if dynamic didn't already get one
  407. if (w=="none" || !w || w=="") {
  408. w=((tests=synApp.tests)?WM.doWhichTestTree(post,tests, testData):"none")||"none";
  409. }
  410.  
  411. //check user built dynamic tests last if enabled and not told to run first
  412. if (w=="none" || !w || w=="") {
  413. if (WM.opts["dynamic"+app.appID] && !WM.opts.dynamicFirst && dynamicTests) {
  414. w=WM.doWhichTestTree(post,dynamicTests,testData, true)||"none";
  415. }
  416. }
  417.  
  418. //switch to undefined collection if enabled
  419. w=(w==="none" && app.opts["doUnknown"])?"doUnknown":w;
  420.  
  421. return w;
  422. }catch(e){log("WM.which: "+e);}},
  423.  
  424. resetAccepted : function(params) {
  425. params=params||{};
  426. var ask=WM.opts.historyConfirmClear;
  427. if (params.noConfirm || !ask || (ask && confirm("Delete all history for this profile?"))){
  428. doAction(function(){
  429. WM.history={};
  430. setOpt('history_'+WM.currentUser.profile,'{}');
  431. });
  432. }
  433. },
  434.  
  435. onWindowResize : function(){
  436. WM.resizeConsole();
  437. },
  438. onHeartbeat : function(){
  439. if (WM.rulesManager.enabled) {
  440.  
  441. //affect rules at the base level
  442. WM.rulesManager.doEvent("onHeartbeat",{});
  443. //affect rules at the app level
  444. if (WM.opts.heartbeatAffectsApps) {
  445. for (var a in WM.apps) {
  446. (function(){
  447. WM.rulesManager.doEvent("onHeartbeat",WM.apps[a]);
  448. })();
  449. }
  450. }
  451.  
  452. //affect rules at the post level
  453. if (WM.opts.heartbeatAffectsPosts) {
  454. for (var p in WM.posts) if (!WM.posts[p].isGhost) {
  455. (function(){
  456. WM.rulesManager.doEvent("onHeartbeat",WM.posts[p]);
  457. })();
  458. }
  459. }
  460.  
  461. //affect rules at the rule level
  462. if (WM.opts.heartbeatAffectsRules) {
  463. for (var r=0; r<WM.rulesManager.rules.length; r++) {
  464. (function(){
  465. WM.rulesManager.doEvent("onHeartbeat",WM.rulesManager.rules[r]);
  466. })();
  467. }
  468. }
  469.  
  470. //affect rules at the feed and feed filter levels
  471. if (WM.opts.heartbeatAffectsFeeds || WM.opts.heartbeatAffectsFeedFilters) {
  472. var feeds=WM.feedManager.feeds;
  473. for (var f=0,len=feeds.length; f<len; f++) {
  474. //do the feed
  475. if (WM.opts.heartbeatAffectsFeeds) (function(){
  476. WM.rulesManager.doEvent("onHeartbeat",feeds[f]);
  477. })();
  478. //do the feed filters
  479. if (WM.opts.heartbeatAffectsFeedFilters) {
  480. for (var ff in feeds[f].filters){
  481. (function(){
  482. WM.rulesManager.doEvent("onHeartbeat",feeds[f].filters[ff]);
  483. })();
  484. }
  485. }
  486. }
  487. }
  488. }
  489. //check for new sidekick arrivals
  490. if (isArrayAndNotEmpty(WM.newSidekicks)) {
  491. while (WM.newSidekicks.length>0) {
  492. var app=WM.newSidekicks.shift();
  493. app.fetchPosts();
  494. }
  495. }
  496. //check for autolike queue contents
  497. var quePost = WM.checkAutoLikeQue();
  498. if (quePost) {
  499. //log([quePost.fn,quePost.post.id]);
  500. switch (quePost.fn) {
  501. case "like":quePost.post.like();break;
  502. case "comment":quePost.post.comment(quePost.say);break;
  503. }
  504. }
  505. },
  506.  
  507. //this is for when the WM.config and globalConfig settings change
  508. onSave : function() {
  509. //recopy the settings array from WM.config
  510. WM.updateSettingsValues();
  511.  
  512. //hide or show counters
  513. if (WM.opts.showcounters) WM.showCounters(); else WM.hideCounters();
  514.  
  515. //update intervals
  516. WM.setIntervals();
  517.  
  518. //set new user colors
  519. WM.setColors();
  520. //update config settings
  521. WM.changeConfigSettings();
  522.  
  523. //update those settings we use as global variables
  524. WM.changeDebugSettings();
  525. //set console heights
  526. //WM.resizeConsole();
  527. },
  528.  
  529. updateSettingsValues : function(){try{
  530. WM.opts = WM.config.values;
  531. //new: do this for each of the apps too
  532. for (var a in WM.apps) WM.apps[a].opts=WM.apps[a].config.values;
  533. }catch(e){"WM.updateSettingsValues: "+e}},
  534.  
  535. getAccText: function(appID,w,past,status){
  536. var app=WM.apps[appID].synApp;
  537. //detect and use a status code message
  538. if (!(status>-1 || status==-4 || status==-6)) return WM.statusText[status];
  539. //or return a generic message based on post type
  540. 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":"") );
  541. },
  542.  
  543. stopCollectionOf : function(w){
  544. for (var p in WM.posts) if (!WM.posts[p].isGhost && WM.posts[p].which==w) WM.posts[p].stopCollect();
  545. },
  546.  
  547. startCollectionOf : function(w){
  548. for (var p in WM.posts) if (!WM.posts[p].isGhost && WM.posts[p].which==w) WM.posts[p].collect();
  549. },
  550.  
  551. pauseByType : function(app,w){
  552. if (!isArray(w)) w=[w];
  553. //mark as paused all those posts not yet done
  554. for (var p in WM.posts) if (!WM.posts[p].isGhost && w.inArray(WM.posts[p].which)) WM.posts[p].pause();
  555. //store the paused type but dont save it
  556. var a=(app.parent||app);
  557. for (var i=0; i<w.length; i++) {
  558. var t=w[i];
  559. //add it to the array without making a duplicate
  560. if (!a.typesPaused.inArray(t)) {
  561. a.typesPaused.push(t);
  562. //add a visible node
  563. a.typesPausedNode.appendChild(
  564. a.pausedTypesListNodes[t]=createElement("div",{className:"line"},[
  565. createElement("span",{textContent:(a.userDefinedTypes[t]||a.accText[t])+" ("+t+") "}),
  566. createElement("div",{className:"littleButton oddGreen", title:"Unpause Type"},[
  567. createElement("img",{className:"resourceIcon playRight"+WM.opts.littleButtonSize,onclick:function(){
  568. WM.unPauseByType(a,t);
  569. }})
  570. ])
  571. ])
  572. );
  573. }
  574. }
  575. },
  576.  
  577. unPauseByType : function(app,w){
  578. if (!isArray(w)) w=[w];
  579. //unpause all those posts not yet done
  580. for (var p in WM.posts) if (!WM.posts[p].isGhost && w.inArray(WM.posts[p].which)) WM.posts[p].unPause();
  581. //remove paused type from list but dont save it
  582. var a=(app.parent||app);
  583. for (var i=0; i<w.length; i++) {
  584. //remove the visible node
  585. remove (a.pausedTypesListNodes[w[i]]);
  586. //delete the visible node entry
  587. delete a.pausedTypesListNodes[w[i]];
  588. //remove it from the array
  589. a.typesPaused.removeByValue(w[i]);
  590. }
  591. },
  592.  
  593. setAsAccepted : function(comment,status, post) {try{
  594. var app=post.app;
  595. var synApp=app.synApp;
  596. post.state="accepted";
  597. post.status=status;
  598. post.accept();
  599.  
  600. WM.history[post.id]={status:status, date:timeStamp(), which:(post.which||"undefined").removePrefix(synApp.appID), appID:app.appID};
  601. setOptJSON('history_'+WM.currentUser.profile,WM.history);
  602.  
  603. //do friend tracking
  604. if (WM.opts.useFriendTracker && WM.opts.trackAccepted){
  605. WM.friendTracker.trackStatus(post,true);
  606. }
  607.  
  608. var postNode=post.node||$("post_"+post.id);
  609. if (postNode){
  610. var link=selectSingleNode(".//a[contains(@class,'linkText')]",{node:postNode});
  611.  
  612. var text=WM.getAccText(synApp.appID,post.which,true,status);
  613.  
  614. link.textContent = (comment || text || WM.statusText[status] || WM.accDefaultText);
  615. WM.updatePostStatus(post.id);
  616. }
  617.  
  618. app.acceptCount++;
  619.  
  620. //perform the onAccepted event
  621. WM.rulesManager.doEvent("onAccepted",post);
  622.  
  623. //try autolike
  624. try{
  625. if (WM.opts.useautolike && (WM.opts.autolikeall || WM.opts.autolikeaccepted || (WM.opts.autolikesent && (post.which||"undefined").startsWith("send")) )) {
  626. if (!WM.opts["nolike"+app.appID]){
  627. WM.queAutoLike(post);
  628. //post.like();
  629. }
  630. }
  631. } catch(e){log("setAsAccepted: autolike failed: "+e,{level:3});}
  632. //try autocomment
  633. try{
  634. if (WM.opts.useautocomment && (WM.opts.autolikeall || WM.opts.autolikeaccepted || (WM.opts.autolikesent && (post.which||"undefined").startsWith(synApp.appID+"send")) )) {
  635. if (!WM.opts["nolike"+app.appID]){
  636. //setTimeout(function(){post.comment();},100+(WM.opts.autolikedelay*1000));
  637. WM.queAutoComment(post,null);
  638. }
  639. }
  640. } catch(e){log("setAsAccepted: autocomment failed: "+e,{level:3});}
  641. }catch(e){log("WM.setAsAccepted: "+e);}},
  642.  
  643. disableOpt : function(w,app){try{
  644. var targetConfig=(app||null)?app.config:WM.config;
  645. ((app||null)?app.opts:WM.opts)[w]=false;
  646. targetConfig.set(w,false);
  647. targetConfig.save();
  648. debug.print([w,app,false]);
  649. }catch(e){log("WM.disableOpt: "+e);}},
  650.  
  651. enableOpt : function(w,app){try{
  652. var targetConfig=(app||null)?app.config:WM.config;
  653. ((app||null)?app.opts:WM.opts)[w]=true;
  654. targetConfig.set(w,true);
  655. targetConfig.save();
  656. debug.print([w,app,true]);
  657. }catch(e){log("WM.enableOpt: "+e);}},
  658.  
  659. setOpt : function(w,v,app){try{
  660. var targetConfig=(app||null)?app.config:WM.config;
  661. ((app||null)?app.opts:WM.opts)[w]=v;
  662. targetConfig.set(w,v);
  663. targetConfig.save();
  664. debug.print([w,app,v]);
  665. }catch(e){log("WM.setOpt: "+e);}},
  666.  
  667. resetCounters : function(){try{
  668. for (var a in WM.apps) WM.apps[a].resetCounter();
  669. }catch(e){log("WM.resetCounters: "+e);}},
  670.  
  671. setAsFailed : function(comment, status, post){try{
  672. var app=post.app;
  673. var synApp=app.synApp;
  674. var postNode=post.node||$("post_"+post.id);
  675.  
  676. //special effects for timeout and cancelProcess
  677. if ((!WM.opts.failontimeout && status==-14) || status==-21) {
  678. post.state="timeout";
  679. post.timeout();
  680. if (status==-14) WM.rulesManager.doEvent("onTimeout",post);
  681.  
  682. } else {
  683. post.state="failed";
  684. post.fail(); // don't pass true or it will loop here
  685.  
  686. WM.history[post.id]={status:status, date:timeStamp(), which:(post.which||"undefined").removePrefix(synApp.appID), appID:app.appID};
  687. setOptJSON('history_'+WM.currentUser.profile,WM.history);
  688. WM.rulesManager.doEvent("onFailed",post);
  689. }
  690. post.status=status;
  691.  
  692. //do friend tracking
  693. if (WM.opts.useFriendTracker && WM.opts.trackFailed){
  694. WM.friendTracker.trackStatus(post,false);
  695. }
  696. if (postNode) {
  697. var link=selectSingleNode(".//a[contains(@class,'linkText')]",{node:postNode});
  698. if (link) {
  699. //I can see no reason the link should be missing, but since its been proven to fail, here is a patch
  700. link.textContent = (comment || WM.statusText[status] || WM.failText);
  701. }
  702. WM.updatePostStatus(post.id);
  703. }
  704.  
  705. app.failCount++;
  706. //try autolike
  707. try{
  708. if (WM.opts.useautolike && WM.opts.autolikeall) {
  709. if (!WM.opts["nolike"+app.appID]){
  710. WM.queAutoLike(post);
  711. //post.like();
  712. //setTimeout(function(){post.like();},100+(WM.opts.autolikedelay*1000));
  713. }
  714. }
  715. } catch(e){log("setAsFailed: autolike failed: "+e,{level:3});}
  716. //try autocomment
  717. try{
  718. if (WM.opts.useautocomment && WM.opts.autolikeall) {
  719. if (!WM.opts["nolike"+app.appID]){
  720. //setTimeout(function(){post.comment();},100+(WM.opts.autolikedelay*1000));
  721. WM.queAutoComment(post,null);
  722. }
  723. }
  724. } catch(e){log("setAsFailed: autocomment failed: "+e,{level:3});}
  725. }catch(e){log("WM.setAsFailed: "+e);}},
  726.  
  727. setPriority : function(){
  728. var postNode=selectSingleNode(".//ancestor::*[starts-with(@id,'post_')]",{node:this});
  729. var id=postNode.id.replace("post_","");
  730. WM.posts[id]["priority"]=this.getAttribute("name");
  731. remove(postNode);
  732. WM.posts[id].draw();
  733. },
  734.  
  735. clearURL : function(tab){
  736. WM.collector.close(tab);
  737. WM.requestsOpen--;
  738. },
  739. //constantly update sidekick channel data
  740. skChannel : {},
  741. fetchSidekickData : function(){try{
  742. if (WM) {
  743. var node=selectSingleNode("./div",{node:$("wmDataDump")});
  744. while (node){
  745. log("WM.fetchSidekickData: found "+JSON.parse(node.getAttribute("data-ft")));
  746. WM.skChannel=mergeJSON(WM.skChannel,JSON.parse(node.getAttribute("data-ft")));
  747. remove(node);
  748. node=selectSingleNode("./div",{node:$("wmDataDump")});
  749. }
  750. setTimeout(WM.fetchSidekickData,1000);
  751. }
  752. }catch(e){log("WM.fetchSidekickData: "+e);}},
  753. //this is WM3's method of handling conversations with sidekicks
  754. onFrameLoad3 : function(tab){try{
  755. log("onFrameLoad3(): tab="+tab.id);
  756. var postID=tab.postID||tab.id;
  757. var post=tab.post||WM.posts[postID];
  758.  
  759. //detect if post process was cancelled by user
  760. if (post.processCancelled){
  761. //reset the cancel memory
  762. post.processCancelled = false;
  763. log("onFrameLoad3: process cancelled by user");
  764. //set the timeout flag even though its not timed out
  765. WM.setAsFailed(null,-21,post);
  766. WM.clearURL(tab);
  767. return;
  768. }
  769.  
  770. //detect if valid WM.collector window still exists
  771. var windowExists=(tab.hwnd && !tab.hwnd.closed);
  772. /*try{
  773. var testUrl=tab.hwnd.location.toString();
  774. } catch(e) {
  775. windowExists=false;
  776. }*/
  777. //make sure the post object still exists
  778. if (!(post||null)){
  779. log("onFrameLoad3: post is null");
  780. WM.clearURL(tab);
  781. return;
  782. }
  783.  
  784. //check if window object is missing
  785. if (!windowExists) {
  786. log("windowExists = false");
  787. if (!tab.hwnd) log("onFrameLoad3: tab.hwnd is null");
  788. if (tab.hwnd.closed) log("onFrameLoad3: tab.hwnd is closed");
  789. WM.setAsFailed(null,-17,post);
  790. WM.clearURL(tab);
  791. return;
  792. }
  793. //check timer on this open post
  794. var openTime=tab.openTime;
  795. var nowTime=timeStamp();
  796. if ((WM.opts.reqtimeout*1000)<(nowTime-openTime)){
  797. log("onFrameLoad3: post timed out");
  798. WM.setAsFailed(null,-14,post);
  799. WM.clearURL(tab);
  800. return;
  801. }
  802. //create the retry function
  803. var retry=function(){setTimeout(function(){WM.onFrameLoad3(tab); return;},1000); return;};
  804. //look for status data
  805. var tabID = tab.id;
  806. var skData = WM.skChannel[tabID]||null;
  807. if (skData) {
  808. //data exists for this post
  809. if (skData.status) {
  810. //status is available
  811. delete WM.skChannel[tabID];
  812. //get useful post data
  813. var app=post.app; var synApp=app.parent||app;
  814. var postNode=post.node||$("post_"+post.id);
  815. var link=selectSingleNode(".//a[contains(@class,'linkText')]",{node:postNode});
  816. var w=post.which||"undefined";
  817. //confirm status
  818. var gotItem=((skData.status>0) || (skData.status==-6) || (skData.status==-4) || (skData.status==-15 && WM.opts.accepton15));
  819. var failedItem=(skData.status<0);
  820. if (gotItem){
  821. //build debug block
  822. switch(skData.status){
  823. case -6: case -4: case 1:
  824. // this bonus is available or we still have the ability to send something for no return
  825. //dont break before next
  826. case -15: case 2:
  827. if (!synApp.flags.requiresTwo){
  828. WM.setAsAccepted(null, skData.status, post);
  829. }
  830. break;
  831.  
  832. default:
  833. //should not have come here for any reason, but if we did assume its a status code I didnt script for
  834. WM.setAsFailed(null, skData.status, post);
  835. log("onFrameLoad3: unexpected status code: "+skData.status,{level:2});
  836. break;
  837. }
  838. } else {
  839. WM.setAsFailed(null,skData.status,post);
  840. }
  841. // click "yes" to accept it, if we got this far we actually found an accept button
  842. if(synApp.flags.requiresTwo && gotItem) {
  843. if (skData.nopopLink) {
  844. var req; req=GM_xmlhttpRequest({
  845. method: "GET",
  846. url: skData.nopopLink,
  847. timeout: WM.opts.reqtimeout*1000,
  848. onload: function(response) {
  849. //search for error messages
  850. var test=response.responseText;
  851. if (test==""){
  852. //no text was found at requested href
  853. log("onFrameLoad3: final stage: null response",{level:2});
  854. WM.setAsFailed(null, -9,post);
  855. } else {
  856. //if no errors then we got it
  857. WM.setAsAccepted(null, skData.status,post);
  858. }
  859. WM.clearURL(tab);
  860. if(req)req=null;
  861. },
  862.  
  863. onerror: function(response) {
  864. log("onFrameLoad3: final stage: error returned",{level:2});
  865. //if final request fails, drop the request for now
  866. WM.setAsFailed(null, -10,post);
  867. WM.clearURL(tab);
  868. if(req)req=null;
  869. },
  870.  
  871. onabort: function(response) {
  872. log("onFrameLoad3: final stage: request aborted",{level:2});
  873. WM.setAsFailed(null, -10,post);
  874. WM.clearURL(tab);
  875. if(req)req=null;
  876. },
  877. ontimeout: function(response) {
  878. log("onFrameLoad3: final stage: request timeout",{level:2});
  879. WM.setAsFailed(null, -10,post);
  880. WM.clearURL(tab);
  881. if(req)req=null;
  882. },
  883. });
  884.  
  885. } else {
  886. log("onFrameLoad3: skData.nopopLink is null and a string was expected",{level:3});
  887. WM.setAsFailed(null, -16,post);
  888. WM.clearURL(tab);
  889. return;
  890. }
  891. } else WM.clearURL(tab); //<- default page clearer, do not remove
  892. } else retry();
  893. } else {
  894. retry();
  895. //send the tab its init message (again)
  896. tab.hwnd.postMessage({
  897. channel:"WallManager",
  898. msg:1,
  899. tabID:tab.id,
  900. },"*");
  901. //log("useGM_openInTab: "+WM.opts.useGM_openInTab);
  902. }
  903. }catch(e){log("WM.onFrameLoad3: "+e);}},
  904. //this is WM1-2's method of handling conversation with sidekicks
  905. //WM3 defaults to this if sidekick is not WM3 compatible
  906. onFrameLoad : function(tab,noDebug){try{
  907. //tab object contains {id,post,url}
  908. if (!noDebug) log("onFrameLoad()",{level:0});
  909.  
  910. var id=tab.id; var post=tab.post||WM.posts[id];
  911. if (!(post||null)) {
  912. //resource deleted while post was out
  913. WM.clearURL(tab);
  914. return;
  915. }
  916.  
  917. //detect if post process was cancelled by user
  918. if (post.processCancelled){
  919. //reset the cancel memory
  920. post.processCancelled = false;
  921. log("onFrameLoad3: process cancelled by user");
  922. //set the timeout flag even though its not timed out
  923. WM.setAsFailed(null,-21,post);
  924. WM.clearURL(tab);
  925. return;
  926. }
  927.  
  928. var app=post.app;
  929. var synApp=app.parent||app;
  930.  
  931. var httpsTrouble=synApp.flags.httpsTrouble;
  932. var responseLess=synApp.flags.skipResponse;
  933.  
  934. var postNode=post.node||$("post_"+post.id);
  935. var link=selectSingleNode(".//a[contains(@class,'linkText')]",{node:postNode});
  936. var w=post.which||"undefined";
  937.  
  938. tab.tries=(tab.tries||0)+1;
  939. if (tab.tries>WM.opts.reqtimeout) {
  940. log("onFrameLoad: request timeout",{level:3});
  941. WM.setAsFailed(null, -14, post);
  942. WM.clearURL(tab);
  943. return;
  944. }
  945.  
  946. var retry=function(){setTimeout(function(e){WM.onFrameLoad(tab, true);}, 1000);};
  947.  
  948. var failedItem=false, gotItem=false, nopopLink;
  949.  
  950. //check if window object is missing
  951. var windowExists=(tab.hwnd && !tab.hwnd.closed);
  952. if (!windowExists) {WM.setAsFailed(null,-17,post); WM.clearURL(tab); return;}
  953. //check if window document does not yet exist
  954. //if (!(tab.hwnd.document||null)) {retry(); return;}
  955.  
  956. //get sidekick return value
  957. var hashMsg="",hashStatus=0;
  958. try{
  959. //if error encountered, reload the page
  960. if (tab.hwnd.document.title==="Problem loading page"){
  961. log("processPosts: problem loading page",{level:1});
  962. tab.hwnd.location.reload();
  963. retry();
  964. return;
  965. }
  966.  
  967. var temphash = tab.hwnd.location.hash; //capture a hash if we can
  968. hashMsg = ((temphash)?temphash.removePrefix("#"):null) || tab.hwnd.location.href.split("#")[1];
  969. hashStatus=(responseLess)?2:(hashMsg||null)?parseInt(hashMsg.split('status=')[1].split("&")[0]):0;
  970. gotItem=((hashStatus>0) || (hashStatus==-6) || (hashStatus==-4) || (hashStatus==-15 && WM.opts.accepton15));
  971. failedItem=(hashStatus<0);
  972. if (!gotItem && !failedItem) {retry(); return;}
  973.  
  974. } catch(e){
  975. var errText=""+e;
  976. if (errText.contains("hashMsg is undefined")) {
  977. //this known issue occurs when a page is not yet fully loaded and the
  978. //WM script tries to read the page content
  979. retry();
  980. return;
  981. }
  982. else if (errText.contains("Permission denied to access property")) {
  983. //we've reached some known cross domain issue
  984. if (responseLess) {
  985. //if the sidekick creator has chosen to use responseless collection
  986. //simply assume the page has loaded and mark the item as collected
  987.  
  988. gotItem=true;failedItem=false;hashStatus=2;
  989. } else {
  990. console.log("WM.onFrameLoad(before retry): "+e);
  991. retry();
  992. return;
  993. }
  994. }
  995. else if (errText.contains("NS_ERROR_INVALID_POINTER")
  996. || errText.contains("tab.hwnd.document is null") ) {
  997.  
  998. WM.setAsFailed(null,-17,post);
  999. WM.clearURL(tab);
  1000. return;
  1001. }
  1002. else {
  1003. log("onFrameLoad: "+e,{level:3});
  1004. retry();
  1005. return;
  1006. }
  1007. }
  1008.  
  1009. //if gotItem then we have been offered the item so far
  1010. if (gotItem){
  1011. //build debug block
  1012. switch(hashStatus){
  1013. case -6: case -4: case 1:
  1014. // this bonus is available or we still have the ability to send something for no return
  1015. if (synApp.flags.requiresTwo){
  1016. try{
  1017. nopopLink=hashMsg.split("&link=[")[1].split("]")[0];
  1018. }catch(e){
  1019. //known rare issue where no link is passed back by pioneer trail
  1020. }
  1021. }
  1022. //dont break before next
  1023. case -15: case 2:
  1024. if (!synApp.flags.requiresTwo){
  1025. WM.setAsAccepted(null, hashStatus,post);
  1026. }
  1027. break;
  1028.  
  1029. default:
  1030. //should not have come here for any reason, but if we did assume its a status code I didnt script for
  1031. WM.setAsFailed(null, hashStatus,post);
  1032. log("onFrameLoad: unexpected status code: "+hashStatus,{level:2});
  1033. break;
  1034. }
  1035. } else {
  1036. WM.setAsFailed(null,hashStatus,post);
  1037. }
  1038.  
  1039.  
  1040. // click "yes" to accept it, if we got this far we actually found an accept button
  1041. if(synApp.flags.requiresTwo && gotItem) {
  1042. if (nopopLink) {
  1043. var req; req=GM_xmlhttpRequest({
  1044. method: "GET",
  1045. url: nopopLink,
  1046. timeout: WM.opts.reqtimeout*1000,
  1047. onload: function(response) {
  1048. //search for error messages
  1049. var test=response.responseText;
  1050. if (test==""){
  1051. //no text was found at requested href
  1052. log("onFrameLoad: final stage: null response",{level:2});
  1053. WM.setAsFailed(null, -9,post);
  1054. } else {
  1055. //if no errors then we got it
  1056. WM.setAsAccepted(null, hashStatus,post);
  1057. }
  1058. WM.clearURL(tab);
  1059. if(req)req=null;
  1060. },
  1061.  
  1062. onerror: function(response) {
  1063. log("onFrameLoad: final stage: error returned",{level:2});
  1064. //if final request fails, drop the request for now
  1065. WM.setAsFailed(null, -10,post);
  1066. WM.clearURL(tab);
  1067. if(req)req=null;
  1068. },
  1069.  
  1070. onabort: function(response) {
  1071. log("onFrameLoad: final stage: request aborted",{level:2});
  1072. WM.setAsFailed(null, -10,post);
  1073. WM.clearURL(tab);
  1074. if(req)req=null;
  1075. },
  1076. ontimeout: function(response) {
  1077. log("onFrameLoad: final stage: request timeout",{level:2});
  1078. WM.setAsFailed(null, -10,post);
  1079. WM.clearURL(tab);
  1080. if(req)req=null;
  1081. },
  1082. });
  1083.  
  1084. } else {
  1085. log("onFrameLoad: nopopLink is null and a string was expected",{level:3});
  1086. WM.setAsFailed(null, -16,post);
  1087. WM.clearURL(tab);
  1088. return;
  1089. }
  1090. } else WM.clearURL(tab);
  1091.  
  1092. }catch(e){log("WM.onFrameLoad: "+e);}},
  1093.  
  1094. toggle : function(opt,app){
  1095. var targetConfig=(app||null)?app.config:WM.config;
  1096. var targetOpts=(app||null)?app.opts:WM.opts;
  1097. if (targetOpts[opt]){
  1098. targetConfig.set(opt, false);
  1099. targetOpts[opt] = false;
  1100. } else {
  1101. targetConfig.set(opt, true);
  1102. targetOpts[opt] = true;
  1103. }
  1104. targetConfig.save();
  1105. },
  1106.  
  1107. getAppDropDownList : function(selectedIndex,allowBlank){
  1108. var retApps=[];
  1109. //add the fake initial option
  1110. retApps.push(createElement("option",{textContent:"select an app",value:""}));
  1111. retApps.push(createElement("option",{textContent:"* All",value:""}));
  1112. if (allowBlank) retApps.push(createElement("option",{textContent:"all apps",value:""}));
  1113.  
  1114. for(var i in WM.apps){
  1115. if (!WM.apps[i].parent) {
  1116. var elem = createElement("option",{textContent:WM.apps[i].name,value:i});
  1117. if ((selectedIndex||null) == i) elem.selected = true;
  1118. retApps.push(elem);
  1119. }
  1120. }
  1121. return retApps;
  1122. },
  1123.  
  1124. getBonusDropDownList : function(params){
  1125. params=params||{};
  1126. var selected = params.selected||"";
  1127. var appID = params.appID||null;
  1128. var dropID = params.dropID||false; //force the element value to drop its appID prefix
  1129.  
  1130. var optsret=[], bonuses={};
  1131. if (appID) bonuses = mergeJSON(WM.apps[appID].accText,WM.apps[appID].userDefinedTypes);
  1132. bonuses["dynamic"]="* Dynamic: Just Grab It";
  1133. bonuses["none"]="* None: Break Identification Circuit";
  1134. bonuses["wishlist"]="* Flag as Wishlist";
  1135. bonuses["exclude"]="* Exclude: Prevent Collection";
  1136. bonuses["send"]="* Send Unknown";
  1137. bonuses["doUnknown"]="* Get Unknown";
  1138.  
  1139. //create option values and names;
  1140. for (var i in bonuses) {
  1141. var elem
  1142. 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)});
  1143. else elem = createElement("option",{textContent:bonuses[i],value:i});
  1144.  
  1145. if (appID) {if (selected==((dropID)?i.removePrefix(appID):i) ) elem.selected = true;}
  1146. else {if (selected==i) elem.selected=true;}
  1147.  
  1148. optsret.push(elem);
  1149. }
  1150.  
  1151. return optsret;
  1152. },
  1153.  
  1154. reIDAll : function(){
  1155. for (var p in WM.posts) {
  1156. if (!WM.posts[p].isGhost && WM.posts[p].identify({reid:true}))
  1157. WM.rulesManager.doEvent("onIdentify",WM.posts[p]);
  1158. }
  1159. WM.sortPosts(); //in this case sorting may cancel movetotop and movetobottom
  1160. WM.clearGroups();
  1161. WM.redrawPosts({postRedraw:true});
  1162. },
  1163.  
  1164. updatePostStatus : function(id){
  1165. var status = WM.posts[id].status;
  1166. var statusNode = selectSingleNode(".//*[contains(@class,'status')]",{node:$("post_"+id)});
  1167. if (statusNode) statusNode.textContent="Status: "+(status||"0") + " " + (WM.statusText[status||"0"]);
  1168. status=null; statusNode=null;
  1169. },
  1170.  
  1171. onLikePost : function(post){
  1172. post.isLiked=true;
  1173. return;
  1174. //pre beta 40 stuff
  1175. var postID=tab.id;
  1176. var post=tab.post||WM.posts[postID];
  1177.  
  1178. //detect if post process was cancelled by user
  1179. if (post.processCancelled){
  1180. //reset the cancel memory
  1181. post.processCancelled = false;
  1182. log("onLikePost: feedback cancelled by user");
  1183. WM.collector.close(tab);
  1184. return;
  1185. }
  1186.  
  1187. //detect if valid WM.collector window still exists
  1188. var windowExists=(tab.hwnd && !tab.hwnd.closed);
  1189.  
  1190. //check if window object is missing
  1191. if (!windowExists) {
  1192. log("onLikePost: tab.hwnd is null or closed");
  1193. WM.collector.close(tab);
  1194. return;
  1195. }
  1196. try{
  1197. var like=tab.hwnd.location.hash.removePrefix("#").getUrlParam("status")=="1";
  1198. if (like) {
  1199. if (tab.post) {
  1200. //tell the post it is liked
  1201. tab.post.isLiked = true;
  1202. //delete the post reference from the tab
  1203. delete tab.post;
  1204. }
  1205. WM.collector.close(tab);
  1206. return;
  1207. }
  1208. } catch (e){
  1209. //log(""+e);
  1210. }
  1211.  
  1212. tab.tries=(tab.tries||0)+1;
  1213. if (tab.tries<WM.opts.autoliketimeout) setTimeout(function(){WM.onLikePost(tab);}, 1000);
  1214. else {
  1215. log("onLikePost: unable to finish feedback",{level:3});
  1216. doAction(function(){WM.collector.close(tab);});
  1217. }
  1218. },
  1219.  
  1220. toggleSidekick : function(){
  1221. var appID = this.id.split("master_")[1];
  1222. var opt = !(WM.quickOpts["masterSwitch"][appID]||false); //toggle
  1223. WM.quickOpts["masterSwitch"][appID]=opt;
  1224. var className = this.parentNode.className;
  1225. this.parentNode.className = ((opt)?className.removeWord("disabled"):className.addWord("disabled"));
  1226. this.textContent=((opt)?"Disable":"Enable");
  1227. WM.saveQuickOpts();
  1228. },
  1229.  
  1230. saveQuickOpts : function(){
  1231. setOptJSON('quickopts_'+WM.currentUser.profile, WM.quickOpts);
  1232. },
  1233.  
  1234. setAppFilter : function(tab){
  1235. WM.quickOpts.filterApp=tab.appFilter;
  1236. WM.saveQuickOpts();
  1237. WM.clearGroups();
  1238. WM.redrawPosts({postRedraw:false,reorder:true});
  1239. WM.rulesManager.doEvent("onSetAppFilter",WM.apps[tab.appFilter]);
  1240. //debug.print(["Collection Tab Selected",WM.currentAppTab,WM.apps[tab.appFilter]]);
  1241. },
  1242. setDisplay : function(){
  1243. var x=this.getAttribute("name");
  1244. WM.quickOpts.displayMode=x;
  1245. WM.saveQuickOpts();
  1246. WM.redrawPosts({postRedraw:true,reorder:true});
  1247. WM.setDisplayCols();
  1248. },
  1249. setDisplayCols : function(params){
  1250. params=params||{};
  1251. params.cols=params.cols||WM.quickOpts.displayCols;
  1252. WM.quickOpts.displayCols=params.cols||1;
  1253. WM.saveQuickOpts();
  1254. with (WM.console.feedNode) {
  1255. className=className
  1256. .toggleWordB(params.cols==1,"singleCol")
  1257. .toggleWordB(params.cols==2,"twoCol")
  1258. .toggleWordB(params.cols==3,"threeCol")
  1259. .toggleWordB(params.cols==4,"fourCol");
  1260. }
  1261. },
  1262.  
  1263. redrawPosts : function(params){
  1264. params=params||{};
  1265. var feedNode=WM.console.feedNode;
  1266. //set the proper display mode
  1267. feedNode.className=feedNode.className
  1268. .toggleWordB((WM.quickOpts.displayMode=="1" || WM.quickOpts.displayMode=="3"),"short");
  1269. //avoid order issues by removing the posts from the panel
  1270. WM.clearPosts();
  1271.  
  1272. //redraw||reorder
  1273. for (var p in WM.posts) {
  1274. var post=WM.posts[p];
  1275. if (!post.isGhost) {
  1276. post.draw(params.postRedraw,params.reorder);
  1277. }
  1278. }
  1279. },
  1280.  
  1281. moveFloater : function(ev){
  1282. if (isChrome) return;
  1283. var img=this, offset=trueOffset(img), scrolled=trueScrollOffset(img),
  1284. post=selectSingleNode(".//ancestor::div[starts-with(@id,'post')]",{node:img}),
  1285. floater=$(post.id.replace("post","floater")), special={};
  1286.  
  1287. //log( (scrolled.left) +","+ (scrolled.top) );
  1288.  
  1289. special.x=(ev.clientX > (document.documentElement.clientWidth/2))?-(240+4+22):0; //width+overshot+BorderAndPadding
  1290. special.y=(ev.clientY > (document.documentElement.clientHeight/2))?-(120+4+12):0;
  1291.  
  1292. floater.style.left=(ev.clientX-(offset.left-scrolled.left))+(2+special.x)+"px";
  1293. floater.style.top=(ev.clientY-(offset.top-scrolled.top))+(2+special.y)+"px";
  1294. },
  1295. //create a drip system for autolike, instead of an offset
  1296. queAutoLike : function(post){
  1297. var nowTime = timeStamp();
  1298. var lastInQue = WM.likeQueue.last();
  1299. var targetTime = nowTime + (1000*WM.opts.autolikedelay);
  1300. if (lastInQue||null) {
  1301. if (lastInQue.timer>nowTime) {
  1302. targetTime = lastInQue.timer + (1000*WM.opts.autolikedelay);
  1303. }
  1304. }
  1305. WM.likeQueue.push({post:post, timer:targetTime, fn:"like"});
  1306. WM.console.likeQueueCounterNode.textContent = WM.likeQueue.length;
  1307. },
  1308. //create a drip system for autolike, instead of an offset
  1309. queAutoComment : function(post,say){
  1310. var nowTime = timeStamp();
  1311. var lastInQue = WM.likeQueue.last();
  1312. var targetTime = nowTime + (1000*WM.opts.autolikedelay);
  1313. if (lastInQue||null) {
  1314. if (lastInQue.timer>nowTime) {
  1315. targetTime = lastInQue.timer + (1000*WM.opts.autolikedelay);
  1316. }
  1317. }
  1318. WM.likeQueue.push({post:post, timer:targetTime, say:say, fn:"comment"});
  1319. WM.console.likeQueueCounterNode.textContent = WM.likeQueue.length;
  1320. //log(["autocomment added",say]);
  1321. },
  1322.  
  1323. //dump the autolike queue
  1324. emptyAutoLikeQue : function() {
  1325. WM.likeQueue=[];
  1326. WM.console.likeQueueCounterNode.textContent = 0;
  1327. },
  1328. //get the next ready autolike target
  1329. checkAutoLikeQue : function() {
  1330. if (WM.likeQueue.length<1) return null;
  1331. var nowTime = timeStamp();
  1332. if (WM.likeQueue[0].timer<=nowTime) {
  1333. WM.console.likeQueueCounterNode.textContent = (WM.likeQueue.length-1);
  1334. var t=nowTime;
  1335. for (var i in WM.likeQueue) {
  1336. i.timer = t;
  1337. t+=(1000*WM.opts.autolikedelay);
  1338. }
  1339. return WM.likeQueue.shift(); // no longer returns the post, but the block of what to do with what post
  1340. }
  1341. return null;
  1342. },
  1343.  
  1344. processPosts : function(){
  1345. //dont run if menu is open or if requests are still out or if the console is paused
  1346. if($("Config") || (WM.requestsOpen >= WM.opts.maxrequests) || WM.paused) return;
  1347.  
  1348. 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});
  1349. if (postNode) {
  1350. var post = WM.posts[postNode.id.replace('post_','')];
  1351. if (post) post.open();
  1352. }
  1353. },
  1354.  
  1355. olderPosts : function (params) {
  1356. WM.fetch({older:true});
  1357. },
  1358.  
  1359. newerPosts : function (params) {
  1360. WM.fetch({newer:true});
  1361. },
  1362. fetchRange : function (params) {
  1363. WM.fetch({bypassPause:true, older:true, targetEdge:params.oldedge, currentEdge:params.newedge});
  1364. },
  1365.  
  1366. cleanPosts : function () {try{
  1367. for (var p in WM.posts) if (!WM.posts[p].isGhost) {
  1368. var post = WM.posts[p];
  1369. with (post) if (!(
  1370. isPinned || isCollect || isWorking ||
  1371. (isTimeout && !WM.opts.cleanTimedOut)
  1372. )) post.remove();
  1373. }
  1374. }catch(e){log("WM.cleanPosts(): "+e);}},
  1375. setIntervals : function() {try{
  1376. //setup the timer to try post collection
  1377. if (procIntv) window.clearInterval(procIntv);
  1378. procIntv=window.setInterval(WM.processPosts, 2000);
  1379.  
  1380. //setup the timer to get new posts
  1381. if (newIntv) window.clearInterval(newIntv);
  1382. if(calcTime(WM.opts.newinterval)>0) newIntv=window.setInterval(WM.newerPosts, calcTime(WM.opts.newinterval));
  1383.  
  1384. //setup the timer to get older posts
  1385. if (oldIntv) window.clearInterval(oldIntv);
  1386. if(calcTime(WM.opts.oldinterval)>0) oldIntv=window.setInterval(WM.olderPosts, calcTime(WM.opts.oldinterval)+2000);
  1387. olderLimit=calcTime(WM.opts.maxinterval)||0;
  1388.  
  1389. //setup the timer to clean up old posts from the feed
  1390. if (cleanIntv) window.clearInterval(cleanIntv);
  1391. if(calcTime(WM.opts.cleaninterval)>0) cleanIntv=window.setInterval(WM.cleanPosts, calcTime(WM.opts.cleaninterval)+250);
  1392.  
  1393. //setup global heartbeat
  1394. if (hbIntv) window.clearInterval(hbIntv);
  1395. hbIntv=window.setInterval(WM.onHeartbeat, WM.opts.heartRate);
  1396. }catch(e){log("WM.setIntervals: "+e);}},
  1397.  
  1398. hideCounters : function(){try{
  1399. hideNodes("//*[contains(@class,'accFailBlock')]");
  1400. }catch(e){log("WM.hideCounters: "+e);}},
  1401.  
  1402. showCounters : function(){try{
  1403. showNodes("//*[contains(@class,'accFailBlock')]");
  1404. }catch(e){log("WM.showCounters: "+e);}},
  1405.  
  1406. validatePost : function(fbPost){try{
  1407. //validate required post fields
  1408. /*if (!( exists(fbPost.application) && exists(fbPost.link) && fbPost.type=="link")) {
  1409. return;
  1410. }*/
  1411.  
  1412. //accept only posts we have sidekicks for
  1413. var app;
  1414. if (!exists(app=WM.apps[fbPost.app_id])) return;
  1415. //prevent redrawing same post in case one slips past the graph validator
  1416. var postID=fbPost.post_id;
  1417. if (WM.posts[postID]||null) return;
  1418.  
  1419. //accept only posts for which a sidekick is enabled
  1420. if (!WM.quickOpts.masterSwitch[app.appID]) return;
  1421.  
  1422. //create a Post object from the post data
  1423. var post=(WM.posts[fbPost]=new WM.Post(fbPost));
  1424. if (post) {
  1425. var hasID=post.identify();
  1426. WM.sortPosts(); //make sure new posts fit the current sort order and direction
  1427. if (hasID) {
  1428. WM.rulesManager.doEvent("onValidate",post);
  1429. WM.rulesManager.doEvent("onIdentify",post);
  1430. post.draw(true,true);
  1431. //track the post
  1432. if (WM.opts.useFriendTracker && !post.isMyPost){
  1433. WM.friendTracker.track(post);
  1434. }
  1435. }
  1436. } else {
  1437. log("WM.validatePost: Unable to transform post data into a useful post object. (id:"+fbPost.post_id+")");
  1438. }
  1439. }catch(e){log("WM.validatePost: "+e);}},
  1440.  
  1441. handleEdges : function(params){
  1442. /*
  1443. apps
  1444. friends
  1445. edge:{newer,older}
  1446. */
  1447. //console.log("handleEdges: "+JSON.stringify(params));
  1448. if (params.friends||null) {
  1449. //update user created feeds
  1450. for (var f=0,l=WM.feedManager.feeds.length;f<l;f++){
  1451. var feed = WM.feedManager.feeds[f];
  1452. //if this feed is listed in those passed back...
  1453. if (params.friends.contains(feed.id)){
  1454. //update each app filter in this feed
  1455. for (var c=0,l=params.apps.length;c<l;c++) {
  1456. var appID=params.apps[c];
  1457. filter = feed.filters["app_"+appID];
  1458. if (!(filter||null)) {
  1459. //this filter does not exist, create one
  1460. filter=feed.addFilter({id:"app_"+appID});
  1461. }
  1462. if (params.edge.older) filter.oldedge = params.edge.older;
  1463. if (params.edge.newer) filter.newedge = params.edge.newer;
  1464. filter.oldedgeNode.textContent = filter.oldedge;
  1465. filter.newedgeNode.textContent = filter.newedge;
  1466. if (timeStamp()-(filter.oldedge*1000)>olderLimit) filter.olderLimitReached=true;
  1467. }
  1468. }
  1469. }
  1470. } else {
  1471. //update base feed
  1472. feed = WM.feedManager.feeds[0];
  1473. for (var c=0,l=params.apps.length;c<l;c++) {
  1474. var appID=params.apps[c];
  1475. //update each app filter in this feed
  1476. filter = feed.filters["app_"+appID];
  1477. if (!(filter||null)) {
  1478. //this filter does not exist, create one
  1479. filter=feed.addFilter({id:"app_"+appID});
  1480. }
  1481. if (params.edge.older) filter.oldedge = params.edge.older;
  1482. if (params.edge.newer) filter.newedge = params.edge.newer;
  1483. filter.oldedgeNode.textContent = filter.oldedge;
  1484. filter.newedgeNode.textContent = filter.newedge;
  1485. if (timeStamp()-(filter.oldedge*1000)>olderLimit) filter.olderLimitReached=true;
  1486. }
  1487. }
  1488. },
  1489. fetch : function(params) {try{
  1490. /*
  1491. older:bool
  1492. newer:bool
  1493. apps:[]
  1494. feed:[]
  1495. targetEdge:unixtime
  1496. currentEdge:unixtime
  1497. bypassPause:bool
  1498. bypassFeedDisabled:bool
  1499. bypassAppDisabled:bool
  1500. */
  1501. params=params||{};
  1502. if (WM.fetchPaused && !params.bypassPause) return;
  1503.  
  1504. //convert a single passed app to a single entry list
  1505. if (exists(params.apps) && ((params.apps.objType||null)=="app")) {
  1506. var ret={};
  1507. ret[params.apps.appID]=params.apps;
  1508. params.apps=ret;
  1509. }
  1510. var useApps = params.apps||WM.apps;
  1511.  
  1512. //convert a single passed feed to an array
  1513. if (exists(params.feeds) && ((params.feeds.objType||null)=="feed")) {
  1514. params.feeds=[params.feeds];
  1515. }
  1516. params.currentEdge = params.currentEdge||null; //nullify undefined edge
  1517. //for each feed individually
  1518. var feeds=params.feeds||WM.feedManager.feeds;
  1519. for (var f=0,len=feeds.length;f<len;f++) {
  1520. var feed=feeds[f];
  1521. var friend=(feed.url!="https://graph.facebook.com/me/home")?[feed.id]:null;
  1522. //ignore the old me feed because it is a duplicate of the wall feed
  1523. if (feed.url!="https://graph.facebook.com/me/feed") if (feed.enabled || params.bypassFeedDisabled) {
  1524. //for each app make a separate fetch call for the given feed
  1525. //override this: no more by-app fetching
  1526. if (false && !WM.opts.groupFetching && (useApps||null)) {
  1527. for (var a in useApps) {
  1528. var app=useApps[a];
  1529. //only fetch for enabled apps
  1530. //where we are fetching new
  1531. //or if we are fetching old we are not at our older limit
  1532. var feedFilter=feed.filters["app_"+a];
  1533. if ((app.enabled || params.bypassAppDisabled) && (feedFilter.enabled || params.bypassFilterDisabled) && !(
  1534. params.older && feedFilter.olderLimitReached
  1535. )
  1536. ){
  1537. var G=Graph.fetchPostsFQL_B({
  1538. callback:WM.validatePost,
  1539. direction:(params.newer?1:(params.older?-1:0)),
  1540. limit:WM.opts.fetchQty,
  1541. targetEdge:(params.targetEdge||null), //special for new rules manager actions
  1542. friends:friend,
  1543. apps:[app.appID],
  1544. currentEdge:params.currentEdge||(params.newer?feedFilter.newedge:(params.older?feedFilter.oldedge:null)),
  1545. edgeHandler:WM.handleEdges,
  1546. noAppFiltering:WM.opts.noAppFiltering
  1547. });
  1548. }
  1549. }
  1550. //join apps together before fetching a single time for the given feed
  1551. } else {
  1552. //get the keys of the apps collection
  1553. var keys=Object.keys(useApps);
  1554. //if any sidekicks are docked
  1555. if (keys.length) {
  1556. //get the values of the apps collection
  1557. var appsToProcess=keys.map(function (key) {
  1558. return useApps[key];
  1559. //filter out which apps are able to be fetched for
  1560. }).filter(function(o,i,p){
  1561. //get the feed filter text
  1562. var feedFilter=feed.filters["app_"+o.appID];
  1563. //get if the app is enabled
  1564. var isEnabled = (o.enabled || params.bypassAppDisabled);
  1565. var isFilterEnabled=true,isOlderLimitReached=false;
  1566. if (feedFilter||null) {
  1567. //get if the feed filter is enabled
  1568. isFilterEnabled = (feedFilter.enabled || params.bypassFilterDisabled);
  1569. //get if the feed filter has already reached its older edge limit
  1570. isOlderLimitReached = (params.older && feedFilter.olderLimitReached);
  1571. } else {
  1572. //feed filter does not exist for this app
  1573. //assume it was deleted by the user on purpose
  1574. //and don't fetch for this app on this feed
  1575. log("WM.fetch: could not find filter for " + o.appID + "in feed " + feed.id);
  1576. return false;
  1577. }
  1578. if (isEnabled && isFilterEnabled && !isOlderLimitReached) return true;
  1579. return false;
  1580. //simply the array
  1581. }).map(function(o,i,p){
  1582. //just get the id's of apps to do, not the entire app object
  1583. return o.appID;
  1584. });
  1585. //make sure we matched filters to process
  1586. if (appsToProcess.length){
  1587. //get the shared edges of the passed apps
  1588. var edges = feed.getMergedEdges({apps:appsToProcess});
  1589. //console.log("getMergedEdges returned: "+JSON.stringify(edges));
  1590. var G=Graph.fetchPostsFQL_B({
  1591. callback:WM.validatePost,
  1592. direction:(params.newer?1:(params.older?-1:0)),
  1593. limit:WM.opts.fetchQty,
  1594. targetEdge:(params.targetEdge||null), //special for new rules manager actions
  1595. friends:friend,
  1596. apps:appsToProcess,
  1597. currentEdge:params.currentEdge||(params.newer?edges.newedge:(params.older?edges.oldedge:null)),
  1598. edgeHandler:WM.handleEdges,
  1599. noAppFiltering:WM.opts.noAppFiltering
  1600. });
  1601. }
  1602. }
  1603. }
  1604. }
  1605. }
  1606. }catch(e){log("WM.fetch: "+e);}},
  1607. changeDebugSettings : function(){try{
  1608. if (debug && debug.initialized) {
  1609. debug.doDebug = WM.opts.debug;
  1610. debug.debugLevel = parseInt(WM.opts.debugLevel);
  1611. debug.debugMaxComments = WM.opts.debugMaxComments;
  1612. debug.useScrollIntoView = WM.opts.debugScrollIntoView;
  1613. debug.stackRepeats = WM.opts.debugStackRepeats;
  1614. } else {
  1615. if (debug) debug.init();
  1616. setTimeout(WM.changeDebugSettings,1000);
  1617. }
  1618. }catch(e){log("WM.changeDebugSettings: "+e);}},
  1619. changeConfigSettings : function(){try{
  1620. WM.config.sectionsAsTabs=WM.opts.configSectionsAsTabs;
  1621. WM.config.separatorsAsTabs=WM.opts.configSeparatorsAsTabs;
  1622. WM.config.useScrollIntoView=WM.opts.configScrollIntoView;
  1623. WM.config.confirms={
  1624. save:WM.opts.configConfirmSave,
  1625. cancel:WM.opts.configConfirmCancel,
  1626. "import":WM.opts.configConfirmImport,
  1627. restore:WM.opts.configConfirmRestore
  1628. };
  1629. }catch(e){log("WM.changeConfigSettings: "+e);}},
  1630.  
  1631. resizeConsole : function(){try{
  1632. //negotiate height with fb bluebar
  1633. var node=$("pagelet_bluebar");
  1634. var h=(node)?elementOuterHeight(node):0;
  1635. with($("wmContent")){
  1636. style.height=document.documentElement.offsetHeight-h+"px";
  1637. style.width=document.documentElement.offsetWidth+"px";
  1638. }
  1639. WM.console.tabContainer.redraw();
  1640. WM.console.collectTabControl.redraw();
  1641.  
  1642. }catch(e){log("WM.resizeConsole: "+e);}},
  1643.  
  1644. setColors : function(){try{
  1645. var colors=["excluded","working","timeout","paused","nodef","failed","accepted","scam","pinned"];
  1646. var css="";
  1647. for (var c=0, color; (color=colors[c]); c++) {
  1648. css+=("div."+color+"{background-color:"+WM.opts["colors"+color]+" !important;}\n");
  1649. }
  1650. //set the new transition delay timer
  1651. 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");
  1652. remove($("user_colors_css"));
  1653. addGlobalStyle(css,"user_colors_css");
  1654. }catch(e){log("WM.setColors: "+e);}},
  1655.  
  1656. initConsole : function(){try{
  1657. WM.console.loading=false;
  1658. if (WM.console.initialized) log("WM Console Initialized");
  1659.  
  1660. //show options menu button
  1661. with (WM.console.configButton) {
  1662. className = className.removeWord("jsfHidden");
  1663. }
  1664.  
  1665. //set console heights
  1666. WM.resizeConsole();
  1667.  
  1668. //load feed sources
  1669. WM.feedManager.init();
  1670. //import friend tracker data
  1671. //and delete posts out of bounds with our "track for how many days"
  1672. WM.friendTracker.init();
  1673. WM.friendTracker.clean();
  1674.  
  1675. //initialize user colors
  1676. WM.setColors();
  1677. //set up the priorities and limits object
  1678. //and new rules manager
  1679. WM.rulesManager.init();
  1680.  
  1681. //decipher the dynamic tests
  1682. WM.grabber.init();
  1683.  
  1684. //show counters
  1685. if (WM.opts.showcounters) WM.showCounters(); else WM.hideCounters();
  1686. //set intervals
  1687. WM.setIntervals();
  1688. //set autopause
  1689. if (WM.opts.autopausecollect) WM.pauseCollecting(true);
  1690. if (WM.opts.autopausefetch) WM.pauseFetching(true);
  1691. //open a channel for sidekick communication
  1692. WM.fetchSidekickData();
  1693.  
  1694. //add an entrypoint for sidekicks since we know FB gave us access
  1695. var createDock = function(){
  1696. document.body.appendChild(
  1697. createElement('div',{id:'wmDock',style:'display:none;',onclick:function(){
  1698. WM.dock.answerDockingDoor();
  1699. }})
  1700. );
  1701. document.body.appendChild(
  1702. createElement('div',{id:'wmDataDump',style:'display:none;'})
  1703. );
  1704. };
  1705. createDock();
  1706.  
  1707. }catch(e){log("WM.initConsole: "+e);}},
  1708.  
  1709. cleanHistory : function(params){try{
  1710. log("Cleaning History");
  1711. params=params||{};
  1712. var ask=WM.opts.historyConfirmClean;
  1713. if (params.noConfirm || !ask || (ask && confirm("Clean and pack history for this profile?"))){
  1714. //history = getOptJSON("history_"+WM.currentUser.profile)||{};
  1715. var ageDays=parseInt(WM.opts.itemage);
  1716. var timeNow=timeStamp();
  1717. for(var i in WM.history) {
  1718. if( ( (timeNow-WM.history[i].date) /day) > ageDays) {
  1719. delete WM.history[i];
  1720. }
  1721. }
  1722. setOptJSON("history_"+WM.currentUser.profile, WM.history);
  1723. }
  1724. }catch(e){log("WM.cleanHistory: "+e);}},
  1725.  
  1726. optionsSetup : function(){try{
  1727. debug.print("WM.optionsSetup:");
  1728.  
  1729. //create the settings tree
  1730. WM.config = new Config({
  1731. storageName:"settings_"+(WM.quickOpts.useGlobalSettings?"global":WM.currentUser.profile),
  1732. onSave:WM.onSave,
  1733. title:"FB Wall Manager "+WM.version+(WM.quickOpts.useGlobalSettings?" (!! Global Settings !!)":""),
  1734. logo:createElement("span",{}[
  1735. createElement("img",{className:"logo",src:"",textContent:"v"+WM.version}),
  1736. createElement("text","v"+WM.version)
  1737. ]),
  1738. css:(
  1739. WM.console.dynamicIcons()+
  1740. jsForms.globalStyle()
  1741. ),
  1742. settings:{
  1743. btn_useGlobal:{
  1744. type:"button",
  1745. label:"Use Global Settings",
  1746. title:"Switch to using a global storage for settings. Those settings can then be used by other accounts (not browser profiles).",
  1747. script:function(){
  1748. if (WM.quickOpts.useGlobalSettings||false) {
  1749. //already using global settings
  1750. return;
  1751. }
  1752. if (confirm("Switch to using global (shared) settings?")){
  1753. WM.quickOpts.useGlobalSettings=true;
  1754. WM.saveQuickOpts();
  1755. WM.config.title = "FB Wall Manager "+WM.version+" (!! Global Settings !!))";
  1756. WM.config.storageName = "settings_global";
  1757. WM.config.values=WM.config.read();
  1758. WM.config.configure();
  1759. WM.config.reload();
  1760. }
  1761. },
  1762. },
  1763. btn_useOwnProfile:{
  1764. type:"button",
  1765. label:"Use Profile Settings",
  1766. title:"Switch to using your own profile storage for settings.",
  1767. script:function(){
  1768. if (!(WM.quickOpts.useGlobalSettings||false)) {
  1769. //already using profile settings
  1770. return;
  1771. }
  1772. if (confirm("Switch to using your own profile settings?")){
  1773. WM.quickOpts.useGlobalSettings=false;
  1774. WM.saveQuickOpts();
  1775. WM.config.title = "FB Wall Manager "+WM.version;
  1776. WM.config.storageName = "settings_"+WM.currentUser.profile;
  1777. WM.config.values=WM.config.read();
  1778. WM.config.configure();
  1779. WM.config.reload();
  1780. }
  1781. },
  1782. },
  1783. wmtab_opts:tabSection("Host Options",{
  1784. section_basicopts:section("Basics",{
  1785. /*authTokenTools:optionBlock("Authorization",{
  1786. devAuthToken:checkBox("Automatically check my developer tool app for my Auth Token"),
  1787. },true),*/
  1788. intervals:optionBlock("Post Fetching",{
  1789. newinterval:{
  1790. label:"Get Newer Posts Interval",
  1791. type:"selecttime",
  1792. title:"Fetch new posts from facebook after a set time.",
  1793. options:{
  1794. "off":"Off",
  1795. "tenth":"6 seconds",
  1796. "sixth":"10 seconds",
  1797. "half":"30 seconds",
  1798. "one":"1 minute",
  1799. "two":"2 minutes",
  1800. "three":"3 minutes",
  1801. "four":"4 minutes",
  1802. "five":"5 minutes",
  1803. "ten":"10 minutes",
  1804. },
  1805. "default":"t:30s"
  1806. },
  1807.  
  1808. fetchQty:{
  1809. label:"Fetch how many? (subject to filtering)",
  1810. type:"select",
  1811. title:"Posts fetched per request. Higher numbers affect speed of fetching.",
  1812. options:{
  1813. "5":"5",
  1814. "10":"10",
  1815. "25":"25",
  1816. "50":"50",
  1817. "100":"100",
  1818. "250":"250",
  1819. "500":"500 (FB maximum)", //known maximum fetch as of 9/8/2013
  1820. },
  1821. "default":"25"
  1822. },
  1823.  
  1824. oldinterval:{
  1825. label:"Get Older Posts Interval",
  1826. type:"selecttime",
  1827. title:"Fetch previous posts from facebook after a set time.",
  1828. options:{
  1829. "off":"Off",
  1830. "tenth":"6 seconds",
  1831. "sixth":"10 seconds",
  1832. "half":"30 seconds",
  1833. "one":"1 minute",
  1834. "two":"2 minutes",
  1835. "three":"3 minutes",
  1836. "four":"4 minutes",
  1837. "five":"5 minutes",
  1838. "ten":"10 minutes",
  1839. },
  1840. "default":"off"
  1841. },
  1842.  
  1843. maxinterval:{
  1844. label:"How old is too old?",
  1845. type:"selecttime",
  1846. title:"Tell WM what you think is a good max post age to fetch. Also affects which posts are considered 'stale'.",
  1847. options:{
  1848. "off":"Off/Infinite",
  1849. "hour":"1",
  1850. "2hour":"2",
  1851. "3hour":"3",
  1852. "4hour":"4",
  1853. "8hour":"8",
  1854. "12hour":"12",
  1855. "18hour":"18",
  1856. "24hour":"24",
  1857. "32hour":"32",
  1858. "48hour":"48",
  1859. },
  1860. "default":"t:1d"
  1861. },
  1862.  
  1863. groupFetching:checkBox("All installed sidekicks in one request (default: one request per sidekick)",false,{},true),
  1864. noAppFiltering:checkBox("Have WM filter posts for you instead of having facebook do it (may prevent some empty data set issues)",false,{},true),
  1865. },true),
  1866.  
  1867. autoPauseBlock:optionBlock("Fetching/Collecting Autopause",{
  1868. autopausefetch:checkBox("Pause Fetching after First Fetch"),
  1869. autopausecollect:checkBox("Pause Collection on Startup"),
  1870. },true),
  1871.  
  1872. multiTaskBlock:optionBlock("Multi-task",{
  1873. maxrequests:inputBox("Max requests simultaneously",1),
  1874. recycletabs:inputBox("Recycle Windows/Tabs",1),
  1875. recycletabsall:checkBox("Recycle All",true),
  1876. },true),
  1877.  
  1878. queBlock:optionBlock("Task-Queue",{
  1879. queuetabs:checkBox("Force all posts and autolikes through one tab using a queue (overrides multi-task)",true),
  1880. },true),
  1881.  
  1882. timeoutBlock:optionBlock("Time-outs",{
  1883. reqtimeout:inputBox("Item Acceptance Page Timeout (seconds)",30),
  1884. failontimeout:checkBox("Mark Timeout as Failure (default: retry indefinitely)"),
  1885. },true),
  1886. }),
  1887.  
  1888. section_access:section("Accessibility",{
  1889. shortModeBlock:optionBlock("Short Mode",{
  1890. thumbsize:{
  1891. label:"Thumbnail Size",
  1892. type:"select",
  1893. title:"Size of bonus thumbnails in display mode: short and .",
  1894. options:{
  1895. "mosquito":"16px",
  1896. "tiny":"24px",
  1897. "small":"32px",
  1898. "medium":"48px",
  1899. "large":"64px",
  1900. "xlarge":"96px",
  1901. },
  1902. "default":"medium"
  1903. },
  1904. transitiondelay:inputBox("Hover Box Delay (s)",1),
  1905. },true),
  1906.  
  1907. accessTweaksBlock:optionBlock("Tweaks",{
  1908. debugrecog:checkBox("Show Identified Text (instead of original link text)",true),
  1909. showcounters:checkBox("Show Accept/Fail Counts",true),
  1910. showdynamictips:checkBox("Show Dynamic Console Tips",true),
  1911. appsConfirmDeleteUDT:checkBox("Confirm Delete User Defined Types",true),
  1912. },true),
  1913.  
  1914. toolBoxBlock:optionBlock("Customize Post Toolbox",{
  1915. showtoolbox:checkBox("Enable ToolBox", true),
  1916.  
  1917. showopen:checkBox("Open Post",true),
  1918. showmarkfailed:checkBox("Mark As Failed",true),
  1919. showmarkaccepted:checkBox("Mark As Accepted",true),
  1920. showlike:checkBox("Like Post",true),
  1921. showreid:checkBox("Re-ID Post",true),
  1922. showmovetop:checkBox("Move to Top",true),
  1923. showmovebottom:checkBox("Move to Bottom",true),
  1924. showpin:checkBox("Pin Post",true),
  1925. showclean:checkBox("Clean Post",true),
  1926. showpostsrc:checkBox("Show Post Source",true),
  1927.  
  1928. //new stuff
  1929. showcancelprocess:checkBox("Cancel Process or Like",true),
  1930. showrestartprocess:checkBox("Restart Process or Like",true),
  1931. showpausetype:checkBox("Pause Bonus Type",true),
  1932. showunpausetype:checkBox("Unpause Bonus Type",true),
  1933. showaddfeed:checkBox("Add To Feeds",true),
  1934.  
  1935. showmakerule:checkBox("Rule From Post",true),
  1936. showoriginaldata:checkBox("Show Original Data",true),
  1937. showautocomment:checkBox("Auto Comment",true),
  1938. },true),
  1939. littleToolBoxBlock:optionBlock("Customize Mini Toolbox",{
  1940. littleButtonSize:{
  1941. label:"Mini Toolbutton Size (requires refresh to redraw)",
  1942. type:"select",
  1943. title:"Size of buttons on mini toolbars",
  1944. options:{
  1945. "16":"16px",
  1946. "24":"24px",
  1947. "32":"32px",
  1948. },
  1949. "default":"24",
  1950. },
  1951. },true),
  1952.  
  1953. userColorsBlock:optionBlock("Colors",{
  1954. colorsaccepted:colorBox("Accepted","limegreen"),
  1955. colorsfailed:colorBox("Failed","red"),
  1956. colorsworking:colorBox("Working","yellow"),
  1957. colorsexcluded:colorBox("Excluded","gray"),
  1958. colorspaused:colorBox("Paused","silver"),
  1959. colorsnodef:colorBox("No Definition","deepskyblue"),
  1960. colorsscam:colorBox("Potential Scam","purple"),
  1961. colorspinned:colorBox("Pinned","black"),
  1962. colorstimeout:colorBox("Timeout","orange"),
  1963. },true),
  1964.  
  1965. }),
  1966.  
  1967. section_feedback:section("Feedback",{
  1968. 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},
  1969. gotoapiexplorer:anchor("Visit API Explorer","http://developers.facebook.com/tools/explorer?&version=v1.0"),
  1970. autoSetup:optionBlock("Setup",{
  1971. useautocomment:checkBox("Use Auto-comment (experimental)"),
  1972. useautolike:checkBox("Use Auto-like"),
  1973. //autoliketimeout:inputBox("Timeout (seconds)",30),
  1974. autolikedelay:inputBox("Ban-Prevention Delay (seconds)",3),
  1975. },true),
  1976. autoLikeBlock:optionBlock("Perform Feedback For",{
  1977. autolikeall:checkBox("All Posts"),
  1978. autolikeaccepted:checkBox("Accepted Posts"),
  1979. autolikesent:checkBox("Sent Posts"),
  1980. },true),
  1981. autoCommentListBlock:optionBlock("Comments (experimental)",{
  1982. autocommentlist:textArea("Random Comments (One per line)","Thanks\nThank you\nthanks"),
  1983. },true),
  1984. blockautolikebygame:optionBlock("Block Feedback by Game",{},false),
  1985. }),
  1986.  
  1987. section_filters:section("Filters",{
  1988. displayfilters:optionBlock("Remove Feed Parts (Classic Mode Only)",{
  1989. hideimages:checkBox("Images (All)"),
  1990. hideimagesunwanted:checkBox("Images (Unwanted Posts)"),
  1991. hidebody:checkBox("Post Body Text"),
  1992. hidevia:checkBox("Via App"),
  1993. hidedate:checkBox("Date/Time"),
  1994. },true),
  1995.  
  1996. filters:optionBlock("Hide By Type",{
  1997. hidemyposts:checkBox("My Posts"),
  1998. hideunwanted:checkBox("Unwanted"),
  1999. hideaccepted:checkBox("Accepted"),
  2000. hidefailed:checkBox("Failed"),
  2001. hidescams:checkBox("Scams"),
  2002. hidestale:checkBox("Stale Posts"),
  2003. hideexcluded:checkBox("Excluded"),
  2004. hideliked:checkBox("Liked By Me"),
  2005. hideunsupported:checkBox("Unsupported Apps"),
  2006.  
  2007. donthidewishlists:checkBox("Don't Hide Known Wish Lists"),
  2008. }),
  2009.  
  2010. //allow hiding all posts by particular games
  2011. filterapps:optionBlock("Hide By App",{}),
  2012.  
  2013. //now added dynamically as appID+"dontsteal"
  2014. dontstealBlock:optionBlock("Don't take W2W posts not for me",{}),
  2015.  
  2016. skipopts:optionBlock("Skip By Type",{
  2017. skipliked:checkBox("Liked By Me"),
  2018. skipstale:checkBox("Day-Old Posts"),
  2019. }),
  2020.  
  2021. filterTweaksBlock:optionBlock("Tweaks",{
  2022. accepton15:checkBox("Mark 'Unrecognized Response' As Accepted"),
  2023. markliked:checkBox("Mark Liked As Accepted (must check Skip Liked)"),
  2024. },true),
  2025.  
  2026. filterCleanupBlock:optionBlock("Cleanup",{
  2027. cleaninterval:{
  2028. label:"Cleanup Interval",
  2029. type:"selecttime",
  2030. title:"Remove unwanted posts from collection console after a set time.",
  2031. options:{
  2032. "off":"Off",
  2033. "one":"1 minute",
  2034. "two":"2 minutes",
  2035. "five":"5 minutes",
  2036. "ten":"10 minutes",
  2037. "fifteen":"15 minutes",
  2038. "thirty":"30 minutes",
  2039. "hour":"1 hour",
  2040. },
  2041. "default":"off"
  2042. },
  2043. cleanTimedOut:checkBox("Clean timed out posts",true),
  2044. },true),
  2045. }),
  2046.  
  2047. section_history:section("History",{
  2048. itemage:inputBox("How long to keep tried items in memory (days)",2),
  2049. oblock_historyConfirms:optionBlock("Confirm (Changes available on next config open)",{
  2050. historyConfirmClear:{type:"checkbox",label:"Clear History",title:"Confirm before clearing history.","default":true},
  2051. },true),
  2052. reset:button("Clear History",
  2053. WM.resetAccepted
  2054. ),
  2055. }),
  2056.  
  2057. section_feedopts:section("Feeds Manager",{
  2058. oblock_feedsConfirms:optionBlock("Confirm",{
  2059. feedsConfirmDeleteFeed:{type:"checkbox",label:"Delete Rule",title:"Require confirmation to delete a feed.","default":true},
  2060. },true),
  2061. }),
  2062.  
  2063. section_dynamicopts:section("Dynamic Grabber",{
  2064. dynamicopts:optionBlock("Dynamic Collection",{
  2065. dynamicFirst:checkBox("Run Dynamics BEFORE Sidekicks",true),
  2066. },true),
  2067. enableDynamic:optionBlock("Enable Dynamics by Game",{}),
  2068. oblock_dynamicConfirms:optionBlock("Confirm",{
  2069. dynamicConfirmDeleteTest:{type:"checkbox",label:"Delete Rule",title:"Require confirmation to delete a test.","default":true},
  2070. },true),
  2071. }),
  2072.  
  2073. section_friendtrackopts:section("Friend Tracker",{
  2074. useFriendTracker:checkBox("Enable Friend Tracking",true),
  2075. trackTime:inputBox("Track For How Many Days",2),
  2076. trackeropts:optionBlock("Track Data",{
  2077. trackCreated:checkBox("Post Creation Counts",true),
  2078. trackLastKnownPost:checkBox("Last Known Post Time",true),
  2079. trackAccepted:checkBox("Bonuses Accepted",true),
  2080. trackFailed:checkBox("Bonuses Failed",true),
  2081. },true),
  2082. oblock_trackerConfirms:optionBlock("Confirm",{
  2083. trackConfirmClearUser:{type:"checkbox",label:"Clear User Data",title:"Require confirmation to clear user data.","default":true},
  2084. },true),
  2085. }),
  2086. section_rulesopts:section("Rules Manager",{
  2087. oblock_rulesHeartbeat:optionBlock("Heartbeat",{
  2088. heartRate:inputBox("Global Heartbeat Delay (ms)",1000),
  2089. 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."},
  2090. 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."},
  2091. 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."},
  2092. 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."},
  2093. 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."},
  2094. },true),
  2095. oblock_rulesConfirms:optionBlock("Confirm",{
  2096. rulesConfirmDeleteValidator:{type:"checkbox",label:"Delete Validator",title:"Require confirmation to delete a rule's validator.","default":true},
  2097. rulesConfirmDeleteAction:{type:"checkbox",label:"Delete Action",title:"Require confirmation to delete a rule's action.","default":true},
  2098. rulesConfirmDeleteRule:{type:"checkbox",label:"Delete Rule",title:"Require confirmation to delete a rule.","default":true},
  2099. rulesConfirmResetLimit:{type:"checkbox",label:"Reset Limit",title:"Require confirmation to reset individual limits.","default":true},
  2100. rulesConfirmResetAllLimits:{type:"checkbox",label:"Reset All Limits",title:"Require confirmation to reset all limits.","default":true},
  2101. rulesConfirmHatch:{type:"checkbox",label:"Hatch Eggs",title:"Require confirmation to hatch eggs.","default":true},
  2102. },true),
  2103. 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},
  2104. }),
  2105.  
  2106. section_dev:section("Debug",{
  2107. oblock_debugTweaks:optionBlock("Tweaks",{
  2108. pinundefined:checkBox("Pin Undefined Bonus Types"),
  2109. },true),
  2110. debugOpts:optionBlock("Debug",{
  2111. debug:checkBox("Enable Debug",true),
  2112. debugLevel:{
  2113. label:"Debug Sensitivity",
  2114. title:"Sets the level of errors and warnings to report. 0 is all, 5 shows only the worst.",
  2115. type:"select",
  2116. options:{
  2117. "0":"Function calls",
  2118. "1":"Function subsections & debug notes",
  2119. "2":"Captured expected errors",
  2120. "3":"Known open errors",
  2121. "4":"Unexpected errors",
  2122. "5":"Fatal errors",
  2123. },
  2124. "default":"0"
  2125. },
  2126. debugMaxComments:inputBox("Max debug lines (0 for no limit)",100),
  2127. debugScrollIntoView:checkBox("Use scrollIntoView"),
  2128. debugStackRepeats:checkBox("Stack Immediate Repeats"),
  2129.  
  2130. },true),
  2131. advDebugOpts:optionBlock("Advanced Debug",{
  2132. devDebugFunctionSubsections:checkBox("Debug Function Subsections",false),
  2133. devDebugGraphData:checkBox("Debug Graph Packets (not available for Chrome)",false),
  2134. },true),
  2135. GM_special:optionBlock("Script-runner Options",{
  2136. useGM_openInTab:checkBox("Use GM_openInTab instead of window.open",false),
  2137. },true),
  2138.  
  2139. }),
  2140.  
  2141. section_configopts:section("Config",{
  2142. oblock_configConfirms:optionBlock("Confirm (Changes available on next config open)",{
  2143. configConfirmSave:{type:"checkbox",label:"Save",title:"Confirm before saving settings.","default":true},
  2144. configConfirmCancel:{type:"checkbox",label:"Cancel",title:"Confirm before closing settings without saving.","default":true},
  2145. configConfirmImport:{type:"checkbox",label:"Import",title:"Confirm before importing settings.","default":true},
  2146. configConfirmRestore:{type:"checkbox",label:"Restore Defaults",title:"Confirm before restoring defaults.","default":true},
  2147. },true),
  2148. oblock_configStyling:optionBlock("Styling (Changes available on next config open)",{
  2149. 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."},
  2150. 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."},
  2151. },true),
  2152. oblock_configTweaks:optionBlock("Tweaks (Changes available on next config open)",{
  2153. 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."},
  2154. },true),
  2155. }),
  2156. }),
  2157.  
  2158. wmtab_games:tabSection("Sidekick Options",{
  2159. 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."},
  2160. }),
  2161.  
  2162. wmtab_info:tabSection("Info",{
  2163. MainMessageCenter:separator("Documentation - Messages - Help",null,{
  2164. Mainupdate:anchor("Update Script","http://userscripts.org/scripts/source/86674.user.js"),
  2165. 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"},
  2166. Mainwikipage:anchor("Wiki Support Page","http://fbwm.wikia.com/wiki/Known_Issues"),
  2167. Mainsetupinfo:anchor("Setup Info","http://fbwm.wikia.com/wiki/New_User_Setup"),
  2168. Maindiscuss:anchor("Known Bugs","http://fbwm.wikia.com/wiki/Known_Issues"),
  2169. Mainrevisionlog:anchor("Revision Log","http://fbwm.wikia.com/wiki/Revisions"),
  2170. },true),
  2171. }),
  2172. wmtab_scripts:tabSection("Get More!",{
  2173. }),
  2174.  
  2175. },
  2176. });
  2177. // add options shortcut to user script commands
  2178. GM_registerMenuCommand("Wall Manager "+WM.version+" Options", function(){WM.config.open();});
  2179. }catch(e){log("WM.optionsSetup: "+e);}},
  2180.  
  2181. init : function(){try{
  2182. //capture user id/alias/name and make it global
  2183. WM.currentUser.id = Graph.userID;
  2184. WM.currentUser.alias = Graph.userAlias;
  2185. WM.currentUser.profile = WM.currentUser.alias||WM.currentUser.id;
  2186.  
  2187. debug.print("UserID:"+WM.currentUser.id+"; UserAlias:"+WM.currentUser.alias+"; WM is Using:"+WM.currentUser.profile);
  2188.  
  2189. //get WM.quickOpts
  2190. WM.quickOpts = getOptJSON('quickopts_'+WM.currentUser.profile)||{};
  2191. WM.quickOpts["filterApp"]=(WM.quickOpts["filterApp"]||"All");
  2192. WM.quickOpts["displayMode"]=(WM.quickOpts["displayMode"]||"0");
  2193. WM.quickOpts["masterSwitch"]=(WM.quickOpts["masterSwitch"]||{});
  2194. WM.quickOpts["useGlobalSettings"]=(WM.quickOpts["useGlobalSettings"]||false);
  2195.  
  2196. //create the options menu
  2197. WM.optionsSetup();
  2198.  
  2199. //duplicate the options saved in WM.config
  2200. WM.updateSettingsValues();
  2201.  
  2202. //set up the config with its internal special variables
  2203. WM.changeConfigSettings();
  2204.  
  2205. //setup debug beyond its defaults
  2206. WM.changeDebugSettings();
  2207. //clean history
  2208. WM.history = getOptJSON('history_'+WM.currentUser.profile)||{};
  2209. WM.cleanHistory();
  2210. //prep the console now that we have an id and/or alias
  2211. //and then carry on with our init
  2212. WM.console.init({callback:WM.initConsole});
  2213. }catch(e){log("WM.init: "+e);}},
  2214. receiveSidekickMessage: function(event) {
  2215. if (isObject(event.data)) {
  2216. var data=event.data; //just shorten the typing
  2217. if (data.channel=="WallManager"){
  2218. //log(JSON.stringify(data));
  2219. //$("WM_debugWindow").childNodes[1].lastChild.scrollIntoView();
  2220. switch (data.msg){
  2221. case 2: //getting a comOpen response from sidekick
  2222. //WM.collector.tabs[data.tabID].comOpen=true;
  2223. break;
  2224. case 4: //getting a status package from sidekick
  2225. switch (data.params.action){
  2226. case "onFrameLoad":
  2227. WM.onFrameLoad(data.params);
  2228. break;
  2229. case "onFrameLoad3":
  2230. WM.onFrameLoad3(data.params);
  2231. break;
  2232. }
  2233. break;
  2234. }
  2235. }
  2236. }
  2237. },
  2238.  
  2239. run : function() {try{
  2240. // pre-load console images
  2241. //for(var img in imgs) try{new Image().src = imgs[img];}catch(e){log("preload: "+e);}
  2242.  
  2243. //special about:config entry for disabling storage of fb auth token
  2244. //should help multi account users
  2245. //if (getOpt("disableSaveAuthToken"))
  2246. Graph.authToken=null;
  2247. //patch 38 auth token stuff
  2248. var flagManualAuthSuccessful=getOpt("flagManualAuthSuccessful")||false;
  2249. if (WallManager.switches.manualAuthToken && !flagManualAuthSuccessful) {
  2250. 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";
  2251. var manualToken = prompt(m,"paste token here");
  2252. //validate manualToken at least somewhat
  2253. //halt if manual token is not given
  2254. if (manualToken=="" || manualToken==null || manualToken=="paste token here") {
  2255. alert("manual token not accepted, please refresh and try again");
  2256. return;
  2257. }
  2258. //pass the manual token along
  2259. Graph.authToken=manualToken;
  2260. //consider saving time by looking for auth tokens automatically from here out
  2261. 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.";
  2262. var saveProgress = confirm(m);
  2263. if (saveProgress) {
  2264. setOpt("flagManualAuthSuccessful",true);
  2265. }
  2266. }
  2267.  
  2268. var G=Graph.fetchUser({callback:WM.init});
  2269. if (G){if (G.requestAlreadyOut) {
  2270. } else if (G.initRequestSlow) {
  2271. } else if (G.olderLimitReached) {
  2272. } else if (G.getAuthTokenFailed) {
  2273. }}
  2274. }catch(e){log("WM.run: "+e);}}
  2275. };
  2276.  
  2277. var WM=WallManager;
  2278.  
  2279. //returns the current date-time in unix format, not localized
  2280. WM.__defineGetter__("currentTime",function(){try{
  2281. return timeStamp();
  2282. }catch(e){log("WM.currentTime: "+e);}});
  2283. //returns the appID of the selected app tab on the collection panel, or 'all' if 'Show All' is selected
  2284. WM.__defineGetter__("currentAppTab",function(){try{
  2285. var tabCtrl=WM.console.collectTabControl;
  2286. if (tabCtrl||null) {
  2287. var tab = tabCtrl._selectedTab;
  2288. if (tab||null) return tab.appFilter;
  2289. }
  2290. return "all";
  2291. }catch(e){log("WM.currentAppTab: "+e);}});
  2292.  
  2293. var sandbox=this;
  2294. //allow certain options to be seen outside of the WallManager object
  2295. //graph extension is external but still calls on WM options if they exist
  2296. opts=WM.opts;
  2297. quickOpts=WM.quickOpts;
  2298. //***************************************************************************************************************************************
  2299. //***** global functions
  2300. //***************************************************************************************************************************************
  2301. sandbox.isNumber = function(o){return ((typeof o) == "number");};
  2302. sandbox.isArray = function(o) {return Object.prototype.toString.call(o) === "[object Array]";};
  2303.  
  2304. //***************************************************************************************************************************************
  2305. //***** Visual Console Object
  2306. //***************************************************************************************************************************************
  2307. WM.console = {
  2308. initialized: false,
  2309. sidekickNode: null, //remember the sidekicks list
  2310. feedNode: null, //remember where to put the feed data
  2311. loading: true, //set to false after sidekicks have time to load
  2312. priorityNode: null,
  2313. priorityBuild: null,
  2314. dynamicBuild: null,
  2315. //new content
  2316. tabContainer:null, //outer tab control
  2317. configButton:null, //userConfig.open control
  2318. collectTabControl:null, //app filter tab control
  2319. dynamicIcons: function(){
  2320. //define a crapload of icons
  2321. var icons={
  2322. //128x128 pixel icons
  2323. row0:["refresh","world","check",null,"moveUpLevelLeft","moveUpLevelRight","moveDownLevelLeft","moveDownLevelRight","filter","plus","minus","multiply","import","reset","object","array"],
  2324. row1:["expandDown","expandUp","expandLeft","expandRight","moveTopLeft","moveBottomLeft",null,"allSidekicks","location","sortAsc","sortDesc","tools","treeExpand","treeCollapse","exportGrab","grab"],
  2325. row2:["playDown","playUp","playLeft","playRight","like","unlike","uncheckAll","checkAll","layoutSmall","layoutDetail","layoutList","sidekick","refreshProcess","cancelProcess","importData","heartbeat"],
  2326. row3:["arrowDown","arrowUp","arrowRight","arrowLeft","rssUpRight","rssUpLeft","rssDownRight","rssDownLeft","pin","pinned","redPhone","shuffle",null,"birth","comment"],
  2327. row4:["plugin","identify","add","remove","openInNewWindow","restoreDown","stop","pause","trash","action","logo",null,"moveOutLevel","moveInLevel","removeGlobal","toGlobal"],
  2328. row5:["clone","hatch","tag","noImage","accordionExpandH","accordionCollapseH","accordionExpandV","accordionCollapseV","gotoLibrary","addFilter","removeFilter","maximize","addFeed","addGlobal","fromGlobal","checkGlobal"],
  2329. //32px icons
  2330. row6:["firefox","chrome",null,"tabs"],
  2331. //16px icons
  2332. row7:["treeCollapseS","treeExpandS","layoutSmallColor","layoutDetailColor","layoutListColor",null,null,null,null,null,null,"noImageSmall"],
  2333. };
  2334. var ret=".resourceIcon {display:block; background-image:url('"+WM.resources.iconsURL+"') !important;}\n";
  2335. //create css statements
  2336. //for rows 0-5,6,7
  2337. var sizes=[8,16,24,32,48,64];
  2338. for (var si=0,len=sizes.length;si<len;si++){
  2339. var s=sizes[si];
  2340. for (var r=0;r<=6;r++){
  2341. for (var i=0;i<20;i++){
  2342. var iconName=icons["row"+r][i];
  2343. if (iconName!=null) {
  2344. ret+="."+iconName+s+" {background-position:"+(-i*s)+"px "+(-r*s)+"px; width:"+s+"px; height:"+s+"px; background-size:"+(1024/(64/s))+"px;}\n";
  2345. }
  2346. }
  2347. }
  2348. r=6;
  2349. for (var i=0;i<20;i++){
  2350. var iconName=icons["row"+r][i];
  2351. if (iconName!=null) {
  2352. //6 rows of icons 2 times this size
  2353. var yOffset=(6*s*2);
  2354. ret+="."+iconName+s+" {background-position:"+(-(i*s))+"px "+(-yOffset)+"px; width:"+s+"px; height:"+s+"px; background-size:"+(1024/(64/(s*2)))+"px;}\n";
  2355. }
  2356. }
  2357. r=7;
  2358. for (var i=0;i<20;i++){
  2359. var iconName=icons["row"+r][i];
  2360. if (iconName!=null) {
  2361. //6 rows of icons 4 times this size
  2362. //plus 1 row of icons twice this size
  2363. var yOffset=(6*s*4) + (1*s*2);
  2364. ret+="."+iconName+s+" {background-position:"+(-(i*s))+"px "+(-yOffset)+"px; width:"+s+"px; height:"+s+"px; background-size:"+(1024/(64/(s*4)))+"px;}\n";
  2365. }
  2366. }
  2367. }
  2368.  
  2369. return ret;
  2370. },
  2371. globalStyle:function(){try{
  2372. return ""+
  2373. //icon sheets
  2374. WM.console.dynamicIcons()+
  2375. "html {height:100%; width:100%;}\n"+
  2376. "body {margin:0 !important; font-family:tahoma,arial; font-size:small;}\n"+
  2377. "a:hover {text-decoration: none !important;}\n"+
  2378.  
  2379. "#content {display:none !important; }\n"+
  2380.  
  2381. "#wmContent {background-color:#DDDDEE; position:relative;}\n"+
  2382.  
  2383. ".post.classic {position:relative; min-height:90px; border-bottom:1px solid #CCCCDD; padding-bottom:10px; padding-top:10px; clear:both;}\n"+
  2384. ".post.classic .actor {margin-top:5px; margin-bottom:10px; font-weight:700; color:#3B5998; display:inline;}\n"+
  2385. ".post.classic .picture {padding-top:5px; padding-right:10px; float:left;}\n"+
  2386. ".post.classic .picture img {width:90px; height:90px; background-color:white; border:1px solid; border-radius:5px;}\n"+
  2387. ".post.classic .body {vertical-align:top;}\n"+
  2388. ".post.classic .title {margin-top:5px; font-weight:700; color:#3B5998;display:block;}\n"+
  2389. ".post.classic .caption {display:block; }\n"+
  2390. ".post.classic .description {padding-top:5px; display:block;}\n"+
  2391. ".post.classic .postDate {}\n"+
  2392. ".post.classic .appName {position:relative; left:10px;}\n"+
  2393. ".post.classic .linkText {color:#899ADB; float:right; padding-right:32px;}\n"+
  2394. ".post.classic.noimage {min-height:1px;}\n"+
  2395. ".post.short {float:left; position:relative;}\n"+
  2396. ".post.short .floater {overflow:hidden; display:block; background-color: white; border:0px solid; border-radius:5px; position:absolute; z-index:3; padding:0; width:0px;}\n"+
  2397. ".post.short:hover .floater {-moz-transition-property: width,height,padding,border;-moz-transition-delay:1s; width:240px; padding:5px 10px;border:1px solid;}\n"+
  2398. ".post.short .actor {display:block;}\n"+
  2399. ".post.short .picture {position:relative;}\n"+
  2400. ".post.short .picture img {position:relative; width:100%; height:100%; background-color:white;}\n"+
  2401. ".post.short .postDate {display:block;}\n"+
  2402. ".post.short .appName {display:block;}\n"+
  2403. ".post.short .linkText {display:block;}\n"+
  2404. ".post.short .progress {opacity:0.25; background-color:#00FF00;}\n"+
  2405.  
  2406. ".post.short.working .picture img {opacity:0.25;}\n"+
  2407. ".post.short.excluded .picture img {opacity:0.25;}\n"+
  2408. ".post.short.timeout .picture img {opacity:0.25;}\n"+
  2409. ".post.short.paused .picture img {opacity:0.25;}\n"+
  2410. ".post.short.nodef .picture img {opacity:0.25;}\n"+
  2411. ".post.short.accepted .picture img {opacity:0.25;}\n"+
  2412. ".post.short.failed .picture img {opacity:0.25;}\n"+
  2413. ".post.short.colored .picture img {opacity:0.25;}\n"+
  2414. ".post.short.scam .picture img {opacity:0.25;}\n"+
  2415. ".post.short.pinned .picture img {opacity:0.25;}\n"+
  2416.  
  2417. ".post.dev {position:relative; min-height:90px; border-bottom:1px solid #CCCCDD; padding-bottom:20px; padding-top:10px; clear:both;}\n"+
  2418. ".post.dev>div:first-child {display: inline-block; margin-right: 16px; border: none;}\n"+
  2419.  
  2420. ".wm.content > div > .toolBox {display:inline;}\n"+
  2421. ".wm.content > div > .toolBox > div {display:inline;}\n"+
  2422. ".post .toolBox {display:block; vertical-align:top; position:relative !important;}\n"+
  2423. ".post .toolBox > div {display:block; float:right;}\n"+
  2424. "div.excluded {background-color:gray !important;}\n"+
  2425. "div.working {background-color:yellow !important;}\n"+
  2426. "div.timeout {background-color:orange !important;}\n"+
  2427. "div.paused {background-color:silver !important;}\n"+
  2428. "div.pinned {background-color:silver !important;}\n"+
  2429. "div.nodef {background-color:deepskyblue !important;}\n"+
  2430. "div.failed {background-color:red !important;}\n"+
  2431. "div.accepted {background-color:limegreen !important;}\n"+
  2432. "div.scam {background-color:purple !important;}\n"+
  2433. ".pausedHover {display:none; position:absolute; right:50%; top:50%;}\n"+
  2434. ".pausedHover>img {margin-left:-32px; margin-top:-32px;}\n"+
  2435. ".pausedHover>img:hover {background-color:rgba(0,255,0,0.5); border-radius:20%;}\n"+
  2436. ".post.paused.short>.floater>.pausedHover>img {background-color:rgba(0,255,0,0.5); border-radius:20%;}\n"+
  2437. ".post.paused>.pausedHover, .post.paused>.floater>.pausedHover {display:block;}\n"+
  2438. ".underline {border-bottom:1px solid #CCCCDD;}\n"+
  2439.  
  2440. ".toolTip {display:none; border:1px solid #767676; border-radius:3px; background-color:white; color:black; position:absolute; font-size:8pt; padding:5px; line-height: 12px; z-index:9999;}\n"+
  2441. "*:hover > .toolTip {display:block;}\n"+
  2442. ".menuNode {width:0px; height:0px; position:absolute; background:none; border:none;top:-5px;}\n"+
  2443. ".toolTip.menuNode > ul {position:absolute; background-color: white; border: 1px solid; border-radius: 5px 5px 5px 5px; padding: 2px; min-width:100px;}\n"+
  2444. ".toolTip.menuNode > ul > li {position:relative; line-height:1.28; }\n"+
  2445. ".toolTip.right.menuNode {right:5px; }\n"+
  2446. ".toolTip.left.menuNode {left:-5px; }\n"+
  2447. ".toolTip.right.menuNode > ul {left:0px;}\n"+
  2448. ".toolTip.right.menuNode > ul > li {text-align:left;}\n"+
  2449. ".toolTip.left.menuNode > ul {right:0px;}\n"+
  2450. ".toolTip.left.menuNode > ul > li {text-align:right;}\n"+
  2451.  
  2452. //little button div
  2453. ".littleButton {background-color:threedshadow; border-radius:5px; margin:1px; display:inline-block; vertical-align:middle;}\n"+
  2454. ".littleButton:hover {background-color:highlight !important;}\n"+
  2455. ".littleButton>img {position:relative; display:block; margin:2px;}\n"+
  2456. ".littleButton.oddOrange {background-color:#FF9968;}\n"+
  2457. ".littleButton.oddBlack {background-color:#82976E;}\n"+
  2458. ".littleButton.oddBlue {background-color:#51D1EA;}\n"+
  2459. ".littleButton.oddGreen {background-color:#B7E54F;}\n"+
  2460. ".menuEntry, .menuList > li {position:relative; border-radius:3px; border:1px solid white; padding:3px; min-width:100px;}\n"+
  2461. ".menuEntry:hover, .menuList > li:hover {border-color:#CCCCDD; background-color:#E0E8F6; }\n"+
  2462.  
  2463. ".accFailBlock {color: white !important;font-size: small !important;left: 16px;line-height: 12px;margin-bottom: -12px;padding: 0 !important;position: relative;top: -32px;}\n"+
  2464. ".accFailBlock .fail {background-color: #C3463A; border-radius: 2px 2px 2px 2px; box-shadow: 1px 1px 1px rgba(0, 39, 121, 0.77); padding: 1px 2px;}\n"+
  2465. ".accFailBlock .accept {background-color: #46B754; border-radius: 2px 2px 2px 2px; box-shadow: 1px 1px 1px rgba(0, 39, 121, 0.77); padding: 1px 2px;}\n"+
  2466.  
  2467. //rules manager
  2468. "#wmPriorityBuilder {margin:5px; position: relative; background-color:white; min-height:95%;}\n"+
  2469. "#wmPriorityBuilder .validator > :before {content:'and: '}\n"+
  2470. "#wmPriorityBuilder .validator:first-child > :before {content:'where: '}\n"+
  2471. "#wmPriorityBuilder .action > :before {content:'and: '}\n"+
  2472. "#wmPriorityBuilder .action:first-child > :before {content:'do: '}\n"+
  2473. //collection feed node
  2474. "#wmFeedNode {margin:5px; position: relative; background-color:white;}\n"+
  2475.  
  2476. //sidekick manager
  2477. "#wmSidekickList {margin:5px; position: relative; background-color:white; min-height:95%;}\n"+
  2478. //feeds manager
  2479. "#wmFeedsList {margin:5px; position: relative; background-color:white; min-height:95%;}\n"+
  2480.  
  2481. //dynamic grabber
  2482. "#wmDynamicBuilder {margin:5px; position: relative; background-color:white; min-height:95%;}\n"+
  2483. //friend tracker
  2484. "#wmFriendTracker {margin:5px; position: relative; background-color:white; min-height:95%;}\n"+
  2485.  
  2486. ".expanded {display:block;}\n"+
  2487. ".collapsed {display:none;}\n"+
  2488. "label {font-weight:bold; margin-right:5px;}\n"+
  2489.  
  2490. ".unsaved {background-color:lightyellow !important;}\n"+
  2491.  
  2492. ".whiteover:hover {background-color:#FFFFFF !important;}\n"+
  2493. ".blueover:hover {background-color:#E0E8F6 !important;}\n"+
  2494.  
  2495. ".red {background-color:#C3463A !important; border: 2px solid #982B2F !important; text-shadow: -1px -1px 1px #982B2F, 1px 1px 1px #982B2F, 1px -1px 1px #982B2F, -1px 1px 1px #982B2F; text-transform: none !important; font-color:white !important;}\n"+
  2496. ".red:hover {background-color:#EA1515 !important;}\n"+
  2497.  
  2498. ".green {background-color:#46B754 !important; border: 2px solid #256E46 !important; text-shadow: -1px -1px 1px #256E46, 1px 1px 1px #256E46, 1px -1px 1px #256E46, -1px 1px 1px #256E46; text-transform: none !important; font-color:white !important;}\n"+
  2499. ".green:hover {background-color:#A6E11D !important;}\n"+
  2500.  
  2501. ".blue {background-color:#51C2FB !important; border: 2px solid #057499 !important; text-shadow: -1px -1px 1px #057499, 1px 1px 1px #057499, 1px -1px 1px #057499, -1px 1px 1px #057499; text-transform: none !important; font-color:white !important;}\n"+
  2502. ".blue:hover {background-color:#C2DEFF !important;}\n"+
  2503.  
  2504. ".gray {background-color:#999999 !important; border: 2px solid #666666 !important; text-shadow: -1px -1px 1px #666666, 1px 1px 1px #666666, 1px -1px 1px #666666, -1px 1px 1px #666666; text-transform: none !important; font-color:white !important;}\n"+
  2505. ".gray:hover {background-color:#C3C3C3 !important;}\n"+
  2506.  
  2507. ".odd {background-image: -moz-linear-gradient(center top , orange, red); box-shadow: 1px 1px 1px black; -moz-transform: rotate(15deg);}\n"+
  2508. ".odd:hover {-moz-transform: none;}\n"+
  2509.  
  2510. ".post.mosquito {width:16px; height:16px;}\n"+
  2511. ".post.tiny {width:24px; height:24px;}\n"+
  2512. ".post.small {width:32px; height:32px;}\n"+
  2513. ".post.medium {width:48px; height:48px;}\n"+
  2514. ".post.large {width:64px; height:64px;}\n"+
  2515. ".post.xlarge {width:96px; height:96px;}\n"+
  2516.  
  2517. ".floater.mosquito {left:8px;top:8px;}\n"+
  2518. ".floater.tiny {left:12px;top:12px;}\n"+
  2519. ".floater.small {left:16px;top:16px;}\n"+
  2520. ".floater.medium {left:24px;top:24px;}\n"+
  2521. ".floater.large {left:32px;top:32px;}\n"+
  2522. ".floater.xlarge {left:48px;top:48px;}\n"+
  2523.  
  2524. ".post.mosquito.working .picture img {width:24px; height:24px;left:-4px;top:-4px;}\n"+
  2525. ".post.tiny.working .picture img {width:32px; height:32px;left:-4px;top:-4px;}\n"+
  2526. ".post.small.working .picture img {width:48px; height:48px;left:-8px;top:-8px;}\n"+
  2527. ".post.medium.working .picture img {width:64px; height:64px;left:-8px;top:-8px;}\n"+
  2528. ".post.large.working .picture img {width:96px; height:96px;left:-16px;top:-16px;}\n"+
  2529. ".post.xlarge.working .picture img {width:128px; height:128px;left:-16px;top:-16px;}\n"+
  2530.  
  2531. //"div.pinned {border-radius: 6px; background-color: black !important;}\n"+
  2532. //".post.short.pinned .picture img {border-radius: 5px; height:80% !important; width:80% !important; margin-left:10%; margin-top:10%;}\n"+
  2533.  
  2534. "#wmContent>.jsfTabControl>.tabs {top:10%; width:100px; position:relative;}\n"+
  2535. "#wmContent>.jsfTabControl>.pages {border-radius:5px;}\n"+
  2536. "#wmContent>.jsfTabControl>.tabs>.jsfTab {text-align:center;}\n"+
  2537. ".jsfTabControl>.tabs {font-family:impact; font-size:large; color:inactivecaptiontext;}\n"+
  2538. "input,select,label,textarea {font-family:tahoma,arial; font-size:small; vertical-align:baseline !important;}\n"+
  2539. ".jsfComboBox {line-height:normal;}\n"+
  2540. "button {font-family:tahoma,arial; font-size:small; vertical-align:top !important;}\n"+
  2541. "input[type=\"checkbox\"] {font-family:tahoma,arial; font-size:small; vertical-align:middle !important;}\n"+
  2542.  
  2543. ".nomargin {margin:0 !important;}\n"+
  2544. ".hidden {display:none !important;}\n"+
  2545. ".block {display:block !important;}\n"+
  2546. ".alignTop {vertical-align:top !important;}\n"+
  2547. ".fit {width:100% !important;}\n"+
  2548. ".indent {margin-left:16px;}\n"+
  2549. "img.crisp {image-rendering: -moz-crisp-edges;}\n"+
  2550. ".listItem {position:relative; clear:both;}\n"+
  2551. ".listItem.disabled {opacity:0.5 !important; background-color:#eeeeee;}\n"+
  2552. ".listItem .toolBox {position: absolute; right: 0px; top: 0;}\n"+
  2553. ".listItem select {border:0px; padding:0px; margin: 0px; margin-left:6px; margin-right:6px; background-color:#eeeeee; vertical-align:middle;}\n"+
  2554. ".listItem input {border:0px; padding:0px; margin: 0px; margin-left:6px; margin-right:6px; background-color:#eeeeee; vertical-align:middle;}\n"+
  2555. ".listItem textarea {background-color:#eeeeee; border:0px;}\n"+
  2556.  
  2557. ".header {background-color: window; font-family:impact; font-size:2em; color:inactivecaptiontext; border-radius:5px 5px 0 0; padding-left:6px;}\n"+
  2558. ".headerCaption {background-color: window; color: inactivecaptiontext; font-family: arial; font-size: small; padding-bottom: 6px; padding-left: 16px; padding-right: 16px; padding-top: 6px; border-radius:0 0 5px 5px;}\n"+
  2559. ".headerCaption+.toolBox, .header+.toolBox {border-bottom:1px solid activeborder; margin-bottom: 5px; margin-top: 5px; padding-bottom: 5px;}\n"+
  2560. ".line {border-top:1px solid #c0c0c0; line-height:2em;}\n"+
  2561. ".subsection {margin-left:28px;}\n"+
  2562. ".optioncontainer {max-height:12em; overflow-y:auto; background-color:rgb(238, 238, 238);}\n"+
  2563. ".optioncontainer>.line {line-height:normal;}\n"+
  2564. ".singleCol .post.classic {}\n"+
  2565. ".twoCol .post.classic {display: inline-block; width: 50%; vertical-align: top;}\n"+
  2566. ".twoCol .post.classic > .body {padding-right:28px;}\n"+
  2567. ".threeCol .post.classic {display: inline-block; width: 33%; vertical-align: top;}\n"+
  2568. ".threeCol .post.classic > .body {padding-right:28px;}\n"+
  2569. ".fourCol .post.classic {display: inline-block; width: 25%; vertical-align: top;}\n"+
  2570. ".fourCol .post.classic > .body {padding-right:28px;}\n"+
  2571.  
  2572. ".w400 {width:400px;}\n"+
  2573.  
  2574.  
  2575. ""
  2576. }catch(e){log("WM.console.globalStyle: "+e);}},
  2577.  
  2578. init: function(params){try{
  2579. debug.print("WM.console.init:");
  2580. var validateFBElements=["globalContainer","content"];
  2581. params=params||{};
  2582.  
  2583. //if console does not already exist
  2584. if (!WM.console.tabContainer) {
  2585. try{
  2586. addGlobalStyle(WM.console.globalStyle(),"styleConsole");
  2587. }catch(e){log("WM.console.init.addGlobalStyle: "+e);};
  2588.  
  2589. //attach to facebook page
  2590. var baseNode=$("globalContainer");
  2591. if (baseNode) baseNode=baseNode.parentNode;
  2592. //or attach to page body
  2593. else baseNode=($("body")||document.body);
  2594.  
  2595. //sort fields shared by post sorting and grouping
  2596. var sortFields = [
  2597. {value:"age",title:"Time elasped since created (ms)."},
  2598. {value:"alreadyProcessed",title:"History contains a status code for this post."},
  2599. {value:"appID"},
  2600. {value:"appName",title:"App name as it appears on FB."},
  2601. {value:"date",title:"The datetime the post was created, in unix format. Does not contain millisecond data."},
  2602. {value:"fromID",title:"The FB id of the person who created this post."},
  2603. {value:"fromName",title:"The display name of the person who created this post."},
  2604. {value:"fromNameLastFirst",title:"As fromName but displayed as LastName, FirstName"},
  2605. {value:"id",title:"The post object id as it is connected with FB."},
  2606. {value:"idText",title:"Either the whichText of the post, or the statusText of a post already processed."},
  2607. {value:"isAccepted"},
  2608. {value:"isFailed"},
  2609. {value:"isTimeout"},
  2610. {value:"isExcluded"},
  2611. {value:"isCollect",title:"A flag for if this post is to be collected."},
  2612. {value:"isForMe"},
  2613. {value:"isLiked"},
  2614. {value:"isMyPost"},
  2615. {value:"isPaused"},
  2616. {value:"isPinned"},
  2617. {value:"isScam"},
  2618. {value:"isStale"},
  2619. {value:"isUndefined"},
  2620. {value:"isWishlist"},
  2621. {value:"isW2W",title:"If this post is a Wall-to-wall post or just a general feed post."},
  2622. {value:"msg",title:"The comment attached to the post body by the post creator."},
  2623. {value:"postedDay",title:"The year/month/day portion of the creation time for this post."},
  2624. {value:"postedHour",title:"The year/month/day/hour portion of the creation time for this post."},
  2625. {value:"priority",title:"Priority 0 being the first post you would want processed, and Priority 50 being default."},
  2626. {value:"status",title:"The status code returned by the sidekick associated with this post."},
  2627. {value:"which",title:"The sidekick-defined bonus type id for this kind of post."},
  2628. {value:"whichText",title:"The text associated with this bonus type id."},
  2629. {value:null,name:"(none)"},
  2630. ];
  2631.  
  2632. //create our content window
  2633. baseNode.insertBefore(createElement("div",{id:"wmContent"},[
  2634. //toolbox
  2635. (WM.console.tabContainer=new jsForms.tabControl({
  2636. dock:"fillAndShare",
  2637. sizeOffset:{height:-3,width:0},
  2638. alignment:"left",
  2639. tabs:[
  2640. { //collect tab
  2641. text:"Collect",
  2642. image:null,
  2643. onSelect:function(){WM.console.collectTabControl.redraw();},
  2644. content:[
  2645. createElement("div",{className:"header",textContent:"Collect"}),
  2646. createElement("div",{className:"headerCaption",textContent:"View friends' posts and manage all your collection needs."}),
  2647. createElement("div",{className:"toolBox medium"},[
  2648. createElement("span",{className:"littleButton oddBlue",title:"Fetch Newer Posts Now",onclick:function(){WM.fetch({newer:true,bypassPause:true});} },[createElement("img",{className:"resourceIcon rssUpRight24"})]),
  2649. createElement("span",{className:"littleButton",title:"Fetch Older Posts Now",onclick:function(){WM.fetch({older:true,bypassPause:true});} },[createElement("img",{className:"resourceIcon rssDownLeft24"})]),
  2650. WM.console.pauseFetchButton=createElement("span",{className:"littleButton oddOrange",title:"Pause Automatic Fetching",onclick:function(){WM.pauseFetching();} },[createElement("img",{className:"resourceIcon expandDown24"})]),
  2651. WM.console.pauseCollectButton=createElement("span",{className:"littleButton oddOrange",title:"Pause Automatic Collection",onclick:function(){WM.pauseCollecting();} },[createElement("img",{className:"resourceIcon stop24"})]),
  2652. createElement("span",{className:"littleButton",name:"0",title:"Classic View",onclick:WM.setDisplay},[createElement("img",{className:"resourceIcon layoutListColor24"})]),
  2653. createElement("span",{className:"littleButton",name:"1",title:"Short View",onclick:WM.setDisplay},[createElement("img",{className:"resourceIcon layoutSmallColor24"})]),
  2654. createElement("span",{className:"littleButton",name:"2",title:"Developer View",onclick:WM.setDisplay},[createElement("img",{className:"resourceIcon layoutDetailColor24"})]),
  2655. createElement("span",{className:"littleButton",title:"Reset Counters",onclick:function(){WM.resetCounters();}},[createElement("img",{className:"resourceIcon refresh24"})]),
  2656. createElement("span",{className:"littleButton oddOrange",title:"Clean Now",onclick:function(){WM.cleanPosts();}},[createElement("img",{className:"resourceIcon trash24"})]),
  2657. createElement("span",{className:"littleButton",title:"ReID All",onclick:function(){WM.reIDAll();}},[createElement("img",{className:"resourceIcon identify24"})]),
  2658. createElement("label",{className:"indent",textContent:"Sort By: "}),
  2659. createElement("select",{id:"wmSortBy",className:"", title:"Sort By:", onchange:function(){WM.sortPosts({by:this.value});WM.redrawPosts({postRedraw:false,reorder:true});} },(function(){
  2660. var ret=[];
  2661. for (var i=0;i<sortFields.length;i++) ret.push(createElement("option",{value:sortFields[i].value,title:sortFields[i].title||"",textContent:sortFields[i].name||sortFields[i].value}));
  2662. return ret;
  2663. })()),
  2664. createElement("span",{className:"littleButton oddGreen",title:"Sort Ascending",onclick:function(){WM.sortPosts({direction:"asc"});WM.redrawPosts({reorder:true, postRedraw:false});}},[createElement("img",{className:"resourceIcon sortAsc24"})]),
  2665. createElement("span",{className:"littleButton oddOrange",title:"Sort Descending",onclick:function(){WM.sortPosts({direction:"desc"});WM.redrawPosts({reorder:true, postRedraw:false});}},[createElement("img",{className:"resourceIcon sortDesc24"})]),
  2666. createElement("label",{className:"indent",textContent:"Group By: "}),
  2667. createElement("select",{id:"wmGroupBy",className:"", title:"Group By:", onchange:function(){WM.constructGroups({by:this.value});WM.redrawPosts({postRedraw:false,reorder:true});} },(function(){
  2668. var ret=[];
  2669. for (var i=0;i<sortFields.length;i++) ret.push(createElement("option",{value:sortFields[i].value,title:sortFields[i].title||"",textContent:sortFields[i].name||sortFields[i].value}));
  2670. return ret;
  2671. })()),
  2672. createElement("span",{className:"littleButton oddGreen",title:"Group Ascending",onclick:function(){WM.sortGroups({direction:"asc"});WM.redrawPosts({reorder:true, postRedraw:false});}},[createElement("img",{className:"resourceIcon sortAsc24"})]),
  2673. createElement("span",{className:"littleButton oddOrange",title:"Group Descending",onclick:function(){WM.sortGroups({direction:"desc"});WM.redrawPosts({reorder:true, postRedraw:false});}},[createElement("img",{className:"resourceIcon sortDesc24"})]),
  2674.  
  2675. createElement("label",{className:"indent",textContent:"Columns: ",title:"Classic Mode Only"}),
  2676. createElement("select",{title:"Cols:", onchange:function(){WM.setDisplayCols({cols:this.value});} },[
  2677. createElement("option",{value:1,textContent:"One",selected:WM.quickOpts.displayCols==1}),
  2678. createElement("option",{value:2,textContent:"Two",selected:WM.quickOpts.displayCols==2}),
  2679. createElement("option",{value:3,textContent:"Three",selected:WM.quickOpts.displayCols==3}),
  2680. createElement("option",{value:4,textContent:"Four",selected:WM.quickOpts.displayCols==4})
  2681. ]),
  2682. createElement("span",{className:"littleButton oddBlue",title:"Autolike Queue"},[
  2683. createElement("img",{className:"resourceIcon like24"}),
  2684. createElement("div",{className:"accFailBlock"},[
  2685. WM.console.likeQueueCounterNode=createElement("span",{className:"accept",textContent:"0"})
  2686. ])
  2687. ]),
  2688. ]),
  2689. //app filter tabs
  2690. (WM.console.collectTabControl=new jsForms.tabControl({
  2691. dock:"fillAndShare",
  2692. subStyle:"coolBar",
  2693. shareSinglePage:true,
  2694. preventAutoSelectTab:true,
  2695. tabs:[
  2696. {
  2697. //default show all tab
  2698. text:"Show ALL",
  2699. image:"",
  2700. imageClass:"resourceIcon allSidekicks32",
  2701. appFilter:"All",
  2702. onSelect:WM.setAppFilter,
  2703. selected:(WM.quickOpts.filterApp=="All"),
  2704. content:null, //because page is shared
  2705. }
  2706. ],
  2707. sharedContent:[
  2708. //bonus display node
  2709. WM.console.feedNode=createElement("div",{id:"wmFeedNode",style:"position: relative;"}),
  2710. ],
  2711. })).node,
  2712. ],
  2713. },
  2714. { //sidekicks tab
  2715. text:"Manage Sidekicks",
  2716. image:null,
  2717. content:[
  2718. createElement("div",{className:"header",textContent:"Manage Sidekicks"}),
  2719. createElement("div",{className:"headerCaption",textContent:"Control some of the features of sidekicks."}),
  2720. WM.console.sidekickNode=createElement("div",{id:"wmSidekickList",className:"scrollY"}),
  2721. ],
  2722. },
  2723. { //feeds tab
  2724. text:"Manage Feeds",
  2725. image:null,
  2726. content:[
  2727. createElement("div",{className:"header",textContent:"Manage Feeds"}),
  2728. createElement("div",{className:"headerCaption",textContent:"Add direct links to friends or other public profiles, and fetch posts from those feeds faster."}),
  2729. createElement("div",{className:"toolBox medium columnRight"},[
  2730. createElement("div",{},[
  2731. createElement("div",{className:"littleButton oddGreen",title:"Add Feed",onclick:WM.feedManager.newFeed},[createElement("img",{className:"resourceIcon plus24"})]),
  2732. ])
  2733. ]),
  2734. WM.console.feedManagerNode=createElement("div",{id:"wmFeedsList",className:"scrollY"}),
  2735. ],
  2736. },
  2737. { //rules tab
  2738. text:"Manage Rules",
  2739. image:null,
  2740. content:[
  2741. createElement("div",{className:"header",textContent:"Manage Rules"}),
  2742. createElement("div",{className:"headerCaption",textContent:"Create rules like macros to control exactly how posts are handled."}),
  2743. createElement("div",{className:"toolBox medium columnRight"},[
  2744. createElement("div",{},[
  2745. createElement("div",{className:"littleButton oddGreen",title:"Add Rule",onclick:WM.rulesManager.newRule},[createElement("img",{className:"resourceIcon plus24"})]),
  2746. createElement("div",{className:"littleButton oddBlue",title:"Reset All Limits",onclick:WM.rulesManager.resetAllLimits},[createElement("img",{className:"resourceIcon reset24"})]),
  2747. createElement("div",{className:"littleButton oddBlue",title:"Convert Dynamics",onclick:WM.rulesManager.convertDynamics},[createElement("img",{className:"resourceIcon exportGrab24"})]),
  2748. createElement("div",{className:"littleButton oddBlue",title:"Import Rule",onclick:WM.rulesManager.importRule},[createElement("img",{className:"resourceIcon importData24"})]),
  2749. createElement("div",{className:"littleButton oddBlue",title:"Export All Rules",onclick:WM.rulesManager.showData},[createElement("img",{className:"resourceIcon object24"})]),
  2750. WM.rulesManager.toggleHBNode=createElement("div",{className:"littleButton "+(WM.quickOpts.heartbeatDisabled?"oddOrange":"oddGreen"),title:"Toggle Heartbeat",onclick:WM.rulesManager.toggleHeartbeat},[createElement("img",{className:"resourceIcon heartbeat24"})]),
  2751. ])
  2752. ]),
  2753. WM.console.priorityBuild=createElement("div",{id:"wmPriorityBuilder",className:"scrollY"}),
  2754. ],
  2755. },
  2756. { //dynamics tab
  2757. text:"Dynamic Grabber",
  2758. image:null,
  2759. content:[
  2760. createElement("div",{className:"header",textContent:"Dynamic Grabber"}),
  2761. createElement("div",{className:"headerCaption",textContent:"Create tests to capture posts sidekicks might not."}),
  2762. createElement("div",{className:"toolBox medium columnRight"},[
  2763. createElement("div",{},[
  2764. createElement("div",{className:"littleButton oddGreen",title:"Add Test",onclick:WM.grabber.newTest},[createElement("img",{className:"resourceIcon plus24"})]),
  2765. createElement("div",{className:"littleButton oddBlue",title:"Import Test",onclick:WM.grabber.importTest},[createElement("img",{className:"resourceIcon importData24"})]),
  2766. ])
  2767. ]),
  2768. WM.console.dynamicBuild=createElement("div",{id:"wmDynamicBuilder",className:"scrollY"}),
  2769. ],
  2770. },
  2771. { //friends tab
  2772. text:"Friend Tracker",
  2773. image:null,
  2774. content:[
  2775. createElement("div",{className:"header",textContent:"Friend Tracker"}),
  2776. createElement("div",{className:"headerCaption",textContent:"Track player friends and your interactions with them."}),
  2777. createElement("div",{className:"toolBox medium columnRight"},[
  2778. createElement("span",{className:"littleButton oddOrange",title:"Clean Now",onclick:function(){WM.friendTracker.clearAll();}},[
  2779. createElement("img",{className:"resourceIcon trash24"})
  2780. ]),
  2781. createElement("label",{className:"indent",textContent:"Sort By: "}),
  2782. createElement("select",{title:"Sort By:", onchange:function(){WM.friendTracker.sort({sortBy:this.value});} },[
  2783. createElement("option",{value:"acceptCount",textContent:"acceptCount",title:"How many posts WM remembers as collected successfully from this user."}),
  2784. createElement("option",{value:"failCount",textContent:"failCount",title:"How many posts WM remembers as failed from this user."}),
  2785. createElement("option",{value:"id",textContent:"id",title:"The facebook id of the user."}),
  2786. createElement("option",{value:"lastKnownPostDate",textContent:"lastKnownPostDate",title:"The date of the last known post WM received for this user."}),
  2787. createElement("option",{value:"name",textContent:"name",title:"The name of the user, with last name first."}),
  2788. createElement("option",{value:"postCount",textContent:"postCount",title:"How many posts WM remembers receiving related to this user."}),
  2789. createElement("option",{value:"totalCount",textContent:"totalCount",title:"How many posts WM remembers failed OR accepted from this user."})
  2790. ]),
  2791. createElement("span",{className:"littleButton oddGreen",title:"Sort Ascending",onclick:function(){WM.friendTracker.sort({sortOrder:"asc"});}},[createElement("img",{className:"resourceIcon sortAsc24"})]),
  2792. createElement("span",{className:"littleButton oddOrange",title:"Sort Descending",onclick:function(){WM.friendTracker.sort({sortOrder:"desc"});}},[createElement("img",{className:"resourceIcon sortDesc24"})]),
  2793. ]),
  2794. WM.console.friendBuild=createElement("div",{id:"wmFriendTracker",className:"scrollY"}),
  2795. ],
  2796. },
  2797. { //options tab
  2798. text:"Options",
  2799. image:null,
  2800. content:[
  2801. createElement("div",{className:"header",textContent:"Options"}),
  2802. createElement("div",{className:"headerCaption",textContent:"Manage script and sidekick configuration, or link to updates."}),
  2803. //config menu button
  2804. createElement("div",{},[
  2805. createElement("label",{textContent:"Open the options menu: "}),
  2806. WM.console.configButton=createElement("button",{
  2807. className:"jsfHidden",
  2808. textContent:"WM Options",
  2809. onclick:function(){
  2810. //open options menu
  2811. WM.config.open();
  2812. },
  2813. }),
  2814. ]),
  2815. //update script button
  2816. createElement("div",{},[
  2817. createElement("label",{textContent:"Update Script (Current Version: "+WM.version+") :"}),
  2818. createElement("button",{
  2819. className:"",
  2820. textContent:"Update Script",
  2821. onclick:function(){
  2822. //open update url in new window/tab
  2823. window.open("http://userscripts.org/scripts/source/86674.user.js","_blank");
  2824. },
  2825. }),
  2826. ]),
  2827. ],
  2828. },
  2829. ]
  2830. })).node
  2831. ]), $("globalContainer"));
  2832.  
  2833. //destroy facebook content on page
  2834. if ($("content")) $("content").style.display="none !important";
  2835. //init sort order
  2836. $("wmSortBy").value=WM.quickOpts.sortBy;
  2837.  
  2838. //init group order
  2839. $("wmGroupBy").value=WM.quickOpts.groupBy;
  2840.  
  2841. //init display mode
  2842. with (WM.console.feedNode){
  2843. className = className.toggleWordB(["1","3"].inArray(WM.quickOpts.displayMode),"short");
  2844. }
  2845. WM.setDisplayCols({cols:WM.quickOpts.displayCols});
  2846. }
  2847. WM.console.initialized = true;
  2848.  
  2849. //give sidekicks time to dock
  2850. if (params["callback"]||null) {
  2851. var fx = params["callback"];
  2852. delete params["callback"];
  2853. doAction(fx);
  2854. }
  2855. }catch(e){log("WM.console.init: "+e);}},
  2856. }; //end WM.console
  2857.  
  2858. //***************************************************************************************************************************************
  2859. //***** Sidekick Docking Object
  2860. //***************************************************************************************************************************************
  2861. WM.dock = {
  2862. //restructure menu to append appID before every object
  2863. fixMenu: function(menu,app){try{
  2864. var ret={};
  2865. //for each object in menu
  2866. for (var o in menu){
  2867. //WM.message(o);
  2868. ret[app+o]=menu[o];
  2869.  
  2870. //fix button functions and arrays to be prepended by the appID of that sidekick
  2871. var t=menu[o]["type"];
  2872. switch(t){
  2873. case "button_highlight":
  2874. case "button_selectmulti":
  2875. case "button_selectprefix":
  2876.  
  2877. //fix elements in the clearfirst array
  2878. if (menu[o]["clearfirst"]){
  2879. for (var i=0,len=ret[app+o]["clearfirst"].length;i<len;i++){
  2880. ret[app+o]["clearfirst"][i] = app+ret[app+o]["clearfirst"][i];
  2881. }
  2882. }
  2883.  
  2884. //fix elements in the options array
  2885. if (menu[o]["options"]){
  2886. for (var i=0,len=ret[app+o]["options"].length;i<len;i++){
  2887. ret[app+o]["options"][i] = app+ret[app+o]["options"][i];
  2888. }
  2889. }
  2890.  
  2891. if (menu[o]["clearPrefix"]){
  2892. ret[app+o]["clearPrefix"]=app+ret[app+o]["clearPrefix"];
  2893. }
  2894.  
  2895. if (menu[o]["prefix"]){
  2896. ret[app+o]["prefix"]=app+ret[app+o]["prefix"];
  2897. }
  2898. }
  2899.  
  2900. //fix kids
  2901. if (menu[o]["kids"]){
  2902. //rebuild kids object
  2903. ret[app+o]["kids"]=WM.dock.fixMenu(menu[o]["kids"],app);
  2904. }
  2905. }
  2906. return ret;
  2907. } catch(e) {log("WM.dock.fixMenu: "+e);}},
  2908.  
  2909. //restructure tests to append appID before every object's return
  2910. fixTests: function(arr,app){try{
  2911. //for each test in array
  2912. for (var t=0,len=arr.length;t<len;t++) {
  2913. var ret=arr[t].ret, kids=arr[t].kids;
  2914. //replace return value
  2915. if (ret) {
  2916. if (ret!="exclude" && ret!="none") {
  2917. arr[t].ret=app.appID+ret;
  2918. }
  2919. }
  2920. //process subtests
  2921. if (kids) WM.dock.fixTests(kids,app);
  2922. }
  2923. } catch(e) {log("WM.dock.fixTests: "+e);}},
  2924.  
  2925. fixAcceptTexts:function(app){try{
  2926. var newAccText={};
  2927. for (var s in app.accText) {
  2928. newAccText[app.appID+s]=app.accText[s];
  2929. }
  2930. app.accText=newAccText;
  2931. } catch(e) {log("WM.dock.fixAcceptTexts: "+e);}},
  2932.  
  2933. onSidekickParsed: function(newset){try{
  2934. //save it into the NEW format for games
  2935. var app=(WM.apps[newset.appID]=new WM.App(newset));
  2936.  
  2937. //promptText JSON.stringify(WM.config.settings));
  2938. WM.updateSettingsValues();
  2939.  
  2940. //detach the menu from the newset to reduce duplication
  2941. delete app.menu;
  2942. //fire priority event
  2943. (function(){WM.rulesManager.doEvent("onSidekickDock",app);})();
  2944. //fetch its initial posts
  2945. //app.fetchPosts();
  2946. WM.newSidekicks.push(app);
  2947. }catch(e){log("WM.dock.onSidekickParsed: "+e);}},
  2948. parseNewSidekick: function(node){try{
  2949. if (node){
  2950. var v = node.getAttribute('data-ft');
  2951. node.setAttribute('data-ft','');
  2952. if (v||null) {
  2953. var newset = JSON.parse(v);
  2954. WM.dock.onSidekickParsed(newset);
  2955. }
  2956. }
  2957. } catch(e) {log("WM.dock.parseNewSidekick: "+e);}},
  2958. answerDockingDoor: function(){try{
  2959. //log("Sidekick requesting to dock");
  2960.  
  2961. //get all sidekicks that left info on the dock;
  2962. forNodes(".//div[@id='wmDock']/div[(@data-ft) and not(@data-ft='')]",{},function(node){
  2963. if (node.getAttribute('data-ft') !=""){
  2964. window.setTimeout(WM.dock.parseNewSidekick,1,node);
  2965. }
  2966. });
  2967. } catch(e) {log("WM.dock.answerDockingDoor: "+e);}},
  2968.  
  2969. };
  2970. //***************************************************************************************************************************************
  2971. //***** Collector Object
  2972. //***************************************************************************************************************************************
  2973. WM.collector = {
  2974. tabs : {}, //container for window objects
  2975. recycle : [], //container for reusable window objects
  2976. queue : [], //container for urls to do in order
  2977. count : 0,
  2978.  
  2979. windowExists : function(hwnd){
  2980. try{
  2981. var testUrl=tab.hwnd.location.toString();
  2982. return true;
  2983. }catch(e) {
  2984. return false;
  2985. }
  2986. },
  2987. //requires id, url and callback
  2988. open : function(params) {try{
  2989. //log("WM.collector.open()",{level:0});
  2990.  
  2991. //check for tab queueing
  2992. if (WM.opts.queuetabs && WM.collector.count && !(params.emergency||false)) {
  2993. if (params.first||false) {
  2994. //cut in line to be next processed
  2995. WM.collector.queue.unshift(params);
  2996. return;
  2997. }
  2998. //toss the next action in the queue while we wait for the current one to finish
  2999. WM.collector.queue.push(params);
  3000. //log("WM.collector.open: request queued",{level:1});
  3001. return;
  3002. }
  3003.  
  3004. var url = params.url;
  3005. var id = params.id;
  3006.  
  3007. //create a window or use a recycled one
  3008. var tabHwnd;
  3009. if (WM.collector.recycle.length) {
  3010. tabHwnd = WM.collector.recycle.shift();
  3011. //watch for missing window objects
  3012. try{
  3013. //use the existing window object if it responds
  3014. tabHwnd.location.href=url;
  3015. } catch (e) {
  3016. //window object missing, make a new one
  3017. //FF22 version
  3018. tabHwnd = GM_openInTab(url,"_blank");
  3019. //FF21 version
  3020. //tabHwnd = ((WM.opts.useGM_openInTab)?GM_openInTab:window.open)(url,"_blank");
  3021. }
  3022. } else {
  3023. //we do not use recycling, just make a new one
  3024. //FF22 version
  3025. tabHwnd = GM_openInTab(url,"_blank");
  3026. //FF21 version
  3027. //tabHwnd = ((WM.opts.useGM_openInTab)?GM_openInTab:window.open)(url,"_blank");
  3028. }
  3029.  
  3030. //window opening
  3031. if (tabHwnd) {
  3032. WM.collector.count++;
  3033. params.hwnd=tabHwnd; //store the window handle
  3034. params.openTime=timeStamp();
  3035. WM.collector.tabs[id]=params; //add the tab and all its data to the array
  3036.  
  3037. //pass data to the sidekick top window
  3038. var callback = params.callback;
  3039. if (callback) delete params.callback;
  3040. if (params.msg) {
  3041. remove($(params.msg));
  3042. delete(params.msg);
  3043. }
  3044. //details for posts, not for likes
  3045. var app, synApp, isPost;
  3046. if (isPost=(params.post||null)){
  3047. app=params.post.app;
  3048. synApp=app.parent||app;
  3049. }
  3050.  
  3051. if (callback) {
  3052. //log("WM.collector.open: callback fired",{level:3});
  3053. doAction(function(){
  3054. callback(params);
  3055. });
  3056. }
  3057. } else {
  3058. log("WM.collector: Tab or Window is not opening or your browser does not support controlling tabs and windows via scripts. Check your popup blocker.",{level:5});
  3059. }
  3060. }catch(e){log("WM.collector.open: "+e);}},
  3061.  
  3062. doNext : function(){try{WM.collector.open(WM.collector.queue.shift());}catch(e){log("WM.collector.doNext: "+e);}},
  3063.  
  3064. close : function(tab) {try{
  3065. //recycle or close the passed tab
  3066. try{
  3067. if (WM.opts.recycletabsall || WM.opts.queuetabs || (WM.collector.recycle.length < WM.opts.recycletabs)) {
  3068. //wipe it and put it away
  3069. if (tab.hwnd){
  3070. WM.collector.recycle.push(tab.hwnd);
  3071. tab.hwnd.location.href="about:blank";
  3072. if (WM.collector.windowExists(tab.hwnd)){
  3073. tab.hwnd.location.hash="";
  3074. }
  3075. } else {
  3076. //tab is busy, laggy or missing
  3077. tab.closeRetries=(tab.closeRetries||0)+1;
  3078. if (tab.closeRetries<3) {
  3079. setTimeout(function(){WM.collector.close(tab);},1000);
  3080. } else {
  3081. log("WM.collector.close: Control of window handle lost; cannot recycle. Window may be too busy to communicate with, or has been closed manually.");
  3082. }
  3083. return;
  3084. }
  3085. } else {
  3086. if (tab.hwnd) tab.hwnd.close();
  3087. }
  3088. } catch (e){log("WM.collector.close: recycler: "+e);}
  3089.  
  3090. try{
  3091. tab.hwnd=null;
  3092. delete tab.signal;
  3093. delete tab.stage;
  3094. delete tab.closeTries;
  3095. if (tab.toIntv) clearInterval(tab.toIntv);
  3096. delete tab;
  3097. tab=null;
  3098. WM.collector.count--
  3099. }catch(e){log("WM.collector.close: destroy tab: "+e);}
  3100.  
  3101. //check for items in queue to do next
  3102. if (WM.collector.queue.length) {
  3103. //check that queueing is still in practice
  3104. if (WM.opts.queuetabs) {
  3105. setTimeout(WM.collector.doNext,1000); //just do one
  3106. } else {
  3107. //options have changed since queueing was enacted, release all the queue into windows right now
  3108. var offset=1000;
  3109. while (WM.collector.queue.length && (WM.collector.count < WM.opts.maxrequests)) {
  3110. setTimeout(WM.collector.doNext,offset); //open all, up to the limit set in options
  3111. offset+=100;
  3112. }
  3113. }
  3114. }
  3115.  
  3116. } catch (e){log("WM.collector.close: "+e);}},
  3117.  
  3118. closeAll : function() {try{
  3119. //first delete the queue so close fx doesnt pick them up
  3120. WM.collector.queue=[]; //empty but dont destroy
  3121.  
  3122. //then close the active windows, moving any to the recycler if that is enabled
  3123. for (var t in WM.collector.tabs) {
  3124. WM.collector.close(WM.collector.tabs[t]);
  3125. }
  3126.  
  3127. //then close any recycled windows
  3128. if (WM.collector.recycle.length) {
  3129. for (var r=0, hwnd; r < WM.collector.recycle.length; r++) {
  3130. if (hwnd=WM.collector.recycle[r]) {
  3131. hwnd.close();
  3132. }
  3133. }
  3134. WM.collector.recycle=[];
  3135. }
  3136. } catch (e){log("WM.collector.closeAll: "+e);}},
  3137. createTimer : function(tab) {try{
  3138. //create a timeout handler based on options and store the timer on the tab
  3139. tab.toIntv=setTimeout(function(){
  3140. if (tab) if (tab.stage!=4) doAction(function(){
  3141. //tab has been active too long, do timeout
  3142. log("WM.collector.timer: request timeout ("+tab.id+")",{level:3});
  3143. WM.setAsFailed(null, -14, tab.post);
  3144. WM.clearURL(tab);
  3145. })
  3146. },WM.opts.reqtimeout*1000);
  3147. } catch (e){
  3148. log("WM.collector.createTimer: "+e);}
  3149. },
  3150.  
  3151. cancelProcess : function(params) {try{
  3152. params=params||{};
  3153. var c = WM.collector;
  3154. for (t in c.tabs) {
  3155. if (c.tabs[t] && c.tabs[t][params.search] && c.tabs[t][params.search]==params.find){
  3156. //matching collector tab found
  3157. tab=c.tabs[t];
  3158. //close the window
  3159. c.close(tab);
  3160. }
  3161. }
  3162. } catch (e){log("WM.collector.cancelProcess: "+e);}},
  3163. refreshProcess : function(params) {try{
  3164. params=params||{};
  3165. var c = WM.collector;
  3166. for (t in c.tabs) {
  3167. if (c.tabs[t] && c.tabs[t][params.search] && c.tabs[t][params.search]==params.find){
  3168. //matching collector tab found
  3169. tab=c.tabs[t];
  3170. //restart the window at its initial url
  3171. if (tab.hwnd.location.href==tab.url) {
  3172. tab.hwnd.location.reload();
  3173. } else {
  3174. tab.hwnd.location.href=tab.url;
  3175. }
  3176. }
  3177. }
  3178. } catch (e){log("WM.collector.refreshProcess: "+e);}},
  3179.  
  3180. };
  3181.  
  3182. //***************************************************************************************************************************************
  3183. //***** Dynamic Grabberrabber Object
  3184. //***************************************************************************************************************************************
  3185. WM.grabber = {
  3186. tests:[],
  3187.  
  3188. methods:["msg","fromID","fromName","url","body","html","targetID","targetName","caption","title","desc","comments",
  3189. "commentorID","commentorName","likeName","likeID","link","either","img","canvas"],
  3190.  
  3191. init:function(params){try{
  3192. params=(params||{});
  3193. var testsIn = getOptJSON("dynamics_"+WM.currentUser.profile) || [];
  3194. var globalsIn = getOptJSON("dynamics_global") || {};
  3195. //import locals and intermix globals we have a placeholder for
  3196. if (isArrayAndNotEmpty(testsIn)) {
  3197. for (var t=0; t<testsIn.length; t++) {
  3198. if (testsIn[t].isGlobal) {
  3199. //make sure the global test still exists
  3200. var glob=globalsIn[testsIn[t].uniqueID]||null;
  3201. if (glob){
  3202. //merge global and local data
  3203. //this retains our expanded/enabled parts
  3204. var merge=mergeJSON(glob, testsIn[t]);
  3205. WM.grabber.newTest(merge);
  3206. //flag it so we don't import it again below
  3207. glob.alreadyUsed=true;
  3208. } else {
  3209. //global missing, can't import
  3210. log("WM.grabber.init: Global test missing, cannot merge");
  3211. }
  3212. } else {
  3213. //load from locals
  3214. WM.grabber.newTest(testsIn[t]);
  3215. }
  3216. }
  3217. }
  3218. //import all globals not already accounted for
  3219. for (var t in globalsIn) {
  3220. var glob=globalsIn[t];
  3221. //avoid already imported globals
  3222. if (!glob.alreadyUsed){
  3223. glob.uniqueID=t;
  3224. glob.isGlobal=true;
  3225. WM.grabber.newTest(glob);
  3226. }
  3227. }
  3228. }catch(e){log("WM.grabber.init: "+e);}},
  3229.  
  3230. save:function(){try{
  3231. var ret=[];
  3232. var retGlobal={};
  3233. if (isArrayAndNotEmpty(WM.grabber.tests)) {
  3234. for (var t=0, len=WM.grabber.tests.length; t<len; t++){
  3235. var test=WM.grabber.tests[t];
  3236. if (!test.isGlobal) {
  3237. //save it locally
  3238. ret.push(test.saveableData);
  3239. } else {
  3240. //make a placeholder locally
  3241. ret.push({isGlobal:true, uniqueID:test.uniqueID, enabled:test.enabled, expanded:test.expanded});
  3242. //and save it globally
  3243. var glob=test.saveableData;
  3244. glob.uniqueID=test.uniqueID;
  3245. retGlobal[test.uniqueID]=glob;
  3246. }
  3247. }
  3248. }
  3249. setOptJSON("dynamics_"+WM.currentUser.profile,ret);
  3250. setOptJSON("dynamics_global",retGlobal);
  3251. }catch(e){log("WM.grabber.save: "+e);}},
  3252.  
  3253. newTest:function(params){try{
  3254. params=params||{};
  3255. var test = new WM.Test(params);
  3256. WM.grabber.tests.push(test);
  3257. WM.grabber.save();
  3258. }catch(e){log("WM.grabber.newTest: "+e);}},
  3259.  
  3260. importTest:function(){try{
  3261. var params=prompt("Input test data",null);
  3262. if (params) {
  3263. WM.grabber.newTest(JSON.parse(params));
  3264. }
  3265. }catch(e){log("WM.grabber.importTest: "+e);}},
  3266.  
  3267. //get the test object with id starting at optional node or at top level
  3268. //may return null
  3269. getTest:function(id,node){try{
  3270. var nodes=(node||WM.grabber.tests);
  3271. for (var i=0,len=nodes.length;i<len;i++){
  3272. if (nodes[i]["id"]==id) {
  3273. return nodes[i];
  3274. } else if (nodes[i]["kids"]) {
  3275. var ret = WM.grabber.getTest(id,nodes[i]["kids"]);
  3276. if (ret) return ret;
  3277. }
  3278. }
  3279. }catch(e){log("WM.grabber.getTest: "+e);}},
  3280. };
  3281. //***************************************************************************************************************************************
  3282. //***** Test Class
  3283. //***************************************************************************************************************************************
  3284. WM.Test = function(params){try{
  3285. this.objType="test";
  3286. var self=this;
  3287. params=params||{};
  3288. //defaults
  3289. this.enabled=!(params.disabled||false); //check for WM2 disabled param
  3290. this.expanded=true;
  3291. this.title="";
  3292. this.search=[]; //strings array
  3293. this.find=""; //string
  3294. this.findArray=[]; //string array
  3295. this.kids=[]; //test array
  3296. this.subTests=[]; //strings array
  3297. this.parent=null;
  3298. this.appID="";
  3299. this.ret="dynamic";
  3300. this._findMode="basic";
  3301. this.subNumRange={low:0,high:0};
  3302. this._isGlobal=false;
  3303. this.__defineGetter__("saveableData",function(){try{
  3304. var dat={};
  3305. //dat.id=this.id;
  3306. dat.label=this.title;
  3307. dat.enabled=this.enabled;
  3308. dat.search=this.search;
  3309. dat.find=(this.findMode=="basic")?this.findArray:this.find;
  3310. dat.ret=this.ret;
  3311. dat.expanded=this.expanded;
  3312. if (this.findMode=="subtests") dat.subTests=this.subTests;
  3313. if (this.findMode=="subnumrange") {
  3314. dat.subNumRange=this.subNumRange.low+","+this.subNumRange.high;
  3315. }
  3316. if (this.findMode=="regex") dat.regex=this.regex;
  3317. dat.appID=this.appID;
  3318. dat.kids=[];
  3319. if (isArrayAndNotEmpty(this.kids)) for (var i=0,kid;(kid=this.kids[i]);i++) {
  3320. dat.kids.push(kid.saveableData);
  3321. }
  3322. return dat;
  3323. }catch(e){log("WM.Test.saveableData: "+e);}});
  3324. //set/get wether this test is saved as global or profile
  3325. this.__defineGetter__("isGlobal",function(){try{
  3326. return this._isGlobal;
  3327. }catch(e){log("WM.Test.isGlobal: "+e);}});
  3328. this.__defineSetter__("isGlobal",function(v){try{
  3329. //only top level tests can be global
  3330. if (this.parent) {
  3331. confirm("Only top level tests can be set to global.");
  3332. return;
  3333. }
  3334. if (!v) {
  3335. if (!confirm("Disabling profile sharing on this test will prevent other users on this machine from loading it. Are you sure you wish to make this test locally available only?")) return;
  3336. }
  3337. this._isGlobal=v;
  3338. //make sure we have a uniqueID
  3339. //but don't destroy one that already exists
  3340. if (v && !exists(this.uniqueID)) this.uniqueID = unique();
  3341. //change the color/icon of the isGlobal button
  3342. if (this.toggleGlobalButton) {
  3343. var s=WM.opts.littleButtonSize;
  3344. with (this.toggleGlobalButton) className=className.swapWordB(v,"removeGlobal"+s,"addGlobal"+s);
  3345. with (this.toggleGlobalButton.parentNode) {
  3346. className=className.swapWordB(v,"oddOrange","oddGreen");
  3347. title=(v)?"Disable Profile Sharing":"Share With Other Profiles";
  3348. }
  3349. }
  3350. }catch(e){log("WM.Test.isGlobal: "+e);}});
  3351. //use passed params
  3352. for (var p in params) {
  3353. //omit specific params
  3354. if (!(["subNumRange","kids","disabled","label","find"].inArray(p)) ) {
  3355. //copy only params that make it past the checker
  3356. this[p]=params[p];
  3357. }
  3358. }
  3359. //calculate subNumRange as an object
  3360. if (exists(params.subNumRange)) {
  3361. var p=params.subNumRange.split(",");
  3362. this.subNumRange={low:p[0]||0, high:p[1]||0};
  3363. this._findMode="subnumrange";
  3364. }
  3365. //get the title from the label field
  3366. if (exists(params.label)) this.title=params.label;
  3367. //detect which findMode we are using
  3368. //subNumRange was already inspected above
  3369. if (this.regex) this._findMode="regex";
  3370. else if (exists(params.subTests)) this._findMode="subtests";
  3371. //and we default to "basic" already
  3372. //import the find field now
  3373. if (isArray(params.find)) this.findArray=params.find;
  3374. else this.find=params.find;
  3375. this.enable=function(){try{
  3376. this.enabled=true;
  3377. this.node.className=this.node.className.removeWord("disabled");
  3378. WM.grabber.save();
  3379. }catch(e){log("WM.Test.enable: "+e);}};
  3380.  
  3381. this.disable=function(){try{
  3382. this.enabled=false;
  3383. this.node.className=this.node.className.addWord("disabled");
  3384. WM.grabber.save();
  3385. }catch(e){log("WM.Test.disable: "+e);}};
  3386.  
  3387. this.remove=function(noConfirm){try{
  3388. var ask=WM.opts.dynamicConfirmDeleteTest;
  3389. if (noConfirm || (this.isGlobal && confirm("This test is shared with other profiles. Deleting it here will prevent it from loading for other users. Are you sure you wish to delete this test and its children.")) || !ask || (!this.isGlobal && ask && confirm("Delete test and all of its child nodes?"))){
  3390. //remove my data
  3391. var parentContainer=(this.parent)?this.parent.kids:WM.grabber.tests;
  3392. parentContainer.removeByValue(this);
  3393. //remove my node
  3394. remove(this.node);
  3395. doAction(WM.grabber.save);
  3396. }
  3397. }catch(e){log("WM.Test.remove: "+e);}};
  3398.  
  3399. this.moveUp=function(){try{
  3400. //where is this
  3401. var parentContainer=(this.parent)?this.parent.kids:WM.grabber.tests;
  3402. //only affects items not already the first in the list
  3403. //and not the only child in the list
  3404. if ((parentContainer.length>1) && (parentContainer[0]!=this)) {
  3405. //which index is this?
  3406. var myIndex=parentContainer.inArrayWhere(this);
  3407. if (myIndex != -1) {
  3408. //I have a proper index here
  3409. //who is my sibling
  3410. var sibling = parentContainer[myIndex-1];
  3411. //swap me with my sibling
  3412. parentContainer[myIndex-1]=this;
  3413. parentContainer[myIndex]=sibling;
  3414. //place my node before my sibling node
  3415. sibling.node.parentNode.insertBefore(this.node,sibling.node);
  3416. //save it
  3417. WM.grabber.save();
  3418. }
  3419. }
  3420. }catch(e){log("WM.Test.moveUp: "+e);}};
  3421. this.moveDown=function(){try{
  3422. //where is this
  3423. var parentContainer=(this.parent)?this.parent.kids:WM.grabber.tests;
  3424. //only affects items not already the last in the list
  3425. //and not the only child in the list
  3426. if ((parentContainer.length>1) && (parentContainer.last()!=this)) {
  3427. //which index is this?
  3428. var myIndex=parentContainer.inArrayWhere(this);
  3429. if (myIndex != -1) {
  3430. //I have a proper index here
  3431. //who is my sibling
  3432. var sibling = parentContainer[myIndex+1];
  3433. //swap me with my sibling
  3434. parentContainer[myIndex+1]=this;
  3435. parentContainer[myIndex]=sibling;
  3436. //place my node before my sibling node
  3437. sibling.node.parentNode.insertBefore(sibling.node,this.node);
  3438. //save it
  3439. WM.grabber.save();
  3440. }
  3441. }
  3442. }catch(e){log("WM.Test.moveDown: "+e);}};
  3443.  
  3444. this.moveUpLevel=function(){try{
  3445. if (this.parent) {
  3446. //this is not a top level node, so we can move it
  3447. var targetContainer=((this.parent.parent)?this.parent.parent.kids:WM.grabber.tests);
  3448. //remove from parent
  3449. this.parent.kids.removeByValue(this);
  3450. //set new parent
  3451. this.parent=(this.parent.parent||null); //never point to the top level
  3452. //move the object
  3453. targetContainer.push(this);
  3454. //move the node
  3455. if (this.parent) this.parent.kidsNode.appendChild(this.node);
  3456. else WM.console.dynamicBuild.appendChild(this.node);
  3457. //save it
  3458. WM.grabber.save();
  3459. }
  3460. }catch(e){log("WM.Test.moveUpLevel: "+e);}};
  3461. this.moveDownLevel=function(){try{
  3462. //where is this
  3463. var parentContainer=(this.parent)?this.parent.kids:WM.grabber.tests;
  3464. //create a new rule at my level
  3465. var newTest = new WM.Test({
  3466. parent:this.parent||null,
  3467. });
  3468. parentContainer.push(newTest);
  3469. //remove me from my current parent
  3470. parentContainer.removeByValue(this);
  3471. //attach me to my new parent
  3472. this.parent=newTest;
  3473. newTest.kids.push(this);
  3474. //move my node
  3475. newTest.kidsNode.appendChild(this.node);
  3476. //save it
  3477. WM.grabber.save();
  3478. }catch(e){log("WM.Test.moveDownLevel: "+e);}};
  3479. this.clone=function(){try{
  3480. var cloneTest=this.saveableData;
  3481. //global clones are not global
  3482. if (this.parent) this.parent.addChild(cloneTest);
  3483. else WM.grabber.newTest(cloneTest);
  3484. }catch(e){log("WM.Test.clone: "+e);}};
  3485.  
  3486. this.addChild=function(p){try{
  3487. var isNew=!exists(p);
  3488. p=p||{};
  3489. p.parent=this;
  3490. var test=new WM.Test(p);
  3491. this.kids.push(test);
  3492. if (isNew) WM.grabber.save();
  3493. }catch(e){log("WM.Test.addChild: "+e);}};
  3494.  
  3495. this.toggleContent=function(){try{
  3496. this.expanded=!this.expanded;
  3497. var btnSize=WM.opts.littleButtonSize;
  3498. with (this.contentNode)
  3499. className=className.swapWordB(this.expanded,"expanded","collapsed");
  3500. with (this.toggleImgNode)
  3501. className=className.swapWordB(this.expanded,"treeCollapse"+btnSize,"treeExpand"+btnSize);
  3502. WM.grabber.save();
  3503. }catch(e){log("WM.Test.toggleContent: "+e);}};
  3504. this.populateBonusList=function(){try{
  3505. var node=this.bonusNode;
  3506. var bonuses={};
  3507. //get the list of accept texts for this app
  3508. if (this.appID!="") {
  3509. if (this.appID=="*") {
  3510. //populate list with bonuses from ALL docked sidekicks
  3511. } else {
  3512. //make sure the app is ready
  3513. //if it has not yet docked, it wont be
  3514. var app=WM.apps[this.appID];
  3515. bonuses = (app?(mergeJSON(app.accText,app.userDefinedTypes)||{}):{});
  3516. }
  3517. }
  3518. //add special return values
  3519. bonuses["dynamic"]="* Dynamic grab";
  3520. bonuses["none"]="* None";
  3521. bonuses["wishlist"]="* Flaged as Wishlist";
  3522. bonuses["exclude"]="* Excluded types";
  3523. bonuses["send"]="* Send Unknown";
  3524. bonuses["doUnknown"]="* Get Unknown";
  3525. bonuses["{%1}"]="* Subtest Value";
  3526.  
  3527. //sort by display text
  3528. bonuses=sortCollection(bonuses,"value");
  3529. //add each element to the dropdown
  3530. var elem;
  3531. node.innerHTML=""; //wipe previous list
  3532. for (var i in bonuses) {
  3533. var showI=i.removePrefix(this.appID);
  3534. node.appendChild(
  3535. elem=createElement("option",{textContent:((bonuses[i].startsWith("*"))?"":((showI.startsWith("send"))?"Send ":"Get "))+bonuses[i], value:i, selected:(this.ret==i)})
  3536. );
  3537. }
  3538.  
  3539. }catch(e){log("WM.Test.populateBonusList: "+e);}};
  3540. this.populateAppList=function(){try{
  3541. var node=this.appListNode;
  3542. var a={};
  3543. for (var i in WM.apps){
  3544. a[WM.apps[i].appID]=WM.apps[i].name;
  3545. }
  3546.  
  3547. //add special return values
  3548. a["*"]="* All";
  3549.  
  3550. //add each element to the dropdown
  3551. var elem;
  3552. node.innerHTML=""; //wipe previous list
  3553. for (var i in a) {
  3554. node.appendChild(elem=createElement("option",{textContent:a[i], value:i,selected:(this.appID==i)}));
  3555. }
  3556.  
  3557. //sort it
  3558. elementSortChildren(node,"textContent");
  3559. }catch(e){log("WM.Test.populateAppList: "+e);}};
  3560.  
  3561. this.calcSearch=function(){try{
  3562. //collect the checked search fields in their listed order
  3563. if (self.searchNode) {
  3564. self.search=[];
  3565. forNodes(".//input[(@type='checkbox')]",{node:self.searchNode},function(e){
  3566. if (e && e.checked){
  3567. self.search.push(e.value);
  3568. log(e.value);
  3569. }
  3570. });
  3571. }
  3572. WM.grabber.save();
  3573. }catch(e){log("WM.Test.calcSearch: "+e);}};
  3574. this.convertToRule=function(p){try{
  3575. var rule;
  3576. WM.rulesManager.rules.push(
  3577. rule=new WM.rulesManager.Rule( WM.rulesManager.ruleFromTest( this.saveableData ) )
  3578. );
  3579. if (WM.opts.rulesJumpToNewRule){
  3580. //jump to rule view
  3581. WM.console.tabContainer.selectTab(3);
  3582. //scroll to new rule
  3583. rule.node.scrollIntoView();
  3584. }
  3585. }catch(e){log("WM.Test.convertToRule: "+e);}};
  3586. //set/get find field modes
  3587. this.__defineGetter__("findMode",function(){try{
  3588. return this._findMode;
  3589. }catch(e){log("WM.Test.findMode: "+e);}});
  3590. this.__defineSetter__("findMode",function(v){try{
  3591. var lastV = this._findMode;
  3592. this._findMode=v;
  3593. if (lastV==v) return; //no change
  3594. //enable disable regex type
  3595. this.regex=(v=="regex" || v=="regexp");
  3596. //switch to array/string find field type
  3597. //this.setFindType((v=="basic")?"array":"string");
  3598. //show the correct find field
  3599. if (this.findNode) this.findNode.value=((v=="basic")?this.findArray.join("\n"):this.find);
  3600.  
  3601. //show/hide the subtests box
  3602. if (this.subTestsBoxNode) with (this.subTestsBoxNode) className=className.toggleWordB((v!="subtests"),"hidden");
  3603. //show/hide the subnumrange picker
  3604. if (this.subNumRangeBoxNode) with (this.subNumRangeBoxNode) className=className.toggleWordB((v!="subnumrange"),"hidden");
  3605. WM.grabber.save();
  3606.  
  3607. }catch(e){log("WM.Test.findMode: "+e);}});
  3608.  
  3609. //draw it
  3610. try{(((this.parent)?this.parent.kidsNode:null)||$("wmDynamicBuilder")).appendChild(
  3611. this.node=createElement("div",{className:"listItem "+((this.enabled)?"enabled":"disabled")},[
  3612. createElement("div",{className:"line"},[
  3613. createElement("div",{className:"littleButton",title:"Toggle Content",onclick:function(){self.toggleContent();}},[
  3614. this.toggleImgNode=createElement("img",{className:"resourceIcon "+(this.expanded?"treeCollapse"+WM.opts.littleButtonSize:"treeExpand"+WM.opts.littleButtonSize)}),
  3615. ]),
  3616. this.toggleNode=createElement("input",{type:"checkbox",checked:this.enabled,onchange:function(){
  3617. self.enabled=this.checked;
  3618. with (self.node) className=className.toggleWordB(!this.checked,"disabled");
  3619. WM.grabber.save();
  3620. }}),
  3621. createElement("label",{textContent:"Title:"}),
  3622. this.titleNode=createElement("input",{value:(this.title||""), onchange:function(){self.title=this.value; WM.grabber.save();}}),
  3623. //toolbox
  3624. createElement("div",{className:"littleButton oddOrange", title:"Remove Test"},[
  3625. createElement("img",{className:"resourceIcon trash"+WM.opts.littleButtonSize,onclick:function(){self.remove();}})]),
  3626. createElement("div",{className:"littleButton oddBlue", title:"Clone Test"},[
  3627. createElement("img",{className:"resourceIcon clone"+WM.opts.littleButtonSize,onclick:function(){self.clone();}})]),
  3628. createElement("div",{className:"littleButton oddGreen", title:"Move Up"},[
  3629. createElement("img",{className:"resourceIcon arrowUp"+WM.opts.littleButtonSize,onclick:function(){self.moveUp();}})]),
  3630. createElement("div",{className:"littleButton oddOrange", title:"Move Down"},[
  3631. createElement("img",{className:"resourceIcon arrowDown"+WM.opts.littleButtonSize,onclick:function(){self.moveDown();}})]),
  3632. createElement("div",{className:"littleButton oddGreen", title:"Move Up Level"},[
  3633. createElement("img",{className:"resourceIcon moveUpLevelLeft"+WM.opts.littleButtonSize,onclick:function(){self.moveUpLevel();}})]),
  3634. createElement("div",{className:"littleButton oddOrange", title:"Move Down Level"},[
  3635. createElement("img",{className:"resourceIcon moveInLevel"+WM.opts.littleButtonSize,onclick:function(){self.moveDownLevel();}})]),
  3636. createElement("div",{className:"littleButton oddBlue", title:"Show Source"},[
  3637. createElement("img",{className:"resourceIcon object"+WM.opts.littleButtonSize,onclick:function(){promptText(JSON.stringify(self.saveableData),true);}})]),
  3638.  
  3639. createElement("div",{className:"indent littleButton oddBlue", title:"Convert To Rule"},[
  3640. createElement("img",{className:"resourceIcon exportGrab"+WM.opts.littleButtonSize,onclick:function(){self.convertToRule();}})]),
  3641.  
  3642. createElement("div",{className:"indent littleButton "+((this.isGlobal)?"oddOrange":"oddGreen"), title:((this.isGlobal)?"Disable Profile Sharing":"Share With Other Profiles")},[
  3643. this.toggleGlobalButton=createElement("img",{className:"resourceIcon "+((this.isGlobal)?"removeGlobal":"addGlobal")+WM.opts.littleButtonSize,onclick:function(){self.isGlobal=!self.isGlobal; WM.grabber.save();}})]),
  3644. ]),
  3645. this.contentNode=createElement("div",{className:"subsection "+(this.expanded?"expanded":"collapsed")},[
  3646. //appID
  3647. createElement("div",{className:"line"},[
  3648. createElement("label",{textContent:"appID:"}),
  3649. this.appIDNode=createElement("input",{value:(this.appID||""), onchange:function(){self.appID=this.value;WM.grabber.save();self.populateBonusList();}}),
  3650. this.appListNode=createElement("select",{onchange:function(){self.appIDNode.value=this.value; self.appID=this.value; WM.grabber.save(); self.populateBonusList();}}),
  3651. createElement("div",{className:"littleButton oddBlue", title:"Refresh App List"},[
  3652. createElement("img",{className:"resourceIcon refresh"+WM.opts.littleButtonSize,onclick:function(){self.populateAppList();}})]),
  3653. ]),
  3654. //return type
  3655. createElement("div",{className:"line"},[
  3656. createElement("label",{textContent:"Return Type ('which'):"}),
  3657. this.retNode=createElement("input",{value:(this.ret||"dynamic"), onchange:function(){self.ret=this.value;WM.grabber.save();}}),
  3658. this.bonusNode=createElement("select",{onchange:function(){self.retNode.value=this.value; self.ret=this.value; WM.grabber.save();}}),
  3659. createElement("div",{className:"littleButton oddBlue", title:"Refresh Bonus List"},[
  3660. createElement("img",{className:"resourceIcon refresh"+WM.opts.littleButtonSize,onclick:function(){self.populateBonusList();}})]),
  3661. ]),
  3662. //search list
  3663. createElement("div",{className:"line"},[
  3664. createElement("label",{textContent:"Search In Field(s):",title:"Specify fields in which to look for data. Adjust order as needed."}),
  3665. this.searchNode=createElement("div",{className:"subsection optioncontainer"},(function(){
  3666. var ret=[];
  3667. //draw first the methods we have already selected
  3668. if (isArrayAndNotEmpty(self.search)) for (var m=0; m<self.search.length; m++) {
  3669. var s = self.search[m];
  3670. ret.push(createElement("div",{className:"line"},[
  3671. createElement("div",{className:"littleButton oddGreen", title:"Move Up"},[
  3672. createElement("img",{className:"resourceIcon nomargin arrowUp16",onclick:function(){elementMoveUp(this.parentNode.parentNode); self.calcSearch();}})
  3673. ]),
  3674. createElement("div",{className:"littleButton oddOrange", title:"Move Down"},[
  3675. createElement("img",{className:"resourceIcon nomargin arrowDown16",onclick:function(){elementMoveDown(this.parentNode.parentNode); self.calcSearch();}})
  3676. ]),
  3677. createElement("div",{className:"littleButton oddGreen", title:"Move To Top"},[
  3678. createElement("img",{className:"resourceIcon nomargin moveTopLeft16",onclick:function(){elementMoveTop(this.parentNode.parentNode); self.calcSearch();}})
  3679. ]),
  3680. createElement("div",{className:"littleButton oddOrange", title:"Move To Bottom"},[
  3681. createElement("img",{className:"resourceIcon nomargin moveBottomLeft16",onclick:function(){elementMoveBottom(this.parentNode.parentNode); self.calcSearch();}})
  3682. ]),
  3683. createElement("input",{type:"checkbox",value:s,checked:true,onchange:function(){self.calcSearch();}}),
  3684. createElement("label",{textContent:s,title:WM.rulesManager.postParts[s]}),
  3685. ]));
  3686. }
  3687. //draw the remaining items in their normal order
  3688. for (var m=0; m<WM.grabber.methods.length; m++){
  3689. var s = WM.grabber.methods[m];
  3690. //prevent duplicates
  3691. if (self.search.inArray(s)) continue;
  3692. ret.push(createElement("div",{className:"line"},[
  3693. createElement("div",{className:"littleButton oddGreen", title:"Move Up"},[
  3694. createElement("img",{className:"resourceIcon nomargin arrowUp16",onclick:function(){elementMoveUp(this.parentNode.parentNode); self.calcSearch();}})
  3695. ]),
  3696. createElement("div",{className:"littleButton oddOrange", title:"Move Down"},[
  3697. createElement("img",{className:"resourceIcon nomargin arrowDown16",onclick:function(){elementMoveDown(this.parentNode.parentNode); self.calcSearch();}})
  3698. ]),
  3699. createElement("div",{className:"littleButton oddGreen", title:"Move To Top"},[
  3700. createElement("img",{className:"resourceIcon nomargin moveTopLeft16",onclick:function(){elementMoveTop(this.parentNode.parentNode); self.calcSearch();}})
  3701. ]),
  3702. createElement("div",{className:"littleButton oddOrange", title:"Move To Bottom"},[
  3703. createElement("img",{className:"resourceIcon nomargin moveBottomLeft16",onclick:function(){elementMoveBottom(this.parentNode.parentNode); self.calcSearch();}})
  3704. ]),
  3705. createElement("input",{type:"checkbox",value:s,onchange:function(){self.calcSearch();}}),
  3706. createElement("label",{textContent:s,title:WM.rulesManager.postParts[s]}),
  3707. ]));
  3708. }
  3709. return ret;
  3710. })()),
  3711. ]),
  3712. //find mode
  3713. createElement("div",{className:"line"},[
  3714. createElement("label",{textContent:"Find Mode:",title:"Choose the mode you will use to find text."}),
  3715. this.findModeNode=createElement("select",{onchange:function(){self.findMode=this.value;}},[
  3716. createElement("option",{selected:(this.findMode=="basic"),value:"basic",textContent:"Basic",title:"Search for a list of words or phrases."}),
  3717. createElement("option",{selected:(this.findMode=="subnumrange"),value:"subnumrange",textContent:"Number Range",title:"Search for a range of numbers using an insertion point '{%1}' in your find parameter."}),
  3718. createElement("option",{selected:(this.findMode=="subtests"),value:"subtests",textContent:"Sub Tests",title:"Search for a list of words or phrases using an insertion point '{%1}' in your find parameter."}),
  3719. createElement("option",{selected:(this.findMode=="regex"),value:"regex",textContent:"Registered Expression",title:"Search for complex phrases using a regular expression."})
  3720. ]),
  3721. ]),
  3722. //find list
  3723. createElement("div",{className:"line"},[
  3724. createElement("label",{textContent:"Find:",title:"One per line (basic mode), or a single regular expression. First match is used, so mind the order."}),
  3725. createElement("div",{className:"subsection"},[
  3726. this.findNode=createElement("textarea",{className:"fit",textContent:((this.findMode=="basic")?this.findArray.join("\n"):this.find), onchange:function(){
  3727. if (self.findMode=="basic") self.findArray=this.value.split("\n");
  3728. else self.find=this.value;
  3729. WM.grabber.save();
  3730. }}),
  3731. ])
  3732. ]),
  3733. //subtests list
  3734. this.subTestsBoxNode=createElement("div",{className:("line").toggleWordB(this.findMode!="subtests","hidden")},[
  3735. createElement("label",{textContent:"Subtest Texts:",title:"Provide text replacements for the insertion point. No regular expressions."}),
  3736. createElement("div",{className:"subsection"},[
  3737. this.subTestsNode=createElement("textarea",{className:"fit",textContent:((isArray(this.subTests)?this.subTests.join("\n"):"")||""), onchange:function(){self.subTests=this.value.split("\n"); WM.grabber.save();}}),
  3738. ])
  3739. ]),
  3740. //subnumrange picker
  3741. this.subNumRangeBoxNode=createElement("div",{className:("line").toggleWordB(this.findMode!="subnumrange","hidden")},[
  3742. createElement("label",{textContent:"Subtest Number Range:",title:"Provide a start and end range for the insertion point."}),
  3743. this.subNumRangeLowNode=createElement("input",{value:this.subNumRange.low||0, onchange:function(){self.subNumRange.low=this.value; WM.grabber.save();}}),
  3744. this.subNumRangeHighNode=createElement("input",{value:this.subNumRange.high||0, onchange:function(){self.subNumRange.high=this.value; WM.grabber.save();}}),
  3745. ]),
  3746. //kids subbox
  3747. createElement("div",{className:"line"},[
  3748. createElement("label",{textContent:"Child Tests:",title:"Child tests are nested tests which are applied to matching posts at the same time the parent test is applied. Child rules can have different return values that override the parent return value."}),
  3749. createElement("div",{className:"littleButton oddGreen",onclick:function(){self.addChild();},title:"Add Child"},[
  3750. createElement("img",{className:"resourceIcon plus"+WM.opts.littleButtonSize}),
  3751. ]),
  3752. this.kidsNode=createElement("div",{className:"subsection"}),
  3753. ]),
  3754. ]),
  3755. ])
  3756. );}catch(e){log("WM.Test.init.drawTest: "+e);}
  3757. //populate my bonus list
  3758. this.populateAppList();
  3759. this.populateBonusList();
  3760.  
  3761. //list the kids for this test
  3762. if (isArrayAndNotEmpty(params.kids)) for (var i=0,kid; (kid=params.kids[i]); i++) {
  3763. this.addChild(kid);
  3764. }
  3765. return self;
  3766. }catch(e){log("WM.Test.init: ")+e}};
  3767.  
  3768. //***************************************************************************************************************************************
  3769. //***** Feed Objects
  3770. //***************************************************************************************************************************************
  3771. WM.feedManager = {
  3772. feeds : [],
  3773. init : function(){
  3774. var feedsIn=(getOptJSON("feeds3_"+WM.currentUser.profile)||[]);
  3775. var feedsInGlobal=(getOptJSON("feeds3_global")||[]);
  3776. if (isArrayAndNotEmpty(feedsIn)) {
  3777. //import feeds from storage
  3778. for (var f=0;f<feedsIn.length;f++){
  3779. feed=feedsIn[f];
  3780. if (!feed.isGlobal){
  3781. WM.feedManager.feeds.push(new WM.Feed(feed));
  3782. } else {
  3783. var glob=feedsInGlobal[feed.uniqueID]||null;
  3784. if (glob){
  3785. var merge=mergeJSON(glob,feed);
  3786. WM.feedManager.newFeed(merge);
  3787. glob.alreadyUsed=true;
  3788. } else {
  3789. log("WM.feedManager.init: Global feed missing, cannot merge");
  3790. }
  3791. }
  3792. }
  3793. } else {
  3794. //never been used before, create base feeds
  3795. WM.feedManager.feeds.push(new WM.Feed({title:"My Home Feed", url:"https://graph.facebook.com/me/home", isRemoveable:false}));
  3796. //WM.feedManager.feeds.push(new WM.Feed({title:"My Profile Wall", url:"https://graph.facebook.com/me/feed", isRemoveable:false}));
  3797. //import oldstyle feeds
  3798. var feedsOld=getOpt("feeds_"+WM.currentUser.profile);
  3799. if (feedsOld){
  3800. feedsOld=feedsOld.split("\n");
  3801. if (isArrayAndNotEmpty(feedsOld)) for (var f=0;f<feedsOld.length;f++) {
  3802. //prevent empties
  3803. if (feedsOld[f]) {
  3804. //create the new feed
  3805. WM.feedManager.newFeed({id:feedsOld[f],title:feedsOld[f]});
  3806. }
  3807. }
  3808. }
  3809. WM.feedManager.save();
  3810. }
  3811. //import all global feeds not already accounted for
  3812. for (var t in feedsInGlobal) {
  3813. var glob=feedsInGlobal[t];
  3814. if (!glob.alreadyUsed){
  3815. glob.uniqueID=t;
  3816. glob.isGlobal=true;
  3817. WM.feedManager.newFeed(glob); //newFeed adds app filters, where New Feed() does not
  3818. }
  3819. }
  3820. },
  3821. newFeed : function(params){
  3822. params=params||{};
  3823. var feed = new WM.Feed(params);
  3824. WM.feedManager.feeds.push(feed);
  3825. //add filters for each app available
  3826. for (var a in WM.apps){
  3827. feed.addFilter({id:"app_"+a});
  3828. }
  3829. },
  3830. save :function(){
  3831. var retFeeds=[];
  3832. var retGlobal={};
  3833. if (isArrayAndNotEmpty(WM.feedManager.feeds)) for (var f=0,len=WM.feedManager.feeds.length; f<len; f++){
  3834. var feed=WM.feedManager.feeds[f];
  3835. if (!feed.isGlobal) {
  3836. retFeeds.push(feed.saveableData);
  3837. } else {
  3838. retFeeds.push({isGlobal:true, uniqueID:feed.uniqueID, enabled:feed.enabled, expanded:feed.expanded});
  3839. var glob=feed.saveableData;
  3840. glob.uniqueID=feed.uniqueID;
  3841. retGlobal[feed.uniqueID]=glob;
  3842. }
  3843. }
  3844. setOptJSON("feeds3_"+WM.currentUser.profile,retFeeds);
  3845. setOptJSON("feeds3_global",retGlobal);
  3846. },
  3847. };
  3848.  
  3849. //***************************************************************************************************************************************
  3850. //***** FeedFilter Class
  3851. //***************************************************************************************************************************************
  3852. WM.FeedFilter = function(params){try{
  3853. this.objType="feedFilter";
  3854. params=params||{};
  3855. var self=this;
  3856. //set defaults
  3857. this.enabled=true;
  3858. this.expanded=true;
  3859. this._olderLimitReached=false;
  3860. //initialize edges to the collector startup time
  3861. this.oldedge=Math.round(timeStamp()/1000); //older edge timestamp
  3862. this.newedge=Math.round(timeStamp()/1000); //newer edge timestamp
  3863. //use passed params
  3864. for (var p in params) this[p]=params[p];
  3865.  
  3866. this.enable=function(){try{
  3867. this.enabled=true;
  3868. this.node.className=this.node.className.removeWord("disabled");
  3869. WM.feedManager.save();
  3870. }catch(e){log("WM.FeedFilter.enable: "+e);}};
  3871.  
  3872. this.disable=function(){try{
  3873. this.enabled=false;
  3874. this.node.className=this.node.className.addWord("disabled");
  3875. WM.feedManager.save();
  3876. }catch(e){log("WM.FeedFilter.disable: "+e);}};
  3877.  
  3878. this.toggle=function(){try{
  3879. this.enabled=this.toggleNode.checked;
  3880. this.node.className=this.node.className.swapWordB(this.enabled,"enabled","disabled");
  3881. WM.feedManager.save();
  3882. }catch(e){log("WM.FeedFilter.toggle: "+e);}};
  3883.  
  3884. this.toggleContent=function(){try{
  3885. this.expanded=!this.expanded;
  3886. var btnSize=WM.opts.littleButtonSize;
  3887. with (this.contentNode)
  3888. className=className.swapWordB(this.expanded,"expanded","collapsed");
  3889. with (this.toggleImgNode)
  3890. className=className.swapWordB(this.expanded,"treeCollapse"+btnSize,"treeExpand"+btnSize);
  3891. WM.feedManager.save();
  3892. }catch(e){log("WM.FeedFilter.toggleContent: "+e);}};
  3893. //remove this
  3894. this.remove=function(){try{
  3895. if (this.node) remove(this.node);
  3896. if (this.parent) delete this.parent.filters[this.id];
  3897. WM.feedManager.save();
  3898. }catch(e){log("WM.FeedFilter.remove: "+e);}};
  3899. //fetch posts for this
  3900. this.fetchNewer=function(){try{
  3901. WM.fetch({
  3902. newer:true,
  3903. apps:WM.apps[this.appID],
  3904. feeds:[this.parent],
  3905. bypassPause:true,
  3906. bypassAppDisabled:true,
  3907. bypassFeedDisabled:true,
  3908. bypassFilterDisabled:true,
  3909. });
  3910. }catch(e){log("WM.FeedFilter.fetchNewer: "+e);}};
  3911.  
  3912. this.fetchOlder=function(){try{
  3913. WM.fetch({
  3914. older:true,
  3915. apps:WM.apps[this.appID],
  3916. feeds:[this.parent],
  3917. bypassPause:true,
  3918. bypassAppDisabled:true,
  3919. bypassFeedDisabled:true,
  3920. bypassFilterDisabled:true,
  3921. });
  3922. }catch(e){log("WM.FeedFilter.fetchOlder: "+e);}};
  3923. this.__defineGetter__("olderLimitReached",function(){try{
  3924. return this._olderLimitReached;
  3925. }catch(e){log("WM.FeedFilter.olderLimitReached: "+e);}});
  3926. this.__defineSetter__("olderLimitReached",function(v){try{
  3927. this._olderLimitReached=v;
  3928. //update the sidekick page button graphics
  3929. var node=this.olderLimitNode;
  3930. if (node) node.textContent=v;
  3931. if (v) {
  3932. WM.rulesManager.doEvent("onFeedFilterOlderLimitReached",this);
  3933. }
  3934. }catch(e){log("WM.FeedFilter.olderLimitReached: "+e);}});
  3935.  
  3936. this.__defineGetter__("appID",function(){try{
  3937. //this assumes its an app filter because so far thats all we use
  3938. return this.id.removePrefix("app_");
  3939. }catch(e){log("WM.FeedFilter.appID: "+e);}});
  3940.  
  3941. this.__defineGetter__("appName",function(){try{
  3942. //this assumes its an app filter because so far thats all we use
  3943. //it also assumes app objects are global, which they are
  3944. //but that they are also available and already filled in by the sidekick, which they may not be
  3945. var a = WM.apps[this.appID];
  3946. if (a!=undefined) {
  3947. //debug.print([this.appID,a]);
  3948. return a.name;
  3949. }
  3950. return "";
  3951. }catch(e){log("WM.FeedFilter.appName: "+e);}});
  3952. //draw it
  3953. try{
  3954. if (this.parent.filtersNode) this.parent.filtersNode.appendChild(
  3955. this.node=createElement("div",{className:"listItem "+((this.enabled)?"enabled":"disabled")},[
  3956. createElement("div",{className:"line"},[
  3957. createElement("div",{className:"littleButton",title:"Toggle Content",onclick:function(){self.toggleContent();}},[
  3958. this.toggleImgNode=createElement("img",{className:"resourceIcon "+(this.expanded?"treeCollapse"+WM.opts.littleButtonSize:"treeExpand"+WM.opts.littleButtonSize)}),
  3959. ]),
  3960. this.toggleNode=createElement("input",{type:"checkbox",checked:this.enabled,onchange:function(){
  3961. self.enabled=this.checked;
  3962. with (self.node) className=className.toggleWordB(!this.checked,"disabled");
  3963. WM.feedManager.save();
  3964. }}),
  3965. createElement("span",{textContent:this.appName + " (" + this.id + ")"}),
  3966. //toolbox
  3967. createElement("div",{className:"littleButton oddBlue", title:"Fetch Newer"},[
  3968. this.fetchNewerButton=createElement("img",{className:"resourceIcon rssUpRight"+WM.opts.littleButtonSize,onclick:function(){self.fetchNewer();} })
  3969. ]),
  3970. createElement("div",{className:"littleButton", title:"Fetch Older"},[
  3971. this.fetchOlderButton=createElement("img",{className:"resourceIcon rssDownLeft"+WM.opts.littleButtonSize,onclick:function(){self.fetchOlder();} })
  3972. ]),
  3973. ]),
  3974. this.contentNode=createElement("div",{className:"subsection "+(this.expanded?"expanded":"collapsed")},[
  3975. createElement("div",{className:"line"},[
  3976. createElement("label",{textContent:"Older Limit Reached: ",title:"Reports if this filter has reached the user defined oldest post limit."}),
  3977. this.olderLimitNode=createElement("span",{textContent:this.olderLimitReached}),
  3978. ]),
  3979. createElement("div",{className:"line"},[
  3980. createElement("label",{textContent:"Newer Edge: ",title:"A Unixtime indicator of the newest post-time you have fetched for this filter."}),
  3981. this.newedgeNode=createElement("span",{textContent:this.newedge}),
  3982. ]),
  3983. createElement("div",{className:"line"},[
  3984. createElement("label",{textContent:"Older Edge: ",title:"A Unixtime indicator of the oldest post-time you have fetched for this filter."}),
  3985. this.oldedgeNode=createElement("span",{textContent:this.oldedge}),
  3986. ]),
  3987. ]),
  3988. ])
  3989. );
  3990. }catch(e){log("WM.FeedFilter.init:addManagerElement: "+e);};
  3991. return self;
  3992. }catch(e){log("WM.FeedFilter.init: "+e);}};
  3993.  
  3994. //***************************************************************************************************************************************
  3995. //***** Feed Class
  3996. //***************************************************************************************************************************************
  3997. WM.Feed = function(params){try{
  3998. this.objType="feed";
  3999. params=params||{};
  4000. var self=this;
  4001.  
  4002. //set defaults
  4003. this.enabled=true;
  4004. this.expanded=true;
  4005. this.url="";
  4006. this.id="";
  4007. this.filters={};
  4008. this.feedName="";
  4009. this.isRemoveable=true; //set to false on own feeds
  4010. this.title="New Feed";
  4011. this._isGlobal=false;
  4012.  
  4013. //use passed params
  4014. var newFilters=params.filters||{};
  4015. delete params.filters;
  4016. this.__defineGetter__("isGlobal",function(){try{
  4017. return this._isGlobal;
  4018. }catch(e){log("WM.Feed.isGlobal: "+e);}});
  4019. this.__defineSetter__("isGlobal",function(v){try{
  4020. if (!v) {
  4021. if (!confirm("Disabling profile sharing on this feed will prevent other users on this machine from loading it. Are you sure you wish to make this feed locally available only?")) return;
  4022. }
  4023. this._isGlobal=v;
  4024. //make sure we have a uniqueID
  4025. //but don't destroy one that already exists
  4026. if (v && !exists(this.uniqueID)) this.uniqueID = unique();
  4027. //change the color/icon of the isGlobal button
  4028. if (this.toggleGlobalButton) {
  4029. var s=WM.opts.littleButtonSize;
  4030. with (this.toggleGlobalButton) className=className.swapWordB(v,"removeGlobal"+s,"addGlobal"+s);
  4031. with (this.toggleGlobalButton.parentNode) {
  4032. className=className.swapWordB(v,"oddOrange","oddGreen");
  4033. title=(v)?"Disable Profile Sharing":"Share With Other Profiles";
  4034. }
  4035. }
  4036. }catch(e){log("WM.Feed.isGlobal: "+e);}});
  4037. this.__defineGetter__("saveableData",function(){try{
  4038. var ret={};
  4039. ret.title=this.title;
  4040. ret.enabled=this.enabled;
  4041. ret.expanded=this.expanded;
  4042. ret.isRemoveable=this.isRemoveable;
  4043. ret.url=this.url;
  4044. if (this.isRemoveable) ret.id=this.id;
  4045. //capture filters
  4046. ret.filters={};
  4047. for (var f in this.filters) {
  4048. ret.filters[f]={
  4049. enabled:this.filters[f].enabled,
  4050. expanded:this.filters[f].expanded,
  4051. id:this.filters[f].id,
  4052. };
  4053. }
  4054. return ret;
  4055. }catch(e){log("WM.Feed.saveableData: "+e);}});
  4056. for (var p in params) this[p]=params[p];
  4057.  
  4058. this.enable=function(){try{
  4059. this.enabled=true;
  4060. this.node.className=this.node.className.removeWord("disabled");
  4061. WM.feedManager.save();
  4062. }catch(e){log("WM.Feed.enable: "+e);}};
  4063.  
  4064. this.disable=function(){try{
  4065. this.enabled=false;
  4066. this.node.className=this.node.className.addWord("disabled");
  4067. WM.feedManager.save();
  4068. }catch(e){log("WM.Feed.disable: "+e);}};
  4069.  
  4070. this.toggle=function(){try{
  4071. this.enabled=this.toggleNode.checked;
  4072. this.node.className=this.node.className.swapWordB(this.enabled,"enabled","disabled");
  4073. WM.feedManager.save();
  4074. }catch(e){log("WM.Feed.toggle: "+e);}};
  4075.  
  4076. //create a filter for a specific app
  4077. //filter id must be "app_"+appID
  4078. //will not add duplicates
  4079. this.addFilter=function(params){try{
  4080. var isNew=!exists(params);
  4081. params=params||{};
  4082. params.parent=this;
  4083. //prevent duplicates
  4084. if (!exists(this.filters[params.id])) {
  4085. return (this.filters[params.id]=new WM.FeedFilter(params));
  4086. if (isNew) WM.feedManager.save();
  4087. } else {
  4088. return this.filters[params.id];
  4089. }
  4090. }catch(e){log("WM.Feed.addFilter: "+e);}};
  4091.  
  4092. //get the extents of the feed by merging all feed filter oldedge/newedge values
  4093. this.getMergedEdges=function(params){
  4094. /*
  4095. apps[]: an array of appID's to test against, otherwise read from all filters
  4096. */
  4097. //console.log("getMergedEdges: "+JSON.stringify(params));
  4098. var retval = {newedge:Math.round(timeStamp()/1000), oldedge:0};
  4099. if (params.apps||null){
  4100. for (var c=0,l=params.apps.length;c<l;c++){
  4101. var filter = this.filters["app_"+params.apps[c]];
  4102. if (filter||null){
  4103. //get the youngest older edge and oldest newer edge so we don't lose posts because one feed is more active.
  4104. //this forces them to run at the same edges after the first pull
  4105. retval.newedge = Math.min(retval.newedge, filter.newedge);
  4106. retval.oldedge = Math.max(retval.oldedge, filter.oldedge);
  4107. } else {
  4108. log("getMergedEdges: no filter matching app_"+params.apps[c]+" on feed " + this.id);
  4109. }
  4110. }
  4111. } else {
  4112. for (var name in this.filters){
  4113. var filter = this.filters[name];
  4114. if (filter||null){
  4115. //get the youngest older edge and oldest newer edge so we don't lose posts because one feed is more active.
  4116. //this forces them to run at the same edges after the first pull
  4117. retval.newedge = Math.min(retval.newedge, filter.newedge);
  4118. retval.oldedge = Math.max(retval.oldedge, filter.oldedge);
  4119. } else {
  4120. log("getMergedEdges: no filter matching "+name+" on feed " + this.id);
  4121. }
  4122. }
  4123. }
  4124. return retval;
  4125. };
  4126. //remove this
  4127. this.remove=function(noConfirm){try{
  4128. if (this.isRemoveable) {
  4129. var ask=WM.opts.feedsConfirmDeleteFeed;
  4130. if (noConfirm || (this.isGlobal && confirm("This feed is shared with other profiles. Deleting it here will prevent it from loading for other users. Are you sure you wish to delete this feed and its filters.")) || !ask || (!this.isGlobal && ask && confirm("Delete feed and all of its filters?"))){
  4131. //remove my data
  4132. if (this.node) remove(this.node);
  4133. WM.feedManager.feeds.removeByValue(this);
  4134. WM.feedManager.save();
  4135. }
  4136. }
  4137. }catch(e){log("WM.Feed.remove: "+e);}};
  4138.  
  4139. //fetch posts for this
  4140. this.fetchNewer=function(){try{
  4141. WM.fetch({
  4142. newer:true,
  4143. feeds:[self],
  4144. bypassPause:true,
  4145. bypassAppDisabled:true,
  4146. bypassFeedDisabled:true,
  4147. });
  4148. }catch(e){log("WM.Feed.fetchNewer: "+e);}};
  4149.  
  4150. this.fetchOlder=function(){try{
  4151. WM.fetch({
  4152. older:true,
  4153. feeds:[self],
  4154. bypassPause:true,
  4155. bypassAppDisabled:true,
  4156. bypassFeedDisabled:true,
  4157. });
  4158. }catch(e){log("WM.Feed.fetchOlder: "+e);}};
  4159.  
  4160. this.toggleContent=function(){try{
  4161. this.expanded=!this.expanded;
  4162. var btnSize=WM.opts.littleButtonSize;
  4163. with (this.contentNode)
  4164. className=className.swapWordB(this.expanded,"expanded","collapsed");
  4165. with (this.toggleImgNode)
  4166. className=className.swapWordB(this.expanded,"treeCollapse"+btnSize,"treeExpand"+btnSize);
  4167. WM.feedManager.save();
  4168. }catch(e){log("WM.Feed.toggleContent: "+e);}};
  4169.  
  4170. if (this.id && !this.url) this.url="https://graph.facebook.com/"+this.id+"/feed";
  4171. //draw it
  4172. try{
  4173. WM.console.feedManagerNode.appendChild(
  4174. this.node=createElement("div",{className:"listItem "+((this.enabled)?"enabled":"disabled")},[
  4175. createElement("div",{className:"line"},[
  4176. createElement("div",{className:"littleButton",title:"Toggle Content",onclick:function(){self.toggleContent();}},[
  4177. this.toggleImgNode=createElement("img",{className:"resourceIcon "+(this.expanded?"treeCollapse"+WM.opts.littleButtonSize:"treeExpand"+WM.opts.littleButtonSize)}),
  4178. ]),
  4179. this.toggleNode=createElement("input",{type:"checkbox",checked:this.enabled,onchange:function(){
  4180. self.enabled=this.checked;
  4181. with (self.node) className=className.toggleWordB(!this.checked,"disabled");
  4182. WM.feedManager.save();
  4183. }}),
  4184. this.titleNode=createElement("input",{value:(this.title||""), onchange:function(){self.title=this.value; WM.feedManager.save();}}),
  4185. //toolbox
  4186. createElement("div",{className:"littleButton oddBlue", title:"Fetch Newer"},[
  4187. this.fetchNewerButton=createElement("img",{className:"resourceIcon rssUpRight"+WM.opts.littleButtonSize,onclick:function(){self.fetchNewer();} })
  4188. ]),
  4189. createElement("div",{className:"littleButton", title:"Fetch Older"},[
  4190. this.fetchOlderButton=createElement("img",{className:"resourceIcon rssDownLeft"+WM.opts.littleButtonSize,onclick:function(){self.fetchOlder();} })
  4191. ]),
  4192. (this.isRemoveable)?createElement("div",{className:"littleButton oddOrange", title:"Remove Feed"},[
  4193. this.removeButtonNode=createElement("img",{className:"resourceIcon trash"+WM.opts.littleButtonSize,onclick:function(){self.remove();} })
  4194. ]):null,
  4195. (this.isRemoveable)?createElement("div",{className:"indent littleButton "+((this.isGlobal)?"oddOrange":"oddGreen"), title:((this.isGlobal)?"Disable Profile Sharing":"Share With Other Profiles")},[
  4196. this.toggleGlobalButton=createElement("img",{className:"resourceIcon "+((this.isGlobal)?"removeGlobal":"addGlobal")+WM.opts.littleButtonSize,onclick:function(){self.isGlobal=!self.isGlobal; WM.feedManager.save();}})
  4197. ]):null,
  4198.  
  4199. ]),
  4200. this.contentNode=createElement("div",{className:"subsection "+(this.expanded?"expanded":"collapsed")},[
  4201. (this.isRemoveable)?createElement("div",{className:"line"},[
  4202. createElement("label",{textContent:"Target FB Entity: ",title:"The request address from where WM gets posts for this fb entity."}),
  4203. this.idNode=createElement("input",{value:(this.id||""), onchange:function(){
  4204. self.id=this.value;
  4205. self.url="https://graph.facebook.com/"+this.value+"/feed";
  4206. self.urlNode.textContent=self.url;
  4207. WM.feedManager.save();
  4208. }}),
  4209. createElement("label",{textContent:"URL: ",title:"The request address from where WM gets posts for this fb entity."}),
  4210. this.urlNode=createElement("span",{textContent:this.url}),
  4211. ]):null,
  4212. //app filters sub box
  4213. createElement("div",{className:"line"},[
  4214. createElement("label",{textContent:"App Filters: ",title:"This is a list of filters run on this feed by apps you collect for. Only filters for sidekick-supported apps are used."}),
  4215. this.filtersNode=createElement("div",{className:"subsection"}),
  4216. ]),
  4217. ]),
  4218. ])
  4219. );
  4220. }catch(e){log("WM.Feed.init:addManagerElement: "+e);};
  4221. //add any passed filters
  4222. for (var f in newFilters){
  4223. this.addFilter(newFilters[f]);
  4224. }
  4225. return self;
  4226. }catch(e){log("WM.Feed.init: "+e);}};
  4227. //***************************************************************************************************************************************
  4228. //***** Friend Objects
  4229. //***************************************************************************************************************************************
  4230. WM.friendTracker = {
  4231. friends: {},
  4232. init : function(){
  4233. //import friends tracker data
  4234. var friendsIn=getOptJSON('friends_'+WM.currentUser.profile)||[];
  4235. if (isArrayAndNotEmpty(friendsIn)) for (var f=0,len=friendsIn.length;f<len;f++) {
  4236. WM.friendTracker.newFriend(friendsIn[f],true);
  4237. }
  4238. WM.friendTracker.sort();
  4239. },
  4240. clean : function(){
  4241. //clean friend tracker data
  4242. var len=0;
  4243. if (WM.opts.useFriendTracker && (len=WM.friendTracker.friends.length)) {
  4244. var ageDays=WM.opts.trackDays*day;
  4245. var timeNow=timeStamp();
  4246. for (var f=0; f<len; f++){
  4247. var friend=WM.friendTracker.friends[f];
  4248. if (friend.data && friend.data.posts){
  4249. for (var p in friend.data.posts){
  4250. var post=friend.data.posts[p];
  4251. if ((timeNow-(post.date*1000)) > ageDays) {
  4252. delete friend.data.posts[p];
  4253. }
  4254. }
  4255. }
  4256. }
  4257. }
  4258. },
  4259. clearAll : function(noConfirm){
  4260. var ask=WM.opts.trackConfirmClearUser;
  4261. if (noConfirm || !ask || (ask && confirm("Clear tracker history for all users?"))){
  4262. for (var f in WM.friendTracker.friends){
  4263. WM.friendTracker.friends[f].remove(true);
  4264. }
  4265. }
  4266. },
  4267. newFriend : function(params,preventSort){
  4268. params=params||{};
  4269. var friend = new WM.Friend(params);
  4270. WM.friendTracker.friends[friend.id]=friend;
  4271. if (!preventSort) WM.friendTracker.sort();
  4272. return friend;
  4273. },
  4274.  
  4275. save :function(){
  4276. var ret=[];
  4277. for (var f in WM.friendTracker.friends){
  4278. ret.push(WM.friendTracker.friends[f].saveableData);
  4279. }
  4280. setOptJSON("friends_"+WM.currentUser.profile,ret);
  4281. },
  4282. sort : function(params){
  4283. params=params||{};
  4284. if (exists(params.sortBy)) WM.quickOpts.sortFriendsBy=params.sortBy;
  4285. if (exists(params.sortOrder)) WM.quickOpts.sortFriendsOrder=params.sortOrder;
  4286. WM.saveQuickOpts();
  4287.  
  4288. var sortBy=params.sortBy||WM.quickOpts.sortFriendsBy||"name"
  4289. var sortOrder=params.sortOrd||WM.quickOpts.sortFriendsOrder||"asc"
  4290. var friendArray=[];
  4291. for (var f in WM.friendTracker.friends) {
  4292. friend=WM.friendTracker.friends[f];
  4293. friendArray.push({id:friend[sortBy],node:friend.node});
  4294. }
  4295. if (["asc","ascending"].inArray(sortOrder)) friendArray.sort(function(a,b){return a.id>b.id;});
  4296. else if (["desc","descending"].inArray(sortOrder)) friendArray.sort(function(a,b){return a.id<b.id;});
  4297. for (var f=0,len=friendArray.length; f<len; f++) {
  4298. WM.console.friendBuild.appendChild(friendArray[f].node);
  4299. }
  4300. },
  4301. track : function(post){
  4302. //dont track stuff older than our older tracking limit
  4303. var limit=WM.opts.trackTime*day;
  4304. if ( ( timeStamp()-(post.date*1000) ) < limit ) {
  4305. //get/create the friend record
  4306. var friend=WM.friendTracker.friends[post.fromID]||null;
  4307. if (!friend) {
  4308. friend=WM.friendTracker.newFriend({id:post.fromID,name:post.fromNameLastFirst});
  4309. }
  4310. //check if this is newer than last known post
  4311. if (WM.opts.trackLastKnownPost) {
  4312. var data=friend.lastKnownPost;
  4313. if (data) {
  4314. if (data.date<post.date){
  4315. data.date=post.date;
  4316. //data.id=post.id.removePrefix(post.fromID+"_");
  4317. }
  4318. } else {
  4319. friend.data.lastKnownPost={date:post.date};
  4320. }
  4321. }
  4322. //add it to history
  4323. if (WM.opts.trackCreated){
  4324. var data={date:post.date};
  4325. if (WM.opts.trackFailed){
  4326. data.failed=(post.status<0 && post.status !=-4 && post.status !=-6);
  4327. }
  4328. if (WM.opts.trackAccepted){
  4329. data.accepted=(post.status>0 || post.status ==-4 || post.status ==-6);
  4330. }
  4331. friend.data.posts[post.id.removePrefix(post.fromID+"_")]=data;
  4332. }
  4333. //save it
  4334. friend.updateStats();
  4335. WM.friendTracker.save();
  4336. //push events
  4337. WM.rulesManager.doEvent("onFriendDataChanged",friend);
  4338. }
  4339. },
  4340. trackStatus : function(post,acceptOrFail){
  4341. var friend=WM.friendTracker.friends[post.fromID]||null;
  4342. if (friend) {
  4343. var data=friend.data.posts[post.id.removePrefix(post.fromID+"_")]||null;
  4344. if (data){
  4345. if (acceptOrFail) {
  4346. data.accepted=true;
  4347. delete data.failed;
  4348. } else {
  4349. data.failed=true;
  4350. delete data.accepted;
  4351. }
  4352. friend.updateStats();
  4353. WM.rulesManager.doEvent("onFriendDataChanged",friend);
  4354. } else {
  4355. debug.print("post does not exist under friend");
  4356. //if post does not exists, we had more errors elsewhere
  4357. //or post id not fit our history range
  4358. }
  4359. } else {
  4360. debug.print("friend does not exist for this post");
  4361. //if friend does not exist, we had errors elsewhere
  4362. //don't bother fixing it here
  4363. }
  4364. },
  4365. };
  4366.  
  4367. //***************************************************************************************************************************************
  4368. //***** Friend Class
  4369. //***************************************************************************************************************************************
  4370. WM.Friend = function(params){try{
  4371. this.objType="friend";
  4372. params=params||{};
  4373. var self=this;
  4374.  
  4375. //set defaults
  4376. this.expanded=false;
  4377. this.id="";
  4378. this.name="";
  4379. this.data={
  4380. lastKnownPost:{date:0},
  4381. posts:{},
  4382. };
  4383.  
  4384. this.__defineGetter__("saveableData",function(){try{
  4385. var ret={};
  4386. ret.id=this.id;
  4387. ret.name=this.name;
  4388. ret.enabled=this.enabled;
  4389. ret.expanded=this.expanded;
  4390. //capture posts data
  4391. ret.data=this.data;
  4392. return ret;
  4393. }catch(e){log("WM.Friend.saveableData: "+e);}});
  4394. for (var p in params) this[p]=params[p];
  4395.  
  4396. //remove this
  4397. this.remove=function(noConfirm){try{
  4398. var ask=WM.opts.trackConfirmClearUser;
  4399. if (noConfirm || !ask || (ask && confirm("Clear history for this user?"))){
  4400. //remove my data
  4401. if (this.node) remove(this.node);
  4402. delete WM.friendTracker.friends[this.id];
  4403. WM.friendTracker.save();
  4404. }
  4405. }catch(e){log("WM.Friend.remove: "+e);}};
  4406.  
  4407. this.toggleContent=function(){try{
  4408. this.expanded=!this.expanded;
  4409. var btnSize=WM.opts.littleButtonSize;
  4410. with (this.contentNode)
  4411. className=className.swapWordB(this.expanded,"expanded","collapsed");
  4412. with (this.toggleImgNode)
  4413. className=className.swapWordB(this.expanded,"treeCollapse"+btnSize,"treeExpand"+btnSize);
  4414. WM.friendTracker.save();
  4415. }catch(e){log("WM.Friend.toggleContent: "+e);}};
  4416. this.addToFeeds=function(){try{
  4417. WM.feedManager.newFeed({id:this.id, title:this.name});
  4418. WM.feedManager.save();
  4419. }catch(e){log("WM.Friend.addToFeeds: "+e);}};
  4420. this.countAccepted=function(){try{
  4421. var c=0;
  4422. if (this.data.posts) for (var p in this.data.posts) {
  4423. var post=this.data.posts[p];
  4424. if (post.accepted) c++;
  4425. }
  4426. return c;
  4427. }catch(e){log("WM.Friend.countAccepted: "+e);}};
  4428.  
  4429. this.countFailed=function(){try{
  4430. var c=0;
  4431. if (this.data.posts) for (var p in this.data.posts) {
  4432. var post=this.data.posts[p];
  4433. if (post.failed) c++;
  4434. }
  4435. return c;
  4436. }catch(e){log("WM.Friend.countFailed: "+e);}};
  4437.  
  4438. this.countCreated=function(){try{
  4439. var c=0;
  4440. if (this.data.posts) for (var p in this.data.posts) {
  4441. c++
  4442. }
  4443. return c;
  4444. }catch(e){log("WM.Friend.countFailed: "+e);}};
  4445.  
  4446. this.__defineGetter__("lastKnownPost",function(){try{
  4447. if (this.data && (this.data.lastKnownPost||null)){
  4448. return this.data.lastKnownPost;
  4449. }
  4450. return {id:null,date:0};
  4451. }catch(e){log("WM.Friend.lastKnownPost: "+e);}});
  4452. this.__defineGetter__("lastKnownPostDate",function(){try{
  4453. if (this.data && (this.data.lastKnownPost||null)){
  4454. return this.data.lastKnownPost.date;
  4455. }
  4456. return 0;
  4457. }catch(e){log("WM.Friend.lastKnownPostDate: "+e);}});
  4458. this.__defineGetter__("acceptCount",function(){try{
  4459. return this.countAccepted();
  4460. }catch(e){log("WM.Friend.acceptCount: "+e);}});
  4461. this.__defineGetter__("failCount",function(){try{
  4462. return this.countFailed();
  4463. }catch(e){log("WM.Friend.failCount: "+e);}});
  4464. this.__defineGetter__("postCount",function(){try{
  4465. return this.countCreated();
  4466. }catch(e){log("WM.Friend.postCount: "+e);}});
  4467. this.__defineGetter__("totalCount",function(){try{
  4468. return this.failCount+this.acceptCount;
  4469. }catch(e){log("WM.Friend.totalCount: "+e);}});
  4470. this.updateStats=function(){try{
  4471. var n=this.statsNode;
  4472. if (n) {
  4473. if (WM.opts.trackLastKnownPost){
  4474. d=new Date(((this.lastKnownPost.date*1000)||0)).toLocaleString();
  4475. if (!this.lastPostNode) {
  4476. n.appendChild(createElement("div",{className:"line"},[
  4477. createElement("label",{textContent:"Last Known Post Date: "}),
  4478. this.lastPostNode=createElement("span",{textContent:d})
  4479. ]));
  4480. } else {
  4481. this.lastPostNode.textContent=d;
  4482. }
  4483. }
  4484.  
  4485. if (WM.opts.trackCreated){
  4486. if (!this.countCreatedNode) {
  4487. n.appendChild(createElement("div",{className:"line"},[
  4488. createElement("label",{textContent:"Posts Created: "}),
  4489. this.countCreatedNode=createElement("span",{textContent:this.countCreated()})
  4490. ]));
  4491. } else {
  4492. this.countCreatedNode.textContent=this.countCreated();
  4493. }
  4494. }
  4495. if (WM.opts.trackAccepted){
  4496. if (!this.countAcceptedNode){
  4497. n.appendChild(createElement("div",{className:"line"},[
  4498. createElement("label",{textContent:"Posts Accepted: "}),
  4499. this.countAcceptedNode=createElement("span",{textContent:this.countAccepted()})
  4500. ]));
  4501. } else {
  4502. this.countAcceptedNode.textContent=this.countAccepted();
  4503. }
  4504. }
  4505. if (WM.opts.trackFailed){
  4506. if (!this.countFailedNode){
  4507. n.appendChild(createElement("div",{className:"line"},[
  4508. createElement("label",{textContent:"Posts Failed: "}),
  4509. this.countFailedNode=createElement("span",{textContent:this.countFailed()})
  4510. ]));
  4511. } else {
  4512. this.countFailedNode.textContent=this.countFailed();
  4513. }
  4514. }
  4515. }
  4516. }catch(e){log("WM.Friend.updateStats: "+e);}};
  4517. //draw it
  4518. try{
  4519. WM.console.friendBuild.appendChild(
  4520. this.node=createElement("div",{className:"listItem"},[
  4521. createElement("div",{className:"line"},[
  4522. createElement("div",{className:"littleButton",title:"Toggle Content",onclick:function(){self.toggleContent();}},[
  4523. this.toggleImgNode=createElement("img",{className:"resourceIcon "+(this.expanded?"treeCollapse"+WM.opts.littleButtonSize:"treeExpand"+WM.opts.littleButtonSize)}),
  4524. ]),
  4525. this.titleNode=createElement("input",{value:(this.name||""), onchange:function(){self.name=this.value; WM.friendTracker.save();}}),
  4526. //toolbox
  4527. createElement("div",{className:"littleButton", title:"Add To Feeds"},[
  4528. createElement("img",{className:"resourceIcon addFeed"+WM.opts.littleButtonSize,onclick:function(){self.addToFeeds();} })
  4529. ]),
  4530. createElement("div",{className:"littleButton oddOrange", title:"Clear Data"},[
  4531. createElement("img",{className:"resourceIcon trash"+WM.opts.littleButtonSize,onclick:function(){self.remove();} })
  4532. ]),
  4533. createElement("div",{onclick:function(){window.open("http://www.facebook.com/profile.php?id="+self.id,"_blank");},title:"Visit Wall",className:"littleButton oddBlue"},[
  4534. createElement("img",{className:"resourceIcon openInNewWindow"+WM.opts.littleButtonSize})
  4535. ]),
  4536. ]),
  4537. this.contentNode=createElement("div",{className:"subsection "+(this.expanded?"expanded":"collapsed")},[
  4538. createElement("div",{className:"line"},[
  4539. createElement("label",{textContent:"ID: ",title:"The facebook id of this user."}),
  4540. createElement("span",{textContent:self.id}),
  4541. ]),
  4542. //post data sub box
  4543. createElement("div",{className:"line"},[
  4544. createElement("label",{textContent:"Statistics: ",title:"Statistics you selected to track."}),
  4545. this.statsNode=createElement("div",{className:"subsection"}),
  4546. ]),
  4547. ]),
  4548. ])
  4549. );
  4550. }catch(e){log("WM.Friend.init:addManagerElement: "+e);};
  4551.  
  4552. this.updateStats();
  4553. return self;
  4554. }catch(e){log("WM.Friend.init: "+e);}};
  4555. //***************************************************************************************************************************************
  4556. //***** Rules Manager Object
  4557. //***************************************************************************************************************************************
  4558. WM.rulesManager = {
  4559. rules:[],
  4560. enabled:function(){return !WM.quickOpts.heartbeatDisabled;},
  4561.  
  4562. init:function(params){try{
  4563. params=(params||{});
  4564. // build a kidsNode getter
  4565. WM.rulesManager.__defineGetter__("kidsNode",function(){try{
  4566. return $("wmPriorityBuilder");
  4567. }catch(e){log("WM.rulesManager.kidsNode: "+e);}});
  4568.  
  4569. //import rules
  4570. WM.rulesManager.rules=[];
  4571. var rulesIn=getOptJSON("priority3_"+WM.currentUser.profile)||{};
  4572. var globalsIn=getOptJSON("priority3_global")||{};
  4573. //detect early beta rule lists
  4574. if (isObject(rulesIn)) for (var i in rulesIn){
  4575. var rule=rulesIn[i];
  4576. WM.rulesManager.rules.push( new WM.rulesManager.Rule(rule) );
  4577. //don't bother with globals here
  4578. //or use current version rule arrays
  4579. } else if (isArrayAndNotEmpty(rulesIn)) for (var i=0,rule;(rule=rulesIn[i]);i++) {
  4580. if (rule.isGlobal) {
  4581. var glob=globalsIn[rule.uniqueID]||null;
  4582. if (glob){
  4583. var merge=mergeJSON(glob,rule);
  4584. WM.rulesManager.rules.push( new WM.rulesManager.Rule(merge) );
  4585. glob.alreadyUsed=true;
  4586. } else {
  4587. log("WM.rulesManager.init: Global rule missing, cannot merge");
  4588. }
  4589. } else {
  4590. WM.rulesManager.rules.push( new WM.rulesManager.Rule(rule) );
  4591. }
  4592. }
  4593. //import all globals not already accounted for
  4594. for (var t in globalsIn) {
  4595. var glob=globalsIn[t];
  4596. //avoid already imported globals
  4597. if (!glob.alreadyUsed){
  4598. glob.uniqueID=t;
  4599. glob.isGlobal=true;
  4600. WM.rulesManager.rule.push( new WM.rulesManager.Rule(glob) );
  4601. }
  4602. }
  4603. }catch(e){log("WM.rulesManager.init: "+e);}},
  4604. //check to see if any rules match the post object
  4605. doEvent:function(event,obj){
  4606. //do nothing if disabled
  4607. if (!WM.rulesManager.enabled) return;
  4608. //log("WM.rulesManager.doEvent: event="+event+", post="+post.id);
  4609. for (var r=0,rule;(rule=WM.rulesManager.rules[r]);r++){
  4610. if (rule.enabled) (function(){rule.doEvent(event,obj);})();
  4611. }
  4612. },
  4613. //convert a test (such as dynamic grab entry) to a rule
  4614. ruleFromTest:function(test){
  4615. //[{"id":"_h6qil21n","label":"new test","search":["body"],"find":["nothing"],"ret":"dynamic","kids":[{"id":"_h6qiw4zf","find":[]}],"appID":"102452128776","disabled":true}]
  4616. //[{"enabled":true,"limit":0,"limitCount":0,"expanded":true,"validators":[{"search":["body"],"operand":"lessThan","find":"chipmunk"}],"actions":[{"event":"onIdentify","action":"setColor","params":"orange"}],"kids":[{"enabled":true,"limit":0,"limitCount":0,"expanded":true,"validators":[],"actions":[],"kids":[],"eggs":[]},{"enabled":true,"limit":0,"limitCount":0,"expanded":true,"validators":[],"actions":[],"kids":[],"eggs":[]}],"eggs":[{"enabled":true,"limit":0,"limitCount":0,"expanded":true,"validators":[],"actions":[],"kids":[],"eggs":[]},{"enabled":true,"limit":0,"limitCount":0,"expanded":true,"validators":[],"actions":[],"kids":[],"eggs":[]}]}]
  4617. var ret={
  4618. title:(test.label||test.title)||"Converted Dynamic Test",
  4619. enabled:!(test.disabled||false),
  4620. limit:0,
  4621. limitCount:0,
  4622. expanded:true,
  4623. validators:function(){
  4624. var ret=[];
  4625. //add the initial validator
  4626. ret.push({
  4627. search:["appID"],
  4628. operand:"equals",
  4629. find:test.appID
  4630. });
  4631. //detect search/find method
  4632. var method="basic";
  4633. if (isArrayAndNotEmpty(test.subTests) && test.find.contains("{%1}")) method="subTests";
  4634. if (exists(test.subNumRange) && test.find.contains("{%1}")) method="subNumRange";
  4635. if (test.regex==true) method="regexp";
  4636. if (method=="regexp") {
  4637. //leave the expression just as it is
  4638. ret.push({
  4639. search:test.search||[],
  4640. operand:"matchRegExp",
  4641. find:test.find,
  4642. });
  4643. } else if (method=="basic") {
  4644. //convert the test.find array into a regular espression
  4645. ret.push({
  4646. search:test.search||[],
  4647. operand:"matchRegExp",
  4648. find:arrayToRegExp(test.find),
  4649. });
  4650. } else if (method=="subTests") {
  4651. //insert the subTests into the find insertion point as a regular expression
  4652. //but make the rest of the find parameter not return if found
  4653. var find=test.find;
  4654. if (find.contains("{%1}")){
  4655. find=find.split("{%1}");
  4656. find=(find[0].length?("(?:"+find[0]+")"):"")+arrayToRegExp(test.subTests)+(find[1].length?("(?:"+find[1]+")"):"");
  4657. }
  4658. ret.push({
  4659. search:test.search||[],
  4660. operand:"matchRegExp",
  4661. find:find
  4662. });
  4663. } else if (method=="subNumRange") {
  4664. //insert the subNumRange into the find insertion point as a regular expression
  4665. //but make the rest of the find parameter not return if found
  4666. var numRange=("string"==typeof test.subNumRange)?test.subNumRange.split(","):[test.subNumRange.low,test.subNumRange.high];
  4667. var find=test.find;
  4668. if (find.contains("{%1}")){
  4669. find=find.split("{%1}");
  4670. find=(find[0].length?("(?:"+find[0]+")"):"")+integerRangeToRegExp({min:numRange[0],max:numRange[1]})+(find[1].length?("(?:"+find[1]+")"):"");
  4671. }
  4672. ret.push({
  4673. search:test.search||[],
  4674. operand:"matchRegExp",
  4675. find:find
  4676. });
  4677. }
  4678. return ret;
  4679. }(),
  4680. actions:[
  4681. {
  4682. event:"onIdentify",
  4683. action:"setWhich",
  4684. params:test.ret||"dynamic",
  4685. }
  4686. ],
  4687. kids:[],
  4688. eggs:[],
  4689. };
  4690. //convert children
  4691. if (isArrayAndNotEmpty(test.kids)) {
  4692. for (var k=0,kid;(kid=test.kids[k]);k++) {
  4693. ret.kids.push(WM.rulesManager.ruleFromTest(kid));
  4694. }
  4695. }
  4696. return ret;
  4697. },
  4698. //create a rule based on a specific post
  4699. ruleFromPost:function(post){
  4700. //create some data to get us started
  4701. var rule={
  4702. basedOn:post,
  4703. title:"Based On: "+post.id,
  4704. enabled:false, //start out not using this rule
  4705. validators:[
  4706. {search:["appID"],find:post.appID,operand:"equals"},
  4707. {search:["title"],find:post.name,operand:"matchRegExp"},
  4708. {search:["caption"],find:post.caption,operand:"matchRegExp"},
  4709. {search:["desc"],find:post.description,operand:"matchRegExp"},
  4710. {search:["link"],find:post.linkText,operand:"matchRegExp"},
  4711. ],
  4712. actions:[
  4713. {event:"onIdentify",action:"setWhich",params:"dynamic"}
  4714. ]
  4715. };
  4716. WM.rulesManager.rules.push(rule=new WM.rulesManager.Rule(rule));
  4717.  
  4718. if (WM.opts.rulesJumpToNewRule){
  4719. //jump to rule view
  4720. WM.console.tabContainer.selectTab(3);
  4721. //scroll to new rule
  4722. rule.node.scrollIntoView();
  4723. }
  4724. },
  4725. //copy all dynamics to new rules
  4726. //does not destroy dynamics as they are converted
  4727. convertDynamics:function(){
  4728. var tests=WM.grabber.tests;
  4729. if (isArrayAndNotEmpty(tests)) {
  4730. for (var t=0,test;(test=tests[t]);t++){
  4731. WM.rulesManager.rules.push( new WM.rulesManager.Rule( WM.rulesManager.ruleFromTest(test) ) );
  4732. }
  4733. }
  4734. },
  4735.  
  4736. //rest rule limits for all rules and their children
  4737. resetAllLimits:function(params){
  4738. params=params||{};
  4739. var ask=WM.opts.rulesConfirmResetLimit;
  4740. if (params.noConfirm || !ask || (ask && confirm("Reset Limit Counter?"))) {
  4741. if (isArrayAndNotEmpty(WM.rulesManager.rules)) for (var r=0,rule;(rule=WM.rulesManager.rules[r]);r++) {
  4742. rule.resetLimit({preventSave:true,resetChildren:true,noConfirm:true});
  4743. }
  4744. WM.rulesManager.saveRules();
  4745. }
  4746. },
  4747. saveRules : function(){try{
  4748. //pack rule objects
  4749. var retRules=[];
  4750. var retGlobal={};
  4751. if (isArrayAndNotEmpty(WM.rulesManager.rules)) {
  4752. for (var r=0,rule; (rule=WM.rulesManager.rules[r]);r++){
  4753. if (!rule.isGlobal) {
  4754. retRules.push(rule.saveableData);
  4755. } else {
  4756. //make a placeholder locally
  4757. retRules.push({isGlobal:true, uniqueID:rule.uniqueID, enabled:rule.enabled, expanded:rule.expanded});
  4758. //and save it globally
  4759. var glob=rule.saveableData;
  4760. glob.uniqueID=rule.uniqueID;
  4761. retGlobal[rule.uniqueID]=glob;
  4762. }
  4763. }
  4764. }
  4765. //save rules
  4766. setOptJSON("priority3_"+WM.currentUser.profile,retRules);
  4767. setOptJSON("priority3_global",retGlobal);
  4768. }catch(e){log("WM.rulesManager.saveRules: "+e);}},
  4769. showData : function(){try{
  4770. promptText(getOpt("priority3_"+WM.currentUser.profile),true);
  4771. }catch(e){log("WM.rulesManager.showData: "+e);}},
  4772.  
  4773. newRule : function(p){try{
  4774. var rule=new WM.rulesManager.Rule(p);
  4775. WM.rulesManager.rules.push(rule);
  4776. WM.rulesManager.saveRules();
  4777. }catch(e){log("WM.rulesManager.newRule: "+e);}},
  4778.  
  4779. importRule: function(){try{
  4780. var params=prompt("Input rule data",null);
  4781. if (params) {
  4782. var convertedInput=JSON.parse(params);
  4783. if (isArray(convertedInput)){
  4784. for (var i=0;i<convertedInput.length;i++){
  4785. WM.rulesManager.newRule(convertedInput[i]);
  4786. }
  4787. } else {
  4788. WM.rulesManager.newRule(convertedInput);
  4789. }
  4790. }
  4791. }catch(e){log("WM.rulesManager.importRule: "+e);}},
  4792.  
  4793. toggleHeartbeat: function(){try{
  4794. WM.quickOpts.heartbeatDisabled=!WM.quickOpts.heartbeatDisabled;
  4795. with (WM.rulesManager.toggleHBNode) {
  4796. className=className.swapWordB(WM.quickOpts.heartbeatDisabled,"oddOrange","oddGreen");
  4797. }
  4798. WM.saveQuickOpts();
  4799. log(WM.quickOpts.heartbeatDisabled);
  4800. }catch(e){log("WM.rulesManager.toggleHeartbeat: "+e);}},
  4801. };
  4802. //***************************************************************************************************************************************
  4803. //***** Rules Manager Enums & Functions
  4804. //***************************************************************************************************************************************
  4805. WM.rulesManager.ruleActions = {
  4806. "addToFeeds":{name:"Add Poster To Feeds",toolTip:"Add the post's creator to your feeds manager. Can also be used with onFriend* events."},
  4807. "appendLink":{name:"Append To Link",toolTip:"Add specific code to the end of the collection link.",hasParam:true,paramType:"textBox","default":""},
  4808. "birth":{name:"Birth Eggs",toolTip:"Clone the egg children to this rule's level, without destroying this rule."},
  4809. "cancelInterval":{name:"Cancel Interval",toolTip:"Destroy the repeating timer on this rule."},
  4810. "cancelTimer":{name:"Cancel Timer",toolTip:"Destroy the timer on this rule."} ,
  4811. "cleanPost":{name:"Clean Post",toolTip:"Remove the calling post from the collector."},
  4812. "commentPost":{name:"Comment Post",toolTip:"Create a comment on the calling post.",hasParam:true,paramLabel:"comment",paramType:"string","default":"Thanks!"},
  4813. "createInterval":{name:"Create Interval",toolTip:"Create a repeating timer on this rule, where 1000 equals 1 second.",hasParam:true,paramType:"timePicker","default":1000} ,
  4814. "createTimer":{name:"Create Timer",toolTip:"Create a timer on this rule, where 1000 equals 1 second.",hasParam:true,paramType:"timePicker","default":1000},
  4815. "decrementCounter":{name:"Decrement Limit Counter",toolTip:"Decrement the rule limit counter.",hasParam:true,paramType:"number","default":1},
  4816. "decrementParentCounter":{name:"Decrement Parent Limit Counter",toolTip:"Decrement the parent rule limit counter.",hasParam:true,paramType:"number","default":1},
  4817. "destroyRule":{name:"Destroy Rule",toolTip:"Permanently removes this rule and all of its children."},
  4818. "disableApp":{name:"Disable App",toolTip:"Disable the specified app. Leave blank to disable the app associated with the activating post.",hasParam:true,paramType:"textBox","default":""},
  4819. "disableAppOption":{name:"Disable App Option",toolTip:"Disable an option in the related sidekick by internal name.",hasParam:true,paramType:"textBox","default":""},
  4820. "disableAutocomment":{name:"Disable Autocomment",toolTip:"Disable the autocomment feature."},
  4821. "disableAutolike":{name:"Disable Autolike",toolTip:"Disable the autolike feature."},
  4822. "disableChildRules":{name:"Disable Child Rules",toolTip:"Disable the immediate children of this rule. Does not disable this rule."},
  4823. "disableHostOption":{name:"Disable Host Option",toolTip:"Disable an option in the wm host by internal name.",hasParam:true},
  4824. "disableRule":{name:"Disable Rule",toolTip:"Disable the current rule."},
  4825. "emergencyOpen":{name:"Emergency Open",toolTip:"Move the calling post directly to a new processing window, no matter what your opened window limit is."},
  4826. "emptyAutolikeQueue":{name:"emptyAutolikeQueue",toolTip:"Destroys the list of posts you intended to autolike or autocomment."},
  4827. "enableApp":{name:"Enable App",toolTip:"Enable the specified app. Leave blank to enable the app associated with the activating post.",hasParam:true,paramType:"textBox","default":""},
  4828. "enableAppOption":{name:"Enable App Option",toolTip:"Enable an option in the related sidekick by internal name.",hasParam:true,paramType:"textBox","default":""},
  4829. "enableAutocomment":{name:"Enable Autocomment",toolTip:"Enable the autocomment feature."},
  4830. "enableAutolike":{name:"Enable Autolike",toolTip:"Enable the autolike feature."},
  4831. "enableChildRules":{name:"Enable Child Rules",toolTip:"Enable the immediate children of this rule."},
  4832. "enableHostOption":{name:"Enable Host Option",toolTip:"Enable an option in the wm host by internal name.",hasParam:true},
  4833. "enableRule":{name:"Enable Rule",toolTip:"Enable the current rule."},
  4834. "fetchNewer":{name:"Fetch Newer Posts",toolTip:"Fetch some more posts for this app, feed or feed filter."},
  4835. "fetchOlder":{name:"Fetch Older Posts",toolTip:"Fetch some more posts for this app, feed or feed filter."},
  4836. "fetchHours":{name:"Fetch Hours of Posts",toolTip:"Fetch some more posts for this app, feed or feed filter.",hasParam:true,paramType:"number","default":24},
  4837. "forceOpen":{name:"Force Open",toolTip:"Move the calling post directly to the collector queue."},
  4838. "forceOpenFirst":{name:"Force Open First",toolTip:"Move the calling post directly to the collector queue AND cut in line to be next processed."},
  4839. "hatch":{name:"Hatch Eggs",toolTip:"Hatch the egg-children of the current rule, and destroy this rule."},
  4840. "incrementCounter":{name:"Increment Limit Counter",toolTip:"Increment the rule limit counter.",hasParam:true,paramType:"number","default":1},
  4841. "incrementParentCounter":{name:"Increment Parent Limit Counter",toolTip:"Increment the parent rule limit counter.",hasParam:true,paramType:"number","default":1},
  4842. "likePost":{name:"Like Post",toolTip:"Like the calling post."},
  4843. "openPostSource":{name:"Open Post Source",toolTip:"Opens the post source in a separate window/tab."},
  4844. "processLast":{name:"Move To Bottom",toolTip:"Move the post to the bottom of the collector window."},
  4845. "processFirst":{name:"Move To Top",toolTip:"Move the post to the top of the collector window."},
  4846. "pauseAllApps":{name:"Pause All Apps",toolTip:"Pause all apps currently associated with docked sidekicks."},
  4847. "pauseApp":{name:"Pause App",toolTip:"Pauses processing anything by this app.",hasParam:true,paramType:"textBox","default":""},
  4848. "pauseWM.collector":{name:"Pause WM.collector",toolTip:"Pauses collection of all posts."},
  4849. "pauseFetch":{name:"Pause Fetching",toolTip:"Pauses fetching of all posts."},
  4850. "pauseType":{name:"Pause Type",toolTip:"Pause collection of all bonuses of this type."},
  4851. "pinPost":{name:"Pin Post",toolTip:"Pins the calling post."}, //pin the post
  4852. "queueCommentPost":{name:"Queue Comment Post",toolTip:"Comment on the calling post by first using the autolike queue system to delay the autocomment.",hasParam:true,paramLabel:"comment",paramType:"string","default":"Thanks!"},
  4853. "queueLikePost":{name:"Queue Like Post",toolTip:"Like the calling post by first using the autolike queue system to delay the autolike."},
  4854. "refreshBrowser":{name:"Refresh Browser",toolTip:"Reloads the browser window."},
  4855. "reIDAll":{name:"ReID All",toolTip:"Re-ID all posts in the collector."},
  4856. "removePriority":{name:"Remove Priority",toolTip:"Sets the priority of the calling post to normal."},
  4857. "removePriorityApp":{name:"Remove Priority (App)",toolTip:"Sets the priority of all posts of the calling or specified app to normal.",hasParam:true,paramType:"textBox","default":""},
  4858. "removePriorityType":{name:"Remove Priority (Type)",toolTip:"Sets the priority of all posts of the calling app with specified or associated type to normal.",hasParam:true,paramType:"textBox","default":"dynamic"},
  4859. "resetAllLimits":{name:"Reset All Limit Counters",toolTip:"Reset all limits in the rules manager."},
  4860. "resetLimit":{name:"Reset Limit Counter",toolTip:"Reset the limit counter of the current rule."},
  4861. "resetBranchLimits":{name:"Reset Branch Limit Counters",toolTip:"Reset the limit counter of ALL rules that are lower in this branch (children, grandchildren, etc.). Does not reset the limit on this rule."},
  4862. "resetChildrenLimits":{name:"Reset Children Limit Counters",toolTip:"Reset the limit counter of immediate child rules of this rule. Does not reset the limit on this rule."},
  4863. "resetParentLimit":{name:"Reset Parent Limit Counter",toolTip:"Reset the limit counter of the parent rule."},
  4864. "setAppOption":{name:"Set App Option",toolTip:"Set an option in the related sidekick by internal name.",hasParam:true,paramCount:2,paramData:[{paramType:"textBox","default":"",paramLabel:"Name"},{paramType:"textBox","default":"",paramLabel:"Value"}]},
  4865. "setAppTab":{name:"Set App Tab",toolTip:"Set the current collection tab by app ID.",hasParam:true,paramType:"textBox","default":"all"},
  4866. "setAsAccepted":{name:"Set As Accepted",toolTip:"Set the calling post as accepted.",hasParam:true,paramType:"checkBox",paramLabel:"saveToHistory","default":false},
  4867. "setAsExcluded":{name:"Set As Excluded",toolTip:"Set the calling post as excluded."},
  4868. "setAsFailed":{name:"Set As Failed",toolTip:"Set the calling post as failed.",hasParam:true,paramType:"checkBox",paramLabel:"saveToHistory","default":false},
  4869. "setColor":{name:"Set Post Color",toolTip:"Set the background color of the calling post.",hasParam:true,paramType:"colorPicker","default":"blue"},
  4870. "setHostOption":{name:"Set Host Option",toolTip:"Set the value a host option by internal name.",hasParam:true,paramCount:2,paramData:[{paramType:"textBox","default":"",paramLabel:"Name"},{paramType:"textBox","default":"",paramLabel:"Value"}]},
  4871. "setPriority":{name:"Set Priority",toolTip:"Set the priority of the calling post.",hasParam:true,paramType:"number","default":50},
  4872. "setPriorityApp":{name:"Set Priority (App)",toolTip:"Set the priority of the calling app or specified app.",hasParam:true,paramCount:2,paramData:[{paramType:"textBox",paramLabel:"App","default":""},{paramType:"number",paramLabel:"Priority","default":50}]},
  4873. "setPriorityType":{name:"Set Priority (Type)",toolTip:"Set the priority of the calling post type or specified type for the same app.",hasParam:true,paramCount:2,paramData:[{paramType:"textBox",paramLabel:"Type Code","default":""},{paramType:"number",paramLabel:"Priority","default":50}]},
  4874. "setToCollect":{name:"Set To Collect",toolTip:"Set the calling post to be collected in normal order. Use Force Open to do more immediate collection, or Emergency Open to override your opened window limit."},
  4875. "setToCollectPriority1":{name:"Set To Collect Top Priority",toolTip:"Set the calling post to be collected and also set its priority to 1. Use Force Open to do more immediate collection, or Emergency Open to override your opened window limit."},
  4876. "setWhich":{name:"Set Type",toolTip:"Set the bonus type id of the calling post.",hasParam:true,paramType:"textBox","default":"dynamic"},
  4877. "uncheckType":{name:"Uncheck Post Type",toolTip:"Unchecks option to collect this bonus in the options menu."},
  4878. "unpauseAllApps":{name:"Unpause All Apps",toolTip:"Unpause all apps currently associated with docked sidekicks."},
  4879. "unpauseAllTypesAllApps":{name:"Unpause All Types",toolTip:"Unpause all bonus types by all apps."},
  4880. "unpauseAllTypesByApp":{name:"Unpause All Types By App",toolTip:"Unpause all bonus types associated with the given app, or the app associated with the activating post.",hasParam:true,paramType:"textBox","default":""},
  4881. "unpauseApp":{name:"Unpause App",toolTip:"Starts processing anything by this app.",hasParam:true,paramType:"textBox","default":""},
  4882. "unpauseWM.collector":{name:"Unpause WM.collector",toolTip:"Starts collection of posts."},
  4883. "unpauseFetch":{name:"Unpause Fetching",toolTip:"Starts fetching of posts."},
  4884. "unpauseType":{name:"Unpause Type",toolTip:"Unpause collection of all bonuses of this type."},
  4885. };
  4886. WM.rulesManager.ruleActionsCodes = {
  4887. "addToFeeds":1,"appendLink":2,"birth":3,"cancelInterval":4,"cancelTimer":5,"cleanPost":6,"commentPost":7,"createInterval":8,"createTimer":9,
  4888. "decrementCounter":10,"decrementParentCounter":11,"destroyRule":12,"disableApp":13,"disableAppOption":14,"disableAutolike":15,"disableChildRules":16,
  4889. "disableHostOption":17,"disableRule":18,"emergencyOpen":19,"emptyAutolikeQueue":20,"enableApp":21,"enableAppOption":22,"enableAutolike":23,
  4890. "enableChildRules":24,"enableHostOption":25,"enableRule":26,"fetchNewer":27,"fetchOlder":28,"forceOpen":29,"forceOpenFirst":30,"hatch":31,
  4891. "incrementCounter":32,"incrementParentCounter":33,"likePost":34,"openPostSource":35,"processLast":36,"processFirst":37,"pauseAllApps":38,
  4892. "pauseApp":39,"pauseWM.collector":40,"pauseFetch":41,"pauseType":42,"pinPost":43,"queueCommentPost":44,"queueLikePost":45,"refreshBrowser":46,
  4893. "reIDAll":47,"removePriority":48,"removePriorityApp":49,"removePriorityType":50,"resetAllLimits":51,"resetLimit":52,"resetBranchLimits":53,
  4894. "resetChildrenLimits":54,"resetParentLimit":55,"setAppOption":56,"setAppTab":57,"setAsAccepted":58,"setAsExcluded":59,"setAsFailed":60,"setColor":61,
  4895. "setHostOption":62,"setPriority":63,"setPriorityApp":64,"setPriorityType":65,"setToCollect":66,"setToCollectPriority1":67,"setWhich":68,
  4896. "uncheckType":69,"unpauseAllApps":70,"unpauseAllTypesAllApps":71,"unpauseAllTypesByApp":72,"unpauseApp":73,"unpauseWM.collector":74,"unpauseFetch":75,
  4897. "unpauseType":76,"fetchHours":77,"enableAutocomment":78,"disableAutocomment":79
  4898. };
  4899. WM.rulesManager.ruleActionByCode = function(code){
  4900. for (c in WM.rulesManager.ruleActionsCodes) {
  4901. if (WM.rulesManager.ruleActionsCodes[c]==code) return c;
  4902. }
  4903. return null;
  4904. };
  4905. WM.rulesManager.ruleEvents = {
  4906. //post events
  4907. "onIdentify":"Called after a post is (re)identified. Posts are first identified as soon as they are fetched.",
  4908. "onBeforeCollect":"Called before collection opens a sidekick window.",
  4909. "onAfterCollect":"Called after collection is tried. Activates regardless of return status.",
  4910. "onFailed":"Called when a post is marked failed. This could be actual or simulated by the user.",
  4911. "onAccepted":"Called when a post is marked accepted. This could be actual or simulated by the user.",
  4912. "onTimeout":"Called when a post is marked as timed out. This could be actual or simulated by the user.",
  4913. "onValidate":"Called when a post is first fetched, but after its first identification. Not called on posts which fail identification.",
  4914. //rule events
  4915. "onLimit":"Called when this rule limit counter equals the rule's limit.",
  4916. "onHatch":"Called when this rule's egg children are hatched.",
  4917. "onTimer":"Called when the timer on this rule activates.",
  4918. "onInterval":"Called when the repeating timer on this rule activates.",
  4919. "onBirth":"Called when this rule's egg children are birthed.",
  4920. "onRuleCreated":"Called when the rule is created (or loaded on startup).",
  4921. "onRuleButtonClicked":"Called when the rule button is clicked. Available only for control rules.",
  4922. //app events
  4923. "onSidekickDock":"Called when the sidekick for this app docks.",
  4924. "onSidekickReady":"Called when the sidekick for this app creates an app object, and after it appends the collection tab for that app.",
  4925. /*
  4926. paused/unpaused
  4927. enabled/disabled
  4928. failCountChanged
  4929. acceptCountChanged
  4930. */
  4931. //console events
  4932. "onHeartbeat":"Called when the global heartbeat interval ticks.",
  4933. "onSetAppFilter":"Called when the collection panel app tab changes, including at startup if 'Show All' is selected as default",
  4934. //feed events
  4935. "onFeedFilterOlderLimitReached":"Called when a specific feed filter reaches its user-defined older limit.",
  4936. };
  4937. WM.rulesManager.ruleEventsCodes ={
  4938. "onIdentify":1,"onBeforeCollect":2,"onAfterCollect":3,"onFailed":4,"onAccepted":5,"onTimeout":6,"onValidate":7,"onLimit":8,"onHatch":9,"onTimer":10,
  4939. "onInterval":11,"onBirth":12,"onRuleCreated":13,"onSidekickDock":14,"onSidekickReady":15,"onHeartbeat":16,"onSetAppFilter":17,
  4940. "onFeedFilterOlderLimitReached":18,"onRuleButtonClicked":19
  4941. };
  4942. WM.rulesManager.ruleEventByCode = function(code){
  4943. for (c in WM.rulesManager.ruleEventsCodes) {
  4944. if (WM.rulesManager.ruleEventsCodes[c]==code) return c;
  4945. }
  4946. return null;
  4947. };
  4948. WM.rulesManager.postParts = {
  4949. "age":"The time between the current time and the post creation time (in ms).",
  4950. "acceptCount":"An app's accept counter value. Friend objects also have an acceptCount.",
  4951. "activatorType":"Returns the object type of the rule-activating object: app, post, rule, feed, feedfilter or unknown.",
  4952. "alreadyProcessed":"Reports if a post has already created a history entry.",
  4953. "appID":"The appID of the game for which a post belongs. You can read the appID from the following affected objects: app, post, and feedFilter.",
  4954. "appName":"The appName of the game for which this post belongs, as reported by the FB database.",
  4955. "body":"The body of a post is a compilation of the title, caption, and desc.",
  4956. "canvas":"The canvas of a post is its namespace granted by FB, ie. FarmVille's namespace is 'onthefarm'.",
  4957. "caption":"The caption of a post is one line just below its title (or 'name'). Not all posts have this field.",
  4958. "commentorID":"The commentorID is a list of IDs of all commentors.",
  4959. "commentorName":"The commentorName is a list of names of all commentors.",
  4960. "comments":"The comments are list of all comments made to the post, excluding the initial msg.",
  4961. "currentTime":"The current time (in ms) on your system, not localized. This global value can be referenced from any activating object type.",
  4962. "currentAppTab":"The currently selected collection tab's appID, or the word 'all' if the 'Show All' tab is selected.",
  4963. "date":"The date of a post is its creation time on FB, and is the 'created_time' parameter in fb data packets.",
  4964. "desc":"The desc of a post is two lines below the title. This is the 'description' parameter in fb data packets. Not all posts have this field.",
  4965. "either":"The either of a post is the compilation of the link and body.",
  4966. "enabled":"The enabled state of an activating object.",
  4967. "expanded":"The expanded state of an activating object.",
  4968. "failCount":"An app's fail counter value. Friend objects also have a failCount.",
  4969. "friendAcceptedCount":"Gets the accepted count from a FriendTracker friend object matching this post creator.",
  4970. "friendFailedCount":"Gets the failed count from a FriendTracker friend object matching this post creator.",
  4971. "fromID":"The fromID is the ID of the poster.",
  4972. "fromName":"The fromName is the name of the poster.",
  4973. "fromNameLastFirst":"The name of the poster, displayed as Lastname, Firstname",
  4974. "html":"The html of a post is the compilation of ALL visible FB attributes.",
  4975. "id":"Normally a post ID, which is usually the post creator's ID and a timestamp separated by an underscore. Alternately, you can ask for the id of an activating friend, feed or feed filter object.",
  4976. "idText":"The identified link text of a post.",
  4977. "img":"The img of a post is the url of the icon that displays with the post. This is the 'picture' parameter in fb data packets.",
  4978. "isAccepted":"Reports if the post is set as having already been successfully collected.",
  4979. "isAppPaused":"Reports if the associated app is paused.",
  4980. "isCollect":"Reports if the post is set to be collected.",
  4981. "isExcluded":"Reports if the post has been set as excluded.",
  4982. "isFailed":"Reports if the post is set as having already failed.",
  4983. "isForMe":"Reports if the W2W post targets the current user.",
  4984. "isLiked":"Reports if the post has been identified as already being liked by the current user.",
  4985. "isMyPost":"Reports if the post belongs to the current user.",
  4986. "isPaused":"Reports if the calling object (post or app) is paused. Not specific!",
  4987. "isPinned":"Reports if the post is marked as being pinned.",
  4988. "isRemovable":"Reports if a feed is removeable. Your own profile wall and your home feed are not removeable, only disableable.",
  4989. "isTimeout":"Reports if the post has been marked as a timed out collection attempt.",
  4990. "isTypePaused":"Reports if the associated bonus type is paused.",
  4991. "isScam":"Reports if a post is suspected of being a scam, usually when the canvas and appName do not match.",
  4992. "isStale":"Reports if a post is older than the user-set older limit.",
  4993. "isUndefined":"Reports if the post does not match any id given by the sidekick.",
  4994. "isWishlist":"Reports if the post is deemed a whichlist request.",
  4995. "isWorking":"Reports if the post is currently in the working state (being processed).",
  4996. "isW2W":"Reports if the post is a Wall-To-Wall post, meaning that it was posted to a specific user's wall.",
  4997. "lastKnownPostDate":"A friend object's last known post date (as unix time, no millisecond data).",
  4998. "likeID":"The likeID is a list of IDs of users who liked the post.",
  4999. "likeName":"The likeName is a list of names of users who liked this post.",
  5000. "limit":"This rule's limit number.",
  5001. "limitCount":"This rule's limit counter.",
  5002. "link":"The 'link' of a post is the link text, not the url. This is the 'action.name' in fb data packets.",
  5003. "linkHref":"The original url as it appeared from facebook. This SHOULD be exactly the same as 'url'.",
  5004. "linkText":"The original link text as it appeared from facebook. You may want to NOT use 'link' and instead use this one.",
  5005. "msg":"The msg of a post is the part the poster added as a comment during the post's creation.",
  5006. "name":"With posts, this is the same as 'title', because its the FB name of a post object. With friend objects, this is the friend's text name.",
  5007. "parentLimit":"The parent rule's limit number, or NULL if no parent exists.",
  5008. "parentLimitCount":"The parent rule's limit counter, or NULL if no parent exists.",
  5009. "postCount":"A friend object's count of posts it is tracking.",
  5010. "postedDay":"A partial date-time value containing only the year/month/day portions, which corresponds to the post creation time.",
  5011. "postedHour":"A partial date-time value containing only the year/month/day/hour portions, which corresponds to the post creation time.",
  5012. "priority":"The priority of a post which could have been set by a rule, or by default of 50.",
  5013. "status":"The status of a post is the return code given by a sidekick, or 0 if it has not been processed.",
  5014. "targetID":"The targetID is a list of targets' IDs that the poster intended the post to display to.",
  5015. "targetName":"The targetName is a list of targets the poster intended the post to display to.",
  5016. "title":"The title of a post contains the bold text, usually including the poster's name, at the top of the post. This is the 'name' parameter in facebook data packets.",
  5017. "totalCount":"An app's failcount and acceptcount combined. Friend objects also have a totalCount.",
  5018. "typesPaused":"An app's list of paused bonus types. Only accessible from an activating post. Please stick to the contains/notContains operators because this is an array, not text.",
  5019. "url":"The url of a post is the address to which the post redirects the user when clicked. This is the 'link' or 'action.link' parameter in fb data packets. This is the original url supplied by the app, not a modified url, such as WM's removal of https or a sidekick-modified url. Alternately, you can ask for the URL of a feed object.",
  5020. "which":"The 'which' of a post is its identified codename that defines its bonus type and ties it to option menu entries. The codename starts with an appID and ends with something the sidekick developer uses to key the bonus type.",
  5021. "whichText":"Text associated with this bonus type.",
  5022. };
  5023. WM.rulesManager.postPartsCodes = {
  5024. "age":1,"acceptCount":2,"activatorType":3,"alreadyProcessed":4,"appID":5,"appName":6,"body":7,"canvas":8,"caption":9,"commentorID":10,
  5025. "commentorName":11,"comments":12,"currentTime":13,"currentAppTab":14,"date":15,"desc":16,"either":17,"enabled":18,"expanded":19,"failCount":20,
  5026. "fromID":21,"fromName":22,"fromNameLastFirst":23,"html":24,"id":25,"idText":26,"img":27,"isAccepted":28,"isAppPaused":29,"isCollect":30,
  5027. "isExcluded":31,"isFailed":32,"isForMe":33,"isLiked":34,"isMyPost":35,"isPaused":36,"isPinned":37,"isRemovable":38,"isTimeout":39,"isTypePaused":40,
  5028. "isScam":41,"isStale":42,"isUndefined":43,"isWishlist":44,"isWorking":45,"isW2W":46,"lastKnownPostDate":47,"likeID":48,"likeName":49,"limit":50,
  5029. "limitCount":51,"link":52,"linkHref":53,"linkText":54,"msg":55,"name":56,"parentLimit":57,"parentLimitCount":58,"postCount":59,"postedDay":60,
  5030. "postedHour":61,"priority":62,"status":63,"targetID":64,"targetName":65,"title":66,"totalCount":67,"typesPaused":68,"url":69,"which":70,
  5031. "whichText":71,"friendAcceptedCount":72,"friendFailedCount":73
  5032. };
  5033. WM.rulesManager.postPartByCode = function(code){
  5034. for (c in WM.rulesManager.postPartsCodes) {
  5035. if (WM.rulesManager.postPartsCodes[c]==code) return c;
  5036. }
  5037. return null;
  5038. };
  5039. WM.rulesManager.ruleOperands = {
  5040. "equals":"Property and query must match.",
  5041. "notEquals":"Property and query must not match.",
  5042. "startsWith":"Property must start with query value.",
  5043. "notStartsWith":"Property cannot start with query value.",
  5044. "endsWith":"Property must end with query value.",
  5045. "notEndsWith":"Property cannot end with query value.",
  5046. "contains":"Property contains anywhere the query value.",
  5047. "notContains":"Property does not contain the query value.",
  5048. "matchRegExp":"Property must match the registered expression.",
  5049. "notMatchRegExp":"Property must not match the registered expression.",
  5050. "greaterThan":"Property must be greater than query value.",
  5051. "lessThan":"Property must be less than query value.",
  5052. "greaterThanOrEquals":"Property must be greater than or equal to query value.",
  5053. "lessThanOrEquals":"Property must be less than or equal to query value.",
  5054.  
  5055. "equalsExactly":"Property and query must match exactly via binary comparison.",
  5056. "notEqualsExactly":"Property and query must not match exactly via binary comparison.",
  5057. };
  5058.  
  5059. WM.rulesManager.ruleOperandsCodes = {
  5060. "equals":1,
  5061. "notEquals":2,
  5062. "startsWith":3,
  5063. "notStartsWith":4,
  5064. "endsWith":5,
  5065. "notEndsWith":6,
  5066. "contains":7,
  5067. "notContains":8,
  5068. "matchRegExp":9,
  5069. "notMatchRegExp":10,
  5070. "greaterThan":11,
  5071. "lessThan":12,
  5072. "greaterThanOrEquals":13,
  5073. "lessThanOrEquals":14,
  5074. "equalsExactly":15,
  5075. "notEqualsExactly":16,
  5076. };
  5077. WM.rulesManager.ruleOperandByCode = function(code){
  5078. for (c in WM.rulesManager.ruleOperandsCodes) {
  5079. if (WM.rulesManager.ruleOperandsCodes[c]==code) return c;
  5080. }
  5081. return null;
  5082. };
  5083. //***************************************************************************************************************************************
  5084. //***** RuleValidator Class
  5085. //***************************************************************************************************************************************
  5086. WM.rulesManager.RuleValidator = function(params){try{
  5087. var isNew=(!exists(params));
  5088. var self=this;
  5089. //return saveable data from this branch
  5090. this.__defineGetter__("saveableData",function(){try{
  5091. var s=self.search, modSearch=[]; //use a second array to avoid accidental overwrite of first byRef
  5092. for (var c=0;c<s.length;c++){
  5093. modSearch.push(WM.rulesManager.postPartsCodes[s[c]]);
  5094. }
  5095. var ret = {search:modSearch, operand:WM.rulesManager.ruleOperandsCodes[self.operand], find:self.find}
  5096. return ret;
  5097. }catch(e){log("WM.rulesManager.RuleValidator.saveableData: "+e);}});
  5098.  
  5099. //remove this from parent
  5100. this.remove=function(){try{
  5101. var ask=WM.opts.rulesConfirmDeleteValidator;
  5102. if (!ask || (ask && confirm("Delete rule validator?"))){
  5103. remove(this.node);
  5104. this.parent.validators.removeByValue(this);
  5105. doAction(WM.rulesManager.saveRules);
  5106. }
  5107. }catch(e){log("WM.rulesManager.RuleValidator.remove: "+e);}};
  5108. this.moveUp=function(){try{
  5109. //where is this
  5110. var parentContainer = this.parent.validators;
  5111. //only affects items not already the first in the list
  5112. //and not the only child in the list
  5113. if ((parentContainer.length>1) && (parentContainer[0]!=this)) {
  5114. //which index is this?
  5115. var myIndex=parentContainer.inArrayWhere(this);
  5116. if (myIndex != -1) {
  5117. //I have a proper index here
  5118. //who is my sibling
  5119. var sibling = parentContainer[myIndex-1];
  5120. //swap me with my sibling
  5121. parentContainer[myIndex-1]=this;
  5122. parentContainer[myIndex]=sibling;
  5123. //place my node before my sibling node
  5124. sibling.node.parentNode.insertBefore(this.node,sibling.node);
  5125. //save it
  5126. WM.rulesManager.saveRules();
  5127. }
  5128. }
  5129. }catch(e){log("WM.rulesManager.RuleValidator.moveUp: "+e);}};
  5130.  
  5131. //move down in the list
  5132. this.moveDown=function(){try{
  5133. //where is this
  5134. var parentContainer = this.parent.validators;
  5135. //only affects items not already the first in the list
  5136. //and not the only child in the list
  5137. if ((parentContainer.length>1) && (parentContainer.last()!=this)) {
  5138. //which index is this?
  5139. var myIndex=parentContainer.inArrayWhere(this);
  5140. if (myIndex != -1) {
  5141. //I have a proper index here
  5142. //who is my sibling
  5143. var sibling = parentContainer[myIndex+1];
  5144. //swap me with my sibling
  5145. parentContainer[myIndex+1]=this;
  5146. parentContainer[myIndex]=sibling;
  5147. //place my node before my sibling node
  5148. sibling.node.parentNode.insertBefore(sibling.node,this.node);
  5149. //save it
  5150. WM.rulesManager.saveRules();
  5151. }
  5152. }
  5153. }catch(e){log("WM.rulesManager.RuleValidator.moveDown: "+e);}};
  5154.  
  5155. //copy this validator on the parent
  5156. this.clone=function(){try{
  5157. this.parent.addValidator({search:this.search, operand:this.operand, find:this.find});
  5158. WM.rulesManager.saveRules();
  5159. }catch(e){log("WM.rulesManager.RuleValidator.clone: "+e);}};
  5160.  
  5161. //init
  5162. //this.id=params.id||unique();
  5163. this.parent=params.parent||null;
  5164. if (!this.parent) {
  5165. log("WM.rulesManager.RuleValidator: no parent specified: abort init");
  5166. return null;
  5167. }
  5168. //this.validationNode=parent.validationNode;
  5169. this.search=params.search||["appID"];
  5170. if (!isArray(this.search)) this.search=[].push(this.search);
  5171. //convert number codes to text commands
  5172. for (var e in this.search) {
  5173. //t=this.search[e];
  5174. if (isNumber(this.search[e])) this.search[e]=WM.rulesManager.postPartByCode(this.search[e]);
  5175. //log([this.search[e],t])
  5176. }
  5177. this.operand=params.operand||"matchRegExp";
  5178. if (isNumber(this.operand)) this.operand=WM.rulesManager.ruleOperandByCode(this.operand);
  5179. this.find=params.find||"";
  5180. //draw it
  5181. this.parent.validationNode.appendChild(this.node=createElement("div",{className:"validator"},[
  5182. //search portion for this validator
  5183. createElement("div",{className:"line"},[
  5184. this.searchNode=(this.objSearch=new jsForms.comboBox({
  5185. className:"jsfComboBox selectPostPart",
  5186. onChange:function(){
  5187. self.search=this.value;
  5188. WM.rulesManager.saveRules();
  5189. },
  5190. items:(function(){
  5191. var ret=[];
  5192. for (var i in WM.rulesManager.postParts){
  5193. ret.push(new jsForms.checkBox({
  5194. text:i,
  5195. value:i,
  5196. toolTipText:WM.rulesManager.postParts[i],
  5197. checked:(self.search.inArray(i)),
  5198. size:{width:"200%"},
  5199. }));
  5200. }
  5201. return ret;
  5202. })(),
  5203. borderStyle:"none",
  5204. //borderRadius:{topLeft:"1px", bottomRight:"1px",topRight:"1px",bottomLeft:"1px"},
  5205. //explicitClose:true,
  5206. highlightSelected:true,
  5207. dropDownSize:{height:"200px"},
  5208. backColor:"#EEEEEE",
  5209. })).node,
  5210. //operator portion for this validator
  5211. this.operandNode=createElement("select",{className:"selectOperand",onchange:function(){self.operand=this.value;WM.rulesManager.saveRules();}},(function(){
  5212. var ret=[],elem;
  5213. for (var i in WM.rulesManager.ruleOperands){
  5214. ret.push(elem=createElement("option",{textContent:i,value:i,title:WM.rulesManager.ruleOperands[i]}));
  5215. if (i==self.operand) elem.selected=true;
  5216. }
  5217. return ret;
  5218. })()),
  5219. //find portion for this validator
  5220. /*
  5221. right here we need to bring up an element based on
  5222. the post part chosen
  5223. for most cases, we just need an input box to accept string values
  5224. for special case "which" we need a dropdown of bonus types
  5225. for boolean flags we need a default value of true and maybe
  5226. some kind of limitation to true and false in the box
  5227. */
  5228. this.findNode=createElement("input",{className:"findBox",value:this.find,onchange:function(){self.find=this.value;WM.rulesManager.saveRules();}}),
  5229. //toolbox
  5230. createElement("div",{className:"littleButton oddOrange",onclick:function(){self.remove();},title:"Delete Validator"},[
  5231. createElement("img",{className:"resourceIcon trash"+WM.opts.littleButtonSize}),
  5232. ]),
  5233. createElement("div",{className:"littleButton oddBlue",onclick:function(){self.clone();},title:"Clone Validator"},[
  5234. createElement("img",{className:"resourceIcon clone"+WM.opts.littleButtonSize}),
  5235. ]),
  5236. createElement("div",{className:"littleButton oddGreen",onclick:function(){self.moveUp();},title:"Move Up"},[
  5237. createElement("img",{className:"resourceIcon arrowUp"+WM.opts.littleButtonSize}),
  5238. ]),
  5239. createElement("div",{className:"littleButton oddOrange",onclick:function(){self.moveDown();},title:"Move Down"},[
  5240. createElement("img",{className:"resourceIcon arrowDown"+WM.opts.littleButtonSize}),
  5241. ]),
  5242. (self.parent.basedOn)?createElement("div",{className:"indent littleButton oddBlue",onclick:function(){
  5243. //if a validator search array exists
  5244. if (isArrayAndNotEmpty(self.search)){
  5245. //fill the 'find' box with the post data linked to the search terms
  5246. var f="";
  5247. var post=self.parent.basedOn;
  5248. for (var s=0;s<self.search.length;s++){
  5249. if (s>0) f+=" ";
  5250. f+=(post.testData[self.search[s]]||post[self.search[s]]||"");
  5251. }
  5252. self.findNode.value=f;
  5253. self.find=f;
  5254. WM.rulesManager.saveRules();
  5255. }
  5256. },title:"Capture Text From Linked Post"},[
  5257. createElement("img",{className:"resourceIcon importData"+WM.opts.littleButtonSize}),
  5258. ]):null,
  5259. ]),
  5260. ]));
  5261. //if (isNew) WM.rulesManager.saveRules();
  5262. return self;
  5263. }catch(e){log("WM.rulesManager.RuleValidator.init(): "+e);}};
  5264.  
  5265. //***************************************************************************************************************************************
  5266. //***** RuleAction Class
  5267. //***************************************************************************************************************************************
  5268. WM.rulesManager.RuleAction = function(params){try{
  5269. var isNew=(!exists(params));
  5270. var self=this;
  5271. //return saveable data from this branch
  5272. this.__defineGetter__("saveableData",function(){try{
  5273. var a= {event:WM.rulesManager.ruleEventsCodes[this.event], action:WM.rulesManager.ruleActionsCodes[this.action]};
  5274. if (this.hasParam) a.params=this.params;
  5275. if (this.paramCount==2) a.params2=this.params2;
  5276. return a;
  5277. }catch(e){log("WM.rulesManager.RuleAction.saveableData: "+e);}});
  5278.  
  5279. //remove this from parent
  5280. this.remove=function(){try{
  5281. var ask=WM.opts.rulesConfirmDeleteAction;
  5282. if (!ask || (ask && confirm("Delete rule action?"))){
  5283. remove(this.node);
  5284. this.parent.actions.removeByValue(this);
  5285. doAction(WM.rulesManager.saveRules);
  5286. }
  5287. }catch(e){log("WM.rulesManager.RuleAction.remove: "+e);}};
  5288. //move up in the list
  5289. this.moveUp=function(){try{
  5290. //where is this
  5291. var parentContainer = this.parent.actions;
  5292. //only affects items not already the first in the list
  5293. //and not the only child in the list
  5294. if ((parentContainer.length>1) && (parentContainer[0]!=this)) {
  5295. //which index is this?
  5296. var myIndex=parentContainer.inArrayWhere(this);
  5297. if (myIndex != -1) {
  5298. //I have a proper index here
  5299. //who is my sibling
  5300. var sibling = parentContainer[myIndex-1];
  5301. //swap me with my sibling
  5302. parentContainer[myIndex-1]=this;
  5303. parentContainer[myIndex]=sibling;
  5304. //place my node before my sibling node
  5305. sibling.node.parentNode.insertBefore(this.node,sibling.node);
  5306. //save it
  5307. WM.rulesManager.saveRules();
  5308. }
  5309. }
  5310. }catch(e){log("WM.rulesManager.RuleAction.moveUp: "+e);}};
  5311.  
  5312. //move down in the list
  5313. this.moveDown=function(){try{
  5314. //where is this
  5315. var parentContainer = this.parent.actions;
  5316. //only affects items not already the first in the list
  5317. //and not the only child in the list
  5318. if ((parentContainer.length>1) && (parentContainer.last()!=this)) {
  5319. //which index is this?
  5320. var myIndex=parentContainer.inArrayWhere(this);
  5321. if (myIndex != -1) {
  5322. //I have a proper index here
  5323. //who is my sibling
  5324. var sibling = parentContainer[myIndex+1];
  5325. //swap me with my sibling
  5326. parentContainer[myIndex+1]=this;
  5327. parentContainer[myIndex]=sibling;
  5328. //place my node before my sibling node
  5329. sibling.node.parentNode.insertBefore(sibling.node,this.node);
  5330. //save it
  5331. WM.rulesManager.saveRules();
  5332. }
  5333. }
  5334. }catch(e){log("WM.rulesManager.RuleAction.moveDown: "+e);}};
  5335.  
  5336. //copy this validator on the parent
  5337. this.clone=function(){try{
  5338. this.parent.addAction(this.saveableData());
  5339. WM.rulesManager.saveRules();
  5340. }catch(e){log("WM.rulesManager.RuleAction.clone: "+e);}};
  5341.  
  5342. //init
  5343. //this.id=params.id||unique();
  5344. this.parent=params.parent||null;
  5345. if (!this.parent) {
  5346. log("WM.rulesManager.RuleAction: no parent specified: abort init");
  5347. return null;
  5348. }
  5349. //this.actionsNode=parent.actionsNode;
  5350. this.action=params.action||"incrementCounter";
  5351. //log(this.action);
  5352. if (isNumber(this.action)) this.action=WM.rulesManager.ruleActionByCode(this.action);
  5353. this.event=params.event||"onAccepted";
  5354. if (isNumber(this.event)) this.event=WM.rulesManager.ruleEventByCode(this.event);
  5355. //setup default values and param types
  5356. //log(this.action);
  5357. var def=WM.rulesManager.ruleActions[this.action];
  5358. this.hasParam = def.hasParam;
  5359. this.params = params.params||def["default"]||((def.paramData||null)?def.paramData[0]["default"]:"");
  5360. this.params2 = params.params2||((def.paramData||null)?def.paramData[1]["default"]:"");
  5361. this.paramCount = def.paramCount;
  5362. //draw it
  5363. this.parent.actionsNode.appendChild(this.node=createElement("div",{className:"action"},[
  5364. //event for this action
  5365. createElement("div",{className:"line"},[
  5366. this.eventNode=createElement("select",{className:"selectEvent",onchange:function(){self.event=this.value; if (self.event=="onRuleButtonClicked") {self.parent.ruleButtonHousingNode.style.display="";} else {self.parent.ruleButtonHousingNode.style.display="none";}; WM.rulesManager.saveRules();}},(function(){
  5367. var actioneventsret=[],elem;
  5368. for (var i in WM.rulesManager.ruleEvents){
  5369. actioneventsret.push(elem=createElement("option",{textContent:i,value:i,title:WM.rulesManager.ruleEvents[i]}));
  5370. if (i==self.event) elem.selected=true;
  5371. }
  5372. return actioneventsret;
  5373. })()),
  5374. //function to call on the event
  5375. this.actionNode=createElement("select",{className:"selectFunction",onchange:function(){
  5376. self.action=this.value;
  5377. WM.rulesManager.saveRules();
  5378. //set the param type
  5379. var action = WM.rulesManager.ruleActions[this.value];
  5380. self.paramNode.style.display=((action.hasParam)?"":"none");
  5381. self.param2Node.style.display=((action.hasParam && (action.paramCount==2))?"":"none");
  5382.  
  5383. }},(function(){
  5384. var actionfuncsret=[],elem;
  5385. for (var i in WM.rulesManager.ruleActions){
  5386. entry=WM.rulesManager.ruleActions[i];
  5387. actionfuncsret.push(elem=createElement("option",{textContent:entry.name,value:i,title:entry.toolTip}));
  5388. if (i==self.action) elem.selected=true;
  5389. }
  5390. return actionfuncsret;
  5391. })()),
  5392. //this is for special cases only and should be hidden otherwise
  5393. this.paramNode=createElement("input",{className:"paramBox",value:this.params,onchange:function(){self.params=this.value;WM.rulesManager.saveRules();}}),
  5394. this.param2Node=createElement("input",{className:"paramBox",value:this.params2,onchange:function(){self.params2=this.value;WM.rulesManager.saveRules();}}),
  5395. //toolbox
  5396. createElement("div",{className:"littleButton oddOrange",onclick:function(){self.remove();},title:"Delete Action"},[
  5397. createElement("img",{className:"resourceIcon trash"+WM.opts.littleButtonSize}),
  5398. ]),
  5399. createElement("div",{className:"littleButton oddBlue",onclick:function(){self.clone();},title:"Clone Action"},[
  5400. createElement("img",{className:"resourceIcon clone"+WM.opts.littleButtonSize}),
  5401. ]),
  5402. createElement("div",{className:"littleButton oddGreen",onclick:function(){self.moveUp();},title:"Move Up"},[
  5403. createElement("img",{className:"resourceIcon arrowUp"+WM.opts.littleButtonSize}),
  5404. ]),
  5405. createElement("div",{className:"littleButton oddOrange",onclick:function(){self.moveDown();},title:"Move Down"},[
  5406. createElement("img",{className:"resourceIcon arrowDown"+WM.opts.littleButtonSize}),
  5407. ]),
  5408.  
  5409. ]),
  5410. ]));
  5411. //hide param node when not used
  5412. self.paramNode.style.display=((self.hasParam)?"":"none");
  5413. self.param2Node.style.display=((self.hasParam && (self.paramCount==2))?"":"none");
  5414.  
  5415. //if (isNew) WM.rulesManager.saveRules();
  5416. return self;
  5417. }catch(e){log("WM.rulesManager.RuleAction.init(): "+e);}};
  5418.  
  5419. //***************************************************************************************************************************************
  5420. //***** Rule Class
  5421. //***************************************************************************************************************************************
  5422. WM.rulesManager.Rule = function(params){try{
  5423. this.objType="rule";
  5424. var self=this;
  5425. params=params||{};
  5426.  
  5427. //set defaults
  5428. this.parent=null;
  5429. this.enabled=true;
  5430. this.kids=[]; //child nodes
  5431. this.eggs=[]; //hatchable child nodes
  5432. this.actions=[]; //events:actions list
  5433. this.validators=[]; //search:find list
  5434. this.limitCount=0;
  5435. this.limit=0;
  5436. this.actionsNode=null;
  5437. this.validationNode=null;
  5438. this.node=null;
  5439. this.isChild=false;
  5440. this.isEgg=false;
  5441. this.expanded=true;
  5442. this.timers={};
  5443. this.intervals={};
  5444. this._isGlobal=false;
  5445. //return savable data from this branch
  5446. this.__defineGetter__("saveableData",function(){try{
  5447. var ret={};
  5448. //ret.id=this.id;
  5449. ret.title=this.title;
  5450. ret.enabled=this.enabled;
  5451. ret.limit=this.limit;
  5452. ret.limitCount=this.limitCount;
  5453. //ret.level=this.level;
  5454. ret.expanded=this.expanded;
  5455. ret.validators=[];
  5456. if (isArrayAndNotEmpty(this.validators)) for (var i=0,validator;(validator=this.validators[i]);i++) {
  5457. ret.validators.push(validator.saveableData);
  5458. }
  5459. ret.actions=[];
  5460. if (isArrayAndNotEmpty(this.actions)) for (var i=0,action;(action=this.actions[i]);i++) {
  5461. ret.actions.push(action.saveableData);
  5462. }
  5463. ret.kids=[];
  5464. if (isArrayAndNotEmpty(this.kids)) for (var i=0,kid;(kid=this.kids[i]);i++) {
  5465. ret.kids.push(kid.saveableData);
  5466. }
  5467. ret.eggs=[];
  5468. if (isArrayAndNotEmpty(this.eggs)) for (var i=0,egg;(egg=this.eggs[i]);i++) {
  5469. ret.eggs.push(egg.saveableData);
  5470. }
  5471. return ret;
  5472. }catch(e){log("WM.rulesManager.Rule.saveableData: "+e);}});
  5473.  
  5474. //set/get wether this rule is saved as global or profile
  5475. this.__defineGetter__("isGlobal",function(){try{
  5476. return self._isGlobal;
  5477. }catch(e){log("WM.rulesManager.Rule.isGlobal: "+e);}});
  5478. this.__defineSetter__("isGlobal",function(v){try{
  5479. //only top level rule can be global
  5480. if (self.parent) {
  5481. confirm("Only top level rule can be set to global.");
  5482. return;
  5483. }
  5484. if (!v) {
  5485. if (!confirm("Disabling profile sharing on this rule will prevent other users on this machine from loading it. Are you sure you wish to make this rule locally available only?")) return;
  5486. }
  5487. self._isGlobal=v;
  5488. //make sure we have a uniqueID
  5489. //but don't destroy one that already exists
  5490. if (v && !exists(self.uniqueID)) self.uniqueID = unique();
  5491. //change the color/icon of the isGlobal button
  5492. if (self.toggleGlobalButton) {
  5493. var s=WM.opts.littleButtonSize;
  5494. with (self.toggleGlobalButton) className=className.swapWordB(v,"removeGlobal"+s,"addGlobal"+s);
  5495. with (self.toggleGlobalButton.parentNode) {
  5496. className=className.swapWordB(v,"oddOrange","oddGreen");
  5497. title=(v)?"Disable Profile Sharing":"Share With Other Profiles";
  5498. }
  5499. }
  5500. }catch(e){log("WM.rulesManager.Rule.isGlobal: "+e);}});
  5501. this.__defineGetter__("parentLimit",function(){try{
  5502. if (self.parent||null) return self.parent.limit;
  5503. return null;
  5504. }catch(e){log("WM.rulesManager.Rule.parentLimit: "+e);}});
  5505.  
  5506. this.__defineGetter__("isBranchDisabled",function(){try{
  5507. var p=self.parent,ret=false;
  5508. while(p) {
  5509. if (!p.enabled) return true;
  5510. p=p.parent;
  5511. }
  5512. return false;
  5513. }catch(e){log("WM.rulesManager.Rule.isBranchDisabled: "+e);}});
  5514.  
  5515. this.__defineGetter__("parentLimitCount",function(){try{
  5516. if (self.parent||null) return self.parent.limitCount;
  5517. return null;
  5518. }catch(e){log("WM.rulesManager.Rule.parentLimitCount: "+e);}});
  5519.  
  5520. //copy passed params to this object
  5521. for (var p in params) {
  5522. //omit specific params
  5523. if (!(["actions","validators","kids","eggs"].inArray(p)) ) {
  5524. //copy only params that make it past the checker
  5525. this[p]=params[p];
  5526. }
  5527. }
  5528. this.usesRuleButton=function(){
  5529. for (var action in this.actions) {
  5530. if (action.event=="onRuleButtonClicked") {return true;}
  5531. }
  5532. return false;
  5533. };
  5534. this.moveUp=function(){try{
  5535. //where is this
  5536. var parentContainer =
  5537. (this.isChild)?this.parent.kids:
  5538. (this.isEgg)?this.parent.eggs:
  5539. WM.rulesManager.rules;
  5540. //only affects items not already the first in the list
  5541. //and not the only child in the list
  5542. if ((parentContainer.length>1) && (parentContainer[0]!=this)) {
  5543. //which index is this?
  5544. var myIndex=parentContainer.inArrayWhere(this);
  5545. if (myIndex != -1) {
  5546. //I have a proper index here
  5547. //who is my sibling
  5548. var sibling = parentContainer[myIndex-1];
  5549. //swap me with my sibling
  5550. parentContainer[myIndex-1]=this;
  5551. parentContainer[myIndex]=sibling;
  5552. //place my node before my sibling node
  5553. sibling.node.parentNode.insertBefore(this.node,sibling.node);
  5554. //save it
  5555. WM.rulesManager.saveRules();
  5556. }
  5557. }
  5558. }catch(e){log("WM.rulesManager.Rule.moveUp: "+e);}};
  5559. this.moveDown=function(){try{
  5560. //where is this
  5561. var parentContainer =
  5562. (this.isChild)?this.parent.kids:
  5563. (this.isEgg)?this.parent.eggs:
  5564. WM.rulesManager.rules;
  5565. //only affects items not already the last in the list
  5566. //and not the only child in the list
  5567. if ((parentContainer.length>1) && (parentContainer.last()!=this)) {
  5568. //which index is this?
  5569. var myIndex=parentContainer.inArrayWhere(this);
  5570. if (myIndex != -1) {
  5571. //I have a proper index here
  5572. //who is my sibling
  5573. var sibling = parentContainer[myIndex+1];
  5574. //swap me with my sibling
  5575. parentContainer[myIndex+1]=this;
  5576. parentContainer[myIndex]=sibling;
  5577. //place my node before my sibling node
  5578. sibling.node.parentNode.insertBefore(sibling.node,this.node);
  5579. //save it
  5580. WM.rulesManager.saveRules();
  5581. }
  5582. }
  5583. }catch(e){log("WM.rulesManager.Rule.moveDown: "+e);}};
  5584.  
  5585. this.moveUpLevel=function(){try{
  5586. if (this.parent) {
  5587. //this is not a top level node, so we can move it
  5588. var targetContainer=((this.parent.parent)?this.parent.parent.kids:WM.rulesManager.rules);
  5589. //remove from parent
  5590. this.parent[(this.isChild)?"kids":(this.isEgg)?"eggs":null].removeByValue(this);
  5591. //set new parent
  5592. this.parent=(this.parent.parent||null); //never point to the top level
  5593. //set flags
  5594. this.isChild=(this.parent!=null);
  5595. this.isEgg=false;
  5596. //move the object
  5597. targetContainer.push(this);
  5598. //move the node
  5599. if (this.parent) this.parent.kidsNode.appendChild(this.node);
  5600. else WM.console.priorityBuild.appendChild(this.node);
  5601. //save it
  5602. WM.rulesManager.saveRules();
  5603. }
  5604. }catch(e){log("WM.rulesManager.Rule.moveUpLevel: "+e);}};
  5605. this.moveDownLevel=function(){try{
  5606. //where is this
  5607. var parentContainer =
  5608. (this.isChild)?this.parent.kids:
  5609. (this.isEgg)?this.parent.eggs:
  5610. WM.rulesManager.rules;
  5611. //create a new rule at my level
  5612. var newRule = new WM.rulesManager.Rule({
  5613. parent:this.parent||null,
  5614. isChild:this.isChild,
  5615. isEgg:this.isEgg,
  5616. });
  5617. parentContainer.push(newRule);
  5618. //remove me from my current parent
  5619. parentContainer.removeByValue(this);
  5620. //attach me to my new parent
  5621. this.parent=newRule;
  5622. this.isChild=true;
  5623. this.isEgg=false;
  5624. newRule.kids.push(this);
  5625. //move my node
  5626. newRule.kidsNode.appendChild(this.node);
  5627. //save it
  5628. WM.rulesManager.saveRules();
  5629. }catch(e){log("WM.rulesManager.Rule.moveDownLevel: "+e);}};
  5630.  
  5631. this.enable=function(){try{
  5632. this.enabled=true;
  5633. this.node.className=this.node.className.removeWord("disabled");
  5634. WM.rulesManager.saveRules();
  5635. }catch(e){log("WM.rulesManager.Rule.enable: "+e);}};
  5636.  
  5637. this.disable=function(){try{
  5638. this.enabled=false;
  5639. this.node.className=this.node.className.addWord("disabled");
  5640. WM.rulesManager.saveRules();
  5641. }catch(e){log("WM.rulesManager.Rule.disable: "+e);}};
  5642.  
  5643. this.disableChildren=function(){try{
  5644. if (isArrayAndNotEmpty(this.kids)) for (var k=0,kid;(kid=this.kids[k]);k++){
  5645. kid.disable();
  5646. }
  5647. }catch(e){log("WM.rulesManager.Rule.disableChildren: "+e);}};
  5648.  
  5649. this.enableChildren=function(){try{
  5650. if (isArrayAndNotEmpty(this.kids)) for (var k=0,kid;(kid=this.kids[k]);k++){
  5651. kid.enable();
  5652. }
  5653. }catch(e){log("WM.rulesManager.Rule.enableChildren: "+e);}};
  5654. this.toggle=function(){try{
  5655. //if(this.enabled)this.disable(); else this.enable();
  5656. //this.enabled=!this.enabled;
  5657. this.enabled=this.toggleNode.checked;
  5658. this.node.className=this.node.className.swapWordB(this.enabled,"enabled","disabled");
  5659. WM.rulesManager.saveRules();
  5660. //this.toggleNode.checked=();
  5661.  
  5662. }catch(e){log("WM.rulesManager.Rule.toggle: "+e);}};
  5663.  
  5664. this.clone=function(){try{
  5665. var cloneRule=this.saveableData;
  5666. //cloneRule.id=unique();
  5667. if (this.isChild) this.parent.addChild(cloneRule);
  5668. else if (this.isEgg) this.parent.addEgg(cloneRule);
  5669. else WM.rulesManager.newRule(cloneRule);
  5670. }catch(e){log("WM.rulesManager.RuleAction.clone: "+e);}};
  5671.  
  5672. this.resetLimit=function(params){try{
  5673. params=params||{};
  5674. var ask=WM.opts.rulesConfirmResetLimit;
  5675. if (params.noConfirm || !ask || (ask && confirm("Reset Limit Counter?"))) {
  5676. this.limitCount=0;
  5677. this.limitCounterNode.value=this.limitCount;
  5678. if (!(params.resetChildren||false)) {
  5679. if (isArrayAndNotEmpty(this.kids)) for (var k=0,kid;(kid=this.kids[k]);k++){
  5680. kid.resetLimit(params);
  5681. }
  5682. }
  5683. if (!(params.preventSave||false)) WM.rulesManager.saveRules();
  5684. }
  5685. }catch(e){log("WM.rulesManager.Rule.resetLimit: "+e);}};
  5686. this.resetBranchLimits=function(params){try{
  5687. params=params||{};
  5688. //resets the limits of entire branch rules, but not self limit
  5689. if (isArrayAndNotEmpty(this.kids)) for (var k=0,kid;(kid=this.kids[k]);k++){
  5690. kid.resetLimit({resetChildren:true,noConfirm:params.noConfirm||false});
  5691. }
  5692. }catch(e){log("WM.rulesManager.Rule.resetBranchLimits: "+e);}};
  5693.  
  5694. this.resetChildrenLimits=function(params){try{
  5695. params=params||{};
  5696. //resets the limits of all immediate children, but not self limit
  5697. if (isArrayAndNotEmpty(this.kids)) for (var k=0,kid;(kid=this.kids[k]);k++){
  5698. kid.resetLimit({noConfirm:params.noConfirm||false});
  5699. }
  5700. }catch(e){log("WM.rulesManager.Rule.resetChildrenLimits: "+e);}};
  5701.  
  5702. this.incrementLimitCounter=function(o,n){try{
  5703. this.limitCount=parseInt(parseInt(this.limitCount)+(exists(n)?parseInt(n):1));
  5704. this.limitCounterNode.value=this.limitCount;
  5705. WM.rulesManager.saveRules();
  5706. //for reaching of limit
  5707. if (this.limit && (this.limitCount>=this.limit)) this.onEvent("onLimit",o);
  5708. }catch(e){log("WM.rulesManager.Rule.incrementLimitCounter: "+e);}};
  5709.  
  5710. this.decrementLimitCounter=function(o,n){try{
  5711. this.limitCount=parseInt(parseInt(this.limitCount)-(exists(n)?parseInt(n):1));
  5712. //dont allow to drop below 0
  5713. if (this.limitCount<0) this.limitCount=0;
  5714. this.limitCounterNode.value=this.limitCount;
  5715. WM.rulesManager.saveRules();
  5716. }catch(e){log("WM.rulesManager.Rule.decrementLimitCounter: "+e);}};
  5717.  
  5718. this.remove=function(noConfirm){try{
  5719. var ask=WM.opts.rulesConfirmDeleteRule;
  5720. if (noConfirm || (this.isGlobal && confirm("This rule is shared with other profiles. Deleting it here will prevent it from loading for other users. Are you sure you wish to delete this rule and its children.")) || !ask || (!this.isGlobal && ask && confirm("Delete rule and all of its child nodes?"))){
  5721. //destroy intervals and timers
  5722. this.cleanup();
  5723. //remove my data
  5724. var parentContainer=((this.isChild)?this.parent.kids:(this.isEgg)?this.parent.eggs:WM.rulesManager.rules);
  5725. parentContainer.removeByValue(this);
  5726. //remove my node
  5727. remove(this.node);
  5728. doAction(WM.rulesManager.saveRules);
  5729. }
  5730. }catch(e){log("WM.rulesManager.Rule.remove: "+e);}};
  5731.  
  5732. this.cancelAllTimers=function(){try{
  5733. //find the correct timer by target
  5734. for (var t in this.timers){
  5735. if (this.timers[t]!=null) {
  5736. window.clearTimeout(this.timers[t]);
  5737. delete this.timers[t];
  5738. }
  5739. }
  5740. }catch(e){log("WM.rulesManager.Rule.cancelAllTimers: "+e);}};
  5741.  
  5742. this.cancelTimer=function(target){try{
  5743. //find the correct timer by target
  5744. var timer=null;
  5745. for (var t in this.timers){
  5746. if (this.timers[t].target==target) {
  5747. timer=this.timers[t];
  5748. break;
  5749. }
  5750. }
  5751. if (timer) {
  5752. window.clearTimeout(timer.timer);
  5753. delete this.timers[timer.id];
  5754. }
  5755. }catch(e){log("WM.rulesManager.Rule.cancelTimer: "+e);}};
  5756. this.createTimer=function(t,o){try{
  5757. this.cancelTimer(o);
  5758. var self=this;
  5759. var stamp=unique();
  5760. var timer=window.setTimeout(function(){
  5761. self.onEvent("onTimer",o);
  5762. },t);
  5763. this.timers[stamp]={timer:timer, target:o, id:stamp};
  5764. }catch(e){log("WM.rulesManager.Rule.createTimer: "+e);}};
  5765. this.cancelAllIntervals=function(){try{
  5766. //find the correct timer by target
  5767. for (var t in this.intervals){
  5768. if (this.intervals[t]!=null) {
  5769. window.clearInterval(this.intervals[t]);
  5770. delete this.intervals[t];
  5771. }
  5772. }
  5773. }catch(e){log("WM.rulesManager.Rule.cancelAllIntervals: "+e);}};
  5774. this.cancelInterval=function(target){try{
  5775. //find the correct timer by target
  5776. var interval=null;
  5777. for (var t in this.intervals){
  5778. if (this.intervals[t].target==target) {
  5779. interval=this.intervals[t];
  5780. break;
  5781. }
  5782. }
  5783. if (interval) {
  5784. window.clearInterval(interval.timer);
  5785. delete this.intervals[interval.id];
  5786. }
  5787. }catch(e){log("WM.rulesManager.Rule.cancelInterval: "+e);}};
  5788. this.createInterval=function(t,o){try{
  5789. this.cancelInterval(o);
  5790. var self=this;
  5791. var stamp=unique();
  5792. var interval=window.setInterval(function(){
  5793. self.onEvent("onInterval",o);
  5794. },t);
  5795. this.intervals[stamp]={timer:interval, target:o, id:stamp};
  5796. }catch(e){log("WM.rulesManager.Rule.createInterval: "+e);}};
  5797. this.doEvent=function(event,obj,logit){try{
  5798. //check to see if post matches this rule, if it does, perform actions
  5799. //if (this.validators){
  5800. //logit=logit||(obj.objType=="post");
  5801. var obj=(obj||{});
  5802. //var self=this;
  5803. var matchPost=true, found=[];
  5804. for (var vl=0,validator;(validator=this.validators[vl]);vl++) { try{
  5805. //within the search array, each result is handled as an OR
  5806. var result=false;
  5807. for (var s=0,search; (search=validator.search[s]); s++) {
  5808. var v =
  5809. //special request for object type of the object that activated this rule
  5810. (search=="activatorType")?(
  5811. (exists(obj))?(obj.objType||"unknown"):"unknown"
  5812. ):
  5813. //special specific app being paused test
  5814. (search=="isAppPaused")?(
  5815. (exists(obj) && exists(obj.app))?obj.app.paused:false
  5816. ):
  5817. //special specific bonus type being paused
  5818. (search=="isTypePaused")?(
  5819. (exists(obj) && exists(obj.which) && exists(obj.app))?obj.app.typesPaused.inArray(obj.which):false
  5820. ):
  5821. //read from post object test data
  5822. (exists(obj) && exists(obj.testData) && exists(obj.testData[search]))?obj.testData[search]:
  5823. //read from activating object standard parameters
  5824. (exists(obj) && exists(obj[search]))?obj[search]:
  5825. //read from this rule object
  5826. exists(self[search])?self[search]:
  5827. //read from parameters in the console/main object
  5828. exists(WM[search])?WM[search]:
  5829. //there is no known reference for this query
  5830. "undefined";
  5831.  
  5832. //fail validators that do not work with this obj
  5833. if (v=="undefined") {result=false; break;}
  5834. //convert functions to values
  5835. if (typeof v=="function") v=v();
  5836. var query = validator.find;
  5837. //make the query the same type as the value
  5838. if (!(typeof query == typeof v)) {
  5839. switch (typeof v) {
  5840. case "boolean":
  5841. //convert texts of false/true to actual booleans
  5842. query = cBool(query);
  5843. break;
  5844. case "number":
  5845. //convert text numbers to real numbers
  5846. query=(query=Number(query))?query:0;
  5847. //if (query==null) query=0;
  5848. break;
  5849. }
  5850. }
  5851. //if (logit) log([search, v, query]);
  5852.  
  5853. //compare
  5854. switch(validator.operand){
  5855. case "equals": result=result||(v==query); break;
  5856. case "notEquals": result=result||(v!=query); break;
  5857. case "startsWith": result=result||(v.startsWith(query)); break;
  5858. case "notStartsWith": result=result||(!v.startsWith(query)); break;
  5859. case "endsWith": result=result||(v.endsWith(query)); break;
  5860. case "notEndsWith": result=result||(!v.endsWith(query)); break;
  5861. case "contains": result=result||(v.contains(query)); break;
  5862. case "notContains": result=result||(!v.contains(query)); break;
  5863. case "greaterThan": result=result||(v>query); break;
  5864. case "lessThan": result=result||(v<query); break;
  5865. case "greaterThanOrEquals": result=result||(v>=query); break;
  5866. case "lessThanOrEquals": result=result||(v<=query); break;
  5867. case "matchRegExp":
  5868. var f; //storage space for match array
  5869. var regex = new RegExp(query,"gi");
  5870. f=regex.exec(v);
  5871. result=result||isArrayAndNotEmpty(f);
  5872. //result=result||((f=v.match(regex))!=null);
  5873. if (f) found=found.concat(f);
  5874. break;
  5875. case "notMatchRegExp":
  5876. var regex = new RegExp(query,"gi");
  5877. result=result||(v.match(regex)==null);
  5878. break;
  5879. case "equalsExactly": result=result||(v===query); break;
  5880. case "notEqualsExactly": result=result||!(v===query); break;
  5881. }
  5882. //any result of true inside this loop is an automatic success
  5883. if (result) break;
  5884. }
  5885. //evaluate our current result with the previous results
  5886. //outside the search array, each value is handled as an AND
  5887. //any one non-match is a complete failure
  5888. matchPost=(matchPost && result);
  5889. if (!matchPost) break;
  5890. }catch(e){
  5891. log("WM.rulesManager.Rule.doEvent: "+e+"{event:" +event+ ", search:"+search+", value:"+v+", query:"+query+", result:"+result+"}");
  5892. continue;
  5893. }}
  5894. //if after all testing we still matched the object
  5895. //then perform this rule's events and check children
  5896. if (matchPost) {
  5897. //log("post matches all validators");
  5898. //first do every child rule
  5899. for (var k=0,kid;(kid=this.kids[k]);k++){
  5900. kid.doEvent(event,obj,true);
  5901. }
  5902. //now finish up with this rule's actions
  5903. this.onEvent(event,obj,found||null);
  5904. }
  5905. //}
  5906. //log("WM.rulesManager.Rule.doEvent: {obj="+obj.id+", event="+event+", matchPost="+matchPost+"}");
  5907. }catch(e){log("WM.rulesManager.Rule.doEvent: "+e);}};
  5908.  
  5909. this.onEvent=function(event,obj,found){try{
  5910. var actionFilter=["*"];
  5911. /*
  5912. handle special events
  5913. */
  5914. if (event=="onRuleCreated") {
  5915. /*
  5916. we do want onRuleCreated events to fire even if the rule is disabled,
  5917. or intervals won't be set and ready for later, if the user does enable the rule
  5918. this session. But we want to filter which actions are available.
  5919. */
  5920. if (!this.enabled || this.isBranchDisabled) actionFilter=["createInterval","createTimer"];
  5921. } else if ((event=="onInterval")||(event=="onTimer")) {
  5922. //special case for intervals and timers
  5923. if (!this.enabled || this.isBranchDisabled) return;
  5924. } else {
  5925. //always break if this rule is disabled
  5926. if (!this.enabled || this.isBranchDisabled) return;
  5927. }
  5928. /*
  5929. end handle special events
  5930. */
  5931. obj=obj||null;
  5932. //var self=this;
  5933. var post=(self!=obj)?obj:null;
  5934. var app=post?(exists(obj.app)?obj.app:obj):null;
  5935. //some insertable post parts
  5936. var inserts=["appID","which","fromID"];
  5937. //perform an action based on an event call
  5938. //post object may be null if called from this
  5939. for (var a1=0,action;(action=this.actions[a1]);a1++){
  5940. //filter actions: allow only those in the filter list, or all actions if "*" is in the list
  5941. if (actionFilter.inArray("*") || actionFilter.inArray(action.action) ) if (action.event==event){
  5942. var param=action.params;
  5943. var param2=action.params2;
  5944. var hasParam=action.hasParam;
  5945. //format some post parts into the param
  5946. if (hasParam && param) {
  5947. for (var i=0;i<inserts.length;i++){
  5948. if (post && (post.replace||null)) {
  5949. param.replace(new RegExp("{%"+inserts[i]+"}","gi"), post.testData[inserts[i]] || post[inserts[i]]);
  5950. }
  5951. }
  5952. }
  5953. switch(action.action){
  5954. case "destroyRule":(function(){
  5955. doAction(function(){
  5956. self.remove(true);
  5957. });
  5958. })(); break;
  5959. case "pauseType":(function(){
  5960. var w=post.which, a=app;
  5961. doAction(function(){
  5962. WM.pauseByType(a,w);
  5963. });
  5964. })(); break;
  5965. case "unpauseType":(function(){
  5966. var w=post.which, a=app;
  5967. doAction(function(){
  5968. WM.unPauseByType(a,w);
  5969. });
  5970. })(); break;
  5971. case "uncheckType":(function(){
  5972. var w=post.which, a=app;
  5973. doAction(function(){
  5974. WM.disableOpt(w,a);
  5975. //WM.stopCollectionOf(post.which);
  5976. });
  5977. })(); break;
  5978.  
  5979. case "checkType":(function(){
  5980. var w=post.which, a=app;
  5981. doAction(function(){
  5982. WM.enableOpt(w,a);
  5983. //WM.startCollectionOf(post.which);
  5984. });
  5985. })(); break;
  5986. case "disableAppOption":(function(){
  5987. var c=param, f=found, a=app;
  5988. if (f) c=c.format2(f);
  5989. doAction(function(){
  5990. WM.disableOpt(c,a);
  5991. });
  5992. })(); break;
  5993. case "enableAppOption":(function(){
  5994. var c=param, f=found, a=app;
  5995. if (f) c=c.format2(f);
  5996. doAction(function(){
  5997. WM.enableOpt(c,a);
  5998. });
  5999. })(); break;
  6000. case "disableHostOption":(function(){
  6001. //debug.print("option disabled");
  6002. var c=param, f=found;
  6003. if (f) c=c.format2(f);
  6004. doAction(function(){
  6005. WM.disableOpt(c);
  6006. });
  6007. })(); break;
  6008. case "enableHostOption":(function(){
  6009. //debug.print("option enabled");
  6010. var c=param, f=found;
  6011. if (f) c=c.format2(f);
  6012. doAction(function(){
  6013. WM.enableOpt(c);
  6014. });
  6015. })(); break;
  6016. case "disableAutolike":(function(){
  6017. doAction(function(){
  6018. //debug.print("autolike disabled");
  6019. WM.disableOpt("useautolike");
  6020. });
  6021. })(); break;
  6022. case "enableAutolike":(function(){
  6023. doAction(function(){
  6024. //debug.print("autolike enabled");
  6025. WM.enableOpt("useautolike");
  6026. });
  6027. })(); break;
  6028. case "disableAutocomment":(function(){
  6029. doAction(function(){
  6030. WM.disableOpt("useautocomment");
  6031. });
  6032. })(); break;
  6033. case "enableAutocomment":(function(){
  6034. doAction(function(){
  6035. WM.enableOpt("useautocomment");
  6036. });
  6037. })(); break;
  6038. case "pauseApp":(function(){
  6039. var a = WM.apps[param]||app;
  6040. doAction(function(){
  6041. a.pause();
  6042. });
  6043. })(); break;
  6044. case "unpauseApp":(function(){
  6045. var a = WM.apps[param]||app;
  6046. doAction(function(){
  6047. a.unPause();
  6048. });
  6049. })(); break;
  6050. case "appendLink":(function(){
  6051. var p=post, v=param||"";
  6052. if (p) doAction(function(){
  6053. p.link=p.linkHref+v;
  6054. });
  6055. })(); break;
  6056. case "unpauseAllTypesByApp":(function(){
  6057. var a = WM.apps[param]||app;
  6058. doAction(function(){
  6059. a.unpauseAllTypes();
  6060. });
  6061. })(); break;
  6062. case "unpauseAllTypesAllApps":(function(){
  6063. doAction(function(){
  6064. for (var a in WM.apps){
  6065. a.unpauseAllTypes();
  6066. }
  6067. });
  6068. })(); break;
  6069. case "unpauseAllApps":(function(){
  6070. doAction(function(){
  6071. for (var a in WM.apps){
  6072. a.unpause();
  6073. }
  6074. });
  6075. })(); break;
  6076. case "pauseAllApps":(function(){
  6077. doAction(function(){
  6078. for (var a in WM.apps){
  6079. a.pause();
  6080. }
  6081. });
  6082. })(); break;
  6083. case "refreshBrowser":(function(){
  6084. doAction(function(){
  6085. window.location.reload();
  6086. });
  6087. })(); break;
  6088. case "pauseWM.collector":(function(){
  6089. doAction(function(){
  6090. WM.pauseCollecting(true);
  6091. });
  6092. })(); break;
  6093. case "unpauseWM.collector":(function(){
  6094. doAction(function(){
  6095. WM.pauseCollecting(false);
  6096. });
  6097. })(); break;
  6098. case "pauseFetch":(function(){
  6099. doAction(function(){
  6100. WM.pauseFetching(true);
  6101. });
  6102. })(); break;
  6103. case "unpauseFetch":(function(){
  6104. doAction(function(){
  6105. WM.pauseFetching(false);
  6106. });
  6107. })(); break;
  6108. case "likePost":(function(){
  6109. doAction(function(){
  6110. post.like();
  6111. });
  6112. })(); break;
  6113. case "queueLikePost":(function(){
  6114. doAction(function(){
  6115. WM.queAutoLike(post);
  6116. });
  6117. })(); break;
  6118. case "commentPost":(function(){
  6119. var p=param,f=found;
  6120. if (f) p=p.format2(f);
  6121. doAction(function(){
  6122. post.comment(p);
  6123. });
  6124. })(); break;
  6125. case "queueCommentPost":(function(){
  6126. var p=param,f=found;
  6127. if (f) p=p.format2(f);
  6128. //log(["queueCommentPost fired",p]);
  6129. doAction(function(){
  6130. WM.queAutoComment(post,p);
  6131. });
  6132. })(); break;
  6133. case "cleanPost":(function(){
  6134. doAction(function(){
  6135. post.remove();
  6136. });
  6137. })(); break;
  6138. case "incrementCounter":(function(){
  6139. var o=obj,p=param,f=found;
  6140. //if (f) p=p.format2(f);
  6141. doAction(function(){
  6142. self.incrementLimitCounter(o,p);
  6143. });
  6144. })(); break;
  6145. case "decrementCounter":(function(){
  6146. var o=obj,p=param,f=found;
  6147. //if (f) p=p.format2(f);
  6148. doAction(function(){
  6149. self.decrementLimitCounter(o,p);
  6150. });
  6151. })(); break;
  6152. case "incrementParentCounter":(function(){
  6153. var o=obj,p=param, f=found;
  6154. //if (f) p=p.format2(f);
  6155. if (this.parent) {
  6156. doAction(function(){
  6157. //passes the activating object, not this rule
  6158. self.parent.incrementLimitCounter(o,p);
  6159. });
  6160. }
  6161. })(); break;
  6162. case "decrementParentCounter":(function(){
  6163. var o=obj,p=param, f=found;
  6164. //if (f) p=p.format2(f);
  6165. if (this.parent){
  6166. doAction(function(){
  6167. //passes the activating object, not this rule
  6168. self.parent.decrementLimitCounter(o,p);
  6169. });
  6170. }
  6171. })(); break;
  6172. case "setColor":(function(){
  6173. var c=param;
  6174. var f=found;
  6175. if (f) c=c.format2(f);
  6176. doAction(function(){
  6177. post.setColor(c);
  6178. });
  6179. })(); break;
  6180. case "pinPost":(function(){
  6181. doAction(function(){
  6182. post.pin();
  6183. });
  6184. })(); break;
  6185. case "setAsAccepted":(function(){
  6186. var saveit=param;
  6187. doAction(function(){
  6188. post.accept(saveit);
  6189. });
  6190. })(); break;
  6191. case "setAsFailed":(function(){
  6192. var saveit=param;
  6193. doAction(function(){
  6194. post.fail(saveit);
  6195. });
  6196. })(); break;
  6197. case "setAsExcluded":(function(){
  6198. doAction(function(){
  6199. post.exclude();
  6200. });
  6201. })(); break;
  6202. case "processFirst":(function(){
  6203. doAction(function(){
  6204. post.moveToTop();
  6205. });
  6206. })(); break;
  6207. case "processLast":(function(){
  6208. doAction(function(){
  6209. post.moveToBottom();
  6210. });
  6211. })(); break;
  6212. case "setPriority":(function(){
  6213. var p=param, f=found;
  6214. if (f) p=p.format2(f);
  6215. doAction(function(){
  6216. post.setPriority(p);
  6217. });
  6218. })(); break;
  6219. case "setPriorityApp":(function(){
  6220. var p=param2, a=WM.apps[param]||app, f=found;
  6221. if (f) p=p.format2(f);
  6222. doAction(function(){
  6223. app.setPriority(p);
  6224. });
  6225. })(); break;
  6226. case "removePriorityApp":(function(){
  6227. var p=param2, a=WM.apps[param]||app, f=found;
  6228. if (f) p=p.format2(f);
  6229. doAction(function(){
  6230. app.setPriority(50);
  6231. });
  6232. })(); break;
  6233. case "setPriorityType":(function(){
  6234. var p=param2, a=app, f=found, w=param||post.which;
  6235. if (f) p=p.format2(f);
  6236. doAction(function(){
  6237. app.setPriorityByType(w,p);
  6238. });
  6239. })(); break;
  6240. case "removePriorityType":(function(){
  6241. var a=app, f=found, w=param||post.which;
  6242. if (f) p=p.format2(f);
  6243. doAction(function(){
  6244. app.setPriorityByType(w,50);
  6245. });
  6246. })(); break;
  6247. case "removePriority":(function(){
  6248. doAction(function(){
  6249. post.setPriority(50);
  6250. });
  6251. })(); break;
  6252. case "resetLimit":(function(){
  6253. doAction(function(){
  6254. self.resetLimit({noConfirm:true});
  6255. });
  6256. })(); break;
  6257. case "resetParentLimit":(function(){
  6258. if (this.parent) {
  6259. doAction(function(){
  6260. self.parent.resetLimit({noConfirm:true});
  6261. });
  6262. }
  6263. })(); break;
  6264. case "resetChildrenLimits":(function(){
  6265. doAction(function(){
  6266. self.resetChildrenLimits({noConfirm:true});
  6267. });
  6268. })(); break;
  6269. case "resetBranchLimits":(function(){
  6270. doAction(function(){
  6271. self.resetBranchLimits({noConfirm:true});
  6272. });
  6273. })(); break;
  6274. case "hatch":(function(){
  6275. var o=obj;
  6276. doAction(function(){
  6277. self.hatch(o);
  6278. });
  6279. })(); break;
  6280. case "birth":(function(){
  6281. var o=obj;
  6282. doAction(function(){
  6283. this.birth(o);
  6284. });
  6285. })(); break;
  6286. case "fetchNewer":(function(){
  6287. doAction(function(){
  6288. app.fetchPosts({newer:true,bypassPause:true});
  6289. });
  6290. })(); break;
  6291. case "fetchOlder":(function(){
  6292. doAction(function(){
  6293. app.fetchPosts({older:true,bypassPause:true});
  6294. });
  6295. })(); break;
  6296. case "fetchHours":(function(){
  6297. var p=param, f=found, a=app;
  6298. if (f) p=p.format2(f);
  6299. doAction(function(){
  6300. //var t0=timestamp()/1000; //let the fetch script calc it from the feed
  6301. var t1=Math.round((timeStamp()-(p*hour))/1000);
  6302. //t=t.substr(0,t.length-3);
  6303. log("fetchHours: "+p+" please wait...");
  6304. WM.fetch({bypassPause:true, older:true, targetEdge:t1, currentEdge:Math.round(timeStamp()/1000), apps:app});
  6305. });
  6306. })(); break;
  6307. case "disableRule":(function(){
  6308. doAction(function(){
  6309. self.disable();
  6310. });
  6311. })(); break;
  6312. case "enableRule":(function(){
  6313. doAction(function(){
  6314. self.enable();
  6315. });
  6316. })(); break;
  6317. case "disableChildRules":(function(){
  6318. doAction(function(){
  6319. self.disableChildren();
  6320. });
  6321. })(); break;
  6322. case "enableChildRules":(function(){
  6323. doAction(function(){
  6324. self.enableChildren();
  6325. });
  6326. })(); break;
  6327. case "disableApp":(function(){
  6328. //check for specified app
  6329. var a = WM.apps[param]||app;
  6330. doAction(function(){
  6331. a.disable();
  6332. });
  6333. })(); break;
  6334. case "enableApp":(function(){
  6335. var a = WM.apps[param]||app;
  6336. doAction(function(){
  6337. a.enable();
  6338. });
  6339. })(); break;
  6340. case "forceOpen":(function(){
  6341. doAction(function(){
  6342. post.forceOpen();
  6343. });
  6344. })(); break;
  6345. case "forceOpenFirst":(function(){
  6346. doAction(function(){
  6347. post.forceOpen({first:true});
  6348. });
  6349. })(); break;
  6350. case "emergencyOpen":(function(){
  6351. doAction(function(){
  6352. post.forceOpen({emergency:true});
  6353. });
  6354. })(); break;
  6355. case "setToCollect":(function(){
  6356. doAction(function(){
  6357. post.collect();
  6358. });
  6359. })(); break;
  6360. case "setToCollectPriority1":(function(){
  6361. doAction(function(){
  6362. post.collect();
  6363. post.setPriority(1);
  6364. });
  6365. })(); break;
  6366. case "createTimer":(function(){
  6367. var o=obj, p=param, f=found;
  6368. if (f) p=p.format2(f);
  6369. //allow new time format entry
  6370. //if the calculated time differs from the passed time, then use that calculated time, as long as it doesn't translate to 0
  6371. var t=calcTime(p);
  6372. if (t!=0 && t!=p) p=t;
  6373. //debug.print(["b",param,t,p]);
  6374. doAction(function(){
  6375. self.createTimer(p,o);
  6376. });
  6377. })(); break;
  6378. case "cancelTimer":(function(){
  6379. var o=obj;
  6380. doAction(function(){
  6381. if (o.objType=="rule") self.cancelAllTimers();
  6382. else self.cancelTimer(o);
  6383. });
  6384. })(); break;
  6385. case "createInterval":(function(){
  6386. var o=obj, p=param, f=found;
  6387. if (f) p=p.format2(f);
  6388. //allow new time format entry
  6389. //if the calculated time differs from the passed time, then use that calculated time, as long as it doesn't translate to 0
  6390. var t=calcTime(p);
  6391. if (t!=0 && t!=p) p=t;
  6392. //debug.print(["b",param,t,p]);
  6393. doAction(function(){
  6394. self.createInterval(p,o);
  6395. });
  6396. })(); break;
  6397. case "cancelInterval":(function(){
  6398. var o=obj;
  6399. doAction(function(){
  6400. if (o.objType=="rule") self.cancelAllIntervals();
  6401. else self.cancelInterval(o);
  6402. });
  6403. })(); break;
  6404. case "setWhich":(function(){
  6405. var w=param;
  6406. var f=found;
  6407. if (f) w=w.format2(f);
  6408. doAction(function(){
  6409. post.setWhich(w);
  6410. });
  6411. })(); break;
  6412. case "reIDAll": (function(){
  6413. doAction(function(){
  6414. WM.reIDAll();
  6415. });
  6416. })(); break;
  6417. case "resetAllLimits":(function(){
  6418. doAction(function(){
  6419. WM.rulesManager.resetAllLimits();
  6420. });
  6421. })(); break;
  6422. case "openPostSource":(function(){
  6423. doAction(function(){
  6424. post.openSource();
  6425. });
  6426. })(); break;
  6427. case "emptyAutolikeQueue":(function(){
  6428. doAction(function(){
  6429. WM.emptyAutoLikeQueue();
  6430. });
  6431. })(); break;
  6432. case "setHostOption":(function(){
  6433. var c=param, c2=param2, f=found;
  6434. if (f) c=c.format2(f); //format only param1
  6435. doAction(function(){
  6436. WM.setOpt(c,c2);
  6437. });
  6438. })(); break;
  6439. case "setAppOption":(function(){
  6440. var c=param, c2=param2, f=found, a=app;
  6441. if (f) c=c.format2(f); //format only param1
  6442. doAction(function(){
  6443. WM.setOpt(c,c2,a);
  6444. });
  6445. })(); break;
  6446. case "setAppTab":(function(){
  6447. if (param=="all") {
  6448. doAction(function(){
  6449. //switch to Show All
  6450. WM.console.collectTabControl.selectTab(0);
  6451. });
  6452. } else {
  6453. //check for specified app
  6454. var a = WM.apps[param]||app;
  6455. if (a||null) doAction(function(){
  6456. //switch to associated tab
  6457. click(a.collectionTabNode);
  6458. });
  6459. }})(); break;
  6460. }
  6461. }
  6462. }
  6463. }catch(e){log("WM.rulesManager.Rule.onEvent: "+e);}};
  6464. this.addAction=function(p){try{
  6465. var isNew=!exists(p);
  6466. p=p||{};
  6467. p.parent=this;
  6468. var ret=new WM.rulesManager.RuleAction(p);
  6469. this.actions.push(ret);
  6470. if (isNew) WM.rulesManager.saveRules();
  6471. }catch(e){log("WM.rulesManager.Rule.addAction: "+e);}};
  6472.  
  6473. this.addValidator=function(p){try{
  6474. var isNew=!exists(p);
  6475. p=p||{};
  6476. p.parent=this;
  6477. var ret=new WM.rulesManager.RuleValidator(p);
  6478. this.validators.push(ret);
  6479. if (isNew) WM.rulesManager.saveRules();
  6480. }catch(e){log("WM.rulesManager.Rule.addValidator: "+e);}};
  6481. this.addChild=function(p){try{
  6482. var isNew=!exists(p);
  6483. p=p||{};
  6484. p.parent=this;
  6485. p.isChild=true;
  6486. var rule=new WM.rulesManager.Rule(p);
  6487. this.kids.push(rule);
  6488. if (isNew) WM.rulesManager.saveRules();
  6489. }catch(e){log("WM.rulesManager.Rule.addChild: "+e);}};
  6490.  
  6491. this.addEgg=function(p){try{
  6492. var isNew=!exists(p);
  6493. p=p||{};
  6494. p.parent=this;
  6495. p.isEgg=true;
  6496. var rule=new WM.rulesManager.Rule(p);
  6497. this.eggs.push(rule);
  6498. if (isNew) WM.rulesManager.saveRules();
  6499. }catch(e){log("WM.rulesManager.Rule.addEgg: "+e);}};
  6500. //move eggs to parent node and destroy this node
  6501. this.hatch=function(obj){try{
  6502. var ask=WM.opts.rulesConfirmHatch
  6503. if (!ask || (ask && confirm("Hatch egg child and remove current rule and all its children?")) ) {
  6504. this.onEvent("onHatch",obj||this);
  6505. for (var e=0,egg; (egg=this.eggs[e]); e++){
  6506. egg.moveUpLevel();
  6507. }
  6508. this.remove(true); //with noConfirm
  6509. }
  6510. }catch(e){log("WM.rulesManager.Rule.hatch: "+e);}};
  6511.  
  6512. //clone eggs to parent node
  6513. this.birth=function(obj){try{
  6514. this.onEvent("onBirth",obj||this);
  6515. for (var e=0,egg; (egg=this.eggs[e]); e++){
  6516. var cloneRule=egg.saveableData;
  6517. if (this.isChild) this.parent.addChild(cloneRule);
  6518. else WM.rulesManager.newRule(cloneRule);
  6519. }
  6520. }catch(e){log("WM.rulesManager.Rule.birth: "+e);}};
  6521.  
  6522. //self rule button clicked
  6523. this.ruleButtonClicked=function(obj){try{
  6524. this.onEvent("onRuleButtonClicked",obj||this);
  6525. }catch(e){log("WM.rulesManager.Rule.ruleButtonClicked: "+e);}};
  6526.  
  6527. this.toggleContent=function(){try{
  6528. this.expanded=!this.expanded;
  6529. var btnSize=WM.opts.littleButtonSize;
  6530. with (this.contentNode)
  6531. className=className.swapWordB(this.expanded,"expanded","collapsed");
  6532. with (this.toggleImgNode)
  6533. className=className.swapWordB(this.expanded,"treeCollapse"+btnSize,"treeExpand"+btnSize);
  6534. WM.rulesManager.saveRules();
  6535. }catch(e){log("WM.rulesManager.Rule.toggleContent: "+e);}};
  6536.  
  6537. this.populateBonusList=function(){try{
  6538. var node=this.bonusDropDown;
  6539. var bonuses=[];
  6540. //get the list of accept texts for this app
  6541. if (this.appID!="") {
  6542. if (this.appID=="* All") {
  6543. //populate list with bonuses from ALL docked sidekicks
  6544. } else bonuses = mergeJSON(WM.apps[this.appID].accText,WM.apps[this.appID].userDefinedTypes);
  6545. }
  6546. bonuses["dynamic"]="* Dynamic grab";
  6547. bonuses["none"]="* None";
  6548. bonuses["wishlist"]="* Flaged as Wishlist";
  6549. bonuses["exclude"]="* Excluded types";
  6550. bonuses["send"]="* Send Unknown";
  6551. bonuses["doUnknown"]="* Get Unknown";
  6552. bonuses["*"]="* All"; //perform rule on ALL bonus types for this app
  6553.  
  6554. //sort by display text
  6555. bonuses=sortCollection(bonuses,"value");
  6556.  
  6557. //add each element to the dropdown
  6558. var elem;
  6559. node.innerHTML=""; //wipe previous list
  6560. for (var i in bonuses) {
  6561. var showI=i.removePrefix(this.appID);
  6562. node.appendChild(elem=createElement("option",{textContent:((bonuses[i].startsWith("*"))?"":((showI.startsWith("send"))?"Send ":"Get "))+bonuses[i], value:showI}));
  6563. if (this.bonus== showI) elem.selected = true;
  6564. }
  6565. }catch(e){log("WM.rulesManager.Rule.populateBonusList: "+e);}};
  6566.  
  6567. //draw to priority/rule manager or to the parent node's kids or eggs section
  6568. try{(((this.parent)?this.parent[(this.isChild)?"kidsNode":"eggsNode"]:null)||$("wmPriorityBuilder")).appendChild(
  6569. this.node=createElement("div",{className:"listItem "+((this.enabled)?"enabled":"disabled")},[
  6570. createElement("div",{className:"line"},[
  6571. createElement("div",{className:"littleButton",title:"Toggle Content",onclick:function(){self.toggleContent();}},[
  6572. this.toggleImgNode=createElement("img",{className:"resourceIcon "+(this.expanded?"treeCollapse"+WM.opts.littleButtonSize:"treeExpand"+WM.opts.littleButtonSize)}),
  6573. ]),
  6574. this.toggleNode=createElement("input",{type:"checkbox",checked:this.enabled,onchange:function(){
  6575. self.enabled=this.checked;
  6576. with (self.node) className=className.toggleWordB(!this.checked,"disabled");
  6577. WM.rulesManager.saveRules();
  6578. }}),
  6579. createElement("label",{textContent:"Title:"}),
  6580. this.titleNode=createElement("input",{className:"w400",value:(this.title||""), onchange:function(){self.title=this.value; WM.rulesManager.saveRules();}}),
  6581. //toolbox
  6582. createElement("div",{className:"littleButton oddOrange", title:"Remove Rule"},[
  6583. createElement("img",{className:"resourceIcon trash"+WM.opts.littleButtonSize,onclick:function(){self.remove();}})]),
  6584. createElement("div",{className:"littleButton oddBlue",title:"Hatch Egg Children"},[
  6585. createElement("img",{className:"resourceIcon hatch"+WM.opts.littleButtonSize,onclick:function(){self.hatch();}})]),
  6586. createElement("div",{className:"littleButton oddBlue", title:"Reset Limit Counter"},[
  6587. createElement("img",{className:"resourceIcon reset"+WM.opts.littleButtonSize,onclick:function(){self.resetLimit();}})]),
  6588. createElement("div",{className:"littleButton oddBlue", title:"Clone Rule"},[
  6589. createElement("img",{className:"resourceIcon clone"+WM.opts.littleButtonSize,onclick:function(){self.clone();}})]),
  6590. createElement("div",{className:"littleButton oddBlue", title:"Birth Egg Children"},[
  6591. createElement("img",{className:"resourceIcon birth"+WM.opts.littleButtonSize,onclick:function(){self.birth();}})]),
  6592. createElement("div",{className:"littleButton oddGreen", title:"Move Up"},[
  6593. createElement("img",{className:"resourceIcon arrowUp"+WM.opts.littleButtonSize,onclick:function(){self.moveUp();}})]),
  6594. createElement("div",{className:"littleButton oddOrange", title:"Move Down"},[
  6595. createElement("img",{className:"resourceIcon arrowDown"+WM.opts.littleButtonSize,onclick:function(){self.moveDown();}})]),
  6596. createElement("div",{className:"littleButton oddGreen", title:"Move Up Level"},[
  6597. createElement("img",{className:"resourceIcon moveUpLevelLeft"+WM.opts.littleButtonSize,onclick:function(){self.moveUpLevel();}})]),
  6598. createElement("div",{className:"littleButton oddOrange", title:"Move Down Level"},[
  6599. createElement("img",{className:"resourceIcon moveInLevel"+WM.opts.littleButtonSize,onclick:function(){self.moveDownLevel();}})]),
  6600. createElement("div",{className:"littleButton oddBlue", title:"Show Source"},[
  6601. createElement("img",{className:"resourceIcon object"+WM.opts.littleButtonSize,onclick:function(){promptText(JSON.stringify(self.saveableData),true);}})]),
  6602.  
  6603. createElement("div",{className:"indent littleButton "+((this.isGlobal)?"oddOrange":"oddGreen"), title:((this.isGlobal)?"Disable Profile Sharing":"Share With Other Profiles")},[
  6604. this.toggleGlobalButton=createElement("img",{className:"resourceIcon "+((this.isGlobal)?"removeGlobal":"addGlobal")+WM.opts.littleButtonSize,onclick:function(){self.isGlobal=!self.isGlobal; WM.rulesManager.saveRules();}})]),
  6605. ]),
  6606. this.contentNode=createElement("div",{className:"subsection "+(this.expanded?"expanded":"collapsed")},[
  6607. (this.basedOn)?createElement("div",{className:"line"},[
  6608. createElement("label",{textContent:"This rule is linked to a post: ",title:"This rule is linked to a post. Validators can draw information from that post so you can easily capture similar posts just by editing the captured texts to suit your needs. Post linking is not carried from session to session."}),
  6609. this.basedOnNode=createElement("span",{textContent:this.basedOn.id}),
  6610. ]):null,
  6611. createElement("div",{className:"line"},[
  6612. createElement("label",{textContent:"Limit:"}),
  6613. this.limitNode=createElement("input",{value:(this.limit||0), onchange:function(){self.limit=this.value;WM.rulesManager.saveRules();}}),
  6614. ]),
  6615. createElement("div",{className:"line"},[
  6616. createElement("label",{textContent:"Counter:"}),
  6617. this.limitCounterNode=createElement("input",{value:(this.limitCount||0), onchange:function(){self.limitCount=this.value;WM.rulesManager.saveRules();}}),
  6618. ]),
  6619. this.ruleButtonHousingNode=createElement("div",{className:"line", style:(this.usesRuleButton())?"":"display:none;"},[
  6620. createElement("label",{textContent:"Rule Button:"}),
  6621. this.ruleButtonNode=createElement("button",{type:"button", textContent:"onRuleButtonClicked()", onclick:function(){self.ruleButtonClicked();}}),
  6622. ]),
  6623. //validation subbox
  6624. createElement("div",{className:"line"},[
  6625. createElement("label",{textContent:"For Activating Objects:",title:"These validators attempt to match a post or other activating object, such as feed, feed filter, app, or this rule. All activators that match here then have the following actions performed at certain events."}),
  6626. createElement("div",{className:"littleButton oddGreen",onclick:function(){self.addValidator();},title:"Add Validator"},[
  6627. createElement("img",{className:"resourceIcon plus"+WM.opts.littleButtonSize}),
  6628. ]),
  6629. this.validationNode=createElement("div",{className:"subsection"}),
  6630. ]),
  6631. //actions subbox
  6632. createElement("div",{className:"line"},[
  6633. createElement("label",{textContent:"Do Actions:",title:"Actions to perform on matching posts."}),
  6634. createElement("div",{className:"littleButton oddGreen",onclick:function(){self.addAction();},title:"Add Action"},[
  6635. createElement("img",{className:"resourceIcon plus"+WM.opts.littleButtonSize}),
  6636. ]),
  6637. this.actionsNode=createElement("div",{className:"subsection"}),
  6638. ]),
  6639. //kids subbox
  6640. createElement("div",{className:"line"},[
  6641. createElement("label",{textContent:"Child Rules:",title:"Child rules are nested rules which are applied to matching posts at the same time the parent rule is applied. Child rules can have different validators, but will only activate if the parent validators have already matched a post."}),
  6642. createElement("div",{className:"littleButton oddGreen",onclick:function(){self.addChild();},title:"Add Child"},[
  6643. createElement("img",{className:"resourceIcon plus"+WM.opts.littleButtonSize}),
  6644. ]),
  6645. this.kidsNode=createElement("div",{className:"subsection"}),
  6646. ]),
  6647. //egg kids subbox
  6648. createElement("div",{className:"line"},[
  6649. createElement("label",{textContent:"Egg Rules:", title:"Eggs are potential future rules. When 'hatched', these eggs take the place of the parent rule. The parent rule and its normal children are destroyed."}),
  6650. createElement("div",{className:"littleButton oddGreen",onclick:function(){self.addEgg();},title:"Add Egg"},[
  6651. createElement("img",{className:"resourceIcon plus"+WM.opts.littleButtonSize}),
  6652. ]),
  6653. this.eggsNode=createElement("div",{className:"subsection"}),
  6654. ]),
  6655. ]),
  6656. ])
  6657. );}catch(e){log("WM.rulesManager.Rule.init.drawRule: "+e);}
  6658.  
  6659. //list the actions for this rule
  6660. if (isArrayAndNotEmpty(params.actions)) for (var i=0,action; (action=params.actions[i]); i++) {
  6661. this.addAction(action);
  6662. }
  6663. //list the validators for this rule
  6664. if (isArrayAndNotEmpty(params.validators)) for (var i=0,validator; (validator=params.validators[i]); i++) {
  6665. this.addValidator(validator);
  6666. }
  6667. //list the kids for this rule
  6668. if (isArrayAndNotEmpty(params.kids)) for (var i=0,kid; (kid=params.kids[i]); i++) {
  6669. this.addChild(kid);
  6670. }
  6671. //list the egg kids for this rule
  6672. if (isArrayAndNotEmpty(params.eggs)) for (var i=0,egg; (egg=params.eggs[i]); i++) {
  6673. this.addEgg(egg);
  6674. }
  6675. //create cleanup function
  6676. this.cleanup=function(){try{
  6677. for (var t in this.timers) {
  6678. window.clearTimeout(this.timers[t]);
  6679. }
  6680. for (var i in this.intervals) {
  6681. window.clearInterval(this.intervals[i]);
  6682. }
  6683. var self=this;
  6684. removeEventListener("beforeunload",self.cleanup,false);
  6685. }catch(e){log("WM.rulesManager.Rule.cleanup: "+e);}};
  6686. addEventListener("beforeunload",self.cleanup,false);
  6687. this.onEvent("onRuleCreated");
  6688. return self;
  6689.  
  6690. }catch(e){log("WM.rulesManager.Rule.init: "+e);}};
  6691.  
  6692. //***************************************************************************************************************************************
  6693. //***** App Class
  6694. //***************************************************************************************************************************************
  6695. WM.App = function(params){try{
  6696. this.objType="app";
  6697. var self=this;
  6698. //expected: id, name, namespace, icon
  6699. params=params||{};
  6700.  
  6701. //create the masterswitch
  6702. var testms=WM.quickOpts.masterSwitch[params.appID];
  6703. WM.quickOpts.masterSwitch[params.appID]=(testms==null||testms=="undefined")?true:testms;
  6704.  
  6705. //set defaults
  6706. this._enabled=WM.quickOpts.masterSwitch[params.appID]||false;
  6707. this._paused=false;
  6708. this.tests={};
  6709. this.typesPaused=[];
  6710. this.pausedTypesListNodes={};
  6711. this._acceptCount=0;
  6712. this._failCount=0;
  6713. this.node=null;
  6714. this.expanded=false;
  6715. this.kids=[]; //contains additional filtered apps
  6716. //setup config for this sidekick
  6717. this.opts = {};
  6718. this.config = new Config({
  6719. storageName:"settings_"+params.appID+"_"+(WM.quickOpts.useGlobalSettings?"global":WM.currentUser.profile),
  6720. onSave:WM.onSave,
  6721. title:"FB Wall Manager "+WM.version+(WM.quickOpts.useGlobalSettings?" (!! Global Settings !!)":""),
  6722. logo:createElement("span",{}[
  6723. createElement("img",{className:"logo",src:"",textContent:"v"+WM.version}),
  6724. createElement("text","v"+WM.version)
  6725. ]),
  6726. css:(
  6727. WM.console.dynamicIcons()+
  6728. jsForms.globalStyle()
  6729. ),
  6730. settings:{
  6731. btn_useGlobal:{
  6732. type:"button",
  6733. label:"Use Global Settings",
  6734. title:"Switch to using a global storage for settings. Those settings can then be used by other accounts (not browser profiles).",
  6735. script:function(){
  6736. if (WM.quickOpts.useGlobalSettings||false) {
  6737. //already using global settings
  6738. return;
  6739. }
  6740. if (confirm("Switch to using global (shared) settings?")){
  6741. WM.quickOpts.useGlobalSettings=true;
  6742. WM.saveQuickOpts();
  6743. this.config.title = "FB Wall Manager "+WM.version+" (!! Global Settings !!))";
  6744. this.config.storageName = "settings_"+params.appID+"_global";
  6745. this.config.values=this.config.read();
  6746. this.config.configure();
  6747. this.config.reload();
  6748. }
  6749. },
  6750. },
  6751. btn_useOwnProfile:{
  6752. type:"button",
  6753. label:"Use Profile Settings",
  6754. title:"Switch to using your own profile storage for settings.",
  6755. script:function(){
  6756. if (!(WM.quickOpts.useGlobalSettings||false)) {
  6757. //already using profile settings
  6758. return;
  6759. }
  6760. if (confirm("Switch to using your own profile settings?")){
  6761. WM.quickOpts.useGlobalSettings=false;
  6762. WM.saveQuickOpts();
  6763. this.config.title = "FB Wall Manager "+WM.version;
  6764. this.config.storageName = "settings_"+params.appID+"_"+WM.currentUser.profile;
  6765. this.config.values=this.config.read();
  6766. this.config.configure();
  6767. this.config.reload();
  6768. }
  6769. },
  6770. },
  6771. },
  6772. });
  6773. //setup user defined accept texts
  6774. try{
  6775. if (WM.quickOpts.userDefinedTypes) {
  6776. this.userDefinedTypes=WM.quickOpts.userDefinedTypes[params.appID]||{};
  6777. } else {
  6778. WM.quickOpts.userDefinedTypes={};
  6779. WM.quickOpts.userDefinedTypes[params.appID]={};
  6780. WM.saveQuickOpts();
  6781. }
  6782. }catch(e){log("WM.App.init: userDefinedTypes: "+e);}
  6783.  
  6784. //use passed params
  6785. for (var p in params) this[p]=params[p];
  6786.  
  6787. //enable/disable all sidekick functions
  6788. this.enable=function(){try{this.enabled=true;}catch(e){log("WM.App.enable: "+e);}};
  6789. this.disable=function(){try{this.enabled=false;}catch(e){log("WM.App.disable: "+e);}};
  6790. this.toggle=function(){try{this.enabled=!this.enabled;}catch(e){log("WM.App.toggle: "+e);}};
  6791.  
  6792. //pause collection for this app
  6793. this.pause=function(){try{this.paused=true;}catch(e){log("WM.App.pause: "+e);}}
  6794. this.unPause=function(){try{this.paused=false;}catch(e){log("WM.App.unPause: "+e);}}
  6795.  
  6796. //user defined types
  6797. this.addUDT=function(params,drawOnly){try{
  6798. //validate params or ask for input
  6799. if (!exists(params) || !params.id) {
  6800. params=params||{};
  6801. var udtname=prompt("Enter the text name of the bonus type you wish to make (ie 'Horse')\n","");
  6802. var udtid=this.appID+udtname.noSpaces().toLowerCase();
  6803. udtid=prompt("OK, your type will read as '"+udtname+"'.\nNow modify this bonus type code to suit your needs.\n\nTip: You should prefix this code with the appID '"+this.appID+"', but it is not required.\nTip: Most sidekicks use lowercase and no spaces, but again, this is not a requirement.\n", udtid);
  6804. if (udtid.trim()){
  6805. params.id=udtid.trim();
  6806. params.name=udtname;
  6807. } else {
  6808. alert("You supplied a blank user defined type ID. No type was created.");
  6809. return false;
  6810. }
  6811. }
  6812. if (!drawOnly){
  6813. this.userDefinedTypes[params.id]=params.name;
  6814. WM.quickOpts.userDefinedTypes[this.appID]=this.userDefinedTypes;
  6815. WM.saveQuickOpts();
  6816. }
  6817. //draw the udt node
  6818. if (this.udtNode){
  6819. this.udtNode.appendChild(
  6820. createElement("div",{className:"listItem"},[
  6821. createElement("label",{textContent:params.id+" : "}),
  6822. createElement("input",{value:params.name,title:"The display name of this type, used wherever bonus types are identified or selected.", onchange:function(){
  6823. self.userDefinedTypes[params.id]=this.value;
  6824. WM.quickOpts.userDefinedTypes[self.appID]=self.userDefinedTypes;
  6825. WM.saveQuickOpts();
  6826. }}),
  6827. createElement("div",{className:"littleButton oddOrange", title:"Remove User-Defined Type"},[
  6828. createElement("img",{className:"resourceIcon trash" +WM.opts.littleButtonSize,onclick:function(){
  6829. var ask=WM.opts.appsConfirmDeleteUDT;
  6830. if (!ask || (ask && confirm("Delete User Defined Type?"))) {
  6831. delete self.userDefinedTypes[params.id];
  6832. WM.quickOpts.userDefinedTypes[self.appID]=self.userDefinedTypes;
  6833. WM.saveQuickOpts();
  6834. remove (this.parentNode.parentNode);
  6835. }
  6836. }})
  6837. ]),
  6838. (this.accText[params.id]||null)?createElement("span",{title:"The type id you created exactly matches one provided by the sidekick for this app. If you did not intend to overwrite that bonus's display text, you may wish to create another type id and destroy this one.",style:"color:red;",textContent:"Overwrites a sidekick-provided bonus type id."}):null,
  6839. ])
  6840. );
  6841. }
  6842. }catch(e){log("WM.App.addUDT: "+e);}}
  6843.  
  6844. //unpause all bonus types for this app
  6845. this.unpauseAllTypes=function(){try{
  6846. for (var i=this.typesPaused.length-1;i>=0;i--){
  6847. WM.unPauseByType(this,this.typesPaused[i]);
  6848. }
  6849. }catch(e){log("WM.App.unpauseAllTypes: "+e);}};
  6850.  
  6851.  
  6852. //mass set priority for entire app post collection
  6853. this.setPriority=function(n){try{
  6854. for (var p in WM.posts) {
  6855. var post=WM.posts[p];
  6856. if (post.app==this) post.setPriority(n);
  6857. }
  6858. }catch(e){log("WM.App.setPriority: "+e);}};
  6859.  
  6860. //mass set priority for all posts of type
  6861. this.setPriorityByType=function(w,n){try{
  6862. for (var p in WM.posts) {
  6863. var post=WM.posts[p];
  6864. if (post.app==this && post.which==w) post.setPriority(n);
  6865. }
  6866. }catch(e){log("WM.App.setPriorityByType: "+e);}};
  6867. //reset accept/fail counters
  6868. this.resetCounter=function(){try{
  6869. this.acceptCount=0;
  6870. this.failCount=0;
  6871. }catch(e){log("WM.App.resetCounter: "+e);}};
  6872.  
  6873. //reset all config options for this app
  6874. //except those outside the standard branch (dontsteal,blockautolike,etc.)
  6875. this.resetConfig=function(){try{
  6876. var ask=WM.opts.configConfirmRestore;
  6877. if (!ask || (ask && confirm("Restore sidekick settings to defaults?"))) {
  6878. this.config.configure({reset:true});
  6879. this.config.save();
  6880. }
  6881. }catch(e){log("WM.App.resetConfig: "+e);}};
  6882. //fetch posts only for this app
  6883. //normally used for initial fetching only
  6884. this.fetchPosts=function(){try{
  6885. WM.fetch({bypassPause:true, apps:this});
  6886. }catch(e){log("WM.App.fetchPosts: "+e);}};
  6887.  
  6888. this.fetchNewer=function(){try{
  6889. WM.fetch({
  6890. newer:true,
  6891. apps:this,
  6892. bypassPause:true,
  6893. bypassAppDisabled:true
  6894. });
  6895. }catch(e){log("WM.App.fetchNewer: "+e);}};
  6896.  
  6897. this.fetchOlder=function(){try{
  6898. WM.fetch({
  6899. older:true,
  6900. apps:this,
  6901. bypassPause:true,
  6902. bypassAppDisabled:true
  6903. });
  6904. }catch(e){log("WM.App.fetchOlder: "+e);}};
  6905.  
  6906. //get a list of posts for this app from the global posts list
  6907. this.__defineGetter__("posts",function(){try{
  6908. return matchByParam(WM.posts,"app",this,"object");
  6909. }catch(e){log("WM.App.getPosts: "+e);}});
  6910. //detect if this sidekick said it was chrome compatible
  6911. this.__defineGetter__("isVer3",function(){try{
  6912. return this.flags.postMessageCompatible || this.flags.worksInChrome;
  6913. }catch(e){log("WM.App.isVer3: "+e);}});
  6914.  
  6915. //detect if is paused
  6916. this.__defineGetter__("paused",function(){try{
  6917. return this._paused;
  6918. }catch(e){log("WM.App.paused: "+e);}});
  6919. this.__defineSetter__("paused",function(v){try{
  6920. this._paused=v;
  6921. //update the sidekick page button graphics
  6922. var btn=this.pauseButtonNode;
  6923. if (btn) {
  6924. var btnSize=WM.opts.littleButtonSize;
  6925. with (btn.parentNode)
  6926. className=className.swapWordB(this._paused,"oddGreen","oddOrange");
  6927. with (btn)
  6928. className=className.swapWordB(this._paused,"playRight"+btnSize,"pause"+btnSize);
  6929. }
  6930. //do events
  6931. if (this._paused) WM.rulesManager.doEvent("onAppPaused",this);
  6932. else WM.rulesManager.doEvent("onAppUnpaused",this);
  6933. }catch(e){log("WM.App.paused: "+e);}});
  6934. //detect if is enabled
  6935. this.__defineGetter__("enabled",function(){try{
  6936. return this._enabled;
  6937. }catch(e){log("WM.App.enabled: "+e);}});
  6938. this.__defineSetter__("enabled",function(v){try{
  6939. this._enabled=v;
  6940. //update the WM.quickOpts
  6941. WM.quickOpts.masterSwitch[this.appID]=this._enabled;
  6942. WM.saveQuickOpts();
  6943. //update the sidekick page graphics
  6944. if (this.toggleNode) this.toggleNode.checked=this._enabled;
  6945. if (this.node) with (this.node){
  6946. className=className.swapWordB(this._enabled,"enabled","disabled");
  6947. }
  6948. //do events
  6949. if (this._enabled) WM.rulesManager.doEvent("onAppEnabled",this);
  6950. else WM.rulesManager.doEvent("onAppDisabled",this);
  6951. }catch(e){log("WM.App.enabled: "+e);}});
  6952. this.__defineGetter__("acceptCount",function(){try{
  6953. return this._acceptCount;
  6954. }catch(e){log("WM.App.acceptCount: "+e);}});
  6955. this.__defineSetter__("acceptCount",function(v){try{
  6956. this._acceptCount=v;
  6957. if (this.acceptCounterNode) this.acceptCounterNode.textContent=v;
  6958. }catch(e){log("WM.App.acceptCount: "+e);}});
  6959. this.__defineGetter__("failCount",function(){try{
  6960. return this._failCount;
  6961. }catch(e){log("WM.App.failCount: "+e);}});
  6962. this.__defineSetter__("failCount",function(v){try{
  6963. this._failCount=v;
  6964. if (this.failCounterNode) this.failCounterNode.textContent=v;
  6965. }catch(e){log("WM.App.failCount: "+e);}});
  6966.  
  6967. this.__defineGetter__("totalCount",function(){try{
  6968. return this._failCount+this._acceptCount;
  6969. }catch(e){log("WM.App.totalCount: "+e);}});
  6970.  
  6971. //detect if this app is bundled with another app
  6972. //return the main app in this bundle
  6973. this.__defineGetter__("synApp",function(){try{
  6974. return this.parent||this;
  6975. }catch(e){log("WM.App.synApp: "+e);}});
  6976. this.toggleContent=function(){try{
  6977. this.expanded=!this.expanded;
  6978. var btnSize=WM.opts.littleButtonSize;
  6979. with (this.contentNode)
  6980. className=className.swapWordB(this.expanded,"expanded","collapsed");
  6981. with (this.toggleImgNode)
  6982. className=className.swapWordB(this.expanded,"treeCollapse"+btnSize,"treeExpand"+btnSize);
  6983. }catch(e){log("WM.App.toggleContent: "+e);}};
  6984.  
  6985. this.showConfig=function(){try{
  6986. this.config.open();
  6987. }catch(e){log("WM.App.showConfig: "+e);}};
  6988.  
  6989. this.disableOpt=function(w){try{
  6990. this.opts[w]=false;
  6991. this.config.set(w,false);
  6992. this.config.save();
  6993. }catch(e){log("WM.App.disableOpt: "+e);}};
  6994.  
  6995. this.enableOpt=function(w){try{
  6996. this.opts[w]=true;
  6997. this.config.set(w,true);
  6998. this.config.save();
  6999. }catch(e){log("WM.App.enableOpt: "+e);}};
  7000. //add menu elements
  7001. try{
  7002. /* no longer used in WM3
  7003. if (this.menu) {
  7004. //prefix all menu elements with the appID
  7005. this.menu=WM.dock.fixMenu(this.menu,this.appID);
  7006. //append this app's menu settings
  7007. this.settingsBranch=WM.config.append({branch:"wmtab_games",data:this.menu});
  7008. }
  7009. //prefix all test returns with the appID
  7010. WM.dock.fixTests(this.tests,this);
  7011. //prefix all accept text id's with the appID
  7012. WM.dock.fixAcceptTexts(this);
  7013. */
  7014. //new method
  7015. if (this.menu) this.config.append({data:this.menu});
  7016. //I should really move these into the sidekick realm
  7017. var data={};
  7018. data["dynamic"+this.appID]=checkBox(this.name+" ("+this.appID+")",true);
  7019. WM.config.append({branch:"enableDynamic",data:data});
  7020.  
  7021. data={}; data[this.appID+"dontsteal"]=checkBox(this.name);
  7022. WM.config.append({branch:"dontstealBlock",data:data});
  7023. data={}; data["hide"+this.appID]=checkBox(this.name);
  7024. WM.config.append({branch:"filterapps",data:data});
  7025. data={}; data["nolike"+this.appID]=checkBox(this.name);
  7026. WM.config.append({branch:"blockautolikebygame",data:data});
  7027. } catch(e) {log("WM.App.init:addMenuElements: "+e);};
  7028. //draw to #sidekickList (WM.console.sidekickNode)
  7029. try{
  7030. WM.console.sidekickNode.appendChild(
  7031. this.node=createElement("div",{className:"listItem "+((this.enabled)?"enabled":"disabled")},[
  7032. createElement("div",{className:"line"},[
  7033. createElement("div",{className:"littleButton",title:"Toggle Content",onclick:function(){self.toggleContent();}},[
  7034. this.toggleImgNode=createElement("img",{className:"resourceIcon "+(this.expanded?"treeCollapse"+WM.opts.littleButtonSize:"treeExpand"+WM.opts.littleButtonSize)}),
  7035. ]),
  7036. this.toggleNode=createElement("input",{type:"checkbox",checked:this.enabled,onchange:function(){
  7037. self.enabled=this.checked;
  7038. with (self.node) className=className.toggleWordB(!this.checked,"disabled");
  7039. }}),
  7040. (this.icon)?createElement("img",{className:"icon crisp", src:this.icon,style:"width: 32px;vertical-align: middle"}):null,
  7041. createElement("label",{textContent: this.name}),
  7042. //toolbox
  7043. createElement("div",{className:"littleButton odd"+(this.paused?"Green":"Orange"), title:"Pause/Unpause"},[
  7044. this.pauseButtonNode=createElement("img",{className:"resourceIcon "+(this.paused?"playRight":"pause")+WM.opts.littleButtonSize,onclick:function(){self.paused=!self.paused;}})]),
  7045. createElement("div",{className:"littleButton oddBlue", title:"Reset config for this app"},[
  7046. createElement("img",{className:"resourceIcon uncheckAll"+WM.opts.littleButtonSize,onclick:function(){self.resetConfig();}})]),
  7047. createElement("div",{className:"littleButton oddBlue", title:"Fetch Newer Posts"},[
  7048. createElement("img",{className:"resourceIcon rssUpRight"+WM.opts.littleButtonSize,onclick:function(){self.fetchNewer();}})]),
  7049. createElement("div",{className:"littleButton", title:"Fetch Older Posts"},[
  7050. createElement("img",{className:"resourceIcon rssDownLeft" +WM.opts.littleButtonSize,onclick:function(){self.fetchOlder();}})]),
  7051. //new sidekick config button
  7052. this.configButton=createElement("button",{textContent:"Options", onclick:function(){self.config.open();}}),
  7053. ]),
  7054. this.contentNode=createElement("div",{className:"subsection "+(this.expanded?"expanded":"collapsed")},[
  7055. createElement("div",{className:"line"},[
  7056. createElement("label",{textContent:"App ID:"}),
  7057. createElement("span",{textContent:this.appID}),
  7058. ]),
  7059. createElement("div",{className:"line"},[
  7060. createElement("label",{textContent:"Support Provided By:"}),
  7061. (this.desc)?createElement("span",{textContent: this.desc}):null, //provided in sidekick block
  7062. ]),
  7063. createElement("div",{className:"line"},[
  7064. createElement("label",{textContent:"Sidekick Help Link:"}),
  7065. (this.helpLink)?createElement("a",{href:this.helpLink,textContent:this.helpLink}):null, //provided in sidekick block
  7066. ]),
  7067. //browsers supported
  7068. createElement("div",{className:"line"},[
  7069. createElement("label",{textContent:"Browsers Supported:",style:"vertical-align:top;"}),
  7070. createElement("img",{className:"resourceIcon firefox16", style:"display:inline-block;",title:"FireFox"}),
  7071. (this.isVer3)?createElement("img",{className:"resourceIcon chrome16", style:"display:inline-block;",title:"Google Chrome"}):null,
  7072. ]),
  7073. //types paused subbox
  7074. createElement("div",{className:"line"},[
  7075. createElement("label",{textContent:"Types Paused:",title:"This is a list of bonus types that are currently paused for this app."}),
  7076. createElement("div",{className:"littleButton oddGreen",onclick:function(){self.unpauseAllTypes();},title:"Unpause all types by this app."},[
  7077. createElement("img",{className:"resourceIcon playRight"+WM.opts.littleButtonSize}),
  7078. ]),
  7079. this.typesPausedNode=createElement("div",{className:"subsection"}),
  7080. ]),
  7081. //attached apps
  7082. createElement("div",{className:"line"},[
  7083. createElement("label",{textContent:"Attached Apps:",title:"Additional apps filtered and processed by this sidekick."}),
  7084. this.filtersNode=createElement("div",{className:"subsection"}),
  7085. ]),
  7086. //helpers subbox
  7087. createElement("div",{className:"line"},[
  7088. createElement("label",{textContent:"Helpers:",title:"Sidekick helpers"}),
  7089. this.helpersNode=createElement("div",{className:"subsection"}),
  7090. ]),
  7091. //user defined types subbox
  7092. createElement("div",{className:"line"},[
  7093. createElement("label",{textContent:"User-Defined Types:",title:"User Defined Types ('which')"}),
  7094. createElement("div",{className:"littleButton oddGreen",onclick:function(){self.addUDT();},title:"Add New User Defined Type"},[
  7095. createElement("img",{className:"resourceIcon plus"+WM.opts.littleButtonSize}),
  7096. ]),
  7097. this.udtNode=createElement("div",{className:"subsection"}),
  7098. ]),
  7099. ]),
  7100. ])
  7101. );
  7102. }catch(e){log("WM.App.init:addSidekickElement: "+e);};
  7103. //create feed filters for this app
  7104. try{
  7105. var feeds=WM.feedManager.feeds;
  7106. for (var f=0,len=feeds.length;f<len;f++){
  7107. feeds[f].addFilter({id:"app_"+this.appID});
  7108. }
  7109. }catch(e){log("WM.App.init:createFeedFilters: ")+e;}
  7110.  
  7111. //draw to collection filter coolbar
  7112. try{
  7113. //create game filter buttons on the WM.console
  7114. var coolBar = WM.console.collectTabControl;
  7115. if (coolBar) {
  7116. //add a tab for this filter
  7117. var tab = coolBar.addTab({
  7118. text:(this.name||""),
  7119. image:(this.icon||null),
  7120. appFilter:this.appID,
  7121. onSelect:WM.setAppFilter,
  7122. selected:(WM.quickOpts.filterApp==this.appID),
  7123. });
  7124. this.collectionTabNode=tab.buttonNode;
  7125. //force the image to have the 'crisp' drawing style
  7126. tab.buttonNode.childNodes[0].className="icon crisp";
  7127. //add accept/fail counters
  7128. this.failCount=0;
  7129. this.acceptCount=0;
  7130. tab.buttonNode.insertBefore(
  7131. createElement("div",{className:"accFailBlock"},[
  7132. this.failCounterNode=createElement("span",{className:"fail",textContent:"0"}),
  7133. this.acceptCounterNode=createElement("span",{className:"accept",textContent:"0"}),
  7134. ])
  7135. , tab.textNode);
  7136. }
  7137. } catch(e) {log("WM.App.init:addConsoleElement: "+e);};
  7138. //show additional filtered apps
  7139. try{
  7140. if (isArrayAndNotEmpty(this.addFilters)) {
  7141. for (var f,filt;(filt=this.addFilters[f]);f++){
  7142. //create an app object for this filter
  7143. filt.parent=this;
  7144. this.kids.push(new WM.App(filt));
  7145. if (this.filtersNode) this.filtersNode.appendChild(
  7146. createElement("div",{className:"line"},[
  7147. createElement("img",{className:"icon crisp", src:filt.icon||null}),
  7148. createElement("text",filt.name),
  7149. ])
  7150. );
  7151. }
  7152. }
  7153. } catch(e) {log("WM.App.init:addFilteredApps: "+e);};
  7154. //draw my user defined types
  7155. try{
  7156. for (var u in this.userDefinedTypes){
  7157. this.addUDT({id:u,name:this.userDefinedTypes[u]},true);
  7158. }
  7159. }catch(e){log("WM.App.init: drawUDTs: "+e);}
  7160. //do events
  7161. WM.rulesManager.doEvent("onSidekickReady",this);
  7162. return self;
  7163. }catch(e){log("WM.App.init: "+e);}};
  7164.  
  7165. //***************************************************************************************************************************************
  7166. //***** Post Class
  7167. //***************************************************************************************************************************************
  7168. WM.Post = function(params){try{
  7169. this.objType="post";
  7170. var self=this;
  7171. params=params||{};
  7172.  
  7173. //set defaults
  7174. this.state=""; //classnames
  7175. this.flags=0; //similar to classnames
  7176. this.node=null; //collector panel node
  7177. this.originalData=JSON.stringify(params); //clone the original data from facebook for viewing later
  7178. //convert FQL data to what we have previously expected from graph api
  7179. this.id=params.post_id;
  7180. this.app=WM.apps[params.app_id];
  7181. this.fromID=params.source_id.toString()||this.id.split("_")[0];
  7182. this.permalink="http://www.facebook.com/"+this.id.split("_")[0]+"/posts/"+this.id.split("_")[1];
  7183. this.fromName=params.fromName;
  7184. this.date=params.created_time;
  7185. this.message=params.message;
  7186. this.name=params.attachment.name;
  7187. this.title=params.name;
  7188. this.caption=params.attachment.caption;
  7189. this.description=params.attachment.description;
  7190. this.picture=params.app_data.images;
  7191. try{
  7192. this.picture = JSON.parse(this.picture)[0].fbml.match(/https?\:.+\.(png|gif|jpg)/)[0];
  7193. } catch(e){
  7194. //picture data match failed, no big deal
  7195. //leave the data messed up because rules manager might still be able to use it
  7196. }
  7197. this.linkHref=(params.action_links||null)?params.action_links[0].href:(params.attachment.media||null)?params.attachment.media[0].href:"";
  7198. this.linkText=(params.action_links||null)?params.action_links[0].text:(params.attachment.media||null)?params.attachment.media[0].alt:"";
  7199. this.targetID=params.target_id;
  7200. this.isLiked=params.like_info.user_likes;
  7201. //convert a unix date to a readable date
  7202. this.realtime=(new Date(this.date*1000).toLocaleString());
  7203. //set a timer on the post for delayed deletion
  7204. this.drawTime=timeStamp();
  7205. this._isLiked=false;
  7206. this._isPinned=false;
  7207. this._isPaused=false;
  7208. this._isScam=false;
  7209. this._isW2W=false;
  7210. this._isForMe=false;
  7211. this._isMyPost=false;
  7212. this._isWishlist=false;
  7213. this._isUndefined=false;
  7214. this._status=0;
  7215. this._isTimeout=false;
  7216. this._isFailed=false;
  7217. this._isAccepted=false;
  7218. this._isExcluded=false;
  7219. this._isStale=false;
  7220. this._isCollect=false;
  7221. this._isWorking=false;
  7222. this._which=null;
  7223. this._idText="";
  7224. //use passed params
  7225. //for (var p in params) this[p]=params[p];
  7226.  
  7227. //link to our application array of objects
  7228. //this.app=WM.apps[this.application.id];
  7229.  
  7230. //shortcuts to app details
  7231. this.__defineGetter__("synApp",function(){try{
  7232. return this.app.synApp;
  7233. }catch(e){log("WM.Post.synApp: "+e);}});
  7234.  
  7235. this.__defineGetter__("postedDay",function(){try{
  7236. var d=new Date(this.date*1000);
  7237. return d.getFullYear()+"/"+d.getMonth()+"/"+d.getDay();
  7238. }catch(e){log("WM.Post.postedDay: "+e);}});
  7239.  
  7240. this.__defineGetter__("postedHour",function(){try{
  7241. var d=new Date(this.date*1000);
  7242. var h=d.getHours();
  7243. var pm=(h/12)>1;
  7244. return d.getFullYear()+"/"+d.getMonth()+"/"+d.getDay()+" "+((h>12)?h-12:h)+":00"+((pm)?"PM":"AM");
  7245. }catch(e){log("WM.Post.postedHour: "+e);}});
  7246.  
  7247. this.__defineGetter__("appID",function(){try{
  7248. return this.app.appID;
  7249. }catch(e){log("WM.Post.appID: "+e);}});
  7250.  
  7251. this.__defineGetter__("appName",function(){try{
  7252. return this.app.name;
  7253. }catch(e){log("WM.Post.appName: "+e);}});
  7254.  
  7255. //get/set priority
  7256. this.__defineGetter__("priority",function(){try{
  7257. return this._priority;
  7258. }catch(e){log("WM.Post.priority: "+e);}});
  7259. this.__defineSetter__("priority",function(v){try{
  7260. this._priority=v;
  7261. }catch(e){log("WM.Post.priority: "+e);}});
  7262.  
  7263. //get/set liked status
  7264. this.__defineGetter__("isLiked",function(){try{
  7265. return this._isLiked;
  7266. }catch(e){log("WM.Post.isLiked: "+e);}});
  7267. this.__defineSetter__("isLiked",function(v){try{
  7268. this._isLiked=v;
  7269. //remove the toolbutton if liked
  7270. if (this.node) with (this.node)
  7271. className=className.toggleWordB(this._isLiked,"liked");
  7272. if (this.likeButtonNode) with (this.likeButtonNode)
  7273. className=className.toggleWordB(this._isLiked,"hidden");
  7274. }catch(e){log("WM.Post.isLiked: "+e);}});
  7275.  
  7276. //identification flags
  7277. this.__defineGetter__("isScam",function(){try{
  7278. return this._isScam;
  7279. }catch(e){log("WM.Post.isScam: "+e);}});
  7280. this.__defineSetter__("isScam",function(v){try{
  7281. this._isScam=v;
  7282. if (this.node) with (this.node)
  7283. className=className.toggleWordB(this._isScam,"scam");
  7284. }catch(e){log("WM.Post.isScam: "+e);}});
  7285.  
  7286. this.__defineGetter__("isMyPost",function(){try{
  7287. return this._isMyPost;
  7288. }catch(e){log("WM.Post.isMyPost: "+e);}});
  7289. this.__defineSetter__("isMyPost",function(v){try{
  7290. this._isMyPost=v;
  7291. if (this.node) with (this.node)
  7292. className=className.toggleWordB(this._isMyPost,"isMyPost");
  7293. }catch(e){log("WM.Post.isMyPost: "+e);}});
  7294.  
  7295. this.__defineGetter__("isW2W",function(){try{
  7296. return this._isW2W;
  7297. }catch(e){log("WM.Post.isW2W: "+e);}});
  7298. this.__defineSetter__("isW2W",function(v){try{
  7299. this._isW2W=v;
  7300. if (this.node) with (this.node)
  7301. className=className.toggleWordB(this._isW2W,"w2w");
  7302. }catch(e){log("WM.Post.isW2W: "+e);}});
  7303.  
  7304. this.__defineGetter__("isForMe",function(){try{
  7305. return this._isForMe;
  7306. }catch(e){log("WM.Post.isForMe: "+e);}});
  7307. this.__defineSetter__("isForMe",function(v){try{
  7308. this._isForMe=v;
  7309. if (this.node) with (this.node)
  7310. className=className.toggleWordB(this._isForMe,"isForMe");
  7311. }catch(e){log("WM.Post.isForMe: "+e);}});
  7312.  
  7313. this.__defineGetter__("isWishlist",function(){try{
  7314. return this._isWishlist;
  7315. }catch(e){log("WM.Post.isWishlist: "+e);}});
  7316. this.__defineSetter__("isWishlist",function(v){try{
  7317. this._isWishlist=v;
  7318. if (this.node) with (this.node)
  7319. className=className.toggleWordB(this._isWishlist,"wishlist");
  7320. }catch(e){log("WM.Post.isWishlist: "+e);}});
  7321.  
  7322. this.__defineGetter__("isUndefined",function(){try{
  7323. return this._isUndefined;
  7324. }catch(e){log("WM.Post.isUndefined: "+e);}});
  7325. this.__defineSetter__("isUndefined",function(v){try{
  7326. this._isUndefined=v;
  7327. if (this.node) with (this.node)
  7328. className=className.toggleWordB(this._isUndefined,"noDef");
  7329. }catch(e){log("WM.Post.isUndefined: "+e);}});
  7330.  
  7331. this.__defineGetter__("isStale",function(){try{
  7332. return this._isStale;
  7333. }catch(e){log("WM.Post.isStale: "+e);}});
  7334. this.__defineSetter__("isStale",function(v){try{
  7335. this._isStale=v;
  7336. if (this.node) with (this.node)
  7337. className=className.toggleWordB(this._isStale,"stale");
  7338. }catch(e){log("WM.Post.isStale: "+e);}});
  7339.  
  7340. this.__defineGetter__("isTimeout",function(){try{
  7341. return this._isTimeout;
  7342. }catch(e){log("WM.Post.isTimeout: "+e);}});
  7343. this.__defineSetter__("isTimeout",function(v){try{
  7344. this._isTimeout=v;
  7345. if (this.node) with (this.node)
  7346. className=className.toggleWordB(this._isTimeout,"timeout");
  7347. }catch(e){log("WM.Post.isTimeout: "+e);}});
  7348.  
  7349. this.__defineGetter__("isCollect",function(){try{
  7350. return this._isCollect;
  7351. }catch(e){log("WM.Post.isCollect: "+e);}});
  7352. this.__defineSetter__("isCollect",function(v){try{
  7353. this._isCollect=v;
  7354. if (this.node) with (this.node)
  7355. className=className.toggleWordB(this._isCollect,"collect");
  7356. }catch(e){log("WM.Post.isCollect: "+e);}});
  7357.  
  7358. this.__defineGetter__("isExcluded",function(){try{
  7359. return this._isExcluded;
  7360. }catch(e){log("WM.Post.isExcluded: "+e);}});
  7361. this.__defineSetter__("isExcluded",function(v){try{
  7362. this._isExcluded=v;
  7363. if (this.node) with (this.node)
  7364. className=className.toggleWordB(this._isExcluded,"excluded");
  7365. }catch(e){log("WM.Post.isExcluded: "+e);}});
  7366.  
  7367. this.__defineGetter__("isAccepted",function(){try{
  7368. return this._isAccepted;
  7369. }catch(e){log("WM.Post.isAccepted: "+e);}});
  7370. this.__defineSetter__("isAccepted",function(v){try{
  7371. this._isAccepted=v;
  7372. if (this.node) with (this.node)
  7373. className=className.toggleWordB(this._isAccepted,"accepted");
  7374. }catch(e){log("WM.Post.isAccepted: "+e);}});
  7375.  
  7376. this.__defineGetter__("isFailed",function(){try{
  7377. return this._isFailed;
  7378. }catch(e){log("WM.Post.isFailed: "+e);}});
  7379. this.__defineSetter__("isFailed",function(v){try{
  7380. this._isFailed=v;
  7381. if (this.node) with (this.node)
  7382. className=className.toggleWordB(this._isFailed,"failed");
  7383. }catch(e){log("WM.Post.isFailed: "+e);}});
  7384.  
  7385. this.__defineGetter__("isWorking",function(){try{
  7386. return this._isWorking;
  7387. }catch(e){log("WM.Post.isWorking: "+e);}});
  7388. this.__defineSetter__("isWorking",function(v){try{
  7389. this._isWorking=v;
  7390. if (this.node) with (this.node)
  7391. className=className.toggleWordB(this._isWorking,"working");
  7392. }catch(e){log("WM.Post.isWorking: "+e);}});
  7393.  
  7394. this.__defineGetter__("isColored",function(){try{
  7395. return this._isColored;
  7396. }catch(e){log("WM.Post.isColored: "+e);}});
  7397. this.__defineSetter__("isColored",function(v){try{
  7398. this._isColored=v;
  7399. if (this._isColored && this.colorOverride && this.node) this.node.style.setProperty("background-color",this.colorOverride,"important");
  7400. }catch(e){log("WM.Post.isColored: "+e);}});
  7401. //get/set post pinned
  7402. this.__defineGetter__("isPinned",function(){try{
  7403. return this._isPinned;
  7404. }catch(e){log("WM.Post.isPinned: "+e);}});
  7405. this.__defineSetter__("isPinned",function(v){try{
  7406. this._isPinned=v;
  7407. //rotate the pin icon
  7408. var btnSize=WM.opts.littleButtonSize;
  7409. if (this.pinImageNode) with (this.pinImageNode)
  7410. className=className.swapWordB(this._isPinned,"pinned"+btnSize,"pin"+btnSize);
  7411. //pinned class
  7412. if (this.node) with (this.node)
  7413. className=className.toggleWordB(this._isPinned,"pinned");
  7414. }catch(e){log("WM.Post.isPinned: "+e);}});
  7415.  
  7416. //get/set post paused
  7417. this.__defineGetter__("isPaused",function(){try{
  7418. return this._isPaused;
  7419. }catch(e){log("WM.Post.isPaused: "+e);}});
  7420. this.__defineSetter__("isPaused",function(v){try{
  7421. this._isPaused=v;
  7422. if (this.node) with (this.node)
  7423. className=className.toggleWordB(this._isPaused,"paused");
  7424. }catch(e){log("WM.Post.isPaused: "+e);}});
  7425.  
  7426. //get/set status
  7427. this.__defineGetter__("status",function(){try{
  7428. return this._status;
  7429. }catch(e){log("WM.Post.status: "+e);}});
  7430. this.__defineSetter__("status",function(v){try{
  7431. this._status=v;
  7432. if (this.statusTextNode) this.statusTextNode.textContent=this._status;
  7433. }catch(e){log("WM.Post.status: "+e);}});
  7434.  
  7435. //get/set idText
  7436. this.__defineGetter__("idText",function(){try{
  7437. return this._idText;
  7438. }catch(e){log("WM.Post.idText: "+e);}});
  7439. this.__defineSetter__("idText",function(v){try{
  7440. this._idText=v;
  7441. if (this.linkNode) this.linkNode.textContent=((this._idText||null) && WM.opts.debugrecog)?this._idText:this.linkText;
  7442. }catch(e){log("WM.Post.idText: "+e);}});
  7443. //get/set which bonus type this is
  7444. this.__defineGetter__("which",function(){try{
  7445. return this._which;
  7446. }catch(e){log("WM.Post.which: "+e);}});
  7447. this.__defineSetter__("which",function(v){try{
  7448. this._which=v;
  7449. if (this.whichTextNode) this.whichTextNode.textContent=this._which;
  7450. }catch(e){log("WM.Post.which: "+e);}});
  7451.  
  7452. //check if in history already
  7453. this.__defineGetter__("alreadyProcessed",function(){try{
  7454. return exists(WM.history[this.id]);
  7455. }catch(e){log("WM.Post.alreadyProcessed: "+e);}});
  7456. /*
  7457. //update the namespace parameter if it does not exist
  7458. if (!exists(this.app.namespace)) this.app.namespace=this.application.namespace;
  7459.  
  7460. //validate the application namespace for sidekicks that provide namespace checking
  7461. if (exists(this.app.namespace) && (this.app.namespace!=this.application.namespace)) {
  7462. //Graph API namespace does not match sidekick known namespace, flag as scam
  7463. this.isScam=true;
  7464. }
  7465.  
  7466. //now drop the application object we got from FB
  7467. if (exists(this.application)) delete this.application;
  7468. */
  7469. //this.fromID=this.from.id;
  7470. //this.fromName=this.from.name;
  7471. this.fromNameLastFirst=this.fromName;
  7472. var sp=this.fromName.split(" ");
  7473. if (isArray(sp) && sp.length>1) {
  7474. this.fromNameLastFirst = sp.pop()+", "+sp.join(" ");
  7475. }
  7476.  
  7477.  
  7478. //(re)identify this post
  7479. this.identify=function(params){try{
  7480. params=params||{};
  7481. //shortcuts
  7482. var post=this;
  7483. var app=post.app;
  7484. var synApp=app.synApp;
  7485. //set/reset priority, state, status & flags
  7486. this.priority=50;
  7487. this.status=0;
  7488. this.state="";
  7489. //prevent reset of some data holders
  7490. if (!params.reid) {
  7491. this.testData={};
  7492. this.isLiked=false;
  7493. this.isMyPost=false;
  7494. this.isW2W=false;
  7495. this.isForMe=false;
  7496. this.isScam=false;
  7497. }
  7498. //reset data holders
  7499. this.isStale=false;
  7500. this.isCollect=false;
  7501. this.isExcluded=false;
  7502. this.isFailed=false;
  7503. this.isAccepted=false;
  7504. this.isPaused=false;
  7505. this.isPinned=false;
  7506. this.isUndefined=false;
  7507. this.isWishlist=false;
  7508. this.isTimeout=false;
  7509. //avoid posts that belong to a disabled sidekick
  7510. if(!WM.quickOpts.masterSwitch[app.appID]) {
  7511. //master switch is off
  7512. this.isExcluded=true;
  7513. return true; //draw without identifying anything
  7514. }
  7515.  
  7516. //hide posts by apps that we specifically told to hide
  7517. if (WM.opts["hide"+app.appID]) {this.remove(); return false;}
  7518.  
  7519. //avoid potential scam posts
  7520. /*if (WM.opts.scamblock) {
  7521. if (!params.reid) {
  7522. this.isScam=(!this.linkHref.match(new RegExp("^http(s):\/\/apps\.facebook\.com\/"+app.namespace))!=null);
  7523. }
  7524. if (this.isScam){
  7525. this.isExcluded=true;
  7526. if (WM.opts.hidescams) {this.remove(); return false;}
  7527. }
  7528. }*/
  7529.  
  7530. //avoid posts by self
  7531. if (!params.reid) {
  7532. var whoPosted = this.fromID;
  7533. var whoName = this.fromName;
  7534. this.isMyPost=(whoPosted==WM.currentUser.id);
  7535. }
  7536. if (this.isMyPost){
  7537. this.isExcluded=true;
  7538. if (WM.opts.hidemyposts) {this.remove(); return false;}
  7539. }
  7540.  
  7541. //avoid W2W posts not for me
  7542. if (!params.reid){
  7543. this.isForMe = (this.targetID==WM.currentUser.id);
  7544. this.isW2W = (this.targetID||null);
  7545. }
  7546. if (WM.opts[app.appID+"dontsteal"] && this.isW2W && !this.isForMe){
  7547. this.isExcluded=true;
  7548. if (WM.opts.hidenotforme) {this.remove(); return false;}
  7549. }
  7550.  
  7551. //avoid posts older than olderLimit
  7552. if (olderLimit!=0) {
  7553. if (this.isStale=this.checkStale(olderLimit)){
  7554. if (WM.opts.skipstale) this.isExcluded=true;
  7555. if (WM.opts.hidestale) {this.remove(); return false;}
  7556. }
  7557. }
  7558.  
  7559. //get bonus type
  7560. var w=(this.which = WM.which(this,{reid:params.reid}));
  7561.  
  7562. //check for exclude type
  7563. if (w=="exclude") {
  7564. this.isExcluded=true;
  7565. }
  7566.  
  7567. //check for pause
  7568. if(synApp.typesPaused.inArray(w)) this.isPaused=true;
  7569. //check if undefined
  7570. if (w=="none") {
  7571. this.isUndefined=true;
  7572. }
  7573. if (w==synApp.appID+"doUnknown" || w==synApp.appID+"send") {
  7574. this.isUndefined=true;
  7575. }
  7576.  
  7577. //special pin undefined option
  7578. if (WM.opts.pinundefined && this.isUndefined) this.isPinned=true;
  7579.  
  7580. //check if liked by me
  7581. if (this.isLiked){
  7582. if (WM.opts.skipliked){
  7583. if (WM.opts.markliked) this.status=1; //mark liked as accepted
  7584. this.isExcluded=true;
  7585. }
  7586. if (WM.opts.hideliked) {this.remove(); return false;}
  7587. }
  7588.  
  7589. //check history
  7590. this.status=this.status||0;
  7591. if (this.alreadyProcessed) {
  7592. //post previously processed
  7593. this.status=(WM.history[this.id].status||0);
  7594.  
  7595. var gotItem=((this.status>0) || (this.status==-6) || (this.status==-4) || (this.status==-15 && WM.opts.accepton15));
  7596. if (gotItem) {
  7597. this.isAccepted=true;
  7598. } else if (this.status<0) {
  7599. this.isFailed=true;
  7600. }
  7601.  
  7602. if (WM.opts.hideaccepted && gotItem) {this.remove(); return false;}
  7603. if (WM.opts.hidefailed && this.status<0) {this.remove(); return false;}
  7604. }
  7605.  
  7606. //check if excluded
  7607. if (this.isExcluded && WM.opts.hideexcluded) {this.remove(); return false;}
  7608.  
  7609. //set identified text
  7610. this.idText=WM.getAccText(synApp.appID,w,(this.alreadyProcessed),this.status);
  7611.  
  7612. //check if wanted
  7613. this.isCollect=(!this.alreadyProcessed &&
  7614. (w=="dynamic" || WM.apps[this.synApp.appID].opts[w] ||
  7615. (w.startsWith("send") && WM.apps[this.synApp.appID].opts["sendall"])
  7616. )
  7617. );
  7618.  
  7619. //check if wishlist
  7620. if (w.find("wishlist")) {
  7621. this.isWishlist=true;
  7622. if (WM.opts.hideunwanted && !WM.opts.donthidewishlists) {this.remove(); return false;}
  7623. }
  7624. //if unwanted
  7625. if (!this.isCollect && WM.opts.hideunwanted) {this.remove(); return false;}
  7626. //debug post
  7627. /*var pkg=debug.print("WM.Post.debug: ");
  7628. pkg.msg.appendChild(createElement("button",{type:"button",onclick:function(){
  7629. //response.responseText.toClipboard();
  7630. promptText(JSON.stringify(self));
  7631. }},[
  7632. createElement("img",{src:"http://i1181.photobucket.com/albums/x430/merricksdad/array.png",title:"Show Data",style:"width:16px;height:16px; vertical-align:bottom;"})
  7633. ]));*/
  7634. //return true to draw, false to hide
  7635. return true;
  7636. }catch(e){log("WM.Post.identify: "+e);}};
  7637.  
  7638. //open this post using the collector system
  7639. this.open=function(params){try{
  7640. params=params||{};
  7641. var post = this;
  7642. var id = this.id;
  7643. var app = this.app;
  7644. var synApp = this.synApp;
  7645.  
  7646. //perform the onBefore Collect event
  7647. WM.rulesManager.doEvent("onBeforeCollect",post);
  7648.  
  7649. //fix the link based on sidekick alterlink information
  7650. var alterLink=(synApp.alterLink||null);
  7651. var targetHref = post.linkHref;
  7652. var doAlterLink=(synApp.flags.alterLink||false);
  7653. if (doAlterLink && alterLink) {
  7654. //alert("doing alterlink...");
  7655. //pack the alterlink into an array, or detect an array
  7656. if (!isArray(alterLink)) alterLink=[alterLink];
  7657. //iterate link alteration commands
  7658. for (var alt=0,alteration;(alteration=alterLink[alt]);alt++) {
  7659. //alert("making alteration...");
  7660. //note that only find and replace functionality is available right now, no wildcards or array inserts will work
  7661. var find = (alteration.find||"");
  7662. alteration.dataSource=(alteration.dataSource||"either");
  7663. //check if user is wanting a regex or string replacement
  7664. if (alteration.isRegex||false) find=new RegExp(find,"");
  7665. targetHref = targetHref.replace(find,(alteration.replace||""));
  7666.  
  7667.  
  7668. //check for word specific changes
  7669. if ((alteration.words||null) && (alteration.conversionChart||null)){
  7670. //alert("inserting words...");
  7671. //new alterlink capability to change data source from 'either' to another post part
  7672. var dataSource = post.testData[alteration.dataSource].toLowerCase();
  7673. //alert(dataSource);
  7674. for (var w=0,len=alteration.words.length; w<len; w++) {
  7675. var word=(alteration.words[w]).toLowerCase();
  7676. if (dataSource.contains(word)) {
  7677. //replace the word
  7678. targetHref=targetHref.replace("{%1}",alteration.conversionChart[word.noSpaces()]);
  7679. break;
  7680. }
  7681. }
  7682. }
  7683. }
  7684. }
  7685.  
  7686. //fix the link, removing https and switching to http only
  7687. targetHref = targetHref.replace('https://','http://');
  7688.  
  7689. //open the bonus page in a new tab or the previously opened tab object to save memory
  7690. this.isWorking=true;
  7691. post.state="working";
  7692. WM.requestsOpen++;
  7693. doAction(function(){WM.collector.open({url:targetHref,id:id,callback:(synApp.isVer3?WM.onFrameLoad3:WM.onFrameLoad),post:post,first:params.first||false,emergency:params.emergency||false});});
  7694. }catch(e){log("WM.Post.open: "+e);}};
  7695.  
  7696. //open this post using the collector system even if already tried
  7697. this.forceOpen=function(params){try{
  7698. var post=self;
  7699. this.isCollect=true;
  7700. this.isFailed=false;
  7701. this.isTimeout=false;
  7702. this.isAccepted=false;
  7703. post.open(params);
  7704. }catch(e){log("WM.Post.forceOpen: "+e);}};
  7705.  
  7706. //like this post using the collector system
  7707. this.like=function(){try{
  7708. //var url=this.permalink;
  7709. var self=this;
  7710. //setTimeout(function(){WM.collector.open({url:url+"#likeit=true",id:url,callback:WM.onLikePost,post:self});},100);
  7711. //Graph.likePost(this.id,{callback:WM.onLikePost,post:self});
  7712. setTimeout(function(){Graph.likePost(self.id,{callback:WM.onLikePost,post:self});},100);
  7713. }catch(e){log("WM.Post.like: "+e);}};
  7714.  
  7715. //comment on this post using the collector system
  7716. this.comment=function(commentOverride){try{
  7717. if (commentOverride=="") commentOverride = null;
  7718. //not ready
  7719. //confirm("Feature not ready");
  7720. //return;
  7721. //var url=this.permalink;
  7722. var self=this;
  7723. var say = commentOverride || WM.opts.autocommentlist.split("\n").pickRandom();
  7724. //setTimeout(function(){WM.collector.open({url:url+"#commentit=true&say="+say,id:url,callback:WM.onLikePost,post:self});},100);
  7725. log("commenting "+say);
  7726. //Graph.commentPost(this.id,say);
  7727. setTimeout(function(){Graph.commentPost(self.id,say);},100);
  7728. }catch(e){log("WM.Post.comment: "+e);}};
  7729.  
  7730. //cancel collection in progress
  7731. this.cancelProcess=function(){
  7732. //tell the collector to cancel any processes with post equal this
  7733. //this will cancel both collect and like activities
  7734. WM.collector.cancelProcess({search:"post",find:this});
  7735. this.processCancelled=true;
  7736. },
  7737.  
  7738. //cancel collection in progress
  7739. this.refreshProcess=function(){
  7740. //tell the collector to reload the href on processes with post equal this
  7741. //this will reload both collect and like activities
  7742. WM.collector.refreshProcess({search:"post",find:this});
  7743. this.processRestarted=true;
  7744. },
  7745.  
  7746. //change the background color of this post
  7747. this.setColor=function(color){try{
  7748. this.colorOverride=color;
  7749. this.isColored=(cBool(color));
  7750. }catch(e){log("WM.Post.setColor: "+e);}};
  7751. //change the bonus type of this post
  7752. //and mark it for collection if needed
  7753. //and update its idText
  7754. this.setWhich=function(w){try{
  7755. this.which=w;
  7756. if ((w=="dynamic") || WM.apps[this.synApp.appID].opts[w] || (w.startsWith("send") && WM.apps[this.synApp.appID].opts["sendall"]) ) {
  7757. this.isCollect=!this.alreadyProcessed;
  7758. }
  7759. //update the identified text
  7760. this.idText=WM.getAccText(this.synApp.appID,w,(this.alreadyProcessed),this.status);
  7761. }catch(e){log("WM.Post.setWhich: "+e);}};
  7762. //get the time passed since this post was created
  7763. this.__defineGetter__("age",function(){try{
  7764. return timeStamp()-(this.date*1000);
  7765. }catch(e){log("WM.Post.age: "+e);}});
  7766.  
  7767. this.__defineGetter__("whichText",function(){try{
  7768. if (this.which=="dynamic") return "Dynamic Grab";
  7769. return this.synApp.userDefinedTypes[this.which]||this.synApp.accText[this.which];
  7770. }catch(e){log("WM.Post.whichText: "+e);}});
  7771.  
  7772. this.draw=function(redraw,reorder){try{
  7773. var post=this;
  7774. var app=post.app;
  7775. var synApp=app.synApp;
  7776. //clean old display
  7777. if (this.node && redraw) {
  7778. remove(this.node);
  7779. this.node=null;
  7780. }
  7781.  
  7782. //prefetch css words
  7783. var tags=("")
  7784. .toggleWordB(post.isAccepted,"accepted")
  7785. .toggleWordB(post.isFailed,"failed")
  7786. .toggleWordB(post.isTimeout,"timeout")
  7787. .toggleWordB(post.isExcluded,"excluded")
  7788. .toggleWordB(post.isStale,"stale")
  7789. .toggleWordB(post.isCollect && !(post.isAccepted||post.isFailed),"collect")
  7790. .toggleWordB(post.isWorking,"working")
  7791. .toggleWordB(post.isW2W,"w2w")
  7792. .toggleWordB(post.isForMe,"isForMe")
  7793. .toggleWordB(post.isMyPost,"isMyPost")
  7794. .toggleWordB(post.isColored,"colored")
  7795. .toggleWordB(post.isPaused,"paused")
  7796. .toggleWordB(post.isPinned,"pinned")
  7797. .toggleWordB(post.isUndefined,"noDef")
  7798. .toggleWordB(post.isWishlist,"wishlist")
  7799. .toggleWordB(post.isScam,"scam")
  7800. .toggleWordB(post.isLiked,"liked");
  7801. //detect hidden/drawn image
  7802. var hideimage = (WM.opts.hideimages || (WM.opts.hideimagesunwanted && (post.which==="none" || post.which==="exclude") ) );
  7803. var fakeimage = hideimage && WM.quickOpts.displayMode!="0";
  7804. hideimage=hideimage && WM.quickOpts.displayMode=="0";
  7805. //shared elements
  7806. if (redraw){
  7807. post.toolboxNode=createElement("div",{className:"toolBox small inline"});
  7808. post.imageNode=createElement("img",{className:((!fakeimage && post.picture)?"":"resourceIcon noImageSmall16"),src:((!fakeimage)?post.picture:""||""),onerror:function(){this.className=this.className+" resourceIcon noImageSmall16"}});
  7809. post.imageLinkNode=(!hideimage)?
  7810. createElement("span",{href:jsVoid,className:"picture",onclick:function(){post.forceOpen();} },[
  7811. post.imageNode
  7812. ]):null;
  7813. post.floaterNode=null;
  7814. post.bodyNode=null;
  7815. post.actorNode=createElement("a",{className:"actor",textContent:post.fromName,href:"http://www.facebook.com/profile.php?id="+post.fromID});
  7816. post.titleNode=createElement("span",{className:"title",textContent:post.name});
  7817. post.captionNode=createElement("span",{className:"caption",textContent:post.caption});
  7818. post.descriptionNode=createElement("span",{className:"description",textContent:post.description});
  7819. post.dateNode=createElement("span",{className:"postDate",textContent:[post.date,post.realtime]});
  7820. post.viaNode=createElement("a",{className:"appName",textContent:" via "+app.name,href:"http://apps.facebook.com/"+app.namespace+"/",title:app.appID});
  7821. post.linkNode=createElement("a",{className:"linkText"+(post.isExcluded?" excluded":"")+(post.idText?" identified":""),textContent:((post.idText||null) && WM.opts.debugrecog)?post.idText:post.linkText,href:post.linkHref,title:post.linkText});
  7822. post.statusNode=createElement("span",{className:"status",textContent:"Status: "+(post.status||"0")+ " " + (WM.statusText[post.status||"0"])});
  7823. post.pausedNode=createElement("div",{className:"pausedHover",title:"Collection for this post is paused, click to reactivate.",onclick:function(){post.isPaused=false;}},[createElement("img",{className:"resourceIcon playRight64"})]);
  7824. //create the layout
  7825. switch (WM.quickOpts.displayMode||"0"){
  7826.  
  7827. case "0": //classic mode
  7828. post.node=createElement("div",{id:"post_"+post.id,className:"wm post classic "+tags+((hideimage)?" noimage":""),title:(post.isScam?"Post is possible scam":"")},[
  7829. post.toolboxNode,
  7830. post.actorNode,
  7831. post.imageLinkNode,
  7832. (!WM.opts.hidebody)?post.bodyNode=createElement("div",{className:"body"},[
  7833. post.titleNode,
  7834. (post.caption||null)?post.captionNode:null,
  7835. (post.description||null)?post.descriptionNode:null,
  7836. ]):null,
  7837. createElement("div",{style:"margin-top:5px;"},[
  7838. (!WM.opts.hidedate)?post.dateNode:null,
  7839. (!WM.opts.hidevia)?post.viaNode:null,
  7840. post.linkNode,
  7841. ]),
  7842. post.pausedNode,
  7843. ]);
  7844. break;
  7845.  
  7846. case "1": case "3": //short mode and old priority mode
  7847. post.node=createElement("div",{id:"post_"+post.id,className:"wm post short "+WM.opts.thumbsize+tags,title:(post.isScam?"Post is possible scam":"")},[
  7848. post.imageLinkNode,
  7849. post.floaterNode=createElement("div",{id:"floater_"+post.id,className:"floater "+WM.opts.thumbsize},[
  7850. post.toolboxNode,
  7851. post.actorNode,
  7852. post.dateNode,
  7853. post.viaNode,
  7854. post.linkNode,
  7855. post.statusNode,
  7856. post.pausedNode,
  7857. ]),
  7858. ]);
  7859. post.imageNode.onmousemove=WM.moveFloater;
  7860. break;
  7861.  
  7862. case "2": //dev mode
  7863. var fnLine=function(label,text){
  7864. return createElement("div",{className:"line"},[
  7865. createElement("label",{textContent:label+": "}),
  7866. createElement("span",{textContent:text})
  7867. ]);
  7868. };
  7869. post.node=createElement("div",{id:"post_"+post.id,className:"listItem wm post dev "+tags,title:(post.isScam?"Post is possible scam":"")},[
  7870. post.idNode=fnLine("id", post.id),
  7871. post.toolboxNode,
  7872. //post.imageLinkNode,
  7873. createElement("div",{className:"subsection"},(function(){
  7874. var ret = [];
  7875. ret.push(post.actorNode=fnLine("fromName (fromID)", post.fromName+"("+post.fromID+")"));
  7876. ret.push(post.titleNode=fnLine("title",post.name));
  7877. if (post.message||null)ret.push(post.messageNode=fnLine("msg",post.message));
  7878. if (post.caption||null)ret.push(post.captionNode=fnLine("caption",post.caption));
  7879. if (post.description||null)ret.push(post.descriptionNode=fnLine("desc",post.description));
  7880. ret.push(post.appImageNode=fnLine("img",post.picture));
  7881. ret.push(post.dateNode=fnLine("date",post.realtime));
  7882. ret.push(post.appNameNode=fnLine("appName",app.name));
  7883. ret.push(post.appIDNode=fnLine("appID",app.appID));
  7884. ret.push(post.canvasNode=fnLine("canvas",app.namespace));
  7885. ret.push(post.urlNode=fnLine("url",post.linkHref));
  7886.  
  7887. //show likes
  7888. if (post.likes||null){
  7889. if (post.likes.data||null){
  7890. ret.push(fnLine("likes",""));
  7891. ret.push(post.likesNode=createElement("div",{className:"subsection"},(function(){
  7892. var data=post.likes.data;
  7893. var retData=[];
  7894. for(var d=0,lenL=data.length; d<lenL; d++){
  7895. retData.push(fnLine("likeName(likeID)",data[d].name+"("+data[d].id+")"));
  7896. }
  7897. return retData;
  7898. })()));
  7899. }
  7900. }
  7901.  
  7902. //show comments
  7903. if (post.comments||null){
  7904. if (post.comments.data||null){
  7905. ret.push(fnLine("comments",""));
  7906. ret.push(post.commentsNode=createElement("div",{className:"subsection"},(function(){
  7907. var data=post.comments.data;
  7908. var retData=[];
  7909. for(var d=0,lenC=data.length; d<lenC; d++){
  7910. retData.push(fnLine("commentorName(commentorID)",data[d].from.name+"("+data[d].from.id+")"));
  7911. retData.push(fnLine("comment",data[d].message));
  7912. }
  7913. return retData;
  7914. })()));
  7915. }
  7916. }
  7917. ret.push(post.idTextNode=fnLine("idText",post.idText));
  7918. ret.push(post.whichNode=fnLine("which",post.which));
  7919. ret.push(post.linkTextNode=fnLine("linkText",post.linkText));
  7920.  
  7921. return ret;
  7922. })() ),
  7923. post.pausedNode
  7924. ]);
  7925. break;
  7926. }
  7927.  
  7928. //add the toolbox
  7929. post.addToolBox();
  7930. //use color override
  7931. if (post.colorOverride) {
  7932. post.node.style.setProperty("background-color",post.colorOverride,"important");
  7933. }
  7934. }
  7935.  
  7936. //if a filter exists check against filter
  7937. var filter=(WM.quickOpts.filterApp||"All");
  7938. if (filter!="All" && filter!=app.appID) {
  7939. //dont show this post in this filter
  7940. if (this.node) remove(this.node);
  7941. return;
  7942. }
  7943.  
  7944. //insert the post into view by sort order
  7945. if (redraw || reorder) {
  7946. var groupBy=WM.quickOpts.groupBy;
  7947. if (groupBy){
  7948. //detect/create group
  7949. var group=WM.newGroup({by:post[groupBy]});
  7950. var sibling=post.nextSibling();
  7951. if (sibling) group.insertBefore(post.node,sibling.node);
  7952. else group.appendChild(post.node);
  7953. } else {
  7954. var sibling=post.nextSibling();
  7955. if (sibling) WM.console.feedNode.insertBefore(post.node, sibling.node);
  7956. else WM.console.feedNode.appendChild(post.node);
  7957. }
  7958. }
  7959. }catch(e){log("WM.Post.draw: "+e);}};
  7960.  
  7961. this.openSource=function(){try{
  7962. var url=this.permalink;
  7963. //FF22 version
  7964. GM_openInTab(url,"_blank");
  7965. //FF21 version
  7966. //((WM.opts.useGM_openInTab)?GM_openInTab:window.open)(url,"_blank");
  7967. }catch(e){log("WM.Post.openSource: "+e);}};
  7968.  
  7969. this.addClass=function(s){try{
  7970. if (this.node){
  7971. this.node.className=this.node.className.addWord(s);
  7972. }
  7973. }catch(e){log("WM.Post.addWord: "+e);}};
  7974.  
  7975. this.removeClass=function(s){try{
  7976. if (this.node){
  7977. this.node.className=this.node.className.removeWord(s);
  7978. }
  7979. }catch(e){log("WM.Post.removeWord: "+e);}};
  7980.  
  7981. this.pause=function(){try{
  7982. this.isPaused=true;
  7983. }catch(e){log("WM.Post.pause: "+e);}};
  7984.  
  7985. this.unPause=function(){try{
  7986. this.isPaused=false;
  7987. }catch(e){log("WM.Post.unPause: "+e);}};
  7988.  
  7989. this.exclude=function(){try{
  7990. this.isExcluded=true;
  7991. }catch(e){log("WM.Post.exclude: "+e);}};
  7992.  
  7993. this.collect=function(){try{
  7994. this.isCollect=true;
  7995. }catch(e){log("WM.Post.collect: "+e);}};
  7996.  
  7997. this.stopCollect=function(){try{
  7998. this.isCollect=false;
  7999. }catch(e){log("WM.Post.collect: "+e);}};
  8000.  
  8001. this.togglePin=function(){try{
  8002. this.isPinned=!this.isPinned;
  8003. }catch(e){log("WM.Post.togglePin: "+e);}};
  8004.  
  8005. this.pin=function(){try{
  8006. this.isPinned=true;
  8007. }catch(e){log("WM.Post.pin: "+e);}};
  8008.  
  8009. this.unPin=function(){try{
  8010. this.isPinned=false;
  8011. }catch(e){log("WM.Post.unPin: "+e);}};
  8012.  
  8013. this.addToFeeds=function(){try{
  8014. WM.feedManager.newFeed({id:this.fromID, title:this.fromName});
  8015. WM.feedManager.save();
  8016. }catch(e){log("WM.Post.addToFeeds: "+e);}};
  8017.  
  8018. this.accept=function(mark){try{
  8019. this.isAccepted=true;
  8020. this.isFailed=false;
  8021. this.isTimeout=false;
  8022. this.isWorking=false;
  8023. this.isCollect=false;
  8024. if (mark) WM.setAsAccepted(null, 3,this);
  8025. }catch(e){log("WM.Post.accept: "+e);}};
  8026.  
  8027. this.fail=function(mark){try{
  8028. this.isFailed=true;
  8029. this.isAccepted=false;
  8030. this.isTimeout=false;
  8031. this.isWorking=false;
  8032. this.isCollect=false;
  8033. if (mark) WM.setAsFailed(null, -18,this);
  8034. }catch(e){log("WM.Post.fail: "+e);}};
  8035.  
  8036. this.timeout=function(){try{
  8037. this.isTimeout=true;
  8038. this.isAccepted=false;
  8039. this.isFailed=false;
  8040. this.isWorking=false;
  8041. this.isCollect=false;
  8042. }catch(e){log("WM.Post.timeout: "+e);}};
  8043.  
  8044. this.remove=function(){try{
  8045. var node=(this.node||$("post_"+this.id));
  8046. if (node && node.parentNode) remove(node);
  8047. this.node=null;
  8048. //turn this post into a ghost so we can keep its data
  8049. //for linked objects, but not process it in reid or redraw
  8050. this.isGhost=true;
  8051. //delete WM.posts[this.id];
  8052. }catch(e){log("WM.Post.remove: "+e);}};
  8053.  
  8054. this.addToolBox=function(){try{
  8055. var post=this;
  8056. if (!WM.opts.showtoolbox) return;
  8057. var toolNode = post.toolboxNode;
  8058. if (toolNode) toolNode.appendChild(
  8059. createElement("div",{},[
  8060. createElement("div",{onclick:function(){post.forceOpen();},title:"Open Post",className:"littleButton oddBlue"+((!WM.opts.showopen)?" hidden":"")},[
  8061. createElement("img",{className:"resourceIcon action"+WM.opts.littleButtonSize})]),
  8062. createElement("div",{onclick:function(){post.cancelProcess();},title:"Cancel Process or AutoLike",className:"littleButton oddBlue"+((!WM.opts.showcancelprocess)?" hidden":"")},[
  8063. createElement("img",{className:"resourceIcon cancelProcess"+WM.opts.littleButtonSize})]),
  8064. createElement("div",{onclick:function(){post.refreshProcess();},title:"Restart Process or AutoLike",className:"littleButton oddBlue"+((!WM.opts.showrestartprocess)?" hidden":"")},[
  8065. createElement("img",{className:"resourceIcon refreshProcess"+WM.opts.littleButtonSize})]),
  8066. createElement("div",{onclick:function(){WM.pauseByType(post.app, post.which);},title:"Pause all bonuses of this type",className:"littleButton oddOrange"+((!WM.opts.showpausetype)?" hidden":"")},[
  8067. createElement("img",{className:"resourceIcon stop"+WM.opts.littleButtonSize})]),
  8068. createElement("div",{onclick:function(){WM.unPauseByType(post.app, post.which);},title:"Unpause all bonuses of this type",className:"littleButton oddGreen"+((!WM.opts.showunpausetype)?" hidden":"")},[
  8069. createElement("img",{className:"resourceIcon playRight"+WM.opts.littleButtonSize})]),
  8070. createElement("div",{onclick:function(){window.open(post.permalink,"_blank");},title:"Show Post Source",className:"littleButton oddBlue"+((!WM.opts.showpostsrc)?" hidden":"")},[
  8071. createElement("img",{className:"resourceIcon openInNewWindow"+WM.opts.littleButtonSize})]),
  8072. createElement("div",{onclick:function(){post.remove();},title:"Clean",className:"littleButton oddOrange"+((!WM.opts.showclean)?" hidden":"")},[
  8073. createElement("img",{className:"resourceIcon trash"+WM.opts.littleButtonSize})]),
  8074. createElement("div",{onclick:function(){post.togglePin();},title:"Pin",className:"littleButton"+((!WM.opts.showpin)?" hidden":"")},[
  8075. post.pinImageNode=createElement("img",{className:"resourceIcon "+(post.isPinned?"pinned":"pin")+WM.opts.littleButtonSize})]),
  8076. createElement("div",{onclick:function(){post.moveToBottom();},title:"Move to Bottom",className:"littleButton oddOrange"+((!WM.opts.showmovebottom)?" hidden":"")},[
  8077. createElement("img",{className:"resourceIcon moveBottomLeft"+WM.opts.littleButtonSize})]),
  8078. createElement("div",{onclick:function(){post.moveToTop();},title:"Move to Top",className:"littleButton oddGreen"+((!WM.opts.showmovetop)?" hidden":"")},[
  8079. createElement("img",{className:"resourceIcon moveTopLeft"+WM.opts.littleButtonSize})]),
  8080. createElement("div",{onclick:function(){post.reID();},title:"ReID Post",className:"littleButton"+((!WM.opts.showreid)?" hidden":"")},[
  8081. createElement("img",{className:"resourceIcon identify"+WM.opts.littleButtonSize})]),
  8082. createElement("div",{onclick:function(){post.addToFeeds();},title:"Add To Feeds",className:"littleButton"+((!WM.opts.showaddfeed)?" hidden":"")},[
  8083. createElement("img",{className:"resourceIcon addFeed"+WM.opts.littleButtonSize})]),
  8084. post.likeButtonNode=createElement("div",{onclick:function(){post.like();},title:"Like Post",className:"likeButton littleButton oddBlue"+(post.isLiked?" hidden":"")+((!WM.opts.showlike)?" hidden":"")},[
  8085. createElement("img",{className:"resourceIcon like"+WM.opts.littleButtonSize})]),
  8086. createElement("div",{onclick:function(){post.comment();},title:"Auto Comment",className:"littleButton oddBlue"+((!WM.opts.showautocomment)?" hidden":"")},[
  8087. createElement("img",{className:"resourceIcon comment"+WM.opts.littleButtonSize})]),
  8088. createElement("div",{onclick:function(){post.fail(true);},title:"Mark as Failed",className:"littleButton oddOrange"+((!WM.opts.showmarkfailed)?" hidden":"")},[
  8089. createElement("img",{className:"resourceIcon minus"+WM.opts.littleButtonSize})]),
  8090. createElement("div",{onclick:function(){post.accept(true);},title:"Mark as Accepted",className:"littleButton oddGreen"+((!WM.opts.showmarkaccepted)?" hidden":"")},[
  8091. createElement("img",{className:"resourceIcon plus"+WM.opts.littleButtonSize})]),
  8092. createElement("div",{onclick:function(){WM.rulesManager.ruleFromPost(post);},title:"Create Rule From Post",className:"littleButton oddBlue"+((!WM.opts.showmakerule)?" hidden":"")},[
  8093. createElement("img",{className:"resourceIcon gotoLibrary"+WM.opts.littleButtonSize})]),
  8094. createElement("div",{onclick:function(){promptText(self.originalData,true);},title:"Show Original Data",className:"littleButton"+((!WM.opts.showoriginaldata)?" hidden":"")},[
  8095. createElement("img",{className:"resourceIcon object"+WM.opts.littleButtonSize})]),
  8096. ])
  8097. );
  8098. }catch(e){log("WM.Post.addToolBox: "+e);}};
  8099.  
  8100. /*
  8101. this.__defineGetter__("linkText", function(){try{
  8102. if (this.actions.length >=3) return this.actions.last().name||"";
  8103. else return "";
  8104. }catch(e){log("WM.Post.linkText: "+e);}});
  8105.  
  8106. this.__defineGetter__("linkHref", function(){try{
  8107. return this.link||((this.actions.length>=3)?(this.actions.last().link||""):"")||"";
  8108.  
  8109. if (this.actions.length >=3) return this.actions.last().link||"";
  8110. else return this.link||"";
  8111. }catch(e){log("WM.Post.linkHref: "+e);}});
  8112. */
  8113.  
  8114. /*this.actionLink=function(action){try{
  8115. //for (var a=0,act;(act=this.actions[a]);a++) if (act.name.toLowerCase()==action.toLowerCase()) {return act.link; break;}
  8116. return this.linkHref;
  8117. }catch(e){log("WM.Post.actionLink: "+e);}};*/
  8118.  
  8119. this.__defineGetter__("body", function(){try{
  8120. return (this.title||"")+" "+(this.caption||"")+" "+(this.description||"");
  8121. }catch(e){log("WM.Post.body: "+e);}});
  8122.  
  8123. this.__defineGetter__("either", function(){try{
  8124. return this.linkText+" "+this.body;
  8125. }catch(e){log("WM.Post.either: "+e);}});
  8126.  
  8127. /*this.__defineGetter__("date", function(){try{
  8128. return this["created_time"];
  8129. }catch(e){log("WM.Post.date: "+e);}});*/
  8130.  
  8131. this.checkStale=function(timeOverride) {try{
  8132. if (exists(timeOverride) && timeOverride==0) return false;
  8133. var now = timeStamp();
  8134. var pubTime = this.date*1000; //(this.date.length < 10)?this.date+"000":this.date;
  8135. //var pubTime = this.date+"000";
  8136. var aDay = (1000 * 60 * 60 * 24);
  8137. return (now-pubTime)>(timeOverride||aDay);
  8138. }catch(e){log("WM.Post.checkStale: "+e);}};
  8139.  
  8140. //req must equal "id" or "name"
  8141. /*this.getTargets=function(req){try{
  8142. req = req||"id";
  8143. var ret = [];
  8144. if (exists(this.to)) {
  8145. for (var i=0,target; (target=this.to.data[i]);i++) ret.push(target[req]);
  8146. }
  8147. return ret;
  8148. }catch(e){log("WM.Post.getTargets: "+e);}};*/
  8149.  
  8150. //ret must equal "id" or "message" or "name" or "count"
  8151. this.getComments=function(req){try{
  8152. var ret = [];
  8153. if (exists(this.comments)) if (this.comments.count) {
  8154. switch(req){
  8155. case "message": for (var i=0,comment; (comment=this.comments.data[i]);i++) ret.push(comment[req]); break;
  8156. case "id":case "name": for (var i=0,comment; (comment=this.comments.data[i]);i++) ret.push(comment.from[req]); break;
  8157. case "count": return this.comments.count; break;
  8158. }
  8159. }
  8160. return ret;
  8161. }catch(e){log("WM.Post.getComments: "+e);}};
  8162.  
  8163. //ret must equal "id" or "name" or "count"
  8164. this.getLikes=function(req){try{
  8165. req = req||"id";
  8166. var ret = [];
  8167. if (exists(this.likes)) if (this.likes.count) {
  8168. switch(req){
  8169. case "id":case "name": for (var i=0,like; (like=this.likes.data[i]); i++) ret.push(like[req]); break;
  8170. case "count": return this.likes.count; break;
  8171. }
  8172. }
  8173. return ret;
  8174. }catch(e){log("WM.Post.getLikes: "+e);}};
  8175.  
  8176. this.moveToTop = function(){try{
  8177. if (this.node||null) this.node.parentNode.insertBefore(this.node,this.node.parentNode.childNodes[0]);
  8178. }catch(e){log("WM.Post.moveToTop: "+e);}};
  8179.  
  8180. this.moveToBottom = function(){try{
  8181. if (this.node||null) this.node.parentNode.appendChild(this.node);
  8182. }catch(e){log("WM.Post.moveToBottom: "+e);}};
  8183.  
  8184. this.setPriority = function(value){try{
  8185. this.priority=value;
  8186. remove(this.node);
  8187. this.draw();
  8188. }catch(e){log("WM.Post.setPriority: "+e);}};
  8189. this.reID = function(params){try{
  8190. params=params||{};
  8191. //reidentify
  8192. var oldW=this.which;
  8193. if (this.identify({reid:true})) {
  8194. WM.rulesManager.doEvent("onIdentify",this);
  8195. }
  8196. //sort me into proper location
  8197. WM.sortPosts();
  8198.  
  8199. //redraw||reorder
  8200. if (!this.isGhost) {
  8201. this.draw(true,true);
  8202. }
  8203. }catch(e){log("WM.Post.reID: "+e);}};
  8204.  
  8205. //return the next visible sibling post
  8206. //now checks for group and visibility
  8207. this.nextSibling = function(){try{
  8208. //determine if there is an app filter
  8209. var filter=(WM.quickOpts.filterApp||"All");
  8210. //determine display grouping
  8211. var groupBy=WM.quickOpts.groupBy;
  8212. var group=(groupBy)?WM.newGroup({by:this[groupBy]}):null;
  8213. //get visible posts
  8214. var visiblePosts=(filter=="All")?WM.posts:matchByParam(WM.posts,"appID",filter);
  8215. if (groupBy) visiblePosts=matchByParam(visiblePosts,groupBy,this[groupBy]);
  8216. //search for the current post
  8217. var found=false, sibling=null;
  8218. for (var p in visiblePosts) {
  8219. if (found && visiblePosts[p].node) {
  8220. if ((!groupBy && visiblePosts[p].node.parentNode==WM.console.feedNode)
  8221. || (groupBy && visiblePosts[p].node.parentNode==group)){
  8222. sibling=visiblePosts[p];
  8223. break
  8224. }
  8225. } else if (visiblePosts[p]==this) found=true;
  8226. }
  8227.  
  8228. //return what is found
  8229. return sibling;
  8230. //warning: returns null if this is the last visible post
  8231. }catch(e){log("WM.Post.nextSibling: "+e);}};
  8232.  
  8233. //return the previous visible sibling post
  8234. //now checks for group and visibility
  8235. this.previousSibling = function(){try{
  8236. //determine if there is an app filter
  8237. var filter=(WM.quickOpts.filterApp||"All");
  8238. //determine display grouping
  8239. var groupBy=WM.quickOpts.groupBy;
  8240. var group=(groupBy)?WM.newGroup({by:this[groupBy]}):null;
  8241.  
  8242. //get visible posts
  8243. var visiblePosts=(filter=="All")?WM.posts:matchByParam(WM.posts,"appID",filter);
  8244. if (groupBy) visiblePosts=matchByParam(visiblePosts,groupBy,this[groupBy]);
  8245. //search for the current post
  8246. var sibling=null;
  8247. for (var p in visiblePosts) {
  8248. if (visiblePosts[p]==this) break;
  8249. else if (visiblePosts[p].node) {
  8250. if ((!groupBy && visiblePosts[p].node.parentNode==WM.console.feedNode)
  8251. || (groupBy && visiblePosts.node.parentNode==group)){
  8252. sibling=visiblePosts[p];
  8253. }
  8254. }
  8255. }
  8256. //return what is found
  8257. return sibling;
  8258. //warning: returns null if this is the first visible post
  8259. }catch(e){log("WM.Post.previousSibling: "+e);}};
  8260.  
  8261.  
  8262. //get the friend object related with this post (from the friend tracker)
  8263. this.__defineGetter__("relatedFriend",function(){try{
  8264. return WM.friendTracker.friends[this.fromID]||null;
  8265. }catch(e){log("WM.Post.relatedFriend: "+e);}});
  8266. this.__defineGetter__("friendAcceptedCount",function(){try{
  8267. return this.relatedFriend.acceptCount;
  8268. }catch(e){log("WM.Post.friendAcceptedCount: "+e);}});
  8269.  
  8270. this.__defineGetter__("friendFailedCount",function(){try{
  8271. return this.relatedFriend.failCount;
  8272. }catch(e){log("WM.Post.friendFailedCount: "+e);}});
  8273.  
  8274. //a little cleanup
  8275. params = null;
  8276. }catch(e){log("WM.Post.init: "+e);}};
  8277.  
  8278. //***************************************************************************************************************************************
  8279. //***** Main Program
  8280. //***************************************************************************************************************************************
  8281.  
  8282. //*****short text versions for WM.config menu options*****
  8283.  
  8284. var checkBox=function(l,d,c,n){return ({type:"checkbox",label:l,"default":d||false,kids:c,newitem:n});}
  8285. var hidden=function(l,d,c){return ({type:"hidden",label:l,"default":d,kids:c});}
  8286. var optionBlock=function(l,c,hideSelectAll,n){hideSelectAll=hideSelectAll||false;return ({type:"optionblock",label:l,kids:c,hideSelectAll:hideSelectAll,newitem:n});}
  8287. var separator=function(l,s,c,hideSelectAll){hideSelectAll=hideSelectAll||false; return ({type:"separator",label:l,section:s,kids:c}); }
  8288. var section=function(l,c){return ({type:"section",label:l,kids:c});}
  8289. var tabSection=function(l,c){return ({type:"tab",label:l,kids:c});}
  8290. var inputBox=function(l,d,n){return ({type:"float",label:l,"default":(d||0),newitem:n});}
  8291. var textArea=function(l,d,n){return ({type:"textarea",label:l,"default":(d||0),newitem:n});}
  8292. var colorBox=function(l,d,n){return ({type:"colorbox",label:l,"default":(d||"black"),newitem:n});}
  8293. var button=function(l,s){return ({type:"button",label:l,script:s});}
  8294. var anchor=function(l,u,t){return ({type:"link",label:l,href:u,title:(t||'')});}
  8295.  
  8296.  
  8297. //***************************************************************************************************************************************
  8298. //***** Immediate
  8299. //***************************************************************************************************************************************
  8300.  
  8301. log("Script: WM initialized",{level:0});
  8302.  
  8303. // section for reclaiming memory and stopping memory leaks
  8304. var newIntv=null; //refresh interval
  8305. var oldIntv=null; //refresh interval
  8306. var procIntv=null; //process interval
  8307. var cleanIntv=null; //post removal interval
  8308. var hbIntv=null; //global heartbeat interval
  8309.  
  8310. var olderLimit=day; //default 1 day
  8311.  
  8312. var cleanup=function() {try{
  8313. //destroy intervals
  8314. if (newIntv) window.clearInterval(newIntv);
  8315. if (oldIntv) window.clearInterval(oldIntv);
  8316. if (procIntv) window.clearInterval(procIntv);
  8317. if (cleanIntv) window.clearInterval(cleanIntv);
  8318. if (hbIntv) window.clearInterval(hbIntv);
  8319. oldIntv = newIntv = procIntv = cleanIntv = hbIntv = null;
  8320.  
  8321. //close the sidekick tabs
  8322. WM.collector.closeAll();
  8323.  
  8324. //remove this event listener
  8325. window.removeEventListener("beforeunload", cleanup, false);
  8326. window.removeEventListener("message", WM.receiveSidekickMessage, false);
  8327. window.removeEventListener("resize", WM.onWindowResize, false);
  8328.  
  8329. //clean up memory
  8330. WallManager=null;
  8331. Graph=null;
  8332. jsForms=null;
  8333. olderLimit=null;
  8334. opts=null; quickOpts=null;
  8335. }catch(e){log("cleanup: "+e);}}
  8336. window.addEventListener("beforeunload", cleanup, false);
  8337. window.addEventListener("resize", WM.onWindowResize, false);
  8338. //start it up
  8339. WM.run();
  8340. })(); // anonymous function wrapper end
  8341.  
  8342. /* recent changes
  8343. 3.1.1
  8344. *fixed the update script button to point to the standard branch
  8345. *posts are once again able to fetch source user names with the post
  8346. *reinstated post.showOriginalData so developers can see the return data from FQL and how it differs from GraphAPI returns
  8347. 3.1.2
  8348. *fixed the button on feed manager where you can fetch for a specific friend feed manually
  8349. *added option to fetch all apps for a specific feed in a single request, default is to fetch for each app with a separate request
  8350. *fixed drawing order bug caused by graph update
  8351. *temporarily removed checking for feed already having reached older limit, feed will now draw until facebook has no posts to show if you tell it to
  8352. **hide stale posts will still make it so you can't see those posts if you have it enabled
  8353. *fixed friend tracker last known post date
  8354. 3.1.3
  8355. *when action_links is null from facebook, WM now uses attachment.href for the link, which should match, but is not a guarantee.
  8356. *when action_links is null from facebook, WM now uses "" as the link text. Neither attachment or app_data appears to have a duplicate of the link text.
  8357. *added error checking for if the return data for a post cannot be transformed into a useful post object. This could happen if some of the data WM requires to function is not available, or is mangled for some reason.
  8358. *added option to have WM filter posts by app instead of asking FB for posts by app. This will hopefully remove a lot of that empty data set issue, but you will occasionally get return data without any useful posts in it.
  8359. 3.1.4
  8360. *reinstated olderLimitReached and any function or rules manager event having to do with that
  8361. *removed some console.log debuggery
  8362. *fixed issue with disabled feeds still being processed when fetching multiple apps at once (with those new options)
  8363. 3.1.4.1
  8364. *fixed alternative spotting of link url and link text associated with app posts (when action_links data is null)
  8365. 3.1.6
  8366. *added rule manager options: friendAcceptCount and friendFailedCount
  8367. */