Some Google Buzz support
[social_wok.user.js] / social_wok.user.js
1 // ==UserScript==
2 // @name        Social commentaries for the Wok
3 // @description Add likes, comments and references from social sites to the Wok pages
4 // @namespace   http://oblomov.myopenid.com
5 // @include     http://wok.oblomov.eu/*
6 // @include     http://localhost/~oblomov/wok/*
7 // @author      Giuseppe "Oblomov" Bilotta
8 // @version     20110123
9 // ==/UserScript==
10
11
12 if (typeof(unsafeWindow) == 'undefined') unsafeWindow = window;
13
14 var social = function() {
15         var img_preload = new Image();
16         var now = new Date();
17         var doc = unsafeWindow.document;
18
19         /* list of the permalinks we want to check social networks for.
20            the first one will always be the document location */
21         this.permalink = [ unsafeWindow.location.toString() ];
22
23         /* parents to the permalink */
24         this.permalink_parent = [ doc.getElementById('pageinfo') ];
25
26         var load_script = function(url) {
27                 var e = doc.createElement('script');
28                 e.type = 'text/javascript';
29                 e.src = url;
30                 e.async = true;
31                 doc.getElementsByTagName('head')[0].appendChild(e);
32         };
33
34         var weekday = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
35         var month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
36
37         var date2link = function(d, l) {
38                 var cal;
39                 var data;
40
41                 if (typeof(d.getDate) == 'function') {
42                         cal = d;
43                 } else {
44                         cal = new Date();
45
46                         cal.setUTCFullYear(parseInt(d.substring(0,4)));
47                         cal.setUTCMonth(parseInt(d.substring(5,7)) - 1);
48                         cal.setUTCDate(parseInt(d.substring(8,10)));
49                         cal.setUTCHours(parseInt(d.substring(11,13)));
50                         cal.setUTCMinutes(parseInt(d.substring(14,16)));
51                         cal.setUTCSeconds(parseInt(d.substring(17,19)));
52                 }
53
54                 var delta = (now - cal)/1000;
55
56                 if (delta < 48*3600) {
57                         if (cal.getDate() == now.getDate()) {
58                                 data = 'today';
59                         } else {
60                                 data = 'yesterday';
61                         }
62                 } else if (cal.getYear() == now.getYear()) {
63                         data = 'on ' + weekday[cal.getDay()] + ', ' + month[cal.getMonth()] + ' ' + cal.getDate(); 
64                 } else {
65                         data = 'on ' + [cal.getYear(), cal.getMonth() + 1, cal.getDate()].join('-');
66                 }
67
68                 data += ' at ' + [cal.getHours(), cal.getMinutes()].join(':');
69
70                 if (l) {
71                         return '<a href="' + l + '">' + data + '</a>';
72                 } else {
73                         return data;
74                 }
75         };
76
77         var create_style = function(string) {
78                 var ssheet = doc.createElement('style');
79                 ssheet.type = 'text/css';
80                 ssheet.innerHTML = string;
81                 doc.getElementsByTagName('head')[0].appendChild(ssheet);
82         };
83
84         this.gen_style = function() {
85                 var cssrules = '.social ';
86                 cssrules += '{display:block;border:2px inset black;height:auto;padding:0;font-size:90%;line-height:1;margin:0}';
87                 cssrules += '.invisible {display:none}';
88                 cssrules += '.social.logo {vertical-align:top}';
89                 cssrules += '.social.logo {padding:0;margin:0 auto;display:inline-block;border:2px lightblue}';
90                 cssrules += '.social.logo.inactive {border-style:outset}';
91                 cssrules += '.social.logo.active {border-style:inset}';
92                 create_style(cssrules);
93         };
94
95         this.friendfeed = new function() {
96                 img_preload.src = 'http://friendfeed.com/favicon.ico';
97                 img_preload.src = 'http://friendfeed.com/static/images/logo-small.png';
98
99                 var from2link = function(from) {
100                         return '<a href="http://friendfeed.com/' + from.id + '">' + from.name + '</a>';
101                 };
102
103                 var from2linkimg = function(from) {
104                         return '<a class="floatlink" href="http://friendfeed.com/' + from.id + '"><img src="http://friendfeed-api.com/v2/picture/' + from.id + '?size=small" /></a>';
105                 };
106
107                 this.gen_style = function() {
108                         var cssrules = '.friendfeed {border-color:lightblue}';
109                         cssrules += '.friendfeed .floatlink {margin:0 1ex .5ex 0;float:left}';
110                         cssrules += '.friendfeed .entrybody {margin:0;clear:both}';
111                         cssrules += '.friendfeed .entrydiv {padding:.5ex;border-top:.25ex dashed lightblue;text-align:justify}';
112                         cssrules += '.friendfeed .entrydiv:first-child {border-top:none}';
113                         cssrules += '.friendfeed .entryfrom {margin:.5ex 1ex .5ex 0;clear:both}';
114                         cssrules += '.friendfeed .entrycomments {margin-left:2.2em}';
115                         cssrules += '.friendfeed .entryliked {margin:0 2ex;text-indent:-2ex}';
116                         cssrules += '.friendfeed .entrycomment {border-top: .1ex dotted blue; padding:1ex .5ex}';
117                         cssrules += '.friendfeed .entrycomment p {margin:0}';
118                         cssrules += '.friendfeed .entrycommentfrom {font-size:smaller;line-height:150%}';
119                         create_style(cssrules);
120                 };
121
122                 this.process = function(linkidx, data) {
123                         var url = unsafeWindow.social.permalink[linkidx];
124                         if (!url)
125                                 return;
126                         if (!(data.entries && data.entries.length > 0))
127                                 return;
128
129                         var last_body = '';
130
131                         var ecount = data.entries.length;
132                         /* check if the only entry is from me and it has no comment --skip it then */
133                         var entry = data.entries[0];
134                         if (entry.from.id == 'oblomov' && !(entry.comments) && !(entry.likes))
135                                 return;
136
137                         var divid = 'friendfeed_'+linkidx;
138                         var pdiv = doc.getElementById(divid);
139
140                         if (!pdiv) {
141                                 pdiv = doc.createElement('div');
142                                 pdiv.setAttribute('class', 'social friendfeed invisible');
143                                 pdiv.setAttribute('id', divid);
144                                 pdiv.setAttribute('title', 'FriendFeed comments on '+url);
145
146                                 unsafeWindow.social.permalink_parent[linkidx].appendChild(pdiv);
147
148                                 var logo = doc.createElement('img');
149                                 logo.className = 'social logo inactive';
150                                 logo.src = linkidx ? 'http://friendfeed.com/favicon.ico' : 'http://friendfeed.com/static/images/logo-small.png';
151                                 logo.setAttribute('alt', 'FriendFeed logo');
152                                 logo.setAttribute('title', 'FriendFeed logo');
153                                 pdiv.parentNode.insertBefore(logo,pdiv);
154
155                                 logo.addEventListener('click', function() {
156                                         if (pdiv.className.match(' invisible')) {
157                                                 logo.className = logo.className.replace(' inactive',' active');
158                                                 pdiv.className = pdiv.className.replace(' invisible','');
159                                         } else {
160                                                 logo.className = logo.className.replace(' active',' inactive');
161                                                 pdiv.className = pdiv.className + ' invisible';
162                                         }
163                                 }, false);
164                         }
165
166                         for (var i=0; i < ecount; ++i) {
167                                 var entry = data.entries[i];
168
169                                 var p_entry_id = divid + '_entry_' + entry.id;
170
171                                 // skip this entry if we already added it from a different source
172                                 if (doc.getElementById(p_entry_id))
173                                         continue;
174
175
176                                 var fed = '';
177                                 var ediv;
178                                 if (entry.body != last_body) {
179                                         last_body = entry.body;
180                                         fed += '<p class="entrybody">' + entry.body;
181
182                                         ediv = doc.createElement('div');
183                                         ediv.className = 'entrydiv';
184                                         pdiv.appendChild(ediv);
185                                 } else {
186                                         ediv = pdiv.lastChild;
187                                         fed = ediv.innerHTML;
188                                 }
189                                 fed += '<p id="' + p_entry_id + '" class="entryfrom">' + from2linkimg(entry.from) + 'By ' + from2link(entry.from) + '<br/>' + date2link(entry.date, entry.url);
190                                 fed += '<div class="entrycomments"></div>';
191                                 ediv.innerHTML = fed;
192                                 ediv = ediv.lastChild;
193
194                                 var likes = entry.likes || [];
195                                 var jcount = likes.length;
196                                 if (jcount > 0) {
197                                         var liked = '<p class="entryliked">Liked by ';
198                                         for (var j=0; j < jcount; ++j) {
199                                                 var like = likes[j];
200                                                 if (like.from)
201                                                         liked += (j > 0 ? ', ' : '') + from2link(like.from);
202                                                 else
203                                                         liked += ' and ' + like.body;
204                                         }
205
206                                         var ldiv = doc.createElement('div');
207                                         ldiv.innerHTML = liked;
208                                         ediv.appendChild(ldiv);
209                                 }
210
211                                 var comments = entry.comments || [];
212                                 jcount = comments.length;
213                                 if (jcount > 0) {
214                                         for (var j=0 ; j < jcount; ++j) {
215                                                 var comment = comments[j];
216                                                 var cdiv = doc.createElement('div');
217                                                 cdiv.className = 'entrycomment';
218                                                 ediv.appendChild(cdiv);
219
220                                                 if (comment.from) {
221                                                         cdiv.innerHTML = from2linkimg(comment.from) +
222                                                                 '<p class="entrycommentfrom">' + date2link(comment.date) + ' by ' + from2link(comment.from) +
223                                                                 '<p>' + comment.body;
224                                                 } else {
225                                                         cdiv.innerHTML += comment.body;
226                                                 }
227                                         }
228                                 }
229                         }
230                 };
231
232                 this.load = function(linkidx) {
233                         var url = unsafeWindow.social.permalink[linkidx];
234                         var callback = '(function(data){social.friendfeed.process('+linkidx+',data)})';
235                         var cb_enc = escape(callback);
236
237                         var wokloc = url.replace(/localhost\/~oblomov\/wok/g,'wok.oblomov.eu');
238                         /* the same page has at least two possible URLs: if it ends with /, it could be present without /,
239                            and if it ends with /?index.html it could be present without it */
240                         var locs = [wokloc];
241                         if (wokloc.charAt(wokloc.length-1) == '/') {
242                                 locs.push(wokloc.substring(0,wokloc.length-1));
243                         }
244                         if (wokloc.match(/\/index.html$/)) {
245                                 locs.push(wokloc.substring(0,wokloc.length-10));
246                                 locs.push(wokloc.substring(0,wokloc.length-11));
247                         }
248
249                         var locs_enc = [];
250                         for (var i=0; i < locs.length; ++i) {
251                                 locs_enc.push(encodeURI(locs[i]));
252                         }
253                         var ff_url = 'http://friendfeed-api.com/v2/url?url=' + locs_enc.join(',') +
254                                 '&callback=' + cb_enc + '&cache=' + Math.floor(Math.random()*1024*1024);
255                         load_script(ff_url);
256
257                         /*
258                         ff_url = 'http://friendfeed-api.com/v2/search?q=' + locs_enc[0] +
259                                 '&callback=' + cb_enc + '&cache=' + Math.floor(Math.random()*1024*1024);
260                         load_script(ff_url);
261                         */
262                 };
263         };
264
265         this.twitter = new function() {
266                 img_preload.src = 'http://twitter.com/favicon.ico';
267
268                 this.gen_style = function() {
269                         var cssrules = '.twitter {border-color:lightblue;min-height:56px}';
270                         cssrules += '.twitter .floatlink {margin:0 1ex .5ex 0;float:left}';
271                         cssrules += '.twitter .entry {margin:0;clear:both;padding:.5ex;border-top:.25ex dashed lightblue;text-align:justify}';
272                         cssrules += '.twitter .entry:first-child {border-top:none}';
273                         cssrules += '.twitter .entryfrom {margin:.5ex 1ex .5ex 0}';
274                         create_style(cssrules);
275                 };
276
277                 this.process = function(linkidx, data) {
278                         var url = unsafeWindow.social.permalink[linkidx];
279                         if (!url)
280                                 return;
281                         if (!(data.results && data.results.length > 0))
282                                 return;
283
284                         var divid = 'twitter_'+linkidx;
285                         var pdiv = doc.getElementById(divid);
286
287                         if (!pdiv) {
288                                 pdiv = doc.createElement('div');
289                                 pdiv.setAttribute('class', 'social twitter invisible');
290                                 pdiv.setAttribute('id', divid);
291                                 pdiv.setAttribute('title', 'Twits on '+url);
292
293                                 unsafeWindow.social.permalink_parent[linkidx].appendChild(pdiv);
294
295                                 var logo = doc.createElement('img');
296                                 logo.className = 'social logo inactive';
297                                 logo.src = 'http://twitter.com/favicon.ico';
298                                 logo.setAttribute('alt', 'Twitter logo');
299                                 logo.setAttribute('title', 'Twitter logo');
300                                 pdiv.parentNode.insertBefore(logo,pdiv);
301
302                                 logo.addEventListener('click', function() {
303                                         if (pdiv.className.match(' invisible')) {
304                                                 logo.className = logo.className.replace(' inactive',' active');
305                                                 pdiv.className = pdiv.className.replace(' invisible','');
306                                         } else {
307                                                 logo.className = logo.className.replace(' active',' inactive');
308                                                 pdiv.className = pdiv.className + ' invisible';
309                                         }
310                                 }, false);
311                         }
312
313                         var ecount = data.results.length;
314                         for (var i=0; i < ecount; ++i) {
315                                 var entry = data.results[i];
316
317                                 var p_entry_id = divid + '_entry_' + entry.id;
318
319                                 // skip this entry if we already added it from a different source
320                                 if (doc.getElementById(p_entry_id))
321                                         continue;
322
323                                 var ediv = doc.createElement('div');
324                                 ediv.className = 'entrydiv';
325                                 pdiv.appendChild(ediv);
326
327                                 var profilink = 'http://twitter.com/' + entry.from_user;
328                                 var content = '<p class="entry"><a class="floatlink" href="' + profilink + '">';
329                                 content += '<img src="' + entry.profile_image_url + '"></a>';
330                                 content += entry.text + '</p>';
331                                 content += '<p class="entryfrom">By <a href="' + profilink + '">'+ entry.from_user + '</a> ';
332                                 var entrydate = new Date(entry.created_at);
333                                 content += date2link(entrydate, profilink + '/status/' + entry.id_str);
334
335                                 ediv.innerHTML = content;
336                         }
337                 }
338
339                 this.load = function(linkidx) {
340                         var url = unsafeWindow.social.permalink[linkidx];
341                         unsafeWindow.social.twitter['process_' + linkidx] = function(data) {
342                                 unsafeWindow.social.twitter.process(linkidx, data);
343                         }
344                         var callback = 'window.social.twitter.process_' + linkidx;
345                         var cb_enc = escape(callback);
346
347                         var wokloc = url.replace(/localhost\/~oblomov\/wok/g,'wok.oblomov.eu');
348                         /* the same page has at least two possible URLs: if it ends with /, it could be present without /,
349                            and if it ends with /?index.html it could be present without it */
350                         var locs = [wokloc];
351                         if (wokloc.charAt(wokloc.length-1) == '/') {
352                                 locs.push(wokloc.substring(0,wokloc.length-1));
353                         }
354                         if (wokloc.match(/\/index.html$/)) {
355                                 locs.push(wokloc.substring(0,wokloc.length-10));
356                                 locs.push(wokloc.substring(0,wokloc.length-11));
357                         }
358
359                         var locs_enc = [];
360                         for (var i=0; i < locs.length; ++i) {
361                                 locs_enc.push(encodeURI(locs[i]));
362                         }
363                         var twurl = 'http://search.twitter.com/search.json?q=' + locs_enc.join('+OR+') +
364                                 '&callback=' + cb_enc + '&cache=' + Math.floor(Math.random()*1024*1024);
365                         load_script(twurl);
366                 }
367         };
368
369         this.buzz = new function() {
370
371                 this.gen_style = function() {
372                         var cssrules = '.social.logo.buzz {border:2px solid transparent;display:inline-block}';
373                         cssrules += '.buzz .counter {background-image: url("http://www.gstatic.com/buzz/api/images/buzz-counter-long.png")}';
374                         cssrules += '.buzz .counter {background-origin: padding-box;background-position:0 0;background-size: auto}';
375                         cssrules += '.buzz .counter {background-clip: border-box;background-attachment:scroll}';
376                         cssrules += '.buzz .counter {color:black;font-family:sans-serif;font-weight:700;font-size:11px}';
377                         cssrules += '.buzz .counter {display:inline-block;margin:0;padding:0;text-align:center;text-decoration:none}';
378                         cssrules += '.buzz .counter {padding-left:58px;width:94px;height:18px;line-height:18px}';
379                         cssrules += '.buzz .counter .value {width:32px;text-align:center;line-height:18px}';
380                         create_style(cssrules);
381                 };
382
383                 this.process = function(linkidx, data) {
384                         var url = unsafeWindow.social.permalink[linkidx];
385                         var count = 0;
386                         for (var link in data) {
387                                 if (opera && opera.postError) {
388                                         opera.postError("link: " + link + ", datum: " + data[link]);
389                                 }
390                                 count += data[link];
391                         }
392                         if (count == 0)
393                                 return;
394
395                         var buzz = doc.createElement('a');
396                         buzz.setAttribute('class', 'social logo buzz');
397                         buzz.setAttribute('title', 'Buzz on '+url);
398                         buzz.setAttribute('href', 'http://www.google.com/buzz/post?url=' + encodeURIComponent(url.replace(/localhost\/~oblomov\/wok/g,'wok.oblomov.eu')));
399
400                         unsafeWindow.social.permalink_parent[linkidx].appendChild(buzz);
401                         buzz.innerHTML = '<span class="counter"><span class="value">' + count + '</span></span>';
402                 }
403
404                 this.load = function(linkidx) {
405                         var url = unsafeWindow.social.permalink[linkidx];
406                         unsafeWindow.social.buzz['process_' + linkidx] = function(data) {
407                                 unsafeWindow.social.buzz.process(linkidx, data);
408                         }
409                         var wokloc = url.replace(/localhost\/~oblomov\/wok/g,'wok.oblomov.eu');
410                         var callback = 'window.social.buzz.process_' + linkidx;
411                         var cb_enc = escape(callback);
412
413                         var burl = 'http://www.google.com/buzz/api/buzzThis/buzzCounter?url='+
414                                 encodeURI(wokloc) + '&callback=' + cb_enc + '&cache=' + Math.floor(Math.random()*1024*1024);
415                         load_script(burl);
416                 }
417         }
418
419         this.gen_style();
420         this.friendfeed.gen_style();
421         this.twitter.gen_style();
422         this.buzz.gen_style();
423
424         this.collect = function() {
425                 /* we don't need stuff like //div[contains(concat(' ',normalize-space(@class),' '),' foo ')]
426                    because we know they are single-classed */
427                 var footers = doc.evaluate('//div[@class="inlinefooter"]',doc,null,XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);
428
429                 try {
430                         var footer = footers.iterateNext();
431                         while (footer) {
432                                 var perma = doc.evaluate('*//span[@class="permalink"]/a',footer,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue;
433                                 if (perma) {
434                                         unsafeWindow.social.permalink.push(perma.href.toString());
435                                         unsafeWindow.social.permalink_parent.push(footer);
436                                 }
437                                 footer = footers.iterateNext();
438                         }
439                 } catch (e) {
440                         if (opera && opera.postError) {
441                                 opera.postError("failed to iterate over permalinks: " + e);
442                         }
443                 }
444         };
445
446         this.load = function() {
447                 for (var i=0; i < this.permalink.length; ++i) {
448                         unsafeWindow.social.friendfeed.load(i);
449                         unsafeWindow.social.twitter.load(i);
450                         unsafeWindow.social.buzz.load(i);
451                 }
452         }
453 };
454
455 unsafeWindow.social = new social();
456 unsafeWindow.social.collect();
457 unsafeWindow.social.load();
458