Recently, I’ve been writing code for various different Predator Alert Tools. There are now five different dating websites for which I’ve written Predator Alert Tool code. They are:

  1. Predator Alert Tool for OkCupid
  2. Predator Alert Tool for FetLife
  3. Predator Alert Tool for Facebook1
  4. Predator Alert Tool for Lulu
  5. Predator Alert Tool for Bang With Friends

You can view screenshots for each of these on this Tumblr photoset, and help spread the word by reblogging it.

Each Predator Alert Tool is custom-designed not only for the technology of the specific website on which it is deployed, but also for the social culture of the website’s userbase. For instance, the Predator Alert Tool for OkCupid takes advantage of OkCupid’s “I’ll show you mine if you show me yours” Match Questions, whereas the Predator Alert Tool for Facebook “automates the process by which (mostly) women warn each other off dangerous men in their social network.”

This culture hacking approach makes each Predator Alert Tool unique, but there is a whole class of websites that deserves special mention: Facebook apps. With the release of the Predator Alert Tool for Facebook server code last week, every website that integrates with Facebook is now ripe for integration with a Predator Alert Tool viewer. These “viewers” are programs that run in your device, scanning the people you encounter on the website and checking their profile against the Predator Alert Tool for Facebook’s database.

There are an enormous number of Facebook apps. Since I don’t have any budget for this work, and I work and live on a donations-only basis, there’s no way I can ever integrate Predator Alert Tool with all of them on my own. So, in this post, I’ll give you a brief tutorial on how you can write your own Predator Alert Tool viewer.

Some technical knowledge is assumed. Even if the technology seems above you while you read it, I hope it inspires you to stand up for your rights and demand better from the corporations that want to sell you for a profit to one another.

Predator Alert Tool for Facebook: Legal and Technological Overview

Every Predator Alert Tool I wrote, including the Predator Alert Tool for Facebook, is released to the public domain. This means you can make any number of copies of the code and do anything you want to with them. You are not even legally required to attribute original authorship to me (although, considering that I live on a donations-only basis, it’d be nice if you did).

If you’d like more information about some legalities of using the Predator Alert Tool for Facebook, please read, “What’s the legal deal with defamation and the Predator Alert Tool for Facebook?

Technologically speaking, Predator Alert Tool for Facebook is very simple. As its README file states:

There are two major parts to the Predator Alert Tool for Facebook software. The first is a server-side Facebook app where Facebook users can share about an experience they had regarding any other Facebook user’s behavior. The second is a browser-side tool that automatically searches for any stories about the people who show up on your Facebook timeline and newsfeed as you browse Facebook.com. If it finds any, the tool “red-boxes” links to those people’s profiles and offers a link to the stories it found.

Both of these pieces work independently. You can use the Predator Alert Tool for Facebook app inside Facebook without ever installing the browser-side tool on your device, and you can use the browser-side tool without ever allowing the Facebook app access to your Facebook account. (However, in the latter case, the tool will only be able to find stories shared with everyone, not friends-only or other visibility-restricted stories.)

I also call the “browser-side” tool a “viewer,” as in “the PAT-Facebook viewer,” or “the PAT-Lulu viewer.” When you install the viewer, it’s as if you’ve put “Predator Alert X-ray vision glasses” on your browser. Whenever the viewer sees a picture of or finds a link to a person’s Facebook profile, it asks the Predator Alert Tool for Facebook whether you can read any statements about that person. If the server responds affirmatively, the viewer highlights that person’s profile on the page you’re browsing, usually by “red-boxing” their pic.

The quickest and simplest way to make a Predator Alert Tool viewer is by writing a small computer program called a userscript. Therefore, another, more technical, name for the browser-side tool is a “userscript.” Since all Facebook apps share key components, a viewer for Facebook apps is the easiest to write.

Anatomy of a Predator Alert Tool for Facebook viewer

For this example, let’s examine the simplest PAT-Facebook viewer, the Predator Alert Tool for Bang With Friends. I just wrote this one this afternoon, and it’s the very first version released to the public (version 0.1). The full code is available in numerous places, but it shows up prettiest on GitHub. The whole thing is a mere 156 lines of JavaScript. Let’s go through it one chunk at a time.

Starting to write a Predator Alert Tool viewer userscript

At the very top of the Predator Alert Tool for Bang With Friends userscript is a code comment:

	
/**
 *
 * This is a Greasemonkey script and must be run using a Greasemonkey-compatible browser.
 *
 * @author maymay <[email protected]>
 */

