// ==UserScript==
// @name                Unheart-reheart
// @version             2.3
// @date                2009-07-17
// @author              Ian Malpass ( ian AT etsyhacks DOT com )
// @namespace           etsy.com
// @description         Remove hearts from the shop and listing views (and add them back again)
// @include             http://www.etsy.com/shop.php?*
// @include             http://www.etsy.com/view_listing.php?*
// @include             http://www.etsy.com/favorite_sellers.php*
// @include             http://www.etsy.com/favorite_listings.php*
// ==/UserScript==

// image data for the "unheart" icons
var remove_img = {
    shop: "data:image/gif,GIF89a%10%00%10%00%E6J%00%A2%D1D%E3%E3%E3%A0%A0%A0%F2%F2%F2%F3%F3%F3%D5%D5%D5%A8%A8%A8%86%B5(%B1%B1%B1%BC%BC%BC%F7%F7%F7%A2%A2%A2%F6%F6%F6%DE%DE%DE%FC%FC%FC%CA%CA%CA%D1%D1%D1%F5%F5%F5%C0%C0%C0%B4%E3V%DF%DF%DF%DD%DD%DD%83%B2%25%F4%F4%F4%E4%E4%E4%A3%D2E%BF%BF%BF%C5%C5%C5%B2%B2%B2%A7%A7%A7%ED%ED%ED%E6%E6%E6%8A%B9%2C%C1%C1%C1%A5%D4G%E1%E1%E1%80%AF%22%9C%CB%3E%FF%FF%FF%EB%EB%EB%E8%E8%E8%C5%F4g%D6%D6%D6%E9%E9%E9%FA%FA%FA%C3%F2e%CD%CD%CD%EC%EC%EC%BA%E9%5C%C9%C9%C9%F1%F1%F1%CC%CC%CC%E2%E2%E2%CE%CE%CE%A4%A4%A4%F8%F8%F8%C9%F8k%BE%BE%BE%B9%B9%B9%FE%FE%FE%C3%C3%C3%B4%B4%B4%D2%D2%D2%EA%EA%EA%BB%BB%BB%F0%F0%F0%8E%BD0%A5%A5%A5%B0%B0%B0%E7%E7%E7%F9%F9%F9%B0%DFR%DB%DB%DB%FD%FD%FD%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00J%00%2C%00%00%00%00%10%00%10%00%00%07%9D%80JJF%3C%1D%08%23%82%89%8A%82%12!%01%14%02'%8B%8A%2C%02%1F%3F%26%051%93%89%0E%0B%01%2B%0E%3E3%9C%89%0F%09%1056%04%A5%89*%09%1BA%AD%8A%04I%B3%82%0C%3D%06%06(%B7%3A%124%05%0B%1E%AD%18%08%14%1F%0A%05D%3B%A5%09%0F%0D%1E%0C7%07%20B%22-%19%00%00J%9EH%0D2%11%0A%16G%13%130)8%DC%01%08%15%01%04%17%11%24%16%07%07%25%DA%DC.%1A%15E%03%F0%0C%0D%80L%DA%90%03B%80%01%081%1400I%C1%03%0E%1D%04%0C%11%C0A%C3%8BI%81%00%00%3B",
    item: "data:image/gif,GIF89a%10%00%10%00%E7%7F%00%C17%25%C86%23%CA6%22%CD6%22%C89%26%C9%3D%2B%A1I%3D%9ELB%A4K%3F%C9C2%BEF7%AEK%3D%95SJ%9EQG%BDK%3D%CAH7%D3F3%D2K%3A%8F%5DV%B0VJ%E3L8%CFSC%8Cgb%E5Q%3D%E1R%3F%89jf%AFaV%E6TA%E0WEsssyrqttt%80sp%EAXEvvv%E6ZH%EA%5BHyyy%EA%5DJ%9Drj%7B%7B%7B%EB_M%E7bQ%EBbP~~~%D1iZ%A3um%B9pd%EDdR%80%80%80%EBeT%EAfT%B0vm%ECgV%B5vl%83%83%83%ECjY%85%85%85%F0lZ%B6%7Bq%88%88%88%F4m%5C%D4xj%F8o%5D%C5%7Dq%FCq_%DEzk%8F%8F%8F%FFsa%FDub%F7we%FFub%91%91%91%92%92%92%FFwd%FAyf%FFyf%FF%7Bg%FF%7Di%99%99%99%FF%80k%9B%9B%9B%FF%82m%81%B0%23%9C%9C%9C%FF%84o%9E%9E%9E%A0%A0%A0%FF%8By%AA%AA%AA%AD%AD%AD%96%BFF%B4%B4%B4%A2%C6K%B6%C0d%A9%C4p%A2%CAQ%B9%B9%B9%9F%CEA%BB%BB%BB%BA%C5i%AF%CAv%A4%D3F%C0%C0%C0%AB%D3Z%B0%DFR%CD%CD%CD%CF%CF%CF%C2%DE%89%B8%E7Z%FF%C9%B5%FF%CB%C2%C6%E2%8D%BD%EC_%FF%CF%BC%C8%E4%90%CA%E4%92%D9%D9%D9%BF%EEa%DF%DF%DF%E6%E6%E6%E9%E9%E9%EE%EE%EE%F0%F0%F0%F5%F5%F5%F8%F8%F8%FD%FD%FD%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF%FF!%FE%11Created%20with%20GIMP%00!%F9%04%01%00%00%80%00%2C%00%00%00%00%10%00%10%00%00%08%D8%00%01%01%BA%A3%05%09%953%7D%04*%5C8%26%83%86%16%3Eh%F0%E0%03%A8%8F%9A%2Ca%F6%00%AAc%A1%C2%88%23J%98%D8%E0%A2%06%C5%89%17%3BJ%E0%B12!%02%8C%20D%8E%18%E9%E0B%08%14)U%80%20%C9%E1%E0%C1%8C%1E%3F%60%16%09%D9%C4%C9M%0F7%14%24P%81C%07P%98%20%99%14%05%11eA%01%0E2j4%7DJD%89%92%25%22%D6H%20%00!%C5%8A%AC8%B6%FEPB%E2%0A%A0%24%0D%02%600aV%06Z%1D%17%DC%02%DA%13%03%81%80%0D%24L%60%91%F3%A7p%E1%3B%02%F1%B080%E0B%089%5E%C8t%89%83f%0E%1D%85z%860%00%40%C1%CD%141i%EC%C4icf%A1%9F1%1F%0C%BC%F9RfK%1A0l%E0%2C%14%98%E7%89%1A%C3%86%11%CF%DE%CD%BB%F7%EC%80%00%3B"
};

