WM Graph API Interface (Beta Branch)

Creates a low-access, read-only interface to the FB Graph API.

Fra og med 14.04.2014. Se den nyeste version.

Dette script bør ikke installeres direkte. Det er et bibliotek, som andre scripts kan inkludere med metadirektivet // @require https://update.greatest.deepsurf.us/scripts/420/1302/WM%20Graph%20API%20Interface%20%28Beta%20Branch%29.js

  1. // ==UserScript==
  2. // @name WM Graph API Interface (Beta Branch)
  3. // @namespace MerricksdadGraphInterface
  4. // @description Creates a low-access, read-only interface to the FB Graph API.
  5. // @license http://creativecommons.org/licenses/by-nc-nd/3.0/us/
  6. // @version 3.1.3
  7. // @copyright Charlie Ewing except where noted
  8. // ==/UserScript==
  9.  
  10. //this script requires some functions in the WM Common Library
  11. //this script needs access to a pre-defined JSON object
  12.  
  13. var workOffline=false;
  14.  
  15. (function(){
  16. this.Graph = {
  17. posts: {}, //location to store adjusted post data
  18. authRequestOut: false, //dont ask for token while asking for token
  19. authToken: (isChrome||getOpt("disableSaveAuthToken"))?null:getOpt("lastAuthToken"), //use stored fb token
  20. userID: null,
  21. userAlias: null,
  22. userName: null,
  23. fetchTimeout: 30,
  24.  
  25. requests: [],
  26. likePost: function(postID,params){try{
  27. //https://graph.facebook.com/POST_ID/likes?access_token=
  28. var req; req=GM_xmlhttpRequest({
  29. method: "POST",
  30. url: "https://graph.facebook.com/"+postID+"/likes?access_token="+Graph.authToken,
  31. timeout: Graph.fetchTimeout*1000,
  32. onload: function(response) {try{
  33. if (response.responseText=="true") {
  34. if (params.callback) params.callback(params.post);
  35. } else {
  36. log(response.responseText);
  37. }
  38. }catch(e){log("Graph.likePost.onload: "+e);}},
  39. onerror: function(response) {try{
  40. }catch(e){log("Graph.likePost.onerror: "+e);}},
  41. onabort: function(response) {try{
  42. }catch(e){log("Graph.likePost.onabort: "+e);}},
  43. ontimeout: function(response) {try{
  44. }catch(e){log("Graph.likePost.ontimeout: "+e);}}
  45. });
  46. }catch(e){log("Graph.likePost: "+e);}},
  47.  
  48. unlikePost: function(postID){try{
  49. //https://graph.facebook.com/POST_ID/likes?access_token=
  50. var req; req=GM_xmlhttpRequest({
  51. method: "DELETE",
  52. url: "https://graph.facebook.com/"+postID+"/likes?access_token="+Graph.authToken,
  53. timeout: Graph.fetchTimeout*1000,
  54. onload: function(response) {try{
  55. }catch(e){log("Graph.unlikePost.onload: "+e);}},
  56. onerror: function(response) {try{
  57. }catch(e){log("Graph.unlikePost.onerror: "+e);}},
  58. onabort: function(response) {try{
  59. }catch(e){log("Graph.unlikePost.onabort: "+e);}},
  60. ontimeout: function(response) {try{
  61. }catch(e){log("Graph.unlikePost.ontimeout: "+e);}}
  62. });
  63. }catch(e){log("Graph.unlikePost: "+e);}},
  64.  
  65. commentPost: function(postID,comment){try{
  66. //https://graph.facebook.com/POST_ID/comments?message=&access_token=
  67. var req; req=GM_xmlhttpRequest({
  68. method: "POST",
  69. url: "https://graph.facebook.com/"+postID+"/comments?access_token="+Graph.authToken+"&message="+comment,
  70. timeout: Graph.fetchTimeout*1000,
  71. onload: function(response) {try{
  72. if (response.responseText=="true") {
  73. //comment successful
  74. } else {
  75. log(response.responseText);
  76. }
  77. }catch(e){log("Graph.commentPost.onload: "+e);}},
  78. onerror: function(response) {try{
  79. }catch(e){log("Graph.commentPost.onerror: "+e);}},
  80. onabort: function(response) {try{
  81. }catch(e){log("Graph.commentPost.onabort: "+e);}},
  82. ontimeout: function(response) {try{
  83. }catch(e){log("Graph.commentPost.ontimeout: "+e);}}
  84. });
  85. }catch(e){log("Graph.commentPost: "+e);}},
  86. requestAuthCodeB: function(callback){try{
  87. log("Graph.requestAuthCodeB()");
  88. if (Graph.authRequestOut) return {requestAlreadyOut:true}; //dont ask again while asking
  89. Graph.authRequestOut = true;
  90. var req; req=GM_xmlhttpRequest({
  91. method: "GET",
  92. url: "http://developers.facebook.com/tools/explorer",
  93. timeout: Graph.fetchTimeout*1000,
  94. onload: function(response) {try{
  95. var test=response.responseText;
  96. var auth=test.longestQuoteWithin();
  97. if (auth!="") {
  98. Graph.authToken = auth;
  99. log("Graph.requestAuthCodeB: got token");
  100. setOpt("lastAuthToken",auth);
  101. } else {
  102. log("Graph.requestAuthCodeB: "+response.responseText,{level:3});
  103. }
  104. Graph.authRequestOut=false;
  105. if (callback) setTimeout(callback,0);
  106. if(req)req=null;
  107. }catch(e){log("Graph.requestAuthCodeB.onload: "+e);}},
  108. onerror: function(response) {try{
  109. Graph.authToken="";
  110. Graph.authRequestOut=false;
  111. log("Graph.requestAuthCodeB: error:"+response.responseText+"\n Trying again in 30 seconds.",{level:3});
  112. setTimeout(function(){Graph.requestAuthCodeB(callback);},30000);
  113. if(req)req=null;
  114. }catch(e){log("Graph.requestAuthCodeB.onerror: "+e);}},
  115. onabort: function(response) {try{
  116. Graph.authToken="";
  117. Graph.authRequestOut=false;
  118. log("Graph.requestAuthCodeB: Request aborted, trying again in 30 seconds",{level:3});
  119. if(req)req=null;
  120. setTimeout(function(){Graph.requestAuthCodeB(callback);},30000);
  121. }catch(e){log("Graph.requestAuthCodeB.onabort: "+e);}},
  122. ontimeout: function(response) {try{
  123. Graph.authToken="";
  124. Graph.authRequestOut=false;
  125. log("Graph.requestAuthCodeB: Request timeout, trying again in 30 seconds",{level:3});
  126. if(req)req=null;
  127. setTimeout(function(){Graph.requestAuthCodeB(callback);},30000);
  128. }catch(e){log("Graph.requestAuthCodeB.ontimeout: "+e);}}
  129. });
  130. }catch(e){log("Graph.requestAuthCodeB: "+e);}},
  131. requestAuthCode: function(callback){try{
  132. log("Graph.requestAuthCode()");
  133. if (Graph.authRequestOut) return {requestAlreadyOut:true}; //dont ask again while asking
  134. Graph.authRequestOut = true;
  135. var req; req=GM_xmlhttpRequest({
  136. method: "GET",
  137. url: "http://developers.facebook.com/docs/reference/api/examples/",
  138. timeout: Graph.fetchTimeout*1000,
  139. onload: function(response) {try{
  140. var test=response.responseText;
  141. var searchString='<a href="https://graph.facebook.com/me/home?access_token=';
  142. var auth = test.indexOf(searchString),authEnd;
  143. if (auth!=-1) {
  144. authEnd = test.indexOf('">',auth);
  145. var authCode = (test.substring(auth+(searchString.length), authEnd));
  146. Graph.authToken = authCode;
  147. setOpt("lastAuthToken",authCode);
  148. log("Graph.requestAuthCode: got token");
  149. } else {
  150. log("Graph.requestAuthCode: "+response.responseText,{level:3});
  151. }
  152. Graph.authRequestOut=false;
  153. if (callback) setTimeout(callback,0);
  154. if(req)req=null;
  155. }catch(e){log("Graph.requestAuthCode.onload: "+e);}},
  156. onerror: function(response) {try{
  157. Graph.authToken="";
  158. Graph.authRequestOut=false;
  159. log("Graph.requestAuthCode: error:"+response.responseText+"\n Trying again in 30 seconds.",{level:3});
  160. setTimeout(function(){Graph.requestAuthCode(callback);},30000);
  161. if(req)req=null;
  162. }catch(e){log("Graph.requestAuthCode.onerror: "+e);}},
  163. onabort: function(response) {try{
  164. Graph.authToken="";
  165. Graph.authRequestOut=false;
  166. log("Graph.requestAuthCode: Request aborted, trying again in 30 seconds",{level:3});
  167. if(req)req=null;
  168. setTimeout(function(){Graph.requestAuthCode(callback);},30000);
  169. }catch(e){log("Graph.requestAuthCode.onabort: "+e);}},
  170. ontimeout: function(response) {try{
  171. Graph.authToken="";
  172. Graph.authRequestOut=false;
  173. log("Graph.requestAuthCode: Request timeout, trying again in 30 seconds",{level:3});
  174. if(req)req=null;
  175. setTimeout(function(){Graph.requestAuthCode(callback);},30000);
  176. }catch(e){log("Graph.requestAuthCode.ontimeout: "+e);}}
  177. });
  178. }catch(e){log("Graph.requestAuthCode: "+e);}},
  179.  
  180. fetchUser: function(params){try{
  181. log("Graph.fetchUser()");
  182. params=params || {};
  183. if (!Graph.authToken) {
  184. log("Graph.fetchUser: no authToken, get one");
  185. params["retries_noToken"]=(params["retries_noToken"])?params["retries_noToken"]+1:1; //count retries
  186. if (params["retries_noToken"]<3) {
  187. Graph.requestAuthCodeB(function(){Graph.fetchUser(params);} );
  188. } else {
  189. log("Graph.fetchUser: cannot get new fb auth token",{level:3});
  190. return {getAuthTokenFailed:true}
  191. }
  192. return;
  193. }
  194. var URL="https://graph.facebook.com/me?access_token="+Graph.authToken;
  195. var req; req=GM_xmlhttpRequest({
  196. method: "GET",
  197. url: URL,
  198. timeout: Graph.fetchTimeout*1000,
  199. onload: function(response) {try{
  200. if (response){
  201. //convert to JSON
  202. try{
  203. var data = JSON.parse(response.responseText);
  204. if (data["id"]||null){
  205. //expected data exists
  206. Graph.userID=data["id"||null];
  207. Graph.userAlias=(data["username"]||null);
  208. Graph.userName=(data["name"]||null);
  209.  
  210. if (params["callback"]) {
  211. var fx=params["callback"];
  212. delete params["callback"];
  213. setTimeout(fx,0);
  214. }
  215. } else if (data["error"]||null) {
  216. var emsg=data.error.message||null;
  217. //check for session expired
  218. if (emsg.find("Session has expired")||emsg.find("session is invalid")){
  219. //session expired or logged out, get a new token
  220. Graph.authToken="";
  221. params["retries_expToken"]=(params["retries_expToken"])?params["retries_expToken"]+1:1; //count retries
  222. if (params["retries_expToken"]<3) {
  223. Graph.requestAuthCodeB(function(){Graph.fetchUser(params);} );
  224. } else log("Graph.fetchUser: cannot refresh expired fb auth token",{level:3});
  225. } else if (emsg) log("Graph.fetchUser: "+emsg,{level:3});
  226.  
  227. } else log("Graph.fetchUser: response was unrecognized",{level:3});
  228. } catch (e){log("Graph.fetchUser: response error: "+e+": "+response);}
  229.  
  230. } else log("Graph.fetchUser: response was empty",{level:3});
  231. if(req)req=null;
  232. }catch(e){log("Graph.fetchUser.onload: "+e);}},
  233.  
  234. onabort: function(response) {try{
  235. log("Graph.fetchUser: Request aborted, trying again in 30 seconds.");
  236. setTimeout(function(){Graph.fetchUser(params);},30000);
  237. if(req)req=null;
  238. }catch(e){log("Graph.fetchUser.onabort: "+e);}},
  239. ontimeout: function(response) {try{
  240. log("Graph.fetchUser: Request timeout, trying again in 30 seconds.");
  241. setTimeout(function(){Graph.fetchUser(params);},30000);
  242. if(req)req=null;
  243. }catch(e){log("Graph.fetchUser.ontimeout: "+e);}},
  244.  
  245. onerror: function(response) {try{
  246. if (response.responseText=="") {
  247. log(JSON.stringify(response));
  248. log("Graph.fetchUser: responseText was empty. Check to make sure your browser is online.", {level:5});
  249. }
  250. var data = JSON.parse(response.responseText);
  251. if (data) {if (data.error||null) {
  252. var emsg=data.error.message||null;
  253. //check for session expired
  254. if (emsg.find("Session has expired")||emsg.find("session is invalid")){
  255. //session expired or logged out, get a new token
  256. Graph.authToken="";
  257. params["retries_expToken"]=(params["retries_expToken"])?params["retries_expToken"]+1:1; //count retries
  258. if (params["retries_expToken"]<3) {
  259. Graph.requestAuthCodeB(function(){Graph.fetchUser(params);} );
  260. } else log("Graph.fetchUser: cannot refresh expired fb auth token",{level:3});
  261. } else if (emsg) log("Graph.fetchUser.onerror: "+emsg,{level:3});
  262. }} else {
  263. log("Graph.fetchUser.onerror: "+response+"\n Trying again in 30 seconds.");
  264. setTimeout(function(){Graph.fetchUser(params);},30000);
  265. if(req)req=null;
  266. }
  267. }catch(e){log("Graph.fetchUser.onerror: "+e);}}
  268. });
  269. }catch(e){log("Graph.fetchUser: "+e);}},
  270.  
  271. matchRequest: function(params){try{
  272. for (var r in Graph.requests) {
  273. var req = Graph.requests[r];
  274.  
  275. //match the feed
  276. if (JSON.stringify(req.friends) == JSON.stringify(params.friends)){
  277. //match the app filters
  278. if (JSON.stringify(req.apps) == JSON.stringify(params.apps)) {
  279. //match direction of request
  280. if (req.direction==params.direction) {
  281. return r;
  282. }
  283. }
  284. }
  285. }
  286. return -1;
  287. }catch(e){log("Graph.matchRequest: "+e);}},
  288.  
  289. validatePost: function(params){try{
  290. var post=params.post;
  291. var callback=params.callback;
  292. var isOlder=params.next;
  293.  
  294. //log("Graph.validatePost()",{level:1});
  295.  
  296. //exclude non-app posts and posts with no action links
  297. //if (!exists(post.actions||null) || !exists(post.application)) return;
  298.  
  299. //exclude posts with less than like and comment and which have no link
  300. //if (!(post.actions.length>=2) || !exists(post.link)) return;
  301. var postID=post["post_id"]||post["id"];
  302.  
  303. //exclude posts already in our repository
  304. if (exists(Graph.posts[postID])) return;
  305.  
  306. //store a reference to this post
  307. Graph.posts[postID]=1;
  308.  
  309. //send the post back to the callback function here
  310. if (callback) setTimeout(function(){callback(post,isOlder);},0);
  311. }catch(e){log("Graph.validatePost: "+e);}},
  312. fetchPostsFQL_B: function(params){try{
  313. console.log(JSON.stringify(params));
  314. if (arguments.length==0) {
  315. log("Graph.fetchPostsFQL: no parameters passed");
  316. return;
  317. }
  318. /*
  319. direction: 0=until now | 1=forward from front edge | -1=backward from back edge
  320. apps = array of app id's to fetch posts for, error on no apps passed
  321. friends = array of friend id's to fetch posts for, default all friends
  322. limit = number to fetch
  323. timeouts = number of timeouts so far when performing retry looping
  324. targetEdge = unix time to continue fetching until
  325. currentEdge = current remembered edge of feed
  326. retries_noToken = number of times this function has called getAuthToken and failed
  327. callback = function to enact on each post
  328. edgeHandler = function to keep track of edges
  329. */
  330. if (!(params.apps||null)) {
  331. log("Graph.fetchPostsFQL: no apps requested");
  332. return;
  333. }
  334. var bypassMatchRequest = (params.targetEdge||null)?true:false;
  335.  
  336. //validate current auth token
  337. if (!Graph.authToken) {
  338. log("Graph.fetchPostsFQL: no authToken, get one");
  339. params["retries_noToken"]=(params["retries_noToken"])?params["retries_noToken"]+1:1; //count retries
  340. if (params["retries_noToken"]<3) {
  341. Graph.requestAuthCodeB(function(){Graph.fetchPostsFQL_B(params);} );
  342. } else {
  343. log("Graph.fetchPostsFQL: cannot get new fb auth token",{level:3});
  344. return {getAuthTokenFailed:true};
  345. }
  346. return;
  347. }
  348.  
  349. //check if there is a request already out with this feed id and matches the direction
  350. if (!bypassMatchRequest) {
  351. var r=Graph.matchRequest(params);
  352. if (r!=-1){
  353. log("Graph.fetchPostsFQL: a request is already out for posts in that direction and has not returned",{level:3});
  354. return {requestAlreadyOut:true};
  355. }
  356. }
  357. //compile feed request strings
  358. var URL_prefix="https://graph.facebook.com/fql?access_token={0}&q=";
  359. var URL="{\"query1\":\"SELECT post_id,target_id,message,app_id,action_links,created_time,attachment,app_data,like_info,source_id FROM stream WHERE source_id IN ({3}){1}{2} ORDER BY created_time DESC{4}\",\"query2\":\"SELECT uid,name FROM user WHERE uid IN (SELECT source_id FROM #query1)\"}";
  360. URL_prefix=URL_prefix.replace("{0}",Graph.authToken);
  361. //specialize call for specific friend post requests
  362. if (params.friends||null) {
  363. URL=URL.replace("{3}",params.friends.join(","));
  364. } else {
  365. URL=URL.replace("{3}","SELECT uid2 FROM friend WHERE uid1=me() LIMIT 5000");
  366. }
  367. //get older posts
  368. //verify that the feed "until" time does not violate olderLimit set by user
  369. if (params.direction<0){
  370. URL=URL.replace("{2}"," AND created_time < "+params.currentEdge);
  371. //get newer posts
  372. } else if (params.direction>0){
  373. URL=URL.replace("{2}"," AND created_time > "+params.currentEdge);
  374. //fetch at current time
  375. } else {
  376. URL=URL.replace("{2}","");
  377. }
  378.  
  379. //filters by apps requested
  380. //unless no apps were passed or we specified no app filtering
  381. if ((params.apps||null) && !(params.noAppFiltering||null)){
  382. URL=URL.replace("{1}"," AND app_id IN ("+params.apps.join(",")+")");
  383. } else {
  384. //no app filtering, let WM do this internally
  385. URL=URL.replace("{1}","");
  386. }
  387. //add the user defined fetchQty
  388. URL=URL.replace("{4}","+LIMIT+"+(params.limit||25));
  389. //encode the url
  390. URL=URL_prefix+encodeURI(URL).replace(/\:/,"%3A").replace(/\#/,"%23");
  391.  
  392. log("Graph.fetchPostsFQL: processing feed <a target='_blank' href='"+URL+"'>"+URL+"</a>");
  393. //remember this request
  394. Graph.requests.push(mergeJSON(params));
  395.  
  396. //make the request
  397. var req; req=GM_xmlhttpRequest({
  398. method: "GET",
  399. url: URL,
  400. timeout: Graph.fetchTimeout*1000,
  401. onload: function(response) {try{
  402. //show dev tools
  403. if (opts && debug && !isChrome) if (opts.devDebugGraphData) {
  404. var pkg=debug.print("Graph.fetchPostsFQL.onload.devDebugGraphData: ");
  405. pkg.msg.appendChild(createElement("button",{type:"button",onclick:function(){
  406. promptText(response.responseText);
  407. }},[
  408. createElement("img",{src:"http://i1181.photobucket.com/albums/x430/merricksdad/array.png",title:"Show Data",style:"width:16px;height:16px; vertical-align:bottom;"})
  409. ]));
  410. }
  411. //remove the memory that a request is out
  412. var r = Graph.matchRequest(params);
  413. if (r!=-1) Graph.requests.remove(r);
  414.  
  415. if (response){
  416. try{
  417. //convert to JSON
  418. var data = JSON.parse(response.responseText);
  419. //manage the return object
  420. if (exists(data.data)) {
  421. //there should be two return queries under data
  422. //zip the two together by matching the name to the source_id in the post
  423. var realData = data.data[0].fql_result_set;
  424. var nameData = data.data[1].fql_result_set;
  425. var uidKeys = {};
  426. for (var n=0,l=nameData.length;n<l;n++){
  427. uidKeys[nameData[n].uid.toString()]=nameData[n].name;
  428. }
  429.  
  430. for (var p=0,l=realData.length;p<l;p++){
  431. realData[p].fromName = (uidKeys[realData[p].source_id.toString()]||"undefined");
  432. }
  433. data.data=realData;
  434. //store posts
  435. if (data.data.length) log("Graph.fetchPostsFQL.onLoad: "+data.data.length+" posts received. Validating data...");
  436. else log("Graph.fetchPostsFQL.onLoad: facebook returned an empty data set.");
  437. //no paging exists in the FQL system, we make our own
  438. var gotMoreToDo=false;
  439. var lastPullOldestPost=null;
  440. var lastPullNewestPost=null;
  441. if (data.data.length) {
  442. lastPullOldestPost=data.data.last().created_time;
  443. lastPullNewestPost=data.data[0].created_time;
  444. }
  445. if ((params.targetEdge||null) && (data.data.length)) {
  446. gotMoreToDo = (params.direction<0)?
  447. (lastPullOldestPost > params.targetEdge): //keep fetching older
  448. (lastPullNewestPost < params.targetEdge); //keep fetching newer
  449. }
  450. //read them in backward
  451. if (data.data.length) for (var i=data.data.length-1;i>=0;i--) {
  452. var post=data.data[i];
  453.  
  454. //exclude posts already in our repository
  455. if (Graph.posts[post["post_id"]]!=1) {
  456.  
  457. //store a reference to this post
  458. Graph.posts[post["post_id"]]=1;
  459.  
  460. //send the post back to the callback function here
  461. params.callback(post);
  462. }
  463. }
  464. //process the edge handler for any request that returned posts
  465. var edgeMsg = {friends:params.friends,apps:params.apps,edge:{}};
  466. if (params.direction>=0) edgeMsg.edge.newer=lastPullNewestPost;
  467. if (params.direction<=0) edgeMsg.edge.older=lastPullOldestPost;
  468. if (data.data.length) if (params.edgeHandler||null) params.edgeHandler(edgeMsg);
  469. //go back and do another request if we have specified we have more to do
  470. //this is for use with fetchHours and similar functions
  471. if (gotMoreToDo) {
  472. log("Graph.fetchPostsFQL.onload: was not able to get enough in one return, going back for more...");
  473. var newParams = mergeJSON(params);
  474. newParams.currentEdge=(params.direction<0)?lastPullOldestPost:lastPullNewestPost;
  475. Graph.fetchPosts(newParams);
  476. }
  477.  
  478.  
  479. } else if (data.error||null) {
  480. //check for session expired
  481. if ((data.error.message||"").find("Session has expired")){
  482. //session expired, get a new token
  483. Graph.authToken="";
  484. params["retries_expToken"]=(params["retries_expToken"])?params["retries_expToken"]+1:1; //count retries
  485. if (params["retries_expToken"]<3) {
  486. Graph.requestAuthCodeB(function(){Graph.fetchPosts(params);} );
  487. } else log("Graph.fetchPostsFQL: cannot refresh expired fb auth token",{level:3});
  488. }
  489. else if (data.error.message||null) log("Graph.fetchPosts: "+data.error.message,{level:3});
  490.  
  491. } else log("Graph.fetchPostsFQL: response was unrecognized",{level:3});
  492. data=null;
  493. } catch (e){log("Graph.fetchPostsFQL: response error: "+e+": "+response);}
  494. } else log("Graph.fetchPostsFQL: response was empty",{level:3});
  495. if(req)req=null;
  496. }catch(e){log("Graph.fetchPostsFQL.onload: "+e);}},
  497.  
  498. onabort: function(response) {try{
  499. //remove the memory that a request is out
  500. var r = Graph.matchRequest(params);
  501. if (r!=-1) Graph.requests.remove(r);
  502. log("Graph.fetchPostsFQL: aborted: "+response.responseText);
  503. if(req)req=null;
  504. }catch(e){log("Graph.fetchPostsFQL.onabort: "+e);}},
  505.  
  506. ontimeout: function(response) {try{
  507. //remove the memory that a request is out
  508. params.timeouts++;
  509. var r = Graph.matchRequest(params);
  510. if (r!=-1) Graph.requests.remove(r);
  511. log("Graph.fetchPostsFQL: timeout: retry="+(params.timeouts<3)+", "+response.responseText);
  512. if(req)req=null;
  513. if (params.timeouts<3) Graph.fetchPosts(params);
  514. }catch(e){log("Graph.fetchPostsFQL.ontimeout: "+e);}},
  515.  
  516. onerror: function(response) {try{
  517. //remove the memory that a request is out
  518. var r = Graph.matchRequest(params);
  519. if (r!=-1) Graph.requests.remove(r);
  520. log("Graph.fetchPostsFQL: error: "+response.responseText);
  521. if(req)req=null;
  522. }catch(e){log("Graph.fetchPostsFQL.onerror: "+e);}}
  523. });
  524. }catch(e){log("Graph.fetchPostsFQL_B: "+e);}},
  525. fetchPostsFQL: function(params){try{
  526. log("Graph.fetchPostsFQL("+((params.newer)?"newer":(params.older)?"older":"")+")",{level:1});
  527. params=params || {};
  528. params.timeouts=params.timeouts||0;
  529. var bypassMatchRequest = (params.range||null)?true:false;
  530. //remember the target position if this is a ranged search
  531. //we'll pass targetrange back to this function later if we need to fetch more
  532. if (params.range||null){
  533. if (params.range.oldedge||null) params.targetedge = params.range.oldedge;
  534. }
  535.  
  536. //validate auth token
  537. if (!Graph.authToken) {
  538. log("Graph.fetchPostsFQL: no authToken, get one");
  539. params["retries_noToken"]=(params["retries_noToken"])?params["retries_noToken"]+1:1; //count retries
  540. if (params["retries_noToken"]<3) {
  541. Graph.requestAuthCodeB(function(){Graph.fetchPostsFQL(params);} );
  542. } else {
  543. log("Graph.fetchPostsFQL: cannot get new fb auth token",{level:3});
  544. return {getAuthTokenFailed:true};
  545. }
  546. return;
  547. }
  548.  
  549. //check if there is a request already out with this fb id and matches the direction
  550. var r=Graph.matchRequest(params);
  551. if (!bypassMatchRequest) if (r!=-1){
  552. if (Graph.requests[r].older==null && Graph.requests[r].newer==null) {
  553. log("Graph.fetchPostsFQL: the initial request for data has not been returned yet",{level:3});
  554. return {initRequestSlow:true};
  555. } else {
  556. log("Graph.fetchPostsFQL: a request is already out for posts in that direction and has not returned",{level:3});
  557. return {requestAlreadyOut:true};
  558. }
  559. }
  560.  
  561. var feed=params.feed||null;
  562. var filter = (params.filter||"default");
  563. //create default filter instances when they do not exist
  564. if (params.groupApps||null) {
  565. //set our filter to our first app in the groupApps
  566. //this will be used below various times
  567. filter = "app_"+params.groupApps[0];
  568. if (feed||null) for (var a=0,l=params.groupApps.length;a<l;a++) {
  569. var filtName = "app_"+params.groupApps[a];
  570. if (!(feed.filters[filtName]||null)) feed.addFilter({id:filtName}); //create filter instance if needed
  571. }
  572. } else {
  573. if (feed||null) if (!(feed.filters[filter]||null)) feed.addFilter({id:filter}); //create filter instance if needed
  574. }
  575. //compile feed request strings
  576. var URL_prefix="https://graph.facebook.com/fql?access_token={0}&q=";
  577. var URL="{\"query1\":\"SELECT post_id,target_id,message,app_id,action_links,created_time,attachment,app_data,like_info,source_id FROM stream WHERE source_id IN ({3}){1}{2} ORDER BY created_time DESC{4}\",\"query2\":\"SELECT uid,name FROM user WHERE uid IN (SELECT source_id FROM #query1)\"}";
  578. URL_prefix=URL_prefix.replace("{0}",Graph.authToken);
  579. //specialize call for specific friend post requests
  580. if (params.specificFriend||null) {
  581. URL=URL.replace("{3}",feed.id);
  582. } else {
  583. URL=URL.replace("{3}","SELECT uid2 FROM friend WHERE uid1=me() LIMIT 5000");
  584. }
  585. //get older posts
  586. //verify that the feed "until" time does not violate olderLimit set by user
  587. if (params.older){
  588. var edge=(params.range||null)?params.range.oldedge:feed.filters[filter].oldedge;
  589. if (edge||null){
  590. var limit=(params.limit||null); //this is not FB search limit keyword, this is a WM timelimit
  591. var timeNow=timeStamp();
  592. //no oldest post limit on range fetches
  593. if (params.range||null) limit=null;
  594. if (limit) {
  595. if ((timeNow-(edge*1000)) > limit) {
  596. log("Graph.fetchPosts("+params.feed.url+"): the user-set older limit of this feed has been reached",{level:2});
  597. return {olderLimitReached:true};
  598. }
  599. }
  600. URL=URL.replace("{2}"," AND created_time < "+edge);
  601.  
  602. } else {
  603. log("Graph.fetchPostsFQL("+params.feed.url+"): The previous result did not return pagination. Restarting fetching from current time.");
  604. URL=URL.replace("{2}","");
  605. }
  606. //get newer posts
  607. } else if (params.newer){
  608. var edge=(params.range||null)?params.range.newedge:feed.filters[filter].newedge;
  609. if (exists(edge)) {
  610. URL=URL.replace("{2}"," AND created_time > "+edge);
  611. }
  612. //fetch at current time
  613. } else {
  614. URL=URL.replace("{2}","");
  615. }
  616.  
  617. //filters come in the form of "app_123456789012"
  618. if (params.groupApps||null) {
  619. //fetch posts for multiple apps at once
  620. URL=URL.replace("{1}"," AND app_id IN ("+params.groupApps.join(",")+")");
  621. } else if (filter!=undefined && filter!=null){
  622. URL=URL.replace("{1}"," AND app_id IN ("+filter.split("app_")[1]+")");
  623. } else {
  624. //no filter, nothing passed
  625. //this should never happen
  626. URL=URL.replace("{1}","");
  627. }
  628. //add the user defined fetchQty
  629. URL=URL.replace("{4}","+LIMIT+"+(params.fetchQty||25));
  630. //encode the url
  631. URL=URL_prefix+encodeURI(URL).replace(/\:/,"%3A").replace(/\#/,"%23");
  632.  
  633. log("Graph.fetchPostsFQL: processing feed <a target='_blank' href='"+URL+"'>"+URL+"</a>");
  634. //console.log(URL);
  635. //return;
  636. //remember this request
  637. Graph.requests.push({feed:params.feed, older:params.older, newer:params.newer, filter:filter, groupApps:params.groupApps, specificFriend:params.specificFriend});
  638. //console.log("request pushed");
  639.  
  640. //return;
  641. var req; req=GM_xmlhttpRequest({
  642. method: "GET",
  643. url: URL,
  644. timeout: Graph.fetchTimeout*1000,
  645. onload: function(response) {try{
  646. //show dev tools
  647. if (opts && debug && !isChrome) if (opts.devDebugGraphData) {
  648. var pkg=debug.print("Graph.fetchPostsFQL.onload.devDebugGraphData: ");
  649. pkg.msg.appendChild(createElement("button",{type:"button",onclick:function(){
  650. //response.responseText.toClipboard();
  651. promptText(response.responseText);
  652. }},[
  653. createElement("img",{src:"http://i1181.photobucket.com/albums/x430/merricksdad/array.png",title:"Show Data",style:"width:16px;height:16px; vertical-align:bottom;"})
  654. ]));
  655. }
  656. //remove the memory that a request is out
  657. var r = Graph.matchRequest(params);
  658. if (r!=-1) Graph.requests.remove(r);
  659.  
  660. if (response){
  661. try{
  662. //convert to JSON
  663. var data = JSON.parse(response.responseText);
  664. //manage the return object
  665. if (exists(data.data)) {
  666. //log("response contains data");
  667. //there should be two return queries under data
  668. //zip the two together by matching the name to the source_id in the post
  669. var realData = data.data[0].fql_result_set;
  670. var nameData = data.data[1].fql_result_set;
  671. var uidKeys = {};
  672. for (var n=0,l=nameData.length;n<l;n++){
  673. uidKeys[nameData[n].uid.toString()]=nameData[n].name;
  674. }
  675.  
  676. for (var p=0,l=realData.length;p<l;p++){
  677. realData[p].fromName = (uidKeys[realData[p].source_id.toString()]||"undefined");
  678. }
  679. data.data=realData;
  680. //store posts
  681. if (data.data.length) log("Graph.fetchPostsFQL.onLoad: "+data.data.length+" posts received. Validating data...");
  682. else log("Graph.fetchPostsFQL.onLoad: facebook returned an empty data set.");
  683. //no paging exists in the FQL system, we make our own
  684. var gotMoreToDo=false;
  685. var lastPullOldestPost=null;
  686. var lastPullNewestPost=null;
  687. if (data.data.length) {
  688. lastPullOldestPost=data.data.last().created_time;
  689. lastPullNewestPost=data.data[0].created_time;
  690. }
  691. if ((params.targetedge||null) && (data.data.length)) {
  692. gotMoreToDo = (lastPullOldestPost > params.targetedge);
  693. }
  694. //read them in backward
  695. if (data.data.length) for (var i=data.data.length-1;i>=0;i--) {
  696. var post=data.data[i];
  697.  
  698. Graph.validatePost({
  699. post:post,
  700. callback:params.callback||null,
  701. older:params.older,
  702. newer:params.newer
  703. });
  704.  
  705. }
  706. //go back and do another request if we have specified we have more to do
  707. //this is for use with fetchHours and similar functions
  708. if (gotMoreToDo) {
  709. log("Graph.fetchPostsFQL.onload: was not able to get enough in one return, going back for more...");
  710. //clone the last set of params
  711. var newParams = mergeJSON(params);
  712. newParams.range={oldedge:0,newedge:0}; //new instance to prevent byRef errors
  713. //update the range settings
  714. newParams.range.newedge=lastPullOldestPost;
  715. newParams.range.oldedge=params.targetedge; //remember the original passed oldest data to target
  716. //make the next request
  717. Graph.fetchPosts(newParams);
  718. }
  719. //start cleanup
  720. if (params.callback) delete params.callback;
  721.  
  722.  
  723. //capture the next and prev urls, but dont overwrite current known time boundaries
  724. if (data.data.length){
  725. if (params.groupApps||null){
  726. //manage all filters at once from the group-fetch apps array
  727. for (var n=0,l=params.groupApps.length;n<l;n++){
  728. var filtName = "app_"+params.groupApps[n];
  729. if (!params.newer && !params.older) {
  730. feed.filters[filtName].newedge = lastPullNewestPost;
  731. feed.filters[filtName].oldedge = lastPullOldestPost;
  732. }
  733. if (params.newer) feed.filters[filtName].newedge = lastPullNewestPost;
  734. if (params.older) feed.filters[filtName].oldedge = lastPullOldestPost;
  735. }
  736. } else if (filter!=undefined && filter!=null) {
  737. //if the current request was a recent posts pull, set both edges
  738. if (!params.newer && !params.older) {
  739. feed.filters[filter].newedge = lastPullNewestPost;
  740. feed.filters[filter].oldedge = lastPullOldestPost;
  741. }
  742.  
  743. //if the current request got newer posts, push the newer post edge
  744. if (params.newer) feed.filters[filter].newedge = lastPullNewestPost;
  745. //if the current request got older posts, push the older post edge
  746. if (params.older) feed.filters[filter].oldedge = lastPullOldestPost;
  747. }
  748. }
  749.  
  750. } else if (data.error||null) {
  751. //check for session expired
  752. if ((data.error.message||"").find("Session has expired")){
  753. //session expired, get a new token
  754. Graph.authToken="";
  755. params["retries_expToken"]=(params["retries_expToken"])?params["retries_expToken"]+1:1; //count retries
  756. if (params["retries_expToken"]<3) {
  757. Graph.requestAuthCodeB(function(){Graph.fetchPosts(params);} );
  758. } else log("Graph.fetchPostsFQL: cannot refresh expired fb auth token",{level:3});
  759. }
  760. else if (data.error.message||null) log("Graph.fetchPosts: "+data.error.message,{level:3});
  761.  
  762. } else log("Graph.fetchPostsFQL: response was unrecognized",{level:3});
  763. data=null;
  764. } catch (e){log("Graph.fetchPostsFQL: response error: "+e+": "+response);}
  765. } else log("Graph.fetchPostsFQL: response was empty",{level:3});
  766. if(req)req=null;
  767. }catch(e){log("Graph.fetchPostsFQL.onload: "+e);}},
  768.  
  769. onabort: function(response) {try{
  770. //remove the memory that a request is out
  771. var r = Graph.matchRequest(params);
  772. if (r!=-1) Graph.requests.remove(r);
  773. log("Graph.fetchPostsFQL: aborted: "+response.responseText);
  774. if(req)req=null;
  775. }catch(e){log("Graph.fetchPostsFQL.onabort: "+e);}},
  776.  
  777. ontimeout: function(response) {try{
  778. //remove the memory that a request is out
  779. params.timeouts++;
  780. var r = Graph.matchRequest(params);
  781. if (r!=-1) Graph.requests.remove(r);
  782. log("Graph.fetchPostsFQL: timeout: retry="+(params.timeouts<3)+", "+response.responseText);
  783. if(req)req=null;
  784. if (params.timeouts<3) Graph.fetchPosts(params);
  785. }catch(e){log("Graph.fetchPostsFQL.ontimeout: "+e);}},
  786.  
  787. onerror: function(response) {try{
  788. //remove the memory that a request is out
  789. var r = Graph.matchRequest(params);
  790. if (r!=-1) Graph.requests.remove(r);
  791. log("Graph.fetchPostsFQL: error: "+response.responseText);
  792. if(req)req=null;
  793. }catch(e){log("Graph.fetchPostsFQL.onerror: "+e);}}
  794. });
  795. }catch(e){log("Graph.fetchPostsFQL: "+e);}},
  796. /* fetchPosts details:
  797. params = {
  798. feed:<feed reference>,
  799. filter:<appID>,
  800. next:<url containing 'until'>,
  801. prev:<url containing 'since'>,
  802. callback:<where to ship the return data>,
  803. retries_noToken:<counter>,
  804. fetchQty:<number>,
  805. specific:<specific range object>
  806. }
  807. */
  808. fetchPosts: function(params){try{
  809. log("Graph.fetchPosts is deprecated. Use Graph.fetchPostsFQL.");
  810. return Graph.fetchPostsFQL(params);
  811. log("Graph.fetchPosts()",{level:1});
  812. params=params || {};
  813. params.timeouts=params.timeouts||0;
  814. var bypassMatchRequest = (params.range||null)?true:false;
  815. //remember the target position if this is a ranged search
  816. //the very first call we make is a "since" call, but all sequential calls are "until" calls due to FB's stupid pagination methods
  817. if (params.range||null){
  818. //log(params.range.since);
  819. if (params.range.since||null) params.targetUntil = params.range.since;
  820. }
  821. if (!Graph.authToken) {
  822. log("Graph.fetchPosts: no authToken, get one");
  823. params["retries_noToken"]=(params["retries_noToken"])?params["retries_noToken"]+1:1; //count retries
  824. if (params["retries_noToken"]<3) {
  825. Graph.requestAuthCodeB(function(){Graph.fetchPosts(params);} );
  826. } else {
  827. log("Graph.fetchPosts: cannot get new fb auth token",{level:3});
  828. return {getAuthTokenFailed:true};
  829. }
  830. return;
  831. }
  832.  
  833. //check if there is a request already out with this fb id and matches the direction
  834. var r=Graph.matchRequest(params);
  835. if (!bypassMatchRequest) if (r!=-1){
  836. if (Graph.requests[r].next==null && Graph.requests[r].prev==null) {
  837. log("Graph.fetchPosts: the initial request for data has not been returned yet",{level:3});
  838. return {initRequestSlow:true};
  839. } else {
  840. log("Graph.fetchPosts: a request is already out for posts in that direction and has not returned",{level:3});
  841. return {requestAlreadyOut:true};
  842. }
  843. }
  844.  
  845. //for each user specified feed source, get posts
  846. var feed=params.feed||null;
  847. var filter = (params.filter||"default");
  848. if (!(feed.filters[filter]||null)) feed.addFilter({id:filter}); //create filter instance if needed
  849.  
  850. var URL=feed.url+"?date_format=U&limit="+((params.range||null)?250:params.fetchQty)+"&access_token="+Graph.authToken;
  851. //get older posts
  852. //verify that the feed "until" time does not violate olderLimit set by user
  853. if (params.next || ((params.range||null)?params.range.until||null:null) ){
  854. var until=(params.range||null)?params.range.until:feed.filters[filter].next.getUrlParam("until");
  855. //debug.print(["var until",until]);
  856. if (until||null){
  857. var limit=(params.limit||null); //this is not FB search limit keyword, this is a WM timelimit
  858. var timeNow=timeStamp();
  859. //no oldest post limit on range fetches
  860. if (params.range||null) limit=null;
  861. var fixTime = (until.length < 10)?(until+"000"):until;
  862.  
  863. //debug.print(["var until:",until, until.length, fixTime])
  864. if (limit) {
  865. if ((timeNow-(fixTime)) > limit) {
  866. //log("Graph.fetchPosts("+params.feed.url+"): the user-set older limit of this feed has been reached",{level:2});
  867. return {olderLimitReached:true};
  868. }
  869. }
  870. URL+="&until="+fixTime;
  871. } else {
  872. log("Graph.fetchPosts("+params.feed.url+"): The previous result did not return pagination. Restarting fetching from current time.");
  873. }
  874. }
  875. //get newer posts
  876. //rules manager action fetchHours will be asking for a range staring at time X, so use range.since
  877. else if (params.prev || ((params.range||null)?params.range.since||null:null) ) {
  878. var since=(params.range||null)?params.range.since:feed.filters[filter].prev.getUrlParam("since");
  879. if (exists(since)) {
  880. URL+="&since="+since;
  881. }
  882. }
  883.  
  884. //add a filter if there is one
  885. if (exists(params.filter)) URL+="&filter="+filter; //check using params.filter, do not use filter here or it may inject "default"
  886.  
  887. log("Graph.fetchPosts: processing feed <a target='_blank' href='"+URL+"'>"+URL+"</a>");
  888. //remember this request
  889. Graph.requests.push({feed:params.feed, next:params.next, prev:params.prev, filter:filter});
  890.  
  891. var req; req=GM_xmlhttpRequest({
  892. method: "GET",
  893. url: URL,
  894. timeout: Graph.fetchTimeout*1000,
  895. onload: function(response) {try{
  896. //show dev tools
  897. if (opts && debug && !isChrome) if (opts.devDebugGraphData) {
  898. var pkg=debug.print("Graph.fetchPosts.onload.devDebugGraphData: ");
  899. pkg.msg.appendChild(createElement("button",{type:"button",onclick:function(){
  900. //response.responseText.toClipboard();
  901. promptText(response.responseText);
  902. }},[
  903. createElement("img",{src:"http://i1181.photobucket.com/albums/x430/merricksdad/array.png",title:"Show Data",style:"width:16px;height:16px; vertical-align:bottom;"})
  904. ]));
  905. }
  906. //remove the memory that a request is out
  907. var r = Graph.matchRequest(params);
  908. if (r!=-1) Graph.requests.remove(r);
  909.  
  910. if (response){
  911. try{
  912. //convert to JSON
  913. var data = JSON.parse(response.responseText);
  914. //add new posts to graph.posts
  915. if (exists(data.data)) {
  916. //log("response contains data");
  917.  
  918. //alert(JSON.stringify(data.data));
  919. //store posts
  920. if (data.data.length) log("Graph.fetchPosts.onLoad: "+data.data.length+" posts received. Validating data...");
  921. else log("Graph.fetchPosts.onLoad: facebook returned an empty data set.");
  922. var gotMoreToDo=false;
  923. if ((params.targetUntil||null) && (data.data.length) && (data.paging.next)) {
  924. var lastPullOldestPost=data.paging.next.getUrlParam("until");
  925. //2013/9/7: known facebook limit maximum is 500, but we are fetching in 250's
  926. //have we maxed out AND is oldest returned post newer than what we asked for
  927. gotMoreToDo = (data.data.length>=250) && (lastPullOldestPost > params.targetUntil);
  928. }
  929. if (data.data.length) for (var i=data.data.length-1;i>=0;i--) {
  930. var post=data.data[i];
  931.  
  932. Graph.validatePost({
  933. post:post,
  934. callback:params.callback||null,
  935. next:params.next
  936. });
  937.  
  938. }
  939. if (gotMoreToDo) {
  940. log("Graph.fetchPosts.onload: was not able to get enough in one return, going back for more...");
  941. //clone the last set of params
  942. var newParams = mergeJSON(params);
  943. newParams.range={since:0,until:0}; //new instance to prevent byRef errors
  944. //update the range settings
  945. //newParams.range.since=data.paging.previous.getUrlParam("since");
  946. newParams.range.until=data.paging.next.getUrlParam("until");
  947. //log([params.range.since,newParams.range.since,newParams.range.until,timeStampNoMS()]);
  948. newParams.targetUntil = params.range.since; //remember the original passed oldest data to target
  949. //make the next request
  950. Graph.fetchPosts(newParams);
  951. }
  952. //start cleanup
  953. if (params.callback) delete params.callback;
  954.  
  955.  
  956. //capture the next and prev urls, but dont overwrite current known time boundaries
  957. if (data.paging||null){
  958. //if this is the first time we've used this object, remember its locations
  959. if (!feed.filters[filter].next) feed.filters[filter].next = data.paging.next;
  960. if (!feed.filters[filter].prev) feed.filters[filter].prev = data.paging.prev;
  961.  
  962. //if the current request did not get older posts, push the newer post bracket
  963. if (!params.prev) feed.filters[filter].next = data.paging.next;
  964. //if the current request did not get newer posts, push the older post bracket
  965. if (!params.next) feed.filters[filter].prev = data.paging.previous;
  966. } else {
  967. log("Graph.fetchPosts.onLoad: facebook failed to return pagination data.")
  968. }
  969.  
  970. } else if (data.error||null) {
  971. //check for session expired
  972. if ((data.error.message||"").find("Session has expired")){
  973. //session expired, get a new token
  974. Graph.authToken="";
  975. params["retries_expToken"]=(params["retries_expToken"])?params["retries_expToken"]+1:1; //count retries
  976. if (params["retries_expToken"]<3) {
  977. Graph.requestAuthCodeB(function(){Graph.fetchPosts(params);} );
  978. } else log("Graph.fetchPosts: cannot refresh expired fb auth token",{level:3});
  979. }
  980. else if (data.error.message||null) log("Graph.fetchPosts: "+data.error.message,{level:3});
  981.  
  982. } else log("Graph.fetchPosts: response was unrecognized",{level:3});
  983. data=null;
  984. } catch (e){log("Graph.fetchPosts: response error: "+e+": "+response);}
  985. } else log("Graph.fetchPosts: response was empty",{level:3});
  986. if(req)req=null;
  987. }catch(e){log("Graph.fetchPosts.onload: "+e);}},
  988.  
  989. onabort: function(response) {try{
  990. //remove the memory that a request is out
  991. var r = Graph.matchRequest(params);
  992. if (r!=-1) Graph.requests.remove(r);
  993. log("Graph.fetchPosts: aborted: "+response.responseText);
  994. if(req)req=null;
  995. }catch(e){log("Graph.fetchPosts.onabort: "+e);}},
  996.  
  997. ontimeout: function(response) {try{
  998. //remove the memory that a request is out
  999. params.timeouts++;
  1000. var r = Graph.matchRequest(params);
  1001. if (r!=-1) Graph.requests.remove(r);
  1002. log("Graph.fetchPosts: timeout: retry="+(params.timeouts<3)+", "+response.responseText);
  1003. if(req)req=null;
  1004. if (params.timeouts<3) Graph.fetchPosts(params);
  1005. }catch(e){log("Graph.fetchPosts.ontimeout: "+e);}},
  1006.  
  1007. onerror: function(response) {try{
  1008. //remove the memory that a request is out
  1009. var r = Graph.matchRequest(params);
  1010. if (r!=-1) Graph.requests.remove(r);
  1011. log("Graph.fetchPosts: error: "+response.responseText);
  1012. if(req)req=null;
  1013. }catch(e){log("Graph.fetchPosts.onerror: "+e);}}
  1014. });
  1015. }catch(e){log("Graph.fetchPosts: "+e);}},
  1016. };
  1017.  
  1018. log("Graph initialized");
  1019. })();