This doesn’t actually run any code (’cause it’s a comment). It’s just a note for people who look at the source code itself, describing what it is and who wrote it. I’ve embedded my name and email address, so that people who want to can email me questions. That being said, nowadays I receive a lot more inquiries than I can reasonably respond to. So if you have a question about any of the Predator Alert Tools, I encourage you to write up a formal bug report.

Next in the userscript, there’s what’s known as a Greasemonkey metadata block. For the Predator Alert Tool for Bang With Friends, it looks like this:

// ==UserScript==
// @name           Predator Alert Tool for Bang With Friends (PAT-BWF)
// @namespace      com.maybemaimed.predator-alert-tool.bwf
// @updateURL      https://userscripts.org/scripts/source/180028.user.js
// @description    PAT-BWF displays Predator Alerts linked to the profiles of the friends that appear on your Bang With Friends dashboard. Powered by Predator Alert Tool for Facebook.
// @include        https://www.bangwithfriends.com/*
// @include        http://www.bangwithfriends.com/*
// @include        https://predator-alert-tool.herokuapp.com/*
// @version        0.1
// @grant          GM_log
// @grant          GM_xmlhttpRequest
// @grant          GM_addStyle
// @grant          GM_setValue
// @grant          GM_getValue
// ==/UserScript==

The really important lines here are the ones that start with @include and @grant. These lines tell the userscript on which pages to activate, and what it’s allowed to do on those pages.

We use three @include lines. Two are for the BangWithFriends server (one with HTTPS and one without it, since Bang With Friends doesn’t require SSL). The last one is for the Predator Alert Tool for Facebook’s server (using HTTPS, because PAT-Facebook forces SSL-encrypted connections on all requests).

The @grant lines confer the userscript access to limited features of the browser. We can send a message to the error log (using GM_log), make a cross-site request (using GM_xmlhttpRequest), apply Cascading Style Sheets (CSS) to the page (using GM_addStyle), and set and get values in the browser’s local data store (using GM_setValue and GM_getValue, respectively). We have to define these @grant lines before we actually try to use the feature.

The rest of the lines starting with an @ (at) sign are pretty self-explanatory; they display the name of the script, its description, and version number to the user. If you were making your own PAT-Facebook viewer for another site, say “DatingWebSiteX.com”, you would also need to change the @namespace line, but it doesn’t actually matter what you change it to. Something memorable like predator-alert-tool.dating-website-x.com would work.

After the metadata block comes the start of the actual code. Here’s where you need to understand a bit of JavaScript to keep up:

PAT_BWF = {};
PAT_BWF.CONFIG = {
    'debug': false, // switch to true to debug.
    'api_url': 'https://predator-alert-tool.herokuapp.com/api.php?fbid='
};

Experienced developers will recognize this beginning as a simple “JavaScript module pattern,” but don’t worry if you don’t know what that means. All you need to know is that the first line here defines a “thing”, (yes, literally, a “thing”) called PAT_BWF (short for Predator Alert Tool for Bang With Friends). Programmers call these kinds of things “objects,” and would refer to the PAT_BWF thing as the “PAT_BWF object.” The rest of the lines define a thing (“object”) inside the PAT_BWF thing called CONFIG. The CONFIG object has two more of its own things, which I’ve named debug and api_url, respectively.

As the name implies, the PAT_BWF.CONFIG object holds configuration settings. The “debug” setting can be either false or, as the code comment next to it makes clear, true, which leaves debug logging off or turns it on. The api_url is the address for the API endpoint of the Predator Alert Tool for Facebook server. This viewer’s api_url is set to https://predator-alert-tool.herokuapp.com/api.php?fbid=, which is the PAT-Facebook server I’m operating. However, if you wanted the viewer you wrote to talk to your own server and not to mine (maybe because you’d rather not share information with servers I run, but still want to use the PAT-Facebook technology with your local community), then you can change this URL to whatever the address of your server is.

Setting up your own PAT-Facebook server is beyond the scope of this short post, but PAT-Facebook developer documentation is available to you.

Moving on, we next define a simple debug logging function (it prints a message if PAT_BWF.CONFIG.debug is true) and the CSS we’re going to add to the page:

// Utility debugging function.
PAT_BWF.log = function (msg) {
    if (!PAT_BWF.CONFIG.debug) { return; }
    console.log('PAT-BWF: ' + msg);
};

