Major changes
[quicknewsreader] / qml / QuickNewsReader / content / js / GoogleReaderAPI.js
1 /*
2     Copyright 2011 - Tommi Laukkanen (www.substanceofcode.com)
3
4     This file is part of NewsFlow.
5
6     NewsFlow is free software: you can redistribute it and/or modify
7     it under the terms of the GNU Lesser General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10
11     NewsFlow is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU Lesser General Public License for more details.
15
16     You should have received a copy of the GNU Lesser General Public License
17     along with NewsFlow. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 var sid = "";
21 var sidToken = "";
22
23 // UI components
24 //var waiting;
25 //var done;
26 //var model;
27 //var tagsModel;
28 //var logo;
29 //var error;
30 //var navigation;
31
32 var continuation = "";
33 var actionID = "";
34 var actionFeedUrl = "";
35 var accessToken = "";
36 var action = "";
37 var tags = "";
38
39 var itemsURL = "";
40
41 function doWebRequest(method, url, params, callback) {
42     var doc = new XMLHttpRequest();
43     //console.log(method + " " + url);
44
45     doc.onreadystatechange = function() {
46         if (doc.readyState == XMLHttpRequest.HEADERS_RECEIVED) {
47             var status = doc.status;
48             if(status!=200) {
49                 showError("Google API returned " + status + " " + doc.statusText);
50             }
51         } else if (doc.readyState == XMLHttpRequest.DONE) {
52             var data;
53             var contentType = doc.getResponseHeader("Content-Type");
54             data = doc.responseText;
55             callback(data);
56         }
57     }
58
59     doc.open(method, url);
60     if(sid.length>0) {
61         //console.log("Authorization GoogleLogin auth=" + sid);
62         doc.setRequestHeader("Authorization", "GoogleLogin auth=" + sid);
63         doc.setRequestHeader("Cookie", "SID=" + sidToken);
64     }
65     if(params.length>0) {
66         //console.log("Sending: " + params);
67         doc.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
68         doc.setRequestHeader("Content-Length", String(params.length));
69         doc.send(params);
70     } else {
71         doc.send();
72     }
73 }
74
75 /** Parse parameter from given URL */
76 function parseAuth(data, parameterName) {
77     var parameterIndex = data.indexOf(parameterName + "=");
78     if(parameterIndex<0) {
79         // We didn't find parameter
80         console.log("Didn't find Auth");
81         addError("Didn't find Auth");
82         return "";
83     }
84     var equalIndex = data.indexOf("=", parameterIndex);
85     if(equalIndex<0) {
86         return "";
87     }
88
89     var lineBreakIndex = data.indexOf("\n", equalIndex+1)
90
91     var value = "";
92     value = data.substring(equalIndex+1, lineBreakIndex);
93     return value;
94 }
95
96 function addError(msg) {
97     console.log(msg)
98     /*
99     model.append({
100                  "title": "Error",
101                  "desc": msg,
102                  "author": "",
103                  "published": "",
104                  "more": true,
105                  "source": ""})
106                  */
107 }
108
109 function login(email, password) {
110     try {
111         // waiting.state = "shown";
112         var url = "https://www.google.com/accounts/ClientLogin?Email=" + encodeURIComponent(email) + "&Passwd=" + encodeURIComponent(password) + "&service=reader";
113         doWebRequest("GET", url, "", parseToken);
114     }catch(err) {
115         showError("Error while logging in.");
116     }
117 }
118
119 function showError(msg) {
120     console.log("ERROR: " + msg)
121 //    waiting.state = "hidden";
122 //    error.reason = msg;
123 //    error.state = "shown";
124 }
125
126 function removeLinks(original) {
127     var txt = original;
128     txt = txt.replace(/<a /g, "<span ");
129     txt = txt.replace(/<\/a>/g, "</span>");
130     return txt;
131 }
132
133 function parseToken(data) {
134     sid = parseAuth(data, "Auth");
135     //console.log("Auth=" + sid);
136     sidToken = parseAuth(data, "SID");
137     //console.log("SID=" + sidToken);
138     // logo.state = "hidden"; //.visible = false;
139     if(sid.length>0) {
140         //navigation.state = "menu";
141         //waiting.state = "hidden";
142
143         WorkerScript.sendMessage({"sid": sid, "sidToken": sidToken});
144
145         //loadUnreadNews();
146     } else {
147         addError("Couldn't parse SID");
148         //waiting.state = "hidden";
149     }
150 }
151
152 function loadSubscriptions() {
153     //waiting.state = "shown";
154     var url = "http://www.google.com/reader/api/0/subscription/list?output=json";
155     doWebRequest("GET", url, "", parseSubscriptions);
156 }
157
158 function parseSubscriptions(data) {
159     //console.log("Subscriptions: " + data);
160
161     var tags = eval("[" + data + "]")[0];
162     for(var i in tags.subscriptions) {
163         var tag = tags.subscriptions[i];
164         var id = tag.id;
165         var title = tag.title;
166
167         WorkerScript.sendMessage({"title": title, "published": '',"tag": tag, "id":id});
168     }
169     //navigation.state = "tags";
170     //waiting.state = "hidden";
171 }
172
173 function loadTags() {
174     //waiting.state = "shown";
175     var url = "http://www.google.com/reader/api/0/tag/list?output=json";
176     doWebRequest("GET", url, "", parseTags);
177 }
178
179 function parseTags(data) {
180     var tags = eval("[" + data + "]")[0];
181     for(var i in tags.tags) {
182         var tag = tags.tags[i];
183         var id = tag.id;
184         var title = id;
185         while(title.indexOf("/")>0) {
186             var index = title.indexOf("/");
187             title = title.substring(index+1);
188         }
189
190         WorkerScript.sendMessage({"title": title, "published": '', "tag": tag, "id":id});
191     }
192     //navigation.state = "tags";
193     //waiting.state = "hidden";
194 }
195
196 function loadAllNews() {
197     itemsURL = "http://www.google.com/reader/api/0/stream/contents/user/-/state/com.google/reading-list";
198     loadNews();
199 }
200
201 function loadUnreadNews() {
202     itemsURL = "http://www.google.com/reader/api/0/stream/contents/user/-/state/com.google/reading-list?xt=user/-/state/com.google/read";
203     loadNews();
204 }
205
206 function loadStarred() {
207     itemsURL = "http://www.google.com/reader/api/0/stream/contents/user/-/state/com.google/starred";
208     loadNews();
209 }
210
211 function loadTaggedItems(tag) {
212     itemsURL = "http://www.google.com/reader/api/0/stream/contents/" + tag;
213     loadNews();
214 }
215
216 function loadSubscriptionItems(subscription) {
217     itemsURL = "http://www.google.com/reader/api/0/stream/contents/" + subscription;
218     loadNews();
219 }
220
221 function loadNews() {
222     try {
223         //waiting.state = "shown";
224         doWebRequest("GET", itemsURL, "", parseNews);
225     } catch(err) {
226         showError("Error while loading news: " + err);
227     }
228 }
229
230 function getNodeValue(node, name) {
231     var nodeValue = "";
232     for(var i=0; i<node.childNodes.length; i++) {
233         var nodeName = node.childNodes[i].nodeName;
234         if(nodeName==name) {
235             nodeValue = node.childNodes[i].firstChild.nodeValue;
236         }
237     }
238     return nodeValue;
239 }
240
241 function parseEntry(item) {
242     var content = ""
243     if(typeof(item.content)!=undefined && item.content!=null) {
244         content = item.content.content;
245     } else if(typeof(item.summary)!=undefined && item.summary!=null) {
246         content = item.summary.content;
247     } else {
248         content = "";
249     }
250     content = removeLinks(content);
251     var milliseconds = parseInt(parseInt(item.published)*1000);
252     var published = new Date(parseInt(milliseconds));
253     var link = item.alternate[0].href;
254     //console.log("Link: " + link);
255     var isRead = true;
256     for(var i in item.categories) {
257         var category = item.categories[i];
258         if(category.indexOf("/reading-list")>0) {
259             isRead = false;
260         }
261     }
262
263
264     WorkerScript.sendMessage({
265                                  "id": item.id,
266                                  "title": item.title,
267                                  "description": content,
268                                  "author": item.origin.title,
269                                  "published": prettyDate(published),
270                                  "more": false,
271                                  "source": item.origin.title,
272                                  "link": link,
273                                  "feedUrl": item.origin.streamId,
274                                  "isRead": isRead
275                     });
276 }
277
278 function parseNews(data) {
279     //try {
280         //console.log("DATA: " + data);
281         var doc = eval("[" + data + "]")[0];
282         if(doc==null || typeof(doc)==undefined) {
283             WorkerScript.sendMessage({
284                          "title": "Error",
285                          "description": "",
286                          "author": "",
287                          "published": "",
288                          "more": true,
289                          "isRead": false,
290                          "source": ""});
291             //waiting.state = "hidden";
292             return;
293         }
294
295         //var moreIndex = model.count - 1;
296
297         continuation = doc.continuation;
298         for(var i in doc.items) {
299             var item = doc.items[i];
300             parseEntry(item);
301         }
302 /*
303         if(moreIndex>0) {
304             if(model.get(moreIndex).title.indexOf("Loading...")>-1) {
305                 model.remove(moreIndex);
306             }
307         }
308
309         model.append({
310                      "title": "Load more...<br/><br/>",
311                      "description": "",
312                      "author": "",
313                      "published": "",
314                      "isRead": false,
315                      "more": true,
316                      "source": ""});
317                      */
318     //}catch(err) {
319     //    addError("Error: " + err);
320     //}
321    // navigation.state = "items";
322    // waiting.state = "hidden";
323 }
324
325 function loadMore() {
326     //waiting.state = "shown";
327     var url = itemsURL;
328     if(itemsURL.indexOf("?")>0) {
329         url += "&";
330     } else {
331         url += "?";
332     }
333     url += "c=" + continuation;
334     doWebRequest("GET", url, "", parseNews);
335 }
336
337 function getToken() {
338     var url = "http://www.google.com/reader/api/0/token";
339     doWebRequest("GET", url, "", parseAccessToken, null);
340 }
341
342 function parseAccessToken(data) {
343     accessToken = data;
344     if(action=="read") {
345         var url = "http://www.google.com/reader/api/0/edit-tag";
346         var dd = "ac=edit-tags&a=user/-/state/com.google/read&i=" + encodeURIComponent(actionID) + "&s=" + encodeURIComponent(actionFeedUrl) + "&T=" + accessToken;
347         doWebRequest("POST", url, dd, showDone, null);
348     } else if(action=="unread") {
349         var url = "http://www.google.com/reader/api/0/edit-tag";
350         var dd = "ac=edit-tags&a=user/-/state/com.google/kept-unread&r=user/-/state/com.google/read&i=" + encodeURIComponent(actionID) + "&s=" + encodeURIComponent(actionFeedUrl) + "&T=" + accessToken;
351         doWebRequest("POST", url, dd, showDone, null);
352     } else if(action=="fav") {
353         var url = "http://www.google.com/reader/api/0/edit-tag?client=-";
354         var dd = "a=user/-/state/com.google/starred&i=" + encodeURIComponent(actionID) + "&s=" + encodeURIComponent(actionFeedUrl) + "&T=" + accessToken;
355         doWebRequest("POST", url, dd, showDone, null);
356     } else if(action=="tags") {
357         var url = "http://www.google.com/reader/api/0/edit-tag?client=-";
358         var dd =
359             "a=user/-/label/" + encodeURIComponent(tags) +
360             "&i=" + encodeURIComponent(actionID) +
361             "&s=" + encodeURIComponent(actionFeedUrl) +
362             "&T=" + accessToken;
363         doWebRequest("POST", url, dd, showDone, null);
364     }
365 }
366
367 function markAsRead(id, feedUrl, showLoadingIndicator) {
368     actionID = id;
369     actionFeedUrl = feedUrl;
370     if(showLoadingIndicator) {
371         waiting.state = "shown";
372     }
373     action = "read";
374     getToken();
375 }
376
377 function markAsUnread(id, feedUrl) {
378     actionID = id;
379     actionFeedUrl = feedUrl;
380     //waiting.state = "shown";
381     action = "unread";
382     //done.status = "Marked as unread";
383     getToken();
384 }
385
386 function addTags(id, feedUrl, newTags) {
387     actionID = id;
388     actionFeedUrl = feedUrl;
389     //waiting.state = "shown";
390     action = "tags";
391     //done.status = "Added tags";
392     tags = newTags;
393     getToken();
394 }
395
396 function markAsFavourite(id, feedUrl) {
397     actionID = id;
398     actionFeedUrl = feedUrl;
399     //waiting.state = "shown";
400     action = "fav";
401     //done.status = "Marked as favourite";
402     getToken();
403 }
404
405 function showDone(data) {
406     console.log("DONE: " + data);
407     //if(waiting.state!="shown") {
408     //    return;
409     //}
410     //waiting.state = "hidden";
411     //done.status = "";
412     if(typeof(data)!=undefined && data!=null) {
413         if(action=="read") {
414             //done.status = "Marked as read " + data;
415         } else if(action=="unread") {
416             //done.status = "Marked as unread " + data;
417         } else {
418             //done.status = "" + data;
419         }
420     }
421     //done.state = "shown";
422 }
423
424 function doNothing(data) {
425     // Nothing...
426 }
427
428 function prettyDate(date){
429     try {
430         var diff = (((new Date()).getTime() - date.getTime()) / 1000);
431         var day_diff = Math.floor(diff / 86400);
432
433         if ( isNaN(day_diff) || day_diff >= 31 ) {
434             //console.log("Days: " + day_diff);
435             return "some time ago";
436         } else if (day_diff < 0) {
437             //console.log("day_diff: " + day_diff);
438             return "just now";
439         }
440
441         return day_diff == 0 && (
442                     diff < 60 && "just now" ||
443                     diff < 120 && "1 minute ago" ||
444                     diff < 3600 && Math.floor( diff / 60 ) + " min ago" ||
445                     diff < 7200 && "1 hour ago" ||
446                     diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
447                 day_diff == 1 && "Yesterday" ||
448                 day_diff < 7 && day_diff + " days ago" ||
449                 day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
450         day_diff >= 31 && Math.ceil( day_diff / 30 ) + " months ago";
451     } catch(err) {
452         console.log("Error: " + err);
453         return "some time ago";
454     }
455 }
456
457 // 2011-01-24T18:48:00Z
458 function parseDate(stamp)
459 {
460     try {
461         //console.log("stamp: " + stamp);
462         var parts = stamp.split("T");
463         var day;
464         var time;
465         var hours;
466         var minutes;
467         var seconds = 0;
468         var year;
469         var month;
470
471         var dates = parts[0].split("-");
472         year = parseInt(dates[0]);
473         month = parseInt(dates[1])-1;
474         day = parseInt(dates[2]);
475
476         var times = parts[1].split(":");
477         hours = parseInt(times[0]);
478         minutes = parseInt(times[1]);
479
480         var dt = new Date();
481         dt.setUTCDate(day);
482         dt.setYear(year);
483         dt.setUTCMonth(month);
484         dt.setUTCHours(hours);
485         dt.setUTCMinutes(minutes);
486         dt.setUTCSeconds(seconds);
487
488         //console.log("day: " + day + " year: " + year + " month " + month + " hour " + hours);
489
490         return dt;
491     } catch(err) {
492         console.log("Error while parsing date: " + err);
493         return new Date();
494     }
495 }
496
497 /// ======== WorkerScript related functions ========
498 WorkerScript.onMessage = function(message) {
499
500      // the structure of "message" object is:
501      //   - attribute 'action' --> what to do
502      //   - other attributes: will be passed on to the right function
503
504      if(message.action === 'login') {
505          login(message.email, message.password)
506      }
507      else {
508          sid = message.sid; sidToken = message.sidToken;
509
510          if(message.action === 'getCategoryContent') {
511              // read the feeds in the right category
512              switch(message.category) {
513                  case 0: loadAllNews(); break;
514                  case 1: loadUnreadNews(); break;
515                  case 2: loadStarred(); break;
516                  case 3: loadSubscriptions(); break;
517                  case 4: loadTags(); break;
518              }
519          }
520          else if(message.action === 'getSubscriptionItems') {
521              // read the feeds of that subscription
522              loadSubscriptionItems(message.subscription)
523          }
524          else if(message.action === 'getTaggedItems') {
525              // read the feeds of that subscription
526              loadTaggedItems(message.tag)
527          }
528      }
529 }
530