Gates of Survival - Active Players scraper

Shows active player data in table format. Based on Opal's Action Scraper: https://greasyfork.org/en/scripts/31091-action-scraper

// ==UserScript==
// @name        Gates of Survival - Active Players scraper
// @namespace   https://www.gatesofsurvival.com
// @version     0.11
// @author      dang
// @homepage    https://github.com/dang-nabbit/gos-active-players-scraper
// @description Shows active player data in table format. Based on Opal's Action Scraper: https://greasyfork.org/en/scripts/31091-action-scraper
// @match       https://www.gatesofsurvival.com/game/online.php
// @icon        https://www.google.com/s2/favicons?domain=https://www.gatesofsurvival.com/
// @grant       none
// ==/UserScript==

// Settings
var summaryShortSkillNames = true; // Turn off if you want full skill names on summary header
var ldLNOnTop = true; // Turn off if you don't want LD/LN to be first clans listed
var italicSkills = ['Agility']; // Make listed skills italic in player list
var boldSkills = ['Not skilling']; // Make listed skills bold in player list
var underlineSkills = ['Fishing']; // Make listed skills underlined in player list
// Settings end

var profileURL = 'https://www.gatesofsurvival.com/game/user2.php?user=';

var errors = [];
var clans = [];
var noClan = {
    initials: 'None',
    players: [],
    skills: []
};
var hardcore = {
    initials: 'HC',
    players: [],
    skills: []
};
var ironman = {
    initials: 'IM',
    players: [],
    skills: []
};
var totals = {
    initials: 'Total',
    players: [],
    skills: []
};
var ld;
var ln;

var playerTypes = [
    'Hardcore Ironman',
    'Ironman'
];
var numPlayerTypes = playerTypes.length;

var skills = [
    {
        name: 'Agility',
        short: 'Agil'
    }, {
        name: 'Arcane',
        short: 'Arcn'
    }, {
        name: 'Archery',
        short: 'Arch'
    }, {
        name: 'Baking',
        short: 'Bakn'
    }, {
        name: 'Botany',
        short: 'Bota'
    }, {
        name: 'Cooking',
        short: 'Cook'
    }, {
        name: 'Crafting',
        short: 'Crft'
    }, {
        name: 'Divination',
        short: 'Divn'
    }, {
        name: 'Exploration',
        short: 'Expl'
    }, {
        name: 'Fighting',
        short: 'Fght'
    }, {
        name: 'Firemaking',
        short: 'Fire'
    }, {
        name: 'Fishing',
        short: 'Fish'
    }, {
        name: 'Fletching',
        short: 'Flet'
    }, {
        name: 'Forestry',
        short: 'Frst'
    }, {
        name: 'Forging',
        short: 'Forg'
    }, {
        name: 'Gathering',
        short: 'Gthr'
    }, {
        name: 'Hunting',
        short: 'Hunt'
    }, {
        name: 'Jewelcrafting',
        short: 'Jwel'
    }, {
        name: 'Looting',
        short: 'Loot'
    }, {
        name: 'Mining',
        short: 'Mini'
    }, {
        name: 'Prayer',
        short: 'Pray'
    }, {
        name: 'Runebinding',
        short: 'Rnbd'
    }, {
        name: 'Skinning',
        short: 'Skin'
    }, {
        name: 'Slaying',
        short: 'Slay'
    }, {
        name: 'Smelting',
        short: 'Smlt'
    }, {
        name: 'Spellcraft',
        short: 'Spll'
    }, {
        name: 'Summoning',
        short: 'Smmn'
    }, {
        name: 'Thieving',
        short: 'Thvn'
    }, {
        name: 'Transmutation',
        short: 'Tran'
    }, {
        name: 'Woodworking',
        short: 'Wood'
    }, {
        name: 'Not skilling',
        short: 'None'
    }
];
var numSkills = skills.length;

function addGlobalStyle(css, head) {
    var style;
    head = head || document.getElementsByTagName('head')[0];
    style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
}

function formatPercentage(number) {
    return (number * 100).toFixed(1) + '%';
}