// Initializations.
GM_addStyle('\
.pat-reports-link { margin-top: 2px; }\
');

Note that the CSS applied via GM_addStyle() is a single string, so we escape each line ending with a \ (backslash). Of course, if you’re writing a new viewer, you may not know what CSS to put in here yet. That’s fine, because you can always come back and fill this in later.

Initializing a Predator Alert Tool viewer

Next, we set up the init() function in our JavaScript module. Thanks to the way Facebook apps work, this might look complicated, but it’s actually mostly boilerplate code you can copy and paste to work with any Facebook app, often with very minor changes:

PAT_BWF.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.
    if (unsafeWindow.location.host == PAT_BWF.parseApiUrl().host) {
        GM_setValue('PAT_BWF_cookies', document.cookie);
    }
    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; }
                    // Process all the rest.
                    PAT_BWF.main(mutation.addedNodes[i]);
                }
            }
        });
    });
    var el = document.body;
    observer.observe(el, {
        'childList': true,
        'subtree': true
    });
    PAT_BWF.main(document);
};
window.addEventListener('DOMContentLoaded', PAT_BWF.init);

The first thing we do in the init() function is check what page we’re on. The function that does this is parseApiUrl(), which is defined later down in the module and that we haven’t encountered yet. For now, suffice it to say that this if statement merely checks whether we’re loading a page on the PAT-Facebook server or not, and if we are, it stores the cookies we’re given, which we save as PAT_BWF_cookies in order to retrieve later.

Then, we create a MutationObserver object. This is a performant way to subscribe to changes on the page. We’re using it to register a callback function that will process every new element that appears on the page. All the callback function does is check whether the new node is a text node (i.e., plain text, not an HTML element). If the node that’s been added is not a text node, we pass it along to the main() function. That’s the heart of our “X-ray glasses.”

With the MutationObserver watching for new nodes, we kick the whole thing off by passing document to the main() function. This begins processing what we’ve already loaded.

The main() Predator Alert Tool viewer

The heart of our “X-ray glasses” is actually pretty simple. All it has to do is scrape out the Facebook user ID, which is usually embedded in people’s profile pictures, and pass that along for some additional processing:

// main() is given a start node (HTML tree) and processes appropriately.
PAT_BWF.main = function (node) {
    PAT_BWF.log('Starting main() on page ' + unsafeWindow.location.toString());
    var img_el = node.querySelector('img[src^="https://graph.facebook.com/"]');
    if (img_el) {
        var fbid = img_el.getAttribute('src').match(/\d+/)[0];
        PAT_BWF.log('Found Facebook ID ' + fbid + '.');
        PAT_BWF.injectReportLink(node, fbid);
        PAT_BWF.maybeFlagEntity(fbid, node);
    }
};

On Bang With Friends, like most Facebook apps, pictures of your friends appear as you scroll or otherwise interact with the app. When Bang With Friends inserts an element with your friend’s picture in it, our MutationObserver (from earlier) notices the new node, checks it out, then passes it to the main() function. Since the node has a picture of your friend, all we need to do is find that picture (which we accomplish with a call to querySelector()), and then scrape the user ID embedded in it from the HTML.

Since Facebook apps almost all use Facebook user IDs in the profile picture, this is super easy. We just getAttribute('src') and match() it to the first numeric string (/\d+/). At this point, fbid contains the Facebook user ID within the the currently processed node.

Now we have everything we need from the app to do whatever processing we want. For Predator Alert Tool viewers, we pass the node we’re procesing and the user ID we found to the injectReportLink() button, which is what produces the in-app link to the Predator Alert Tool for Facebook. If you’ve done any old-school Dynamic HTML, it should look really familiar:

PAT_BWF.injectReportLink = function (el, fbid) {
    var a = document.createElement('a');
    a.setAttribute('href',
        PAT_BWF.parseApiUrl().protocol + '//' + PAT_BWF.parseApiUrl().host
        + '/reports.php?action=new&reportee_id=' + fbid
    );
    a.setAttribute('class', 'pat-reports-link');
    a.innerHTML = 'Add new Predator Alert';
    el.querySelector('.action').appendChild(a);
};

The only part of this function you’ll likely need to change if you’re writing a Predator Alert Tool viewer for another site is the querySelector() parameter. On Bang With Friends, we want to insert the “Add new Predator Alert” link right under the “Down to Bang” button, so we appendChild() to the HTML element with the class action. On your app, you might need to attach the link to a different spot.

