FetLife Spyscope

Hover over FetLife user avatar pictures to see their recent activity, vitals, group subscriptions, and more. Quickly discern whether they're worth talking back to or not.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

/**
 *
 * This is a Greasemonkey script and must be run using a Greasemonkey-compatible browser.
 *
 * @author maymay <[email protected]>
 */
// ==UserScript==
// @name           FetLife Spyscope
// @version        0.1.1
// @namespace      http://maybemaimed.com/playground/fetlife-spyscope/
// @description    Hover over FetLife user avatar pictures to see their recent activity, vitals, group subscriptions, and more. Quickly discern whether they're worth talking back to or not.
// @include        https://fetlife.com/*
// @exclude        https://fetlife.com/adgear/*
// @exclude        https://fetlife.com/chat/*
// @exclude        https://fetlife.com/im_sessions*
// @exclude        https://fetlife.com/polling/*
// @exclude        https://fetlife.com/users/*/friends
// @grant          GM_xmlhttpRequest
// @grant          GM_addStyle
// ==/UserScript==

FL_SPYSCOPE = {};
FL_SPYSCOPE.CONFIG = {
    'debug': false, // switch to true to debug.
};

// Utility debugging function.
FL_SPYSCOPE.log = function (msg) {
    if (!FL_SPYSCOPE.CONFIG.debug) { return; }
    GM_log('FETLIFE SPYSCOPE: ' + msg);
};

// Initializations.
var uw = (unsafeWindow) ? unsafeWindow : window ; // Help with Chrome compatibility?
GM_addStyle('\
.fl-spyscope {\
    border: 1px solid gray;\
    max-width: 500px;\
}\
.group_mods .fl-spyscope {\
    float: left;\
}\
.post .fl-spyscope,\
.pictures .fl-spyscope,\
.friends .fl-spyscope {\
    position: absolute;\
    background: black;\
}\
.post .last_comment .fl-spyscope {\
    position: static;\
}\
/* Events page and friends lists. */\
.pictures > li,\
.friends > li { position: relative; }\
.pictures .fl-spyscope,\
.friends .fl-spyscope { z-index: 100; }\
.pictures .fl-spyscope { top: 60px; }\
.friends .fl-spyscope { top: 40px; }\
.pictures .fl-spyscope li,\
#profile .friends .fl-spyscope li { width: 300px; }\
.pictures .fl-spyscope a { float: none; }\
#profile .friends .fl-spyscope a { display: inline; }\
.friends .fl-spyscope img { float: none; }\
');
FL_SPYSCOPE.users_cache = {};
FL_SPYSCOPE.init = function () {
    FL_SPYSCOPE.main();
};
window.addEventListener('DOMContentLoaded', FL_SPYSCOPE.init);

/**
 * Given a user ID, fetch and store the complete FetLife profile HTML for this user.
 *
 * @see https://userscripts.org/scripts/review/146293#function.FL_SPYSCOPE.getUserProfile
 */
FL_SPYSCOPE.fetchUserProfile = function (id) {
    if (FL_SPYSCOPE.users_cache[id].profile_html) {
        return; // we've already got a cache, so don't do this again.
    }
    FL_SPYSCOPE.users_cache[id] = {};
    GM_xmlhttpRequest({
        'method': 'GET',
        'url': 'https://fetlife.com/users/' + id.toString(),
        'onload': function (response) {
            // Get profile HTML.
            var html = response.responseText;
            // Store it in a local cache?
            FL_SPYSCOPE.users_cache[id].profile_html = html;

            // Parse the returned profile HTML and save relevant info.
            var parser = new DOMParser();
            var doc = parser.parseFromString(html, 'text/html');
            // NOTE: We collect the nickname in the FL_SPYSCOPE.main() function, since we can.
            FL_SPYSCOPE.users_cache[id].nickname = doc.querySelector('.bottom').childNodes[0].nodeValue;
            FL_SPYSCOPE.users_cache[id].sex      = FL_SPYSCOPE.getSex(doc.querySelector('.bottom'));
            FL_SPYSCOPE.users_cache[id].age      = FL_SPYSCOPE.getAge(doc.querySelector('.bottom'));
            FL_SPYSCOPE.users_cache[id].role     = FL_SPYSCOPE.getRole(doc.querySelector('.bottom'));
            FL_SPYSCOPE.users_cache[id].loc_str  = doc.querySelector('.bottom + p').innerHTML;
            FL_SPYSCOPE.users_cache[id].activity = doc.querySelector('#mini_feed');
        }
    });
};

/**
 * Various user info parsing functions.
 *
 * @see https://userscripts.org/scripts/review/146293#function.FL_SPYSCOPE.getSex
 */