function addClanSkillCount(clan) {
    var skill;

    for (var i = 0; i < numSkills; i++) {
        skill = skills[i];

        clan.skills.push({
            name: skill.name,
            count: 0
        });
    }
}

function scrapePlayer(tdInnerHTML) {
    var mainRegEx = /<img src="images\/crowns\/(.+?)\.png".*?>.*?<input value="(\d+)".*?><input.*?>(.+?)<\/.*?<\/form>.*?\(Last Active: (.+?)\)/;
    var activityRegEx = /<b>Currently<\/b>: (.+?)<br>/;
    var clanRegex = /<b>Clan<\/b>: \[(.+?)\]/;

    var result = mainRegEx.exec(tdInnerHTML);

    var player = {};
    var crown;
    var activityResult;
    var activity;
    var skill;
    var type;
    var clanResult;
    var i;

    if (result === null) {
        return {
            error: 'Player data not recognized',
            content: tdInnerHTML
        };
    } else {
        player = {
            id: result[2],
            name: result[3],
            lastActive: result[4]
        };

        crown = result[1];
        if (crown.indexOf('crown_') > -1) {
            player.rank =  crown.replace('crown_', '');
        } else if (crown === 'sponsor') {
            player.rank = 'Sponsor';
        } else {
            player.rank = 'Unidentified';
        }

        activityResult = activityRegEx.exec(tdInnerHTML);

        if (activityResult !== null) {
            activity = activityResult[1];

            for (i = 0; i < numSkills; i++) {
                skillName = skills[i].name;
                if (activity.indexOf(skillName) > -1) {
                    player.skill = skillName;

                    if (skillName === 'Fighting') {
                        player.mob = activity.replace('Fighting a ', '');
                    }

                    break;
                }
            }
        }
        if (!player.skill) {
            player.skill = 'Not skilling';
            player.activity = activity || 'None';
        }


        for (i = 0; i < numPlayerTypes; i++) {
            type = playerTypes[i];
            if (tdInnerHTML.indexOf(type) > -1) {
                player.type = type;
                break;
            }
        }

        clanResult = clanRegex.exec(tdInnerHTML);
        if (clanResult !== null) {
            player.clan = clanResult[1];
        }
    }

    return player;
}

function addPlayerToList(td) {
    var player = scrapePlayer(td.innerHTML);
    var playerClan = player.clan;
    var playerType = player.type;
    var clan = {};
    var skill;
    var skillObj;

    if (player.error) {
        errors.push(player);
    } else if (playerClan) {
        clan = clans.find(function(clan) {return clan.initials === playerClan;});
        if (!clan) {
            clan = {
                initials: playerClan,
                players: [],
                skills: []
            };

            addClanSkillCount(clan);

            if (['LD', 'LN'].indexOf(playerClan) > -1) {
                if (playerClan === 'LD') {
                    ld = clan;
                } else if (playerClan === 'LN') {
                    ln = clan;
                }

                if (ldLNOnTop) {
                    clans.splice(0, 0, clan);
                }
            } else {
                clans.push(clan);
            }
        }
    } else if (playerType === 'Hardcore Ironman') {
        clan = hardcore;
    } else if (playerType === 'Ironman') {
        clan = ironman;
    } else {
        clan = noClan;
    }

    skillObj = clan.skills.find(function(skill) {return skill.name === player.skill;});
    skillObj.count++;
    skillObj = totals.skills.find(function(skill) {return skill.name === player.skill;});
    skillObj.count++;

    clan.players.push(player);
    totals.players.push(player);
}

function scrapeAll() {
    document.querySelectorAll('#third-container5 td').forEach(addPlayerToList);
}

