FetLife Text Search

Searches through FetLife group discussions for a specific keyword or phrase.

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 Text Search
// @version        0.1.1
// @namespace      com.maybemaimed.fetlife.textsearch
// @description    Searches through FetLife group discussions for a specific keyword or phrase.
// @include        https://fetlife.com/groups*
// @exclude        https://fetlife.com/adgear/*
// @exclude        https://fetlife.com/chat/*
// @exclude        https://fetlife.com/im_sessions*
// @exclude        https://fetlife.com/polling/*
// @grant          GM_xmlhttpRequest
// @grant          GM_addStyle
// @grant          GM_log
// ==/UserScript==

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

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

// Initializations.
var uw = (unsafeWindow) ? unsafeWindow : window ; // Help with Chrome compatibility?
GM_addStyle('\
#tabnav li:last-child { position: relative; }\
#tabnav li:last-child div {\
    opacity: .95;\
    display: none;\
    position: absolute;\
    background: gray;\
    width: 930px;\
    min-height: 125px;\
    padding: 3px 10px;\
}\
#tabnav li:last-child input[type="text"] { width: 100%; }\
#tabnav #fetlife_text_search_results li {\
    opacity: 1;\
    display: list-item;\
    margin: 1em 0;\
    clear: left;\
}\
#tabnav #fetlife_text_search_results .avatar { float: left; }\
#tabnav #fetlife_text_search_results .permalink {\
    font-weight: normal;\
    background-color: transparent;\
    padding: 0;\
    border: none;\
    color: black;\
    line-height: 1.3em;\
}\
.comment::target { border: 1px solid red; }\
');
FL_TXT.init = function () {
    FL_TXT.currentSearch = {};
    FL_TXT.CONFIG.search_form = document.getElementById('tabnav');
    FL_TXT.main();
};
window.addEventListener('DOMContentLoaded', FL_TXT.init);

FL_TXT.getPageFromURL = function (url) {
    FL_TXT.log('Getting page from URL: ' + url);
    GM_xmlhttpRequest({
        'method': 'GET',
        'url': url,
        'onload': function (response) {

            // Parse page now to find pagination, in a moment.
            var parser = new DOMParser();
            var doc = parser.parseFromString(response.responseText, 'text/html');

            // Set up next request.
            my_page = (url.match(/\?page=\d+$/)) ? parseInt(url.match(/\d+$/)[0]) : 1 ;
            next_page = my_page + 1;
            if (next_page > 2) {
                next_url = url.replace(/\d+$/, next_page.toString());
            } else {
                next_url = url + '?page=' + next_page.toString();
            }

            // If this was a discussion listing page
            if (!url.match(/group_posts/)) {
                // Make a list of links to each thread, and recurse on those.
                var as = doc.querySelectorAll('#discussions h4 a');
                for (var i = 0; i < as.length; i++) {
                    FL_TXT.getPageFromURL(as[i].href);
                }

                // Check for pagination on discussion lists, follow if necessary.
                if (FL_TXT.isPaginated(doc)) {
                    FL_TXT.getPageFromURL(next_url);
                }
                return;
            }

            // Search group post content, too, if on first page.
            if (my_page === 1 && (-1 !== doc.querySelector('.group_post').textContent.toLowerCase().search(FL_TXT.currentSearch.search_string.toLowerCase()))) {
                FL_TXT.displayResult(doc.querySelector('.group_post'), doc.querySelector('.group_post').textContent.toLowerCase().search(FL_TXT.currentSearch.search_string.toLowerCase()), url);
            }

            var els = doc.querySelectorAll('.comment');

            result_count = 0;
            for (var i = 0; i < els.length; i++) {
                // Parse results for this page and make note of any search string hits.
                var x = els[i].textContent.toLowerCase().search(FL_TXT.currentSearch.search_string.toLowerCase());
                if (-1 !== x) {
                    FL_TXT.displayResult(els[i], x, url);
                }
                result_count++;
            }

            // Check for pagination of group thread itself, follow.
            if (FL_TXT.isPaginated(doc)) {
                FL_TXT.getPageFromURL(next_url);
            }
        }
    });
};

FL_TXT.isPaginated = function (doc) {
    if (!doc.querySelector('.pagination')) {
        return false;
    } else if (!doc.querySelector('.pagination .next_page.disabled')) {
        return true;
    } else {
        return false;
    }
};

