Predator Alert Tool for Facebook (PAT-Facebook)

Alerts you of people who have allegedly violated other people's consent as you browse Facebook.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

/**
 *
 * This is a Greasemonkey script and must be run using a Greasemonkey-compatible browser.
 *
 * @author maymay <[email protected]>
 */
// ==UserScript==
// @name           Predator Alert Tool for Facebook (PAT-Facebook)
// @version        0.1.3
// @namespace      com.maybemaimed.facebook.predator-alert-tool
// @description    Alerts you of people who have allegedly violated other people's consent as you browse Facebook.
// @include        https://www.facebook.com/*
// @include        http://www.facebook.com/*
// @include        https://apps.facebook.com/*
// @include        http://apps.facebook.com/*
// @include        https://predator-alert-tool.herokuapp.com/*
// @exclude        https://www.facebook.com/ajax*
// @exclude        http://www.facebook.com/ajax*
// @exclude        https://www.facebook.com/ai.php*
// @exclude        http://www.facebook.com/ai.php*
// @grant          GM_log
// @grant          GM_xmlhttpRequest
// @grant          GM_addStyle
// @grant          GM_setValue
// @grant          GM_getValue
// ==/UserScript==
PAT_FB = {};
PAT_FB.CONFIG = {
    'debug': false, // switch to true to debug.
    'api_url': 'https://predator-alert-tool.herokuapp.com/api.php?fbid='
};

// Utility debugging function.
PAT_FB.log = function (msg) {
    if (!PAT_FB.CONFIG.debug) { return; }
    GM_log('PAT-Facebook: ' + msg);
};
// Utility function to read cookie values.
function getCookie (n) {
    try {
        return unescape(document.cookie.match('(^|;)?'+n+'=([^;]*)(;|$)')[2]);
    } catch (e) {
        return null;
    }
}
PAT_FB.getUserID = function () {
    try {
        return unsafeWindow.Env.user
    } catch (e) {
        return getCookie('c_user');
    }
};

// Initializations.
GM_addStyle('\
');
PAT_FB.current_user = null; // Who are we?
PAT_FB.init = function () {
    // We need to capture the session cookies from the PAT-FB server, so if we
    // loaded the server's pages, save the cookies locally for later use.
    // TODO: But, um, this functionality really starts requiring a browser ext.
    if (unsafeWindow.location.host == PAT_FB.parseApiUrl().host) {
        GM_setValue('pat_fb_cookies', document.cookie);
    }
    PAT_FB.current_user = PAT_FB.getUserID();
    var MutationObserver = unsafeWindow.MutationObserver || unsafeWindow.WebKitMutationObserver;
    var observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            if (mutation.addedNodes) {
                for (var i = 0; i < mutation.addedNodes.length; i++) {
                    // Skip text nodes.
                    if (mutation.addedNodes[i].nodeType == Node.TEXT_NODE) { continue; }

                    // If the new node is a hovercard
                    var ownerid = mutation.addedNodes[i].getAttribute('data-ownerid');
                    var num_pat;
                    // and this hovercard has any PAT data, grab that data
                    if (ownerid && (num_pat = document.getElementById(ownerid).getAttribute('data-num-pat-reports'))) {
                        // and insert it into the hovercard HTML.
                        var a = document.createElement('a');
                        a.setAttribute('href',
                            PAT_FB.parseApiUrl().protocol + '//' + PAT_FB.parseApiUrl().host
                            + '/reports.php?action=lookup&reportee_id='
                            + document.getElementById(ownerid).getAttribute('data-pat-reportee-id')
                        );
                        a.setAttribute('class', 'pat-reports-link');
                        a.innerHTML = 'View ' + num_pat + ' PAT-FB stories';
                        // Don't append a duplicate link if we're re-processing this node.
                        // TODO: Uhm, yeah, why don't we just not re-process it multiple times?
                        if (!mutation.addedNodes[i].querySelector('.pat-reports-link')) {
                            mutation.addedNodes[i].querySelectorAll('table td')[1].appendChild(a);
                        }
                    }
                    // Process all the rest.
                    PAT_FB.main(mutation.addedNodes[i]);
                }
            }
        });
    });
    var el = document.body;
    observer.observe(el, {
        'childList': true,
        'subtree': true
    });
    PAT_FB.main(document);
};
window.addEventListener('DOMContentLoaded', PAT_FB.init);

// main() is given a start node (HTML tree) and processes appropriately.
PAT_FB.main = function (node) {
    PAT_FB.log('Starting main() on page ' + unsafeWindow.location.toString());
    var els = node.querySelectorAll('[data-hovercard]');
    for (var i = 0; i < els.length; i++) {
        var fbid = els[i].getAttribute('data-hovercard').match(/id=(\d+)/)[1];
        PAT_FB.log('Found Facebook ID ' + fbid + '.');
        PAT_FB.maybeFlagEntity(fbid, els[i]);
    }
};

PAT_FB.parseApiUrl = function () {
    var a = document.createElement('a');
    a.setAttribute('href', PAT_FB.CONFIG.api_url);
    return {
        'protocol': a.protocol,
        'host': a.host,
        'port': a.port,
        'pathname': a.pathname
    };
}

/**
 * Queries the PAT-FB server for reports by Facebook ID. If a result is found,
 * applies styling to the HTML node appropriately.
 *
 * @param fbid The numeric Facebook ID to query.
 * @param el The HTML node from which the ID was scraped.
 */
PAT_FB.maybeFlagEntity = function (fbid, el) {
    if (!fbid) { PAT_FB.log('Invalid ID passed to maybeFlagEntity().'); return false; }
    if (fbid == PAT_FB.current_user && GM_getValue('ignore_reports_about_me', true)) {
        PAT_FB.log('Refusing to flag ourselves (current user ID ' + PAT_FB.current_user + ').');
        return false;
    }
    PAT_FB.log('About to query for reports on ID ' + fbid.toString());
    GM_xmlhttpRequest({
        'method': 'GET',
        'url': PAT_FB.CONFIG.api_url + fbid.toString(),
        'headers': {
            'Cookie': GM_getValue('pat_fb_cookies')
        },
        'onload': function (response) {
            try {
                resp = JSON.parse(response.responseText);
                PAT_FB.log('Parsed response from PAT-FB for ' + fbid.toString() + ': ' + response.responseText);
            } catch (e) {
                PAT_FB.log('Caught error from reply: ' + response.responseText);
                return;
            }
            if (resp.reports) {
                el.style.border = PAT_FB.setBorderWidthByReportCount(resp.reports).toString() + 'px solid red';
                // Store data in the element.
                el.setAttribute('data-num-pat-reports', resp.reports.toString());
                el.setAttribute('data-pat-reportee-id', resp.reportee_id.toString());
            }
        }
    });
};

PAT_FB.setBorderWidthByReportCount = function (n) {
    if (n < 2) {
        n = 1;
    } else if (n > 4) {
        n = 5;
    }
    return n;
};