function getErrorsTable() {
    var numErrors = errors.length;
    var div = document.createElement('div');

    if (numErrors > 0) {
        var title = document.createElement('b');
        title.appendChild(document.createTextNode('Errors'));
        div.appendChild(title);

        var table = document.createElement('table');
        div.appendChild(table);

        div.appendChild(document.createElement('hr'));

        var row = table.insertRow();
        var errorCell = row.insertCell();
        errorCell.outerHTML = '<th>Error</th>';
        var errorContentCell = row.insertCell();
        errorContentCell.outerHTML = '<th>Content</th>';

        var error;
        for (var i = 0; i < numErrors; i++) {
            error = errors[i];

            row = table.insertRow();

            errorCell = row.insertCell();
            errorCell.appendChild(document.createTextNode(error.error || ''));

            errorContentCell = row.insertCell();
            errorContentCell.appendChild(document.createTextNode(error.content || ''));
        }
    }

    return div;
}

function getPlayerTable(clan) {
    var players = clan.players;
    var numPlayers = players.length;
    var div = document.createElement('div');

    if (numPlayers > 0) {
        var title = document.createElement('b');
        title.appendChild(document.createTextNode(clan.initials));
        div.appendChild(title);

        var table = document.createElement('table');
        div.appendChild(table);

        div.appendChild(document.createElement('hr'));

        var row = table.insertRow();
        var nameCell = row.insertCell();
        nameCell.outerHTML = '<th>Name</th>';
        var idCell = row.insertCell();
        idCell.outerHTML = '<th>ID</th>';

        var lastActiveCell = row.insertCell();
        lastActiveCell.outerHTML = '<th>Last active</th>';
        var rankCell = row.insertCell();
        rankCell.outerHTML = '<th>Rank</th>';
        var skillCell = row.insertCell();
        skillCell.outerHTML = '<th>Skill</th>';
        var mobCell = row.insertCell();
        mobCell.outerHTML = '<th>Enemy</th>';
        var activityCell = row.insertCell();
        activityCell.outerHTML = '<th>Activity</th>';

        var player;
        var playerAnchor;
        var playerSkill;
        for (var i = 0; i < numPlayers; i++) {
            player = players[i];

            row = table.insertRow();

            nameCell = row.insertCell();
            playerAnchor = document.createElement('a');
            playerAnchor.href = profileURL + (player.name || '');
            playerAnchor.target = '_blank';
            playerAnchor.appendChild(document.createTextNode(player.name || ''));
            nameCell.appendChild(playerAnchor);

            idCell = row.insertCell();
            idCell.classList.add('number-cell');
            idCell.appendChild(document.createTextNode(player.id || ''));

            lastActiveCell = row.insertCell();
            lastActiveCell.appendChild(document.createTextNode(player.lastActive || ''));

            rankCell = row.insertCell();
            rankCell.classList.add('number-cell');
            rankCell.appendChild(document.createTextNode(player.rank || ''));

            playerSkill = player.skill;
            skillCell = row.insertCell();
            skillCell.appendChild(document.createTextNode(playerSkill || ''));
            if (italicSkills.indexOf(playerSkill) > -1) {
                skillCell.classList.add('italic');
            }
            if (boldSkills.indexOf(playerSkill) > -1) {
                skillCell.classList.add('bold');
            }
            if (underlineSkills.indexOf(playerSkill) > -1) {
                skillCell.classList.add('underline');
            }

            mobCell = row.insertCell();
            mobCell.appendChild(document.createTextNode(player.mob || ''));

            activityCell = row.insertCell();
            activityCell.appendChild(document.createTextNode(player.activity || ''));
        }
    }

    return div;
}

