rojo_del.icio.us

By Mark Hanson Last update Feb 10, 2006 — Installed 613 times.
// Rojo Del.icio.us
// version 0.1 Alpha
// 2006-02-02
// Copyright (c) 2006, Mark Hanson
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
// ==UserScript==
// @name          rojo_del.icio.us
// @version       0.1
// @description   Post tags applied in Rojo as bookmarks in del.icio.us
// @namespace     http://markerichanson.com/greasemonkey/rojo
// @include       http://www.rojo.com/*
// @include       http://rojo.com/*
// @exclude       http://www.rojo.com/javascript/dojo/iframe_history.html
// @exclude       http://rojo.com/javascript/dojo/iframe_history.html

// Whats New
// =========
// v 0.1    First cut
//
// Description
// ===========
// Tag an arcticle of feed in rojo, and it's tagged in del.icio.us.  Delete a tag  
// in rojo and it's deleted in del.icio.us.  Why?  Because.  Plus, it gave me an 
// excuse to play with DOM mutation events.
//
// To-Do:
// Handle tags on feed management page.  Started, not working.  See the to-be-renamed
//   getPostItem2...
// Abstract the del.icio.us portion to allow plug in of other targets.
// Reflect changes made at del.icio.us to tags into rojo.
// Contact
// =======
// Mark Hanson ( http://markerichanson.com/ )

// ==/UserScript==