FL_TXT.toggleSearchInterface = function (e) {
    var lis = e.target.parentNode.parentNode.children;
    for (var i = 0; i < lis.length; i++) {
        lis[i].className = lis[i].className.replace('in_section', '');
    }
    e.target.parentNode.className = "in_section";
    e.target.nextSibling.style.display = 'block';
};

FL_TXT.getGroupName = function () {
    return document.querySelector('.group_name a').innerHTML;
};

FL_TXT.doTextSearch = function (e) {
    e.preventDefault();
    var group_id = document.getElementById('fetlife_text_search_group_id').value;
    var search_string = document.getElementById('fetlife_text_search').value;
    FL_TXT.currentSearch.search_string = search_string;
    switch (e.explicitOriginalTarget.value) {
        case 'Search all discussions.':
            FL_TXT.searchGroup(search_string, group_id);
            break;
        case 'Search this thread.':
        default:
            var group_post_id = document.getElementById('fetlife_text_search_group_post_id').value;
            FL_TXT.searchGroup(search_string, group_id, group_post_id);
            break;
    };
};

FL_TXT.searchGroup = function (str, group_id, group_post_id) {
    var url = 'https://fetlife.com/groups/' + group_id;
    if (group_post_id) {
        url += '/group_posts/' + group_post_id;
    }
    FL_TXT.getPageFromURL(url);
};

FL_TXT.displayResult = function (el, pos, url) {
    var ul = document.getElementById('fetlife_text_search_results');
    var li = document.createElement('li');

    var icon = el.querySelector('.avatar');
    var nick = el.querySelector('.nickname');
    var permalink = url;
    if (el.getAttribute('id')) {
        url += '#' + el.getAttribute('id');
    }

    var start = (100 > pos) ? 0 : pos - 100;
    var end = pos + 100;
    var html_string = '<a href="' + permalink + '" class="permalink">' + icon.innerHTML + ': ' + el.textContent.substring(start, end) + '</a>';

    li.innerHTML = html_string;
    ul.appendChild(li);
};

// This is the main() function, executed on page load.
FL_TXT.main = function () {
    // Get relevant object IDs.
    var group_id, group_post_id;
    var m = window.location.href.match(/https:\/\/fetlife\.com\/groups\/(\d+)(\/group_posts\/(\d+))?/);
    if (!m) {
        FL_TXT.log('Failed to find FetLife Group ID or group post ID in ' + window.location.href);
        return false;
    } else {
        group_id = m[1];
        group_post_id = m[3];
    }

    // Insert FetLife Text Search button interface.
    var li = document.createElement('li');
    li.setAttribute('id', 'tab-text-search');
    var a  = document.createElement('a');
    a.setAttribute('href', '#');
    a.addEventListener('click', FL_TXT.toggleSearchInterface);
    a.innerHTML = 'Search ' + this.getGroupName();
    li.appendChild(a);

    var div = document.createElement('div');
    var form = document.createElement('form');
    form.setAttribute('action', window.location.href)
    form.setAttribute('method', 'GET');
    form.addEventListener('submit', FL_TXT.doTextSearch);
    var html_string = '<input type="hidden" id="fetlife_text_search_group_id" name="fetlife_text_search_group_id" value="' + group_id + '" />';
    html_string += '<label><input type="text" id="fetlife_text_search" name="fetlife_text_search" value="" placeholder="Search for&hellip;"/></label>';
    html_string += '<input type="submit" name="fetlife_text_search_discussions" value="Search all discussions." />';
    // If this is a specific discussion, offer to search only within that discussion thread.
    if (group_post_id) {
        html_string += '<input type="submit" name="fetlife_text_search_this_thread" value="Search this thread." />';
        html_string += '<input type="hidden" id="fetlife_text_search_group_post_id" name="fetlife_text_search_group_post_id" value="' + group_post_id + '" />';
    }
    form.innerHTML = html_string;

    div.appendChild(form);
    li.appendChild(div);
    document.getElementById('tabnav').appendChild(li);

    var ul = document.createElement('ul');
    ul.setAttribute('id', 'fetlife_text_search_results');
    div.appendChild(ul);
};

// 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));