function getSummary() {
    // In summary, show clans and add other categories at end
    var summaryClans = clans.concat([noClan, ironman, hardcore, totals]);
    var numSummaryClans = summaryClans.length;
    var i;
    var j;

    var div = document.createElement('div');

    // Title
    var title = document.createElement('b');
    title.appendChild(document.createTextNode('Summary'));
    div.appendChild(title);

    var table = document.createElement('table');
    div.appendChild(table);

    // Header row
    var row = table.insertRow();

    // Clan name header
    var clanCell = row.insertCell();
    clanCell.outerHTML = '<th>Clan</th>';

    // Skill headers
    var skillCell;
    var skillHeader = (summaryShortSkillNames) ? 'short' : 'name'
    for (i = 0; i < numSkills; i++) {
        skillCell = row.insertCell();
        skillCell.outerHTML = '<th>' + skills[i][skillHeader] + '</th>';
    }

    // Total column header
    var clanTotalCell = row.insertCell();
    clanTotalCell.outerHTML = '<th>Total</th>';

    // Total % column header
    var clanTotalPercCell = row.insertCell();
    clanTotalPercCell.outerHTML = '<th>%</th>';

    // Lucky % row
    row = table.insertRow();

    // Lucky % "clan name"
    clanCell = row.insertCell();
    clanCell.appendChild(document.createTextNode('L%'));

    // Lucky % skill numbers
    var totalPlayers = ld.players.length + ln.players.length;
    for (i = 0; i < numSkills; i++) {
        skillCell = row.insertCell();
        skillCell.classList.add('number-cell');
        skillCell.appendChild(document.createTextNode(formatPercentage((ld.skills[i].count + ln.skills[i].count)/totalPlayers)));
    }

    // Empty total and total % cells for Lucky %
    clanTotalCell = row.insertCell();
    clanTotalCell.classList.add('number-cell');

    clanTotalPercCell = row.insertCell();
    clanTotalPercCell.classList.add('number-cell');

    var clan;
    totalPlayers = totals.players.length;
    for (i = 0; i < numSummaryClans; i++) {
        clan = summaryClans[i];

        // Clan row
        row = table.insertRow();

        // Clan name cell
        clanCell = row.insertCell();
        clanCell.appendChild(document.createTextNode(clan.initials));

        // Clan skill numbers
        for (j = 0; j < numSkills; j++) {
            skillCell = row.insertCell();
            skillCell.classList.add('number-cell');
            skillCell.appendChild(document.createTextNode(clan.skills[j].count));
        }

        // Clan total count
        clanTotalCell = row.insertCell();
        clanTotalCell.classList.add('number-cell');
        clanTotalCell.appendChild(document.createTextNode(clan.players.length));

        // Clan total %
        clanTotalPercCell = row.insertCell();
        clanTotalPercCell.classList.add('number-cell');
        clanTotalPercCell.appendChild(document.createTextNode(formatPercentage(clan.players.length/totalPlayers)));
    }

    // % row
    row = table.insertRow();

    // % name cell
    clanCell = row.insertCell();
    clanCell.appendChild(document.createTextNode('%'));

    // % skill numbers
    for (j = 0; j < numSkills; j++) {
        skillCell = row.insertCell();
        skillCell.classList.add('number-cell');
        skillCell.appendChild(document.createTextNode(formatPercentage(totals.skills[j].count/totalPlayers)));
    }

    // Empty total and total % cells for Lucky %
    clanTotalCell = row.insertCell();
    clanTotalCell.classList.add('number-cell');

    clanTotalPercCell = row.insertCell();
    clanTotalPercCell.classList.add('number-cell');

    return div;
}

function addReportStyles(doc) {
    var css = '.italic {\n' +
        '    font-style: italic;\n' +
        '}\n\n' +
        '.bold {\n' +
        '    font-weight: bold;\n' +
        '}\n\n' +
        '.underline {\n' +
        '    text-decoration: underline;\n' +
        '}\n\n' +
        '.number-cell {\n' +
        '    text-align: right;\n' +
        '    padding-right: 3px;\n' +
        '}';
    addGlobalStyle(css, doc.head);
}

function printDataTables() {
    addClanSkillCount(noClan);
    addClanSkillCount(ironman);
    addClanSkillCount(hardcore);
    addClanSkillCount(totals);
    scrapeAll();

    var reportDoc = window.open().document;
    addReportStyles(reportDoc);

    var reportBody = reportDoc.body;
    reportBody.appendChild(getErrorsTable());

    var numClans = clans.length;
    for (var i = 0; i < numClans; i++) {
        reportBody.appendChild(getPlayerTable(clans[i]));
    }

    reportBody.appendChild(getPlayerTable(noClan));
    reportBody.appendChild(getPlayerTable(ironman));
    reportBody.appendChild(getPlayerTable(hardcore));

    reportBody.appendChild(getSummary());
}

printDataTables();