2 // @name Flickr Check Buddy Activity
3 // @namespace http://6v8.gamboni.org/
4 // @description Check when a particular user commented/faved a photo of your for the last time
6 // @identifier http://6v8.gamboni.org/IMG/js/flickrcheckbuddyactivity.user.js
8 // @creator Pierre Andrews (mortimer.pa@free.fr)
9 // @include http://www.flickr.com*
10 // @include http://flickr.com*
13 // --------------------------------------------------------------------
15 // This is a Greasemonkey user script.
17 // To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
18 // Then restart Firefox and revisit this script.
19 // Under Tools, there will be a new menu item to "Install User Script".
20 // Accept the default configuration and install.
22 // --------------------------------------------------------------------
23 // Copyright (C) 2007 Pierre Andrews
25 // This program is free software; you can redistribute it and/or
26 // modify it under the terms of the GNU General Public License
27 // as published by the Free Software Foundation; either version 2
28 // of the License, or (at your option) any later version.
30 // This program is distributed in the hope that it will be useful,
31 // but WITHOUT ANY WARRANTY; without even the implied warranty of
32 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 // GNU General Public License for more details.
35 // The GNU General Public License is available by visiting
36 // http://www.gnu.org/copyleft/gpl.html
38 // Free Software Foundation, Inc.
39 // 51 Franklin Street, Fifth Floor
40 // Boston, MA 02110-1301
50 name: "Flickr Check Buddy Activity",
51 namespace: "http://6v8.gamboni.org/",
52 description: "Check when a particular user commented/faved a photo of your for the last time",
53 identifier: "http://6v8.gamboni.org/IMG/js/flickrcheckbuddyactivity.user.js",
54 version: "0.5", // version
55 date: (new Date("2007-07-02")) // update date
61 if(unsafeWindow.console)
62 unsafeWindow.console.log(arguments);
69 http://ecmanaut.blogspot.com/2006/07/expressive-user-scripts-with-xpath-and.html
71 function $x( xpath, root )
73 var doc = root ? root.evaluate?root:root.ownerDocument : document;
74 var got = doc.evaluate( xpath, root||doc, null, 0, null ), next;
76 while( next = got.iterateNext() )
83 return document.evaluate(
87 XPathResult.FIRST_ORDERED_NODE_TYPE, null
91 function foreach( xpath, cb, root )
93 var nodes = $x( xpath, root ), e = 0;
94 for( var i=0; i<nodes.length; i++ )
95 e += cb( nodes[i], i ) || 0;
101 function getObjectMethodClosure(object, method) {
102 return function(arg) {
103 return object[method](arg);
107 /***********************************************************************
108 * Flickr Localisation
109 **********************************************************************/
111 var FlickrLocaliser = function(locals) {
114 FlickrLocaliser.prototype = {
115 selectedLang: undefined,
116 localisations: undefined,
117 getLanguage: function() {
118 if(!this.selectedLang) {
119 var langA = $x1("//p[@class='LanguageSelector']//a[contains(@class,'selected')]");
121 var matches = /\/change_language.gne\?lang=([^&]+)&.*/.exec(langA.href);
122 if(matches && matches[1]) {
123 this.selectedLang = matches[1];
124 return this.selectedLang;
128 } else return this.selectedLang;
131 init: function(locals) {
132 this.localisations = locals;
135 localise: function(string, params) {
136 if(this.localisations && this.getLanguage()) {
137 var currentLang = this.localisations[this.selectedLang];
138 if(!currentLang) currentLang = this.localisations[this.localisations.defaultLang];
139 var local = currentLang[string];
141 local = this.localisations[this.localisations.defaultLang][string];
143 if(!local) return string;
145 while(matches = /@([^@]+)@/g.exec(local)) {
147 var arg = matches[1];
148 var rep = new RegExp('@'+arg+'@','g');
150 toRet = toRet.replace(rep,params[arg]);
152 toRet = toRet.replace(rep,'');
156 } else return undefined;
161 /*****************************Flickr Localisation**********************/
166 LightWeightBox - Thom Shannon
172 var LightWeightBoxOn=false;
173 var LightWeightBox = function(ele){
175 this.backgroundColor = '#CCC';
178 with (LightWeightBox){
179 prototype.Render = function(){
180 if (!LightWeightBoxOn){
181 bgDiv = document.createElement('div');
183 bgDiv.style.backgroundColor = this.backgroundColor;
184 bgDiv.style.position='fixed';
185 bgDiv.style.height='100%';
186 bgDiv.style.width='100%';
188 bgDiv.style.left='0';
189 bgDiv.style.opacity=this.opacity;
190 this.ele.style.position='fixed';
192 document.body.appendChild(this.bgDiv);
193 document.body.appendChild(this.ele);
195 LightWeightBoxOn = true;
197 this.sizeCheck = setInterval(function(){oSelf.CheckSize();},20);
200 prototype.CheckSize = function(){
201 if (this.ele.offsetHeight!=this.currentHeight) {
202 this.offsetTop = (self.innerHeight/2)-(this.ele.offsetHeight/2);
203 this.ele.style.top = this.offsetTop+'px';
204 this.currentHeight=this.ele.offsetHeight;
206 if (this.ele.offsetWidth!=this.currentWidth) {
207 this.offsetLeft = (self.innerWidth/2)-(this.ele.offsetWidth/2);
208 this.ele.style.left = this.offsetLeft+'px';
209 this.currentWidth=this.ele.offsetWidth;
213 prototype.Close=function(oSelf){
214 document.body.removeChild(oSelf.bgDiv);
215 document.body.removeChild(oSelf.ele);
216 LightWeightBoxOn = false;
222 var flickrcheckbuddyactivity = function() {this.init();}
224 flickrcheckbuddyactivity.prototype = {
225 localiser: new FlickrLocaliser({
227 'check_activity' : 'Check Buddy Activity',
229 'no_activity' : "no activity in the last @timeframe@.",
231 'comment' : '@username@ commented @photo@ on the @date@.',
232 'fave' : '@username@ faved @photo@ on the @date@.',
233 'tag' : '@username@ tagged @photo@ on the @date@.',
234 'note' : '@username@ added a note to @photo@ on the @date@.',
235 'activity_title' : 'Last Activity',
236 'please_wait' : 'Please Wait',
237 'menu_item' : 'CheckBuddyActivity: Set the timeframe',
238 'prompt_timeframe' : 'What timeframe should the script check (enter value in hours (e.g. 24h) or days (e.g. 31d).',
239 'additional_count' : 'During this @timeframe@, this user left: @comment_cnt@ comment(s), @fave_link@ , @tag_cnt@ tag(s) and @note_cnt@ note(s).',
240 'fave_cnt' : '@fave_cnt@ favorite(s)',
241 'tag_note_cnt' : 'During this @timeframe@, @username@ left @tag_cnt@ tag(s) and @note_cnt@ note(s).',
242 'tag_cnt' : 'During this @timeframe@, @username@ left @tag_cnt@ tag(s).',
243 'note_cnt' : 'During this @timeframe@, @username@ left @tag_cnt@ tag(s).',
249 'activity_title' : 'Dernière Activité',
250 'additional_count' : 'Pendant ce @timeframe@,cet utilisateur a laissé: @comment_cnt@ commentaire(s), @fave_link@ , @tag_cnt@ tag(s) et @note_cnt@ note(s).',
252 'check_activity' : 'Verrifier l\'Activité',
254 'comment' : '@username@ a commenté @photo@ le @date@',
256 'fave' : '@username@ a ajouté @photo@ comme favorit le @date@',
257 'fave_cnt' : '@fave_cnt@ favorit(s)',
259 'menu_item' : 'CheckBuddyActivity: Ajuster l\'intervale de temps',
261 'no_activity' : 'Aucune activité dans le dernier @timeframe@.',
262 'note' : '@username@ a ajouté une note à @photo@ le @date@',
263 'note_cnt' : 'Pendant ce @timeframe@, @username@ a laissé @tag_cnt@ tag(s).',
265 'please_wait' : 'Patienter',
266 'prompt_timeframe' : 'Quelle intervale de temps vérifier (donner une valeur en heures (e.g. 24h) ou jours (e.g. 31d).',
268 'tag' : '@username@ a taggé @photo@ le @date@',
269 'tag_cnt' : 'Pendant ce @timeframe@, @username@ a laissé @tag_cnt@ tag(s).',
270 'tag_note_cnt' : 'Pendant ce @timeframe@, @username@ a laissé @tag_cnt@ tag(s) et @note_cnt@ note(s).'
274 'activity_title' : 'Última atividade',
276 'check_activity' : 'Checar atividade do camarada',
278 'comment' : '@username@ comentado @photo@ na @date@',
280 'fave' : '@username@ favorito @photo@ na @date@',
282 'no_activity' : 'Sem atividade no último @timeframe@.',
285 'please_wait' : 'Por favor, aguarde'
292 var menu = document.getElementById('personmenu_contacts_link');
294 this.timeframe = GM_getValue('timeframe');
295 if(!this.timeframe) {
296 this.timeframe = '31d';
297 GM_setValue('timeframe','31d');
300 var link =document.createElement('a');
301 link.setAttribute('class','block');
302 link.setAttribute('id','tag_person_link');
303 link.setAttribute('href','javascript:;');
304 link.addEventListener('click',getObjectMethodClosure(this,'showDialogue'),true);
305 link.innerHTML = this.localiser.localise('check_activity');
307 menu.parentNode.insertBefore(link,menu.nextSibling);
310 GM_registerMenuCommand( this.localiser.localise("menu_item"), function() {self.timeframe = prompt(self.localiser.localise('prompt_timeframe'),self.timeframe);GM_setValue('timeframe',self.timeframe)});
317 foundEvent: undefined,
319 processRSP: function(rsp,page,user_id) {
320 for(var i=0;i<rsp.items.item.length;i++) {
321 for(var j=0;j<rsp.items.item[i].activity.event.length;j++) {
322 if(rsp.items.item[i].activity.event[j].user == user_id) {
323 this.username = rsp.items.item[i].activity.event[j].username;
325 if((this.timeframe.indexOf('D') >= 0) || (this.timeframe.indexOf('d') >= 0)) {
326 if(DEBUG) M8_log('counting in days for user_id: '+user_id);
327 period = (Date.now() - rsp.items.item[i].activity.event[j].dateadded*1000)/(3600*24*1000);
329 if(DEUBG) M8_log('counting in hours for user_id: '+user_id);
330 period = (Date.now() - rsp.items.item[i].activity.event[j].dateadded*1000)/(3600*1000);
332 if(DEBUG) M8_log(rsp.items.item[i].activity.event[j].type+':'+period+':'+parseInt(this.timeframe));
333 if(period <= parseInt(this.timeframe)) {
334 if(DEBUG) M8_log('this event is in the timeframe.');
335 this['count_'+rsp.items.item[i].activity.event[j].type]++;
336 if((rsp.items.item[i].activity.event[j].type == 'comment' ||
337 rsp.items.item[i].activity.event[j].type == 'fave') &&
338 (rsp.items.item[i].activity.event[j].dateadded > this.foundActivity)) {
339 this.foundEvent = {event: rsp.items.item[i].activity.event[j],
340 first: rsp.items.item[i]};
341 this.foundActivity = rsp.items.item[i].activity.event[j].dateadded;
349 if(page < rsp.items.pages) {
350 this.checkPage(page+1,user_id);
352 this.boxEle.removeChild(this.dial);
353 var dial = this.boxEle.appendChild(document.createElement('div'));
354 dial.setAttribute('style',"clear:both; width:70%;overflow:auto;margin-left:15%;");
355 if(this.foundEvent && this.foundEvent.event && this.foundEvent.first) {
356 var first = this.foundEvent.first;
357 var event = this.foundEvent.event;
359 if(first.type == 'photoset')
361 dial.innerHTML = this.localiser.localise(event.type,
363 'username' : '<a href="http://www.flickr.com/photos/'+encodeURIComponent(event.user)+'" >'+event.username+'</a>',
364 'photo' : '<br/><a href="http://www.flickr.com/photos/'+encodeURIComponent(unsafeWindow.global_nsid)+'/'+first.id+'"><img src="http://farm'+first.farm+'.static.flickr.com/'+first.server+'/'+id+'_'+first.secret+'_m.jpg"/></a><br/>',
365 'date' : "<br/>"+(new Date(event.dateadded*1000)).toUTCString()
367 dial.innerHTML += '<br/>'+this.localiser.localise('additional_count',{
368 'tag_cnt':this.count_tag+0+' ',
369 'note_cnt':this.count_note+0+' ',
370 'fave_link':((this.count_fave > 0)?('<a title="Favorite Finder" href="http://bighugelabs.com/flickr/favorites.php?user1='+encodeURIComponent(unsafeWindow.global_nsid)+'&user2='+encodeURIComponent(user_id)+'&button=Go">'+this.localiser.localise('fave_cnt',{'fave_cnt':this.count_fave+0+''})+'</a>'):(this.localiser.localise('fave_cnt',{'fave_cnt':this.count_fave+0+''}))),
371 'fave_cnt':this.count_fave+0+' ',
372 'comment_cnt':this.count_comment+0+' ',
373 'timeframe':this.localiser.localise(this.timeframe)
375 } else if(this.countTag > 0 && this.countNote > 0) {
376 dial.innerHTML = this.localiser.localise('tag_note_cnt',{'username' : '<a href="http://www.flickr.com/photos/'+encodeURIComponent(user_id)+'" >'+this.username+'</a>','tag_cnt':this.count_tag, 'note_cnt':this.count_note, 'timeframe':this.localiser.localise(this.timeframe)});
377 } else if(this.count_tag > 0) {
378 dial.innerHTML = this.localiser.localise('tag_cnt',{'username' : '<a href="http://www.flickr.com/photos/'+encodeURIComponent(user_id)+'" >'+this.username+'</a>','tag_cnt':this.count_tag, 'timeframe':this.localiser.localise(this.timeframe)});
379 } else if(this.count_note > 0) {
380 dial.innerHTML = this.localiser.localise('note_cnt',{'username' : '<a href="http://www.flickr.com/photos/'+encodeURIComponent(user_id)+'" >'+this.username+'</a>','note_cnt':this.count_note, 'timeframe':this.localiser.localise(this.timeframe)});
382 dial.innerHTML = this.localiser.localise('no_activity',{'timeframe':this.localiser.localise(this.timeframe)});
387 checkPage: function(page,user_id) {
389 var lastCheck = eval(GM_getValue('cached_page_'+page));
392 var when = Date.now() - lastCheck.timestamp;
393 when = when / (3600*1000);
397 flickr_activity_userPhotos_onLoad: function(success, responseXML, responseText, params){
399 var rsp = responseText.replace(/jsonFlickrApi\(/,'');
401 if(rsp.stat == 'ok') {
402 GM_setValue('cached_page_'+page,uneval({timestamp: Date.now(),
404 self.processRSP(rsp,page,user_id);
406 M8_log("Error2 "+responseText);
408 M8_log("Error1 "+responseText);
414 unsafeWindow.F.API.callMethod('flickr.activity.userPhotos', {
415 timeframe: this.timeframe,
421 this.processRSP(lastCheck.rsp,page,user_id);
424 showDialogue: function(ev) {
425 var block = ev.target.parentNode;
426 var matches = /messages_write\.gne\?to=([^"]*)"/.exec(block.innerHTML);
427 if(matches && matches[1]) {
428 var user_id=matches[1]
429 // create a block element of some kind
430 this.boxEle = document.createElement('div');
431 // style it up with a class or inline
432 this.boxEle.className = 'popup';
433 // create something to act as a close button
434 btnClose = document.createElement('a');
435 btnClose.href='javascript:;';
436 btnClose.innerHTML='<img style="margin: 0; padding: 0; border:0px !important; vertical-align: top;" src="http://flickr.com/images/window_close_grey.gif" alt="'+this.localiser.localise('close')+'"/>';
437 btnClose.title = this.localiser.localise('close');
439 // add close button to block element
440 this.boxEle.appendChild(btnClose);
441 // create box with block element
442 var lwBox = new LightWeightBox(this.boxEle);
443 // optional bg color and opacity
444 this.boxEle.style.paddingTop= '10px';
445 this.boxEle.style.width= '350px';
446 this.boxEle.style.paddingBottom = '10px';
447 this.boxEle.style.backgroundColor = '#fff';
448 this.boxEle.style.border = '1px solid black';
449 // attach close event and add your own code
450 btnClose.addEventListener('click',function(){
451 // you have to pass box object into event
452 // because of the js event scoping
454 // false to cancel link
457 btnClose.setAttribute('style','float:right;margin-bottom:10px;margin-right:5px;');
459 var title = this.boxEle.appendChild(document.createElement('div'));
460 title.setAttribute('style',"padding:12px;background-color: #EEEEEE;clear:both;font-size: 14px;margin-bottom:10px");
461 title.innerHTML = '<h3 style="margin:0;">'+this.localiser.localise('activity_title')+'</h3>';
463 this.dial = this.boxEle.appendChild(document.createElement('div'));
464 this.dial.setAttribute('style',"clear:both; width:70%;overflow:auto;margin-left:15%;");
470 this.count_comment=0;
471 this.foundActivity=0;
472 this.foundEvent=undefined;
474 this.checkPage(1,user_id);
476 this.dial.innerHTML = this.localiser.localise('please_wait')+' <img height="24" width="24" src="data:image/gif,GIF89a%10%00%10%00%D5%3D%00%D3%D3%D3%F7%F7%F7%D1%D1%D1%D7%D7%D7%C9%C9%C9%CA%CA%CA%C6%C6%C6%FC%FC%FC%F0%F0%F0%E8%E8%E8%D5%D5%D5%FB%FB%FB%F8%F8%F8%CF%CF%CF%C5%C5%C5%C4%C4%C4%E2%E2%E2%D9%D9%D9%EF%EF%EF%F3%F3%F3%D4%D4%D4%C8%C8%C8%F4%F4%F4%E6%E6%E6%E3%E3%E3%DA%DA%DA%F5%F5%F5%F1%F1%F1%D2%D2%D2%F9%F9%F9%E4%E4%E4%E1%E1%E1%F6%F6%F6%EB%EB%EB%CB%CB%CB%EC%EC%EC%C3%C3%C3%D0%D0%D0%E7%E7%E7%CC%CC%CC%E9%E9%E9%DC%DC%DC%E5%E5%E5%D8%D8%D8%CE%CE%CE%C1%C1%C1%DB%DB%DB%EA%EA%EA%EE%EE%EE%DD%DD%DD%DF%DF%DF%DE%DE%DE%D6%D6%D6%F2%F2%F2%ED%ED%ED%FA%FA%FA%E0%E0%E0%FE%FE%FE%FD%FD%FD%C7%C7%C7%FF%FF%FF%FF%FF%FF%00%00%00%00%00%00!%FF%0BNETSCAPE2.0%03%01%00%00%00!%F9%04%05%00%00%3D%00%2C%00%00%00%00%10%00%10%00%00%06%C3%40%9EP%B7%C9%F1%04%14J%26w%10%3A%3B%1F%C2%84%17%D9%3D%00%07%86%C6%C9%C8%ECv%1F%5Ebg%B0%F1B%91%80%F0%96z8%04%3C%C6%23%A3%5B%B0%0C%B8Cs%01%2B%19%8C%00%1B%3C8_%3B%08%18%177%3C%3A)F%1D%07%08%22%1C%18%16%3C%14%24%0A%2F%0B9%3AN7FN%00%06%0E\'.N%A7%20%26%03%0D%02%03%26%20B%A0%3C%07%0A%15%24%3B%1C%A79%16%8A%3C99%01%1F%2C-%B9%16%18%14s%0BN%17B%1313%13%15_%22%B0%10B%22%03%95u%3A%10%B6%D6%13%22%5B%A2%04%89B%0C.%00%01%3A%03%0E%1E%3C*%06%06%11%1D%5C%23g%F1%0A%3C%01%058%F4%A7%CA*((h%20dC\'!A%00%00!%F9%04%05%00%00%3D%00%2C%00%00%00%00%10%00%10%00%00%06l%40%9E%90%87%C0%01%84%00%1Cb%C8%84%14v%3B!%B4%80c%0A%25%04%A8%14%9A%60%5En%3Cl%94%C7%E5%E9P%C4%82%00%8C%95vu%83B%11%BA%0E%0B%DF%03%A8%11%BA%AB%0F%E1%7CGV%83%84%85V%00%7C%3B%5DCbPI%7CoBlY%3BJOxBuXr%3C8xc%7Dl%10L%80%A0%A1%83(O%A6%05%17%84E%82IKCA%00!%F9%04%05%00%00%3D%00%2C%00%00%00%00%10%00%10%00%00%06%BE%40%9E%90%B7%C0Q%14%0D%E1F7%14%06%02%BC%D4c%A7%E0%05%0A8%C6%90%11%F1%F0%2C%84%9DW%B5%A32u%90%5DE%93%8384%3C%85%83%C4%81N*%E5%15%8FQ%D5UN%18PP%16%18%00%15(97%3C7%2Bl%3C!%5EC%89M%1D%3A5%19%3B%0D%02%03%26%20MC%1E%0D%0E%0E%02%00%3BS%25%13%9F%3C%20%17%03%0D4-%05%11%1BM%0779M8%0A!%B9%23C%3A8%0F%14%18%16%3C%13P%0B2\'%B98%3C%07%25e%3B%0899%2F%0D%99%7B%04%BF%23S8%07%3C9%11%0E%06%1F%3C%09%06%0D%07%07%19%11PB%0B%1F%04%AA%11%3B%0E%179%1A%EF%C0%1B%B9%02%14%1C%20%2Ch%12%04%00%3B" alt="'+this.localiser.localise('please_wait')+'" />';
483 //======================================================================
486 window.addEventListener("load", function () {
489 // update automatically (http://userscripts.org/scripts/show/2296)
490 win.UserScriptUpdates.requestAutomaticUpdates(SCRIPT);
493 var flickrgp = new flickrcheckbuddyactivity();