// URLs for the heart icons already on Etsy
var add_img = {
    shop: "http://www.etsy.com/images/icon_add_seller_to_favorite.gif",
    item: "http://www.etsy.com/images/icon_add_item_to_favorites.gif"
}

var user = whoami();
var id = {};
var nnc;

if ( user ) {
    getToken( user );
}

function decoratePage () {
    nnc = GM_getValue( user + '_nnc' );
    if ( document.location.href.indexOf( 'shop.php' ) > -1 || document.location.href.indexOf( 'view_listing.php' ) > -1 ) {
        // dealing with shop or listing heart/unheart
        // find the ID
        var url_id = document.location.search.match( /(user|listing)_id=(\d+)/ )[ 2 ];

        var links = document.getElementsByTagName( 'a' );
        for ( l = 0; l < links.length; l++ ) {
            var link = links[ l ];
            if ( link.innerHTML.indexOf( 'Sign In' ) > -1 ) {
                // not signed in - fall back to standard behaviour
                break;
            } else if ( link.innerHTML.indexOf( 'Sign Out' ) > -1 ) {
                if ( document.location.href.indexOf( 'shop.php' ) > -1 ) {
                    // Signed in, and on a shop page - we have all the info we need, so fix the heart links
                    id.shop = url_id;
                    toggle( 'shop', true );
                } else {
                    // Signed in on a listing page - find out which shop we're on
                    for ( l = l + 2; l < links.length; l++ ) {
                        link = links[ l ];
                        if ( link.href && link.href.indexOf( 'shop.php' ) > -1 ) {
                            // found the shop link in the breadcrumb nav - get the ID and fix the heart links
                            id.shop = link.href.match( /user_id=(\d+)/ )[ 1 ];
                            id.item = url_id;
                            toggle( 'shop', true );
                            toggle( 'item', true );
                            break;
                        }
                    }
                }
                break;
            }
        }
    } else {
        // we're on a "favourites" page - sellers or listings
        // find the X buttons on the page
        var dgl = getElementsByClassName( 'dark_grey_link' );
        // 0-2 are the navigation links - ignore them
        for ( var d = 3; d < dgl.length; d++ ) {
            cell = dgl[ d ];
            var form = cell.getElementsByTagName( 'form' )[ 0 ];
            
            // find the X
            var x = document.createElement( 'a' );
            x.innerHTML = 'x';
            x.href = form.action;
            x.href += '&remove_id=' + form.elements.namedItem( 'remove_id' ).value;
            x.style.textDecoration = 'none';
            cell.innerHTML = '';
            cell.appendChild( x );
            // find the containing table
            var container = cell;
            while ( container.className != 'grey_border' ) container = container.parentNode;
            var gt = getElementsByClassName( 'green_text', container );
            var allowReheart = true;
            if ( gt.length > 0 ) {
                price = gt[ 0 ];
                if ( price.innerHTML == 'Sold' || price.innerHTML == '0' ) {
                    allowReheart = false;
                }
            }
            // make the "reheart" link
            var p = x.cloneNode( true );
            p.innerHTML = '+';
            p.style.display = 'none'; // hide
            p.style.color = '#ff0000';
            p.style.fontSize = '120%';
            p.style.fontWeight = 'bold';

            // fix the reheart link's href
            var match = p.href.match( /favorite_(sellers|listings).*remove_id=(\d+)/ );
            if ( match ) {
                if ( match[ 1 ] == 'sellers' ) {
                    p.href = 'http://www.etsy.com/add_favorite_shop.php?ajax=1&user_id=' + match[ 2 ];
                } else {
                    p.href = 'http://www.etsy.com/add_favorite_listing.php?ajax=1&listing_id=' + match[ 2 ];
                }
            }
            // the ubiquitous "AJAX request loading" icon
            var loading = document.createElement( 'img' );
            loading.height = "16";
            loading.width = "16";
            loading.style.display = 'none';
            loading.src = "http://www.etsy.com/images/ajax-loader.gif";

            // add the loading icon and the reheart link
            cell.appendChild( p );
            cell.appendChild( loading );

            // add event listeners to intercept the clicks and do AJAX requests
            // rather than server round-trips
            x.addEventListener( 'click', genToggle( x, p, container, loading, allowReheart ), false );
            p.addEventListener( 'click', genToggle( p, x, container, loading, true ), false );
        }
    }
}