(function() {
    var h = {};
    
    function initialize () {
        // GM_log("initialize("+unsafeWindow.location+")");
        // GM_log("initialize("+unsafeWindow.location.pathname+")");
        // initialize
		// initialize page specific handler array
        h["/doIRPCTagArticle.jsp"] = doPostItem;
        h["/doIRPCdeleteTagArticle.jsp"] = doPostItem;
        h["/doIRPCTagFeed.jsp"] = startTagFeed;
        h["/view-feed/"] = endTagFeed;
        h["/doIRPCdeleteTagFeed.jsp"] = startTagFeed;
        h["/manage-feeds/doIRPCTagFeed.jsp"] = asYetUnnamed;
        
        // defer activities until load event fires.
        window.addEventListener("load", onLoad, false);
    }

    initialize ();
    // looking for events to identify 

	// assuming some special processing will be required on the add/delete tag
	// pages for the /manage-feeds/ handling
    function asYetUnnamed () {
      
    }
    
    // managing tag feeds takes two steps.  First, here, note the GET to the
    // path that causes tags on feeds to be added or deleted (via "h[]" lookup
    // on load.  On this event, pull the records stored with GM_setValue and
    // load an ifrane with the info url for the feed in rojo.  Wait for the 
    // onload call of endTagFeed for that iframe.
	// btw, I'd have done this with a GM_xmlhttprequest and domparser, but rojo's
	// html wasn't well-formed when I was writing this so the domparser bonked.
    function startTagFeed () {
        // have to get the rojo feed description to get the real url before
        // posting.
        // parse out //div[@class='description']/A[1]
        GM_log("startTagFeed");
        var src = GM_getValue("postItem");

        var o = eval(src);
		// some of this iframe code came from:
		// http://youngpup.net/userscripts/htmlparserexample.user.js
		// but the approach didn't.  Using the greasemonkey hooks to do onload
		// processing for the page in the iframe isn't how the link does it.
		// doing it this way works b/c i'm passing data between instances of the
		// script with GM_setValue & GM_getValue which is, no doubt, an abuse of
		// some rule somewhere..
		
        // create an IFRAME to write the document into. the iframe must be added
	    // to the document and rendered (eg display != none) to be property 
        // initialized.
        var iframe = document.createElement("IFRAME");
        iframe.style.visibility = "hidden";
        iframe.style.position = "absolute";
        document.body.appendChild(iframe);
        // set the iframe to load the link from the PostItem.  This loads the
        // rojo feed info display from which I can parse (in endTagFeed) the 
        // sites URL which is otherwise not available on the page where the tag
        // was applied.
        iframe.contentWindow.location.href = o.url;
    }

	// called when the feed info page is loaded.  Parses the feed URL from that
	// page, updates the PostItem stored in GM_setValue ("postItem"...).  Calls
	// doPostItem so that the feed tag is reflected into del.icio.us.
    function endTagFeed () {
        GM_log("endTagFeed");
        var src = GM_getValue("postItem");

        var o = eval(src);
        var query = "//div[@class='description']/A[1]";
          var iter= document.evaluate(query,document, null, XPathResult.ANY_TYPE, null);
         
        var i = iter.iterateNext();
        GM_log (i+": "+i.href);
        o.url = i.href;    
        GM_setValue("postItem",o.toSource());
        doPostItem();
    }
    
    // parse tag text out of node.  trim.
    function getTag (node) {
//        GM_log("getTag: "+node);
        // text from the first <a> child is the tag
        var query = "./a[1]/text()";
        iter= document.evaluate(query,node,null, XPathResult.ANY_TYPE, null);
        i = iter.iterateNext();
        return i.textContent.replace(/\n/g,'').replace(/^\s+/g, '').replace(/\s+$/g, '');
    }
    
    // constructor for tuple that is a rojo tag / del.icio.us post 
    function PostItem (url, title, tags) {
      this.url = url;
      this.title = title;
      this.tags = tags;
    }

    // parse a post item off of the /manage-feed/ html.
    // partial implementation, untested xpath.  not working currently.
    function getPostItem2 (node) {
        var query = "..//li";
        var iter= document.evaluate(query,node,null, XPathResult.ANY_TYPE, null);
        li = iter.iterateNext();
        var tags = new Array();
        var url = "";
        var title = "";
        
        while (li != null) {
            tags.push(getTag(li));
            li = iter.iterateNext();
        }       
 	    GM_log("tags: "+tags.toSource());
		query="../../../TD[class='subscription-info']/A";	
        iter = document.evaluate(query,document,null, XPathResult.ANY_TYPE, null);
		url = iter.iterateNext().href;
		GM_log("url: "+url);
		query="../../../TD[class='subscription-title']/SPAN";	
        iter = document.evaluate(query,document,null, XPathResult.ANY_TYPE, null);
		title = iter.iterateNext().textContent;
		GM_log("title: "+title);
		// must be the manage feed page.
		// title 
		// link is the [i] image    
		return new PostItem(url,title, tags);
    }

	// parse post items out of html.  
	// a little ugly: try to parse as if it's part of an article tag first.  if 
	// that fails, then parse as a feed tag.
	// check at the outset if it's on /manage-feeds/ and send out to the other
	// method.  Doing it that way is better than forking a separate onDOMNodeInserted
	// listener and registration based on the current location.
    function getPostItem (node) {
        // ../../../span has the link if an article,
        // ../../../../../../H1 has the rojo link and title if feed
        // distinguish - if there's a span sibling to ../../.., then article, else
        // feed
        var path = unsafeWindow.location.pathname;
        if (path == "/manage-feeds/") {
			return getPostItem2(node);    
	    }
        // need to ignore LI without IDs.  looks like a rendering bug on
        // rojo's side that ghost tags are showing up without ids.
        var query = "..//li[@id]";
        var iter= document.evaluate(query,node,null, XPathResult.ANY_TYPE, null);
        li = iter.iterateNext();
        var tags = new Array();
        while (li != null) {
            tags.push(getTag(li));
            li = iter.iterateNext();
        }        

        query="../../../../span[1]";
        iter= document.evaluate(query,node,null, XPathResult.ANY_TYPE, null);
        i = iter.iterateNext();
        var title = "";
        var url = "";
        if (i != null) {
            // it's an article being tagged
            // 12 hours later, what the hell was the line below?  what i get for
            // coding between playing with a 6 year old and 3 year old...  
            // seems to work, oddly enough.  
            url = i.title.replace(/\n/g,'').replace(/^\s+/g, '').replace(/\s+$/g, '');
            query="./a/text()"
            iter = document.evaluate(query,i,null, XPathResult.ANY_TYPE, null);
            title = iter.iterateNext().textContent;
            title = title.replace(/\n/g,'').replace(/^\s+/g, '').replace(/\s+$/g, '');
        }
        else {
            // must be the feed itself
            query="//H1/text()";
            iter = document.evaluate(query,document,null, XPathResult.ANY_TYPE, null);
			var titleElement = iter.iterateNext();
			if (titleElement != null) {
              title = titleElement.textContent;

              query="//H1/SPAN/A[2]";
              iter = document.evaluate(query,document,null, XPathResult.ANY_TYPE, null);
              url = iter.iterateNext().href;
            }
        }
            
        return new PostItem(url,title, tags);      
    }

    // note that a tag is removed.  create and store a PostItem.
 	// posting will be done when the page rojo uses for back end communication 
 	// loads.
    function onDOMNodeRemoved (event) {
        GM_log("onDOMNodeRemoved: "+event.target);
        GM_log("event.target.className: "+ event.target.className);
        if (event.target.className == "tagLi") {
            // tags removed are not yet removed from the postItem
            // remove it here.
            var theTag = getTag(event.target);
            var postItem = getPostItem(event.target);
            var newTags = new Array();
            // remove tag from tags
            for (var i = 0; i < postItem.tags.length; i++) {
                if (postItem.tags[i] != theTag) {
                  newTags.push(postItem.tags[i]);
                }
            }
            postItem.tags = newTags;
            GM_log("postItem: "+postItem.toSource());
            GM_setValue("postItem",postItem.toSource());
        }
    }

    // note addition of a tag.  Create a post item and store via gm_setvalue.
    // if multiple tags are added, a PostItem will be created for each and 
    // only the final one left stored via gm_setvalue (all but last are overwritten)
    // actual post of postitem will happen when the page rojo uses for back end
    // communication is written.
    function onDOMNodeInserted (event) {
        GM_log("onDOMNodeInserted: "+event.target);
        GM_log("event.target.className: "+ event.target.className);
        if (event.target.className == "tagLi") {
            // var theTag = getTag(event.target);
            // tags added are already in the postItem.
            // no need to add it here.
            var postItem = getPostItem(event.target);
            GM_log("postItem: "+postItem.toSource());
            GM_setValue("postItem",postItem.toSource());
        }
    }

    // Post whatever post item is currently stored.  If the currently stored item
    // has 0 tags, treat it as a delete.
    // NOTE: this relies on you being logged into del.icio.us.  You will be 
    // prompted to login if you are not.  Not sure what happens when you log out
    // of del.icio.us.  Early testing indicated posting would somehow still work
    // but I'm not sure... 
    function doPostItem () {
        GM_log("doPostItem");
        var src = GM_getValue("postItem");
        GM_log("src: "+src);
        // if tags.length == 0, then delete
        var o = eval(src);
        if (o.tags.length == 0) {
            // delete
            var url = "http://del.icio.us/api/posts/delete?";
            url += "url="+o.url;

            yield_get_response(url, function (resp) {
                GM_log("response received: "+resp.toSource());
            });
        }        
        else {
	        var url = "http://del.icio.us/api/posts/add?";
	        url += "&tags="+o.tags.join(" ");
	        url += "&description="+o.title;
	        url += "&url="+o.url;
	
	        yield_get_response(url, function (resp) {
	            GM_log("response received: "+resp.toSource());
	        });
        }
    }

    // Issue a GET to the specified URL, then pass the response to the
    // function argument.  Utility method.
    // TODO: make this degrade gracefully?
    function yield_get_response (url, response_first_argument) {
        GM_log("yield_get_response("+url+",..)");
        
        GM_xmlhttpRequest ({
            method: 'GET',
            url: url,
            headers: {
                'User-agent': 'Mozilla/4.0 (compatible) rojo_delicios.user.js'
            },
            onload: response_first_argument
        });
    }
    function onLoad () {
		// run the page-specific function, if any.
        var path = unsafeWindow.location.pathname;
        var f = h[path];
        if (f != null) f();

		// put the mutation event handlers in place if this is a window with
		// TagArticle available.
		// in the end, the tags might be captured more easily by 
		// intercepting specific page logs (doIRPCTagArticle etc) but the 
		// mutation events were fun to play with so I'm leaving them in until 
		// there's some compelling reason to rewrite their functionality.
		// later note: not so sure it would be doable without mutation events.
		// mutation events are in-place in the html and i can get to the rest of
		// the tags on an item.  at least some of the doIRPC... methods on pass
		// the changed tag & del.icio.us api doesn't allow passing only the 
		// changed tags.
		
		// only put mutation event listeners on pages that have access to the 
		// javascript object that makes the mutations happen.  this is an attempt
		// to avoid monkeying with all the iframe-based backend communication
		// rojo does.
        if (unsafeWindow.TagArticle != null) {
			//        document.addEventListener("click", onEvent, false);
			//        document.addEventListener("DOMAttrModified", onEvent, false);
			//          document.addEventListener("DOMCharacterDataModified", onEvent, false);
            document.addEventListener("DOMNodeInserted", onDOMNodeInserted, false);
			//          document.addEventListener("DOMNodeInsertedIntoDocument", onEvent, false);
            document.addEventListener("DOMNodeRemoved", onDOMNodeRemoved, false);        
			//          document.addEventListener("DOMNodeRemovedFromDocument", onEvent, false);
			//          document.addEventListener("DOMSubtreeModified", onEvent, false);        
        }
     }    
})();