WM Graph API Interface (Beta Branch)

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

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/411/1288/WM%20Graph%20API%20Interface%20%28Beta%20Branch%29.js

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