function toggle ( type, entry ) {
    var suffix, url;
    // set up various bits we'll need to customise URLs etc., depending on what type of thing we're hearting
    if ( type == 'shop' ) {
        suffix = 'Seller';
        url_add = 'http://www.etsy.com/add_favorite_shop.php?ajax=1&user_id=' + id.shop;
        url_remove = 'http://www.etsy.com/favorite_sellers.php?remove_id=' + id.shop;
    } else {
        suffix = 'Item';
        url_add = 'http://www.etsy.com/add_favorite_listing.php?ajax=1&listing_id=' + id.item;
        url_remove = 'http://www.etsy.com/favorite_listings.php?remove_id=' + id.item;
    }
    // find the nodes we're interested in
    var loading = document.getElementById( 'ajaxLoaderIconHeart' + suffix );
    if ( loading == null ) {
        loading = document.getElementById( 'ajaxLoaderIconHeartSeller' );
        if ( loading == null ) return;
        loading = loading.cloneNode( true );
        loading.id = 'ajaxLoaderIconHeart' + suffix;
    }
    var heart = document.getElementById( 'imgHeart' + suffix );
    if ( heart == null ) return;
    var cell = heart.parentNode;
    while ( cell.nodeName != 'TD' ) cell = cell.parentNode;
    var cellHTML = cell.innerHTML;
    var row = cell.parentNode;
    if ( entry ) {
        var sli = document.getElementById( 'seller_listing_id' );
        if ( sli ) {
            document.body.appendChild( sli );
        }
        var table = row.parentNode;
        for ( var r = row.rowIndex + 1; r < table.rows.length; r++ ) {
            if ( table.rows[ r ].innerHTML.indexOf( 'See who hearts' ) > -1 ) {
                newRow = table.rows[ r ].cloneNode( true );
                break;
            }
        }
        heartCell = newRow.cells[ 0 ];
        heartCell.innerHTML = '';
        heartCell.appendChild( loading );
        heartCell.appendChild( heart );
        cell = newRow.cells[ 1 ];
        row.parentNode.replaceChild( newRow, row );
    } else {
        cell = cell.parentNode.cells[ 1 ];
    }
    cell.innerHTML = '';
    var a = document.createElement( 'a' );
    if ( ( entry && cellHTML.indexOf( 'Already a favorite ' + type ) > -1 ) || ( ! entry && heart.src.indexOf( 'icon_add_' + suffix.toLowerCase() ) > -1 ) ) {
        // either we've just loaded the page and the item's already a favourite
        // or we're toggling and the item's marked as a favourite
        a.innerHTML = 'Remove ' + suffix.toLowerCase() + ' from favorites';
        a.href = url_remove;
        heart.src = remove_img[ type ];
    } else {
        // not a favourite - replace the current "add to favorites" link
        // with our own
        a.innerHTML = 'Add ' + suffix.toLowerCase() + ' to favorites';
        a.href = url_add;
        heart.src = add_img[ type ];
    }
    // add a click handler to the link to do the work
    a.addEventListener( 'click', genHandler( type, a, loading, heart ), false );
    // add our new link to the cell
    cell.appendChild( a );
    // hide the loading icon and show the heart icon (for when we're coming in from a heart/unheart toggle
    loading.style.display = 'none';
    heart.style.display = '';
}