FL_SPYSCOPE.getSex = function (el) {
    x = el.querySelector('.quiet').innerHTML;
    sex = x.match(/^\d\d(\S*)/);
    return sex[1];
};

FL_SPYSCOPE.getAge = function (el) {
    x = el.querySelector('.quiet').innerHTML;
    age = x.match(/^\d\d/);
    return parseInt(age);
};

FL_SPYSCOPE.getRole = function (el) {
    x = el.querySelector('.quiet').innerHTML;
    role = x.match(/ ?(\S+)?$/);
    return role[1];
};

FL_SPYSCOPE.createScope = function (id) {
    var div = document.createElement('div');
    div.setAttribute('class', 'fl-spyscope');

    var ul = document.createElement('ul');
    var li = document.createElement('li');
    
    var html_str = '';
    // Fill list items appropriately.
    
    html_str += '<b><font color="red">';
    html_str += FL_SPYSCOPE.users_cache[id].nickname;
    html_str += '</font> ';

    html_str += FL_SPYSCOPE.users_cache[id].age;
    html_str += FL_SPYSCOPE.users_cache[id].sex;
    html_str += ' ';
    html_str += FL_SPYSCOPE.users_cache[id].role + '</b><br />';
    html_str += '(' + FL_SPYSCOPE.users_cache[id].loc_str + ')';
    li.innerHTML = html_str;

    ul.appendChild(li);
    div.appendChild(ul);
    
    // Show last three items from "Latest activity" mini feed.
    //var acts = FL_SPYSCOPE.users_cache[id].activity.children;
    //div.innerHTML += '<ul><li>' + acts[0].innerHTML + '</li><li>' + acts[1].innerHTML + '</li><li>' + acts[2].innerHTML + '</li></ul>';

    return div;
};

/**
 * Handles spyscope rollovers.
 */
FL_SPYSCOPE.show = function (e) {
    scope = FL_SPYSCOPE.createScope(parseInt(e.currentTarget.href.match(/\d+$/)));
    e.currentTarget.parentNode.appendChild(scope);
};
FL_SPYSCOPE.hide = function (e) {
    var scope = e.currentTarget.parentNode.lastChild;
    scope.parentNode.removeChild(scope);
};

// This is the main() function, executed on page load.
FL_SPYSCOPE.main = function () {
    // Find all FetLife users on this page that aren't the current (logged-in) user.
    var user_links = document.querySelectorAll('a[href^="/users/"]:not([href^="/users/' + uw.FetLife.currentUser.id + '"])');
    // For each user,
    for (var i = 0; i < user_links.length; i++) {
        // Collect its user ID number.
        var id = parseInt(user_links[i].href.match(/(\d+)\/?$/));
        FL_SPYSCOPE.users_cache[id] = {};
        if (null !== id) {
            // Gather profile data for this user.

            // Get nickname.
            var n;
            if (user_links[i].children.length) {
                // This is an avatar link, not a text link.
                n = user_links[i].childNodes[0].alt;
                user_links[i].childNodes[0].title = "";
            } else {
                // This is a text link. Easy.
                n = user_links[i].innerHTML;
            }
            FL_SPYSCOPE.users_cache[id].nickname = n;

            // Collect age/sex/role/location.
            // TODO: Loading them all on page load really slows things down.
            //       Can we optimize so that we fetch on rollover rather than load?
            FL_SPYSCOPE.fetchUserProfile(id);
        }
        // Attach the spyscope show/hide event handler.
        user_links[i].addEventListener('mouseover', FL_SPYSCOPE.show);
        user_links[i].addEventListener('mouseout', FL_SPYSCOPE.hide);
    }
};

// The following is required for Chrome compatibility, as we need "text/html" parsing.
/*
 * DOMParser HTML extension
 * 2012-09-04
 *
 * By Eli Grey, http://eligrey.com
 * Public domain.
 * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
 */

/*! @source https://gist.github.com/1129031 */
/*global document, DOMParser*/

(function(DOMParser) {
	"use strict";

	var
	  DOMParser_proto = DOMParser.prototype
	, real_parseFromString = DOMParser_proto.parseFromString
	;

	// Firefox/Opera/IE throw errors on unsupported types
	try {
		// WebKit returns null on unsupported types
		if ((new DOMParser).parseFromString("", "text/html")) {
			// text/html parsing is natively supported
			return;
		}
	} catch (ex) {}

	DOMParser_proto.parseFromString = function(markup, type) {
		if (/^\s*text\/html\s*(?:;|$)/i.test(type)) {
			var
			  doc = document.implementation.createHTMLDocument("")
			;

			doc.body.innerHTML = markup;
			return doc;
		} else {
			return real_parseFromString.apply(this, arguments);
		}
	};
}(DOMParser));