The last thing that the main() function did was “maybeFlagEntity()“. Just as with injectReportLink(), maybeFlagEntity() accepts the current node and Facebook user ID. This is where the viewer connects to the server to ask if this particular Facebook user ID has any Predator Alert statements associated with it:

/**
 * 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_BWF.maybeFlagEntity = function (fbid, el) {
    if (!fbid) { PAT_BWF.log('Invalid ID passed to maybeFlagEntity().'); return false; }
    PAT_BWF.log('About to query for reports on ID ' + fbid.toString());
    GM_xmlhttpRequest({
        'method': 'GET',
        'url': PAT_BWF.CONFIG.api_url + fbid.toString(),
        'headers': {
            'Cookie': GM_getValue('PAT_BWF_cookies')
        },
        'onload': function (response) {
            try {
                resp = JSON.parse(response.responseText);
                PAT_BWF.log('Parsed response from PAT-FB for ' + fbid.toString() + ': ' + response.responseText);
            } catch (e) {
                PAT_BWF.log('Caught error from reply: ' + response.responseText);
                return;
            }
            if (resp.reports) {
                el.style.border = PAT_BWF.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());

                // Make PAT data visible to Bang With Friends interface.
                // and insert it into the card HTML.
                var a = document.createElement('a');
                a.setAttribute('href',
                    PAT_BWF.parseApiUrl().protocol + '//' + PAT_BWF.parseApiUrl().host
                    + '/reports.php?action=lookup&reportee_id='
                    + el.getAttribute('data-pat-reportee-id')
                );
                a.setAttribute('class', 'pat-reports-link');
                a.innerHTML = 'View ' + resp.reports.toString() + ' Predator Alerts';
                el.querySelector('.action').appendChild(a);
            }
        }
    });
};

It may look a little intimidating, but this function is almost entirely a simple wrapper around GM_xmlhttpRequest(). All it does is load the URL at the api_url, sends along the cookies we saved from an earlier visit to the api_url, and tacks the Facebook user ID we scraped from the page onto the end of the querystring. It then waits for a response. The Predator Alert Tool for Facebook server will respond with JSON-formatted message indicating the number of Predator Alerts you (the user) currently have access to read that are linked to the ID number of the profile you’re looking at.

If there’s something to see, the onload callback creates a link very much like the other one from the injectReportLink() function. Also like the injectReportLink() function, the only part of this function you’re likely going to need to change is near the bottom where it calls querySelector('.action'). Again, with Bang With Friends, it’s the very end of the action element (the button that says “Down to Bang!”) where we want to attach the new link. This might be different on your own app.

That’s all. The remainder of the script is the setBorderWidthByReportCount() function, which is a cosmetic flourish that makes the “red box” thicker if there are more Predator Alerts associated with a specific user ID, and thinner if not:

PAT_BWF.setBorderWidthByReportCount = function (n) {
    if (n < 2) {
        n = 1;
    } else if (n > 4) {
        n = 5;
    }
    return n * 2; // Double the width so it's more visible in Bang With Friends's interface.
};

Voila. That’s the entire viewer.

Patches and contributions welcome

Obviously, it’s not perfect. It’s doesn’t do any caching, and it initiates an HTTP request for every ID it wants to lookup. These things could be improved. I’ve opened tickets for them on the GitHub project and if you’d like to help move Predator Alert Tool forward, and you code, please consider sending me a patch.

Obviously, it would also be great to have more Predator Alert Tool for Facebook viewers. To reiterate, now that the PAT-Facebook server is up and running, every single Facebook app could plug into it and provide Predator Alerts to its users. Anything that connects to Facebook, from Zynga games to Foursquare, could potentially be integrated with the Predator Alert Tool for Facebook.

It’s just one more step towards our vision of building sexual violence prevention tools into every social network on the Internet.

If you don’t code, but you’d still like to help move Predator Alert Tool forward, there are so many other things you can do. Please read our How to help page for some starting suggestions.

  1. Yes, Facebook can accurately be described as a “dating website.” Not only are there speculations that Mark Zuckerberg initially created Facebook so he would have an easier time picking up girls, everything Facebook does facilitates online dating: finding, meeting, and interacting with new people, comparing common interests or “Likes,” and posting pretty photos of yourself. []