function toggleFav ( node, other, container, loading, allowReheart ) {
    // hide the loading icon
    loading.style.display = 'none';
    // show the +/x icon (whichever is provided
    if ( allowReheart ) other.style.display = '';
    // toggle the opacity
    if ( container.style.opacity == '' ) {
        container.style.opacity = '0.3';
    } else {
        container.style.opacity = '';
    }
}

function genHandler( type, a, loading, heart ) {
    return function ( event ) {
        event.preventDefault(); // don't follow through on the click event
        loading.style.display = 'block'; // show the loading icon
        heart.style.display = 'none'; // hide the add/remove icon
        var bits = a.href.split( '?' );
        var url = bits[ 0 ];
        var data = bits[ 1 ];
        data += '&_nnc=' + encodeURIComponent( nnc );
        // send the AJAX request to heart/unheart
        // and toggle the UI when it's done
        GM_xmlhttpRequest( {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Cookie: document.cookie },
            url: url,
            data: data,
            onload: function ( response ) { toggle( type ) }
        } );
    }
}

function genToggle( node, other, container, loading, allowReheart ) {
    return function ( event ) {
        event.preventDefault(); // don't follow through on the click event
        loading.style.display = ''; // show the loading icon
        node.style.display = 'none'; // hide the x or + link
        var bits = node.href.split( '?' );
        var url = bits[ 0 ];
        var data = bits[ 1 ];
        data += '&_nnc=' + encodeURIComponent( nnc );
        // send the AJAX request to heart/unheart
        // and toggle the UI when it's done
        GM_xmlhttpRequest( {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Cookie: document.cookie },
            url: url,
            data: data,
            onload: function ( response ) { toggleFav( node, other, container, loading, allowReheart ) }
        } );
    };
}

// utility function to replicate getElementsByClassName() on older Firefoxes
function getElementsByClassName ( class, node ) {
    if ( node == null ) node = document;
    if ( node.getElementsByClassName ) {
        return node.getElementsByClassName( class );
    } else {
        var classElements = new Array();
        var els = node.getElementsByTagName( '*' );
        var elsLen = els.length;
        var pattern = new RegExp("(^|\\s)"+class+"(\\s|$)");
        for (i = 0, j = 0; i < elsLen; i++) {
            if ( pattern.test(els[i].className) ) {
                classElements[j] = els[i];
                j++;
            }
        }
        return classElements;
    }
}

// find out who I'm logged in as
function whoami () {
    // find the user name by parsing links
    var links = document.getElementsByTagName( 'a' );
    for ( var l = 0; l < links.length; l++ ) {
        var link = links[ l ];
        if ( link.href ) {
            if ( link.href.indexOf( 'login.php' ) > -1 ) {
                // not logged in - no username
                return null;
            }
            if ( link.href.indexOf( 'your_etsy.php' ) > -1  ){
                // username is next to the Your Etsy link
                var cell = link.parentNode;
                var text = cell.parentNode.cells[ cell.cellIndex - 1 ].innerHTML;
                var match = text.match( /Happy Birthday (.+)!/ );
                text = ( match ) ? match[ 1 ] : text.substring( 0, text.indexOf( ':' ) );
                return text;
            }
         }
     }
}


function getToken ( shop ) {
    var token = GM_getValue( shop + '_token' );
    var cookies = document.cookie.split( /;\s*/ );
    var cToken;
    for ( var c = 0; c < cookies.length; c++ ) {
        var bits = cookies[ c ].split( '=' );
        if ( bits[ 0 ] == 'token' ) {
            cToken = bits[ 1 ];
            break;
        }        
    }
    if ( token && cToken == token && GM_getValue( shop + '_nnc' ) ) {
        decoratePage();
        return;
    }
    var inputs = document.getElementsByTagName( 'input' );
    for ( var i = inputs.length - 1; i >= 0; i-- ) {
        if ( inputs[ i ].name == '_nnc' ) {
            nnc = inputs[ i ].value;
            GM_setValue( shop + '_nnc', nnc );
            GM_setValue( shop + '_token', String( cToken ) );
            decoratePage();
            return;
        }
    }
    GM_xmlhttpRequest( {
        method: 'GET',
        url: 'http://www.etsy.com/expiring_listings.php',
        headers: { Cookie: document.cookie },
        onload: function ( response ) { parseUpdate( shop, response, cToken ) }
    } );
}

function parseUpdate ( shop, response, token ) {
    if ( response.status == 200 ) {
        var d = document.createDocumentFragment();
        d.innerHTML = response.responseText;
        var nnc = response.responseText.match( /<[^>]*name="_nnc"[^<]*>/ );
        if ( nnc ) {
            var match = nnc[0].match( /value="([^"]+)"/ );
            if ( match ) {
                GM_setValue( shop + '_nnc', match[ 1 ] );
                GM_setValue( shop + '_token', String( token ) );
                decoratePage();
            }
        }
    }
}



