<commit_before>/**
 * This module provides an API for interaction with leaguegaming.com.
 */
'use strict';

const crypto = require('crypto');
const moment = require('moment-timezone');
const url = require('url');

const API = require('./api');

const config = require('../../config.json');

const timezone = 'America/New_York';

const helper = {
    news: {
        cleanMessage: (message = '') => {
            return message
                .replace(/]+)?\/team(\d+)\.png(?:[^>]+)?>]+)?>.*?<\/span>/g, (a, b) => `::team=${b}::`)
                .replace(/<\/span><span /g, ' <span ')
                .replace(/<[^>]+>/g, '')
                .replace(/[ ]+/g, ' ');
        },
        generateHash: (item = []) => {
            const buf = [
                helper.news.cleanMessage(item[4]),
                helper.news.getTeams(item[4]),
                moment.tz((item[5] || '').trim(), 'YYYY-MM-DD hh:mma', timezone).valueOf()
            ];

            const shasum = crypto.createHash('sha1');
            shasum.update(JSON.stringify(buf));
            return shasum.digest('hex');
        },
        getTeams: (message = '') => {
            return (message.match(/]+)?\/team(\d+)\.png(?:[^>]+)?>]+)?>(.*?)<\/span>/g) || []).map(t => t.replace(/.*?team(\d+).*/, '$1'));
        }
    }
};

class LeagueGaming_API extends API {
    constructor(...args) {
        super(...args);

        this.buildUrl = this.buildUrl.bind(this);
        this.getGames = this.getGames.bind(this);
        this.getInfo = this.getInfo.bind(this);
        this.getNews = this.getNews.bind(this);
        this.getTeams = this.getTeams.bind(this);
    }

    buildUrl({
        file = 'index.php',
        path = 'leaguegaming/league',
        ...params
    } = {}) {
        return `https://www.leaguegaming.com/forums/${file}?${path || ''}&${this.buildQueryString(params)}`.replace(/\?&/, '&');
    }

    getDailyStars({forumId, date = moment.tz(timezone).subtract(1, 'days')}) {
        if (!forumId) {
            throw new TypeError(`forumId is not defined`);
        }

        return new Promise((resolve, reject) => {
            this.get({
                path: 'search/1/',
                q: moment.tz(date, timezone).format('[Daily 3 Stars For] dddd MMMM Do, YYYY'),
                o: 'date',
                'c[title_only]': 1,
                'c[node]': forumId
            }).then(html => {
                const reThread = /
]+)?>]+)?href="(.*?)"(?:[^>]+)?>(.*?)<\/a><\/h3>/i;
                let data;

                if (!(data = reThread.exec(html))) {
                    return resolve({});
                }

                this.get(`https://www.leaguegaming.com/forums/${data[1]}`).then(html => {
                    const reGroup = /
]+)?d3_title(?:[^>]+)?>(.*?)<\/div>/i;
                    const reStar = /
]+)?>]+)?>((?:]+)?\/star\.gif(?:[^>]+)?>)+|\d+\.)<\/td>(?:]+)?t_threestars(?:[^>]+)?>
]+)?>]+)?\/team(\d+)\.svg(?:[^>]+)?>]+)?><\/div><\/td>]+)?>(.*?)
]+)?>\((.*?)\)<\/span><\/td>|]+)?>]+)?\/team(\d+)\.png(?:[^>]+)?> (.*?) \((.*?)\)<\/td>)]+)?>]+)?>.*?<\/a><\/td>((?:(.*?)<\/td>)+)<\/tr>/i;
                    const reGroupOrStar = new RegExp(`(?:${reGroup.source}|${reStar.source})`, 'ig');
                    let stars = {};
                    let group;
                    let line;

                    while (line = reGroupOrStar.exec(html)) {
                        let data;

                        if (data = reGroup.exec(line)) {
                            group = data[1].toLowerCase();
                        }

                        if (data = reStar.exec(line)) {
                            const metadata = data[8].match(/(.*?)<\/td>/g).map(v => v.replace(/<(?:[^>]+)?>/g, ''));

                            stars[group] = [
                                ...(stars[group] || []),
                                {
                                    rank: ((data[1].match(/star\.gif/g) || []).length) || parseInt(data[1]),
                                    team: parseInt(data[2]) || parseInt(data[5]),
                                    name: data[3] || data[6],
                                    position: data[4] || data[7],
                                    metadata: (group !== 'goalies')
                                        ? {
                                            'Points': parseInt(metadata[0]),
                                            'Goals': parseInt(metadata[1]),
                                            'Assists': parseInt(metadata[2]),
                                            '+/-': (metadata[3] >= 0 ? '+' : '') + parseInt(metadata[3])
                                        } : {
                                            'SV%': Number((parseFloat(metadata[0]) / 100) + 0.0005).toFixed(3),
                                            'GAA': Number(parseFloat(metadata[1]) + 0.005).toFixed(2),
                                            'Saves': parseInt(metadata[2]),
                                            'Shots': parseInt(metadata[3])
                                        }
                                }
                            ];
                        }
                    }

                    resolve(stars);
                });
            }).catch(reject);
        });
    }

    getGames({leagueId, seasonId}) {
        if (!leagueId) {
            throw new TypeError(`leagueId is not defined`);
        }

        if (!seasonId) {
            throw new TypeError(`seasonId is not defined`);
        }

        return new Promise((resolve, reject) => {
            this.get({
                action: 'league',
                page: 'league_schedule_all',
                leagueid: leagueId,
                seasonid: seasonId
            }).then(html => {
                const reDate = /
]+)?sh4(?:[^>]+)?>(.*?)<\/h4>/i;
                const reGame = /]+)?sweekid(?:[^>]+)?>Week (\d+)<\/span>(?:]+)?sgamenumber(?:[^>]+)?>Game# (\d+)<\/span>)]+)?\/team(\d+)\.png(?:[^>]+)?>]+)?&gameid=(\d+)(?:[^>]+)?>]+)?steamname(?:[^>]+)?>(.*?)<\/span>]+)?sscore(?:[^>]+)?>(vs|(\d+)\D+(\d+))<\/span>]+)?steamname(?:[^>]+)?>(.*?)<\/span><\/a>]+)?\/team(\d+)\.png(?:[^>]+)?>/i;
                const reDateOrGame = new RegExp(`(?:${reDate.source}|${reGame.source})`, 'ig');
                const games = [];
                let date;
                let line;

                while (line = reDateOrGame.exec(html)) {
                    let data;

                    if (data = reDate.exec(line)) {
                        date = moment.tz(data[1], 'MMMM DD[th]', timezone);

                        if (moment.tz(timezone).month() <= 6 && date.month() >= 7) {
                            date.subtract(1, 'years');
                        } else if (moment.tz(timezone).month() >= 7 && date.month() <= 6) {
                            date.add(1, 'years');
                        }
                    }

                    if (data = reGame.exec(line)) {
                        games.push({
                            id: parseInt(data[4]),
                            date: date.toISOString(),
                            visitor: {
                                id: parseInt(data[10]),
                                score: !Number.isNaN(Number(data[8])) ? parseInt(data[8]) : null
                            },
                            home: {
                                id: parseInt(data[3]),
                                score: !Number.isNaN(Number(data[7])) ? parseInt(data[7]) : null
                            }
                        });
                    }
                }

                resolve(games);
            }).catch(reject);
        });
    }

    getInfo({leagueId}) {
        if (!leagueId) {
            throw new TypeError(`leagueId is not defined`);
        }

        return new Promise((resolve, reject) => {
            this.get({
                action: 'league',
                page: 'standings',
                leagueid: leagueId,
                seasonid: 1
            }).then(html => {
                const reInfo = new RegExp(`<li(?:[^>]+)? custom-tab-${leagueId} (?:[^>]+)?><a(?:[^>]+)?/league\\.(\\d+)/(?:[^>]+)?>.*?<span(?:[^>]+)?>(.*?)</span>.*?</a>`, 'i');
                const reSeason = new RegExp(`<a(?:[^>]+)?leagueid=${leagueId}&(?:amp;)?seasonid=(\\d+)(?:[^>]+)?>Standings</a>`, 'i');
                const info = {codename: undefined, forumId: undefined, id: parseInt(leagueId), name: undefined, seasonId: undefined};
                let data;

                if (data = reInfo.exec(html)) {
                    info.codename = data[2].trim().toUpperCase().replace(/[^A-Z0-9]/g, '');
                    info.forumId = parseInt(data[1]);
                    info.name = this.normalize(data[2].trim());

                    if (data = reSeason.exec(html)) {
                        info.seasonId = parseInt(data[1]);
                    }
                }

                resolve(info);
            }).catch(reject)
        });
    }

    getNews({leagueId, seasonId, teamid = 0, typeid = 0, displaylimit = 500} = {}) {
        if (!leagueId) {
            throw new TypeError(`leagueId is not defined`);
        }

        if (!seasonId) {
            throw new TypeError(`seasonId is not defined`);
        }

        return new Promise((resolve, reject) => {
            this.get({
                action: 'league',
                page: 'team_news',
                teamid,
                typeid,
                displaylimit,
                leagueid: leagueId,
                seasonid: seasonId
            }).then(html => {
                const reNews = /
]+)? NewsFeedItem(?:[^>]+)?>(?:]+)?>]+)?\/team(\d+).png(?:[^>]+)?>)?(?:]+)?>]+)?\/(?:feed|icons?)\/(.*?).png(?:[^>]+)?>)?(?:]+)?>]+)?\/team(\d+).png(?:[^>]+)?>)?<\/a>
]+)?>
]+)?>(.*?)<\/h3>]+)?>(.*?)<\/abbr><\/div><\/li>/ig;
                const items = [];
                let item;

                while (item = reNews.exec(html)) {
                    item = {
                        id: helper.news.generateHash(item),
                        message: this.normalize(helper.news.cleanMessage(item[4])),
                        teams: [...new Set([item[1], item[3], ...helper.news.getTeams(item[4])])].filter(team => !Number.isNaN(Number(team)) && team > 0).map(team => parseInt(team)).sort(),
                        timestamp: moment.tz((item[5] || '').trim(), 'YYYY-MM-DD hh:mma', timezone).toISOString(),
                        type: (item[2] || '').trim()
                    };

                    switch(true) {
                        case / have (placed .*? on|claimed .*? off of) waivers /i.test(item.message):
                            item.type = 'waiver';
                            break;

                        case / have traded /i.test(item.message):
                            item.type = 'trade';
                            break;

                        case /^arrow\d+$/.test(item.type):
                            item.type = 'roster';
                            break;

                        case / have drafted /i.test(item.message):
                            item.type = 'draft';
                            break;

                        case / have agreed to a \d+ season deal /i.test(item.message):
                            item.type = 'contract';
                            break;

                        case / have earned the player rights for .*? with a bid /i.test(item.message):
                            item.type = 'bid';
                            break;

                        case /^profile$/.test(item.type):
                        case / gamertag has changed from /i.test(item.message):
                            item.type = 'account';
                            break;

                        default:
                            item.type = 'news';
                    }

                    items.push(item);
                }

                resolve(items);
            }).catch(reject);
        });
    }

    getTeams({leagueId, seasonId}) {
        if (!leagueId) {
            throw new TypeError(`leagueId is not defined`);
        }

        if (!seasonId) {
            throw new TypeError(`seasonId is not defined`);
        }

        return new Promise((resolve, reject) => {
            this.get({
                action: 'league',
                page: 'standing',
                leagueid: leagueId,
                seasonid: seasonId
            }).then(html => {
                const reNames = new RegExp(`<div(?:[^>]+)?class="team_box_icon"(?:[^>]+)?>.*?<a(?:[^>]+)?page=team_page&(?:amp;)?teamid=(\\d+)&(?:amp;)?leagueid=${leagueId}&(?:amp;)?seasonid=${seasonId}(?:[^>]+)?>(.*?)</a></div>`, 'ig');
                const reShortnames = new RegExp(`]+)?>]+)?/team\\d+.png(?:[^>]+)?> \\d+\\) .*?\\*?]+)?page=team_page&(?:amp;)?teamid=(\\d+)&(?:amp;)?leagueid=${leagueId}&(?:amp;)?seasonid=${seasonId}(?:[^>]+)?>(.*?)	`, 'ig');
                const reInfo = /]+)?href="(.*?)"(?:[^>]+)?>(.*?)<\/a>/i;
                const teams = {};
                let team;

                while (team = reNames.exec(html)) {
                    const id = parseInt(team[1]);
                    teams[id] = {id: id, name: this.normalize(team[2].trim()), shortname: this.normalize(team[2].trim())};
                }

                while (team = reShortnames.exec(html)) {
                    const id = parseInt(team[1]);
                    teams[id] = {id: id, ...teams[id], shortname: this.normalize(team[2].trim())};
                }

                resolve(teams);
            }).catch(reject);
        });
    }
};

module.exports = LeagueGaming_API;
module.exports.new = (options = {...config.sites}.leaguegaming) => new LeagueGaming_API(options);
<commit_msg>Fixed preseason games not parsing<commit_after>/**
 * This module provides an API for interaction with leaguegaming.com.
 */
'use strict';

const crypto = require('crypto');
const moment = require('moment-timezone');
const url = require('url');

const API = require('./api');

const config = require('../../config.json');

const timezone = 'America/New_York';

const helper = {
    news: {
        cleanMessage: (message = '') => {
            return message
                .replace(/]+)?\/team(\d+)\.png(?:[^>]+)?>]+)?>.*?<\/span>/g, (a, b) => `::team=${b}::`)
                .replace(/<\/span><span /g, ' <span ')
                .replace(/<[^>]+>/g, '')
                .replace(/[ ]+/g, ' ');
        },
        generateHash: (item = []) => {
            const buf = [
                helper.news.cleanMessage(item[4]),
                helper.news.getTeams(item[4]),
                moment.tz((item[5] || '').trim(), 'YYYY-MM-DD hh:mma', timezone).valueOf()
            ];

            const shasum = crypto.createHash('sha1');
            shasum.update(JSON.stringify(buf));
            return shasum.digest('hex');
        },
        getTeams: (message = '') => {
            return (message.match(/]+)?\/team(\d+)\.png(?:[^>]+)?>]+)?>(.*?)<\/span>/g) || []).map(t => t.replace(/.*?team(\d+).*/, '$1'));
        }
    }
};

class LeagueGaming_API extends API {
    constructor(...args) {
        super(...args);

        this.buildUrl = this.buildUrl.bind(this);
        this.getGames = this.getGames.bind(this);
        this.getInfo = this.getInfo.bind(this);
        this.getNews = this.getNews.bind(this);
        this.getTeams = this.getTeams.bind(this);
    }

    buildUrl({
        file = 'index.php',
        path = 'leaguegaming/league',
        ...params
    } = {}) {
        return `https://www.leaguegaming.com/forums/${file}?${path || ''}&${this.buildQueryString(params)}`.replace(/\?&/, '&');
    }

    getDailyStars({forumId, date = moment.tz(timezone).subtract(1, 'days')}) {
        if (!forumId) {
            throw new TypeError(`forumId is not defined`);
        }

        return new Promise((resolve, reject) => {
            this.get({
                path: 'search/1/',
                q: moment.tz(date, timezone).format('[Daily 3 Stars For] dddd MMMM Do, YYYY'),
                o: 'date',
                'c[title_only]': 1,
                'c[node]': forumId
            }).then(html => {
                const reThread = /
]+)?>]+)?href="(.*?)"(?:[^>]+)?>(.*?)<\/a><\/h3>/i;
                let data;

                if (!(data = reThread.exec(html))) {
                    return resolve({});
                }

                this.get(`https://www.leaguegaming.com/forums/${data[1]}`).then(html => {
                    const reGroup = /
]+)?d3_title(?:[^>]+)?>(.*?)<\/div>/i;
                    const reStar = /
]+)?>]+)?>((?:]+)?\/star\.gif(?:[^>]+)?>)+|\d+\.)<\/td>(?:]+)?t_threestars(?:[^>]+)?>
]+)?>]+)?\/team(\d+)\.svg(?:[^>]+)?>]+)?><\/div><\/td>]+)?>(.*?)
]+)?>\((.*?)\)<\/span><\/td>|]+)?>]+)?\/team(\d+)\.png(?:[^>]+)?> (.*?) \((.*?)\)<\/td>)]+)?>]+)?>.*?<\/a><\/td>((?:(.*?)<\/td>)+)<\/tr>/i;
                    const reGroupOrStar = new RegExp(`(?:${reGroup.source}|${reStar.source})`, 'ig');
                    let stars = {};
                    let group;
                    let line;

                    while (line = reGroupOrStar.exec(html)) {
                        let data;

                        if (data = reGroup.exec(line)) {
                            group = data[1].toLowerCase();
                        }

                        if (data = reStar.exec(line)) {
                            const metadata = data[8].match(/(.*?)<\/td>/g).map(v => v.replace(/<(?:[^>]+)?>/g, ''));

                            stars[group] = [
                                ...(stars[group] || []),
                                {
                                    rank: ((data[1].match(/star\.gif/g) || []).length) || parseInt(data[1]),
                                    team: parseInt(data[2]) || parseInt(data[5]),
                                    name: data[3] || data[6],
                                    position: data[4] || data[7],
                                    metadata: (group !== 'goalies')
                                        ? {
                                            'Points': parseInt(metadata[0]),
                                            'Goals': parseInt(metadata[1]),
                                            'Assists': parseInt(metadata[2]),
                                            '+/-': (metadata[3] >= 0 ? '+' : '') + parseInt(metadata[3])
                                        } : {
                                            'SV%': Number((parseFloat(metadata[0]) / 100) + 0.0005).toFixed(3),
                                            'GAA': Number(parseFloat(metadata[1]) + 0.005).toFixed(2),
                                            'Saves': parseInt(metadata[2]),
                                            'Shots': parseInt(metadata[3])
                                        }
                                }
                            ];
                        }
                    }

                    resolve(stars);
                });
            }).catch(reject);
        });
    }

    getGames({leagueId, seasonId}) {
        if (!leagueId) {
            throw new TypeError(`leagueId is not defined`);
        }

        if (!seasonId) {
            throw new TypeError(`seasonId is not defined`);
        }

        return new Promise((resolve, reject) => {
            this.get({
                action: 'league',
                page: 'league_schedule_all',
                leagueid: leagueId,
                seasonid: seasonId
            }).then(html => {
                const reDate = /
]+)?sh4(?:[^>]+)?>(.*?)<\/h4>/i;
                const reGame = /]+)?sweekid(?:[^>]+)?>Week (\d+)<\/span>(?:]+)?sgamenumber(?:[^>]+)?>Game# (\d+)<\/span>)?]+)?\/team(\d+)\.png(?:[^>]+)?>]+)?&gameid=(\d+)(?:[^>]+)?>]+)?steamname(?:[^>]+)?>(.*?)<\/span>]+)?sscore(?:[^>]+)?>(vs|(\d+)\D+(\d+))<\/span>]+)?steamname(?:[^>]+)?>(.*?)<\/span><\/a>]+)?\/team(\d+)\.png(?:[^>]+)?>/i;
                const reDateOrGame = new RegExp(`(?:${reDate.source}|${reGame.source})`, 'ig');
                const games = [];
                let date;
                let line;

                while (line = reDateOrGame.exec(html)) {
                    let data;

                    if (data = reDate.exec(line)) {
                        date = moment.tz(data[1], 'MMMM DD[th]', timezone);

                        if (moment.tz(timezone).month() <= 6 && date.month() >= 7) {
                            date.subtract(1, 'years');
                        } else if (moment.tz(timezone).month() >= 7 && date.month() <= 6) {
                            date.add(1, 'years');
                        }
                    }

                    if (data = reGame.exec(line)) {
                        games.push({
                            id: parseInt(data[4]),
                            date: date.toISOString(),
                            visitor: {
                                id: parseInt(data[10]),
                                score: !Number.isNaN(Number(data[8])) ? parseInt(data[8]) : null
                            },
                            home: {
                                id: parseInt(data[3]),
                                score: !Number.isNaN(Number(data[7])) ? parseInt(data[7]) : null
                            }
                        });
                    }
                }

                resolve(games);
            }).catch(reject);
        });
    }

    getInfo({leagueId}) {
        if (!leagueId) {
            throw new TypeError(`leagueId is not defined`);
        }

        return new Promise((resolve, reject) => {
            this.get({
                action: 'league',
                page: 'standings',
                leagueid: leagueId,
                seasonid: 1
            }).then(html => {
                const reInfo = new RegExp(`<li(?:[^>]+)? custom-tab-${leagueId} (?:[^>]+)?><a(?:[^>]+)?/league\\.(\\d+)/(?:[^>]+)?>.*?<span(?:[^>]+)?>(.*?)</span>.*?</a>`, 'i');
                const reSeason = new RegExp(`<a(?:[^>]+)?leagueid=${leagueId}&(?:amp;)?seasonid=(\\d+)(?:[^>]+)?>Standings</a>`, 'i');
                const info = {codename: undefined, forumId: undefined, id: parseInt(leagueId), name: undefined, seasonId: undefined};
                let data;

                if (data = reInfo.exec(html)) {
                    info.codename = data[2].trim().toUpperCase().replace(/[^A-Z0-9]/g, '');
                    info.forumId = parseInt(data[1]);
                    info.name = this.normalize(data[2].trim());

                    if (data = reSeason.exec(html)) {
                        info.seasonId = parseInt(data[1]);
                    }
                }

                resolve(info);
            }).catch(reject)
        });
    }

    getNews({leagueId, seasonId, teamid = 0, typeid = 0, displaylimit = 500} = {}) {
        if (!leagueId) {
            throw new TypeError(`leagueId is not defined`);
        }

        if (!seasonId) {
            throw new TypeError(`seasonId is not defined`);
        }

        return new Promise((resolve, reject) => {
            this.get({
                action: 'league',
                page: 'team_news',
                teamid,
                typeid,
                displaylimit,
                leagueid: leagueId,
                seasonid: seasonId
            }).then(html => {
                const reNews = /
]+)? NewsFeedItem(?:[^>]+)?>(?:]+)?>]+)?\/team(\d+).png(?:[^>]+)?>)?(?:]+)?>]+)?\/(?:feed|icons?)\/(.*?).png(?:[^>]+)?>)?(?:]+)?>]+)?\/team(\d+).png(?:[^>]+)?>)?<\/a>
]+)?>
]+)?>(.*?)<\/h3>]+)?>(.*?)<\/abbr><\/div><\/li>/ig;
                const items = [];
                let item;

                while (item = reNews.exec(html)) {
                    item = {
                        id: helper.news.generateHash(item),
                        message: this.normalize(helper.news.cleanMessage(item[4])),
                        teams: [...new Set([item[1], item[3], ...helper.news.getTeams(item[4])])].filter(team => !Number.isNaN(Number(team)) && team > 0).map(team => parseInt(team)).sort(),
                        timestamp: moment.tz((item[5] || '').trim(), 'YYYY-MM-DD hh:mma', timezone).toISOString(),
                        type: (item[2] || '').trim()
                    };

                    switch(true) {
                        case / have (placed .*? on|claimed .*? off of) waivers /i.test(item.message):
                            item.type = 'waiver';
                            break;

                        case / have traded /i.test(item.message):
                            item.type = 'trade';
                            break;

                        case /^arrow\d+$/.test(item.type):
                            item.type = 'roster';
                            break;

                        case / have drafted /i.test(item.message):
                            item.type = 'draft';
                            break;

                        case / have agreed to a \d+ season deal /i.test(item.message):
                            item.type = 'contract';
                            break;

                        case / have earned the player rights for .*? with a bid /i.test(item.message):
                            item.type = 'bid';
                            break;

                        case /^profile$/.test(item.type):
                        case / gamertag has changed from /i.test(item.message):
                            item.type = 'account';
                            break;

                        default:
                            item.type = 'news';
                    }

                    items.push(item);
                }

                resolve(items);
            }).catch(reject);
        });
    }

    getTeams({leagueId, seasonId}) {
        if (!leagueId) {
            throw new TypeError(`leagueId is not defined`);
        }

        if (!seasonId) {
            throw new TypeError(`seasonId is not defined`);
        }

        return new Promise((resolve, reject) => {
            this.get({
                action: 'league',
                page: 'standing',
                leagueid: leagueId,
                seasonid: seasonId
            }).then(html => {
                const reNames = new RegExp(`<div(?:[^>]+)?class="team_box_icon"(?:[^>]+)?>.*?<a(?:[^>]+)?page=team_page&(?:amp;)?teamid=(\\d+)&(?:amp;)?leagueid=${leagueId}&(?:amp;)?seasonid=${seasonId}(?:[^>]+)?>(.*?)</a></div>`, 'ig');
                const reShortnames = new RegExp(`]+)?>]+)?/team\\d+.png(?:[^>]+)?> \\d+\\) .*?\\*?]+)?page=team_page&(?:amp;)?teamid=(\\d+)&(?:amp;)?leagueid=${leagueId}&(?:amp;)?seasonid=${seasonId}(?:[^>]+)?>(.*?)	`, 'ig');
                const reInfo = /]+)?href="(.*?)"(?:[^>]+)?>(.*?)<\/a>/i;
                const teams = {};
                let team;

                while (team = reNames.exec(html)) {
                    const id = parseInt(team[1]);
                    teams[id] = {id: id, name: this.normalize(team[2].trim()), shortname: this.normalize(team[2].trim())};
                }

                while (team = reShortnames.exec(html)) {
                    const id = parseInt(team[1]);
                    teams[id] = {id: id, ...teams[id], shortname: this.normalize(team[2].trim())};
                }

                resolve(teams);
            }).catch(reject);
        });
    }
};

module.exports = LeagueGaming_API;
module.exports.new = (options = {...config.sites}.leaguegaming) => new LeagueGaming_API(options);
<|endoftext|>


<commit_before>import AbstractModelService from './abstract-model-service'
import { axiosGet, axiosPost } from '@/helpers/axios'

/**
 * API service for Images.
 */
class ImageService extends AbstractModelService {
	constructor() {
		super('images/')
	}

	/**
	 * Finds the Models which depend on this image.
	 * @param {Number} id The Image ID
	 */
	getImageDependencies(id) {
		return axiosGet(`images/${id}/dependencies`, {})
	}

	/**
	 * Detects non-registered images currently on the disk.
	 * @return {Promise<String[]>} The image paths.
	 */
	detectDiskImages() {
		return axiosGet('/images/detect')
	}

	/**
	 * Saves a set of detected images.
	 * @param {String[]} images The paths of the images to add.
	 * @return {Promise<Number[]>} The added image IDs.
	 */
	saveDiskImages(images) {
		return axiosPost('/images/detect', images, [])
	}
}

export default new ImageService()
<commit_msg>Fixed image detection with empty context root.<commit_after>import AbstractModelService from './abstract-model-service'
import { axiosGet, axiosPost } from '@/helpers/axios'

/**
 * API service for Images.
 */
class ImageService extends AbstractModelService {
	constructor() {
		super('images/')
	}

	/**
	 * Finds the Models which depend on this image.
	 * @param {Number} id The Image ID
	 */
	getImageDependencies(id) {
		return axiosGet(`images/${id}/dependencies`, {})
	}

	/**
	 * Detects non-registered images currently on the disk.
	 * @return {Promise<String[]>} The image paths.
	 */
	detectDiskImages() {
		return axiosGet('images/detect')
	}

	/**
	 * Saves a set of detected images.
	 * @param {String[]} images The paths of the images to add.
	 * @return {Promise<Number[]>} The added image IDs.
	 */
	saveDiskImages(images) {
		return axiosPost('images/detect', images, [])
	}
}

export default new ImageService()
<|endoftext|>


<commit_before>beforeEach(function() {
  shouldFail = function shouldFail(thunk) {
    try {
      thunk();
      fail('expected failure did not occur');
    } catch (e) {}
  }

  shouldFailWith = function shouldFailWith(expectedFailure, thunk) {
    try {
      thunk();
      fail('expected failure did not occur');
    } catch (e) {
      expect(e.message).toBe(expectedFailure);
    }
  }
});
<commit_msg>Relaxed shouldFailWith to require only that the provided string be contained in the failure message and added old, stricter version as shouldFailWithExactly<commit_after>beforeEach(function() {
  shouldFail = function shouldFail(thunk) {
    try {
      thunk();
      fail('expected failure did not occur');
    } catch (e) {}
  }

  shouldFailWith = function shouldFailWith(expectedFailure, thunk) {
    try {
      thunk();
      fail('expected failure did not occur');
    } catch (e) {
      expect(e.message.toString()).toContain(expectedFailure);
    }
  }

  shouldFailWithExactly = function shouldFailWith(expectedFailure, thunk) {
    try {
      thunk();
      fail('expected failure did not occur');
    } catch (e) {
      expect(e.message.toString()).toBe(expectedFailure);
    }
  }
});
<|endoftext|>


<commit_before>// Google BSD license http://code.google.com/google_bsd_license.html
// Copyright 2013 Google Inc. johnjbarton@google.com

/**
 * overview: Function enter/leave tracing
 */

import {
  ParseTreeTransformer,
} from '../third_party/traceur-compiler/src/codegeneration/ParseTreeTransformer';

import {
  CallExpression,
  CommaExpression,
  FunctionBody,
  ParenExpression,
  ReturnStatement
} from '../third_party/traceur-compiler/src/syntax/trees/ParseTrees';
import {
  parseExpression,
  parseStatement
} from '../third_party/traceur-compiler/src/codegeneration/PlaceholderParser';
import {
  RETURN_STATEMENT
} from '../third_party/traceur-compiler/src/syntax/trees/ParseTreeType';

import {dumpeur} from './dumpeur';

export class TraceFunctionsTransformer extends ParseTreeTransformer {

  constructor(sourceId) {
    this.sourceId = sourceId;
  }
  enterStatement(tree) {
    var offset = tree.location.start.offset;
    return parseStatement `__F_.enterF(${this.sourceId}, ${offset});`;
  }
  exitExpression(tree) {
    var offset = tree.location.end.offset;
    return parseExpression `__F_.exitF(${offset})`;
  }
  callExpression(tree) {
    var offset = tree.location.end.offset;
    return parseExpression `__F_.callF(${this.sourceId}, ${offset})`;
  }
  transformCallExpression(tree) {
    var operand = this.transformAny(tree.operand);
    var args = this.transformAny(tree.args);
    return new ParenExpression(tree.location,
        new CommaExpression(tree.location, [
            this.callExpression(tree),
            new CallExpression(tree.location, operand, args)
          ])
        );
  }
  transformFunctionBody(tree) {
    var statements = this.transformList(tree.statements);
    statements.unshift(this.enterStatement(tree));
    if (statements[statements.length - 1].type !== RETURN_STATEMENT) {
      statements.push(new ReturnStatement(tree.location, this.exitExpression(tree)));
    }
    return new FunctionBody(tree.location, statements);
  }
  transformReturnStatement(tree) {
    var expression = this.transformAny(tree.expression);
    // return $__exitF('name'), expression
    var exitExpression = this.exitExpression(tree);
    if (expression)
      expression = new CommaExpression(tree.location, [exitExpression, expression]);
    else
      expression = exitExpression;
    return new ReturnStatement(tree.location, expression);
  }
}<commit_msg>Trace enter and exit around files for outer JS function call<commit_after>// Google BSD license http://code.google.com/google_bsd_license.html
// Copyright 2013 Google Inc. johnjbarton@google.com

/**
 * overview: Function enter/leave tracing
 */

import {
  ParseTreeTransformer,
} from '../third_party/traceur-compiler/src/codegeneration/ParseTreeTransformer';

import {
  CallExpression,
  CommaExpression,
  ExpressionStatement,
  FunctionBody,
  Module,
  NewExpression,
  ParenExpression,
  ReturnStatement,
  Script
} from '../third_party/traceur-compiler/src/syntax/trees/ParseTrees';
import {
  parseExpression,
  parseStatement
} from '../third_party/traceur-compiler/src/codegeneration/PlaceholderParser';
import {
  RETURN_STATEMENT
} from '../third_party/traceur-compiler/src/syntax/trees/ParseTreeType';

import {dumpeur} from './dumpeur';

export class TraceFunctionsTransformer extends ParseTreeTransformer {

  constructor(sourceId) {
    this.sourceId = sourceId;
  }
  enterStatement(tree) {
    var offset = tree.location.start.offset;
    return parseStatement `__F_.enterF(${this.sourceId}, ${offset});`;
  }
  exitExpression(tree) {
    var offset = tree.location.end.offset;
    return parseExpression `__F_.exitF(${offset})`;
  }
  callExpression(tree) {
    var offset = tree.location.end.offset;
    return parseExpression `__F_.callF(${this.sourceId}, ${offset})`;
  }
  wrapScriptItemList(tree) {
    return [
        this.enterStatement(tree),
        ...tree.scriptItemList,
        new ExpressionStatement(tree.location, this.exitExpression(tree))
    ];
  }
  transformScript(tree) {
    tree = super(tree);
    var wrappedItems = this.wrapScriptItemList(tree);
    return new Script(tree.location, wrappedItems, tree.moduleName);
  }
  transformModule(tree) {
    tree = super(tree);
    var wrappedItems = this.wrapScriptItemList(tree);
    return new Module(tree.location, wrappedItems, tree.moduleName);
  }

  transformCallExpression(tree) {
    var operand = this.transformAny(tree.operand);
    var args = this.transformAny(tree.args);
    return new ParenExpression(tree.location,
        new CommaExpression(tree.location, [
            this.callExpression(tree),
            new CallExpression(tree.location, operand, args)
          ])
        );
  }
  transformNewExpression(tree) {
    var operand = this.transformAny(tree.operand);
    var args = this.transformAny(tree.args);
    return new ParenExpression(tree.location,
        new CommaExpression(tree.location, [
            this.callExpression(tree),
            new NewExpression(tree.location, operand, args)
          ])
        );
  }
  transformFunctionBody(tree) {
    var statements = this.transformList(tree.statements);
    statements.unshift(this.enterStatement(tree));
    if (statements[statements.length - 1].type !== RETURN_STATEMENT) {
      statements.push(new ReturnStatement(tree.location, this.exitExpression(tree)));
    }
    return new FunctionBody(tree.location, statements);
  }
  transformReturnStatement(tree) {
    var expression = this.transformAny(tree.expression);
    // return $__exitF('name'), expression
    var exitExpression = this.exitExpression(tree);
    if (expression)
      expression = new CommaExpression(tree.location, [exitExpression, expression]);
    else
      expression = exitExpression;
    return new ReturnStatement(tree.location, expression);
  }
}<|endoftext|>


<commit_before>

define(["sugar-web/activity/activity","sugar-web/datastore", "sugar-web/env", "webL10n"], function (activity, datastore, env, webL10n) {

	// Manipulate the DOM only when it is ready.
	requirejs(['domReady!', 'humane'], function (doc, humane) {

		// Initialize the activity.
		activity.setup();
		var isMobile = (/Android/i.test(navigator.userAgent) || /iPad/i.test(navigator.userAgent) || /iPhone/i.test(navigator.userAgent)) && document.location.protocol.substr(0,4) != "http";
		var currentCamera = 0;
		var history = [];
		env.getEnvironment(function(err, environment) {
			var defaultLanguage = (typeof chrome != 'undefined' && chrome.app && chrome.app.runtime) ? chrome.i18n.getUILanguage() : navigator.language;
			var language = environment.user ? environment.user.language : defaultLanguage;
			webL10n.language.code = language;
		});

		// Compute size of QR Code
		var headerSize = 55 + 40;
		var marginPercent = 20;
		var qrSize = document.getElementById("canvas").parentNode.offsetHeight - headerSize;
		qrSize -= (marginPercent*qrSize)/100;

		// Init QR Code object
		var qrCode = new QRCode("qr-code", {
			width: qrSize,
			height: qrSize,
			colorDark : "#000000",
			colorLight : "#ffffff",
			correctLevel : QRCode.CorrectLevel.H
		});
		var margin = (document.getElementById("canvas").parentNode.offsetWidth - qrSize) / 2;
		document.getElementById("qr-code").style.marginLeft = margin + "px";
		document.getElementById("qr-code").style.marginTop = "20px";

		// Generate Code
		var generateCode = function(text) {
			qrCode.clear();
			qrCode.makeCode(text);
			var text = userText.value.toLowerCase();
			if (text.indexOf("http://") == 0 || text.indexOf("https://") == 0) {
				document.getElementById("user-text").classList.add("text-url");
			} else {
				document.getElementById("user-text").classList.remove("text-url");
			}
			addToHistory(text);
		}

		// Process Resize events
		window.addEventListener('resize', function() {
			var windowSize = document.body.clientHeight - headerSize;
			var zoom = windowSize/((qrSize*(100+marginPercent))/100);
			document.getElementById("qr-code").style.zoom = zoom;
			var useragent = navigator.userAgent.toLowerCase();
			if (useragent.indexOf('chrome') == -1) {
				document.getElementById("qr-code").style.MozTransform = "scale("+zoom+")";
				document.getElementById("qr-code").style.MozTransformOrigin = "0 0";
			}
			if (photoInitialized) {
				document.getElementById("outdiv").style.zoom = zoom;
				if (useragent.indexOf('chrome') == -1) {
					document.getElementById("outdiv").style.MozTransform = "scale("+zoom+")";
					document.getElementById("outdiv").style.MozTransformOrigin = "0 0";
				}
			}
		});

		// Get settings
		var userText = document.getElementById("user-text");
		userText.addEventListener('keyup', function() {
			var text = userText.value.toLowerCase();
			if (text.indexOf("http://") == 0 || text.indexOf("https://") == 0) {
				document.getElementById("user-text").classList.add("text-url");
			} else {
				document.getElementById("user-text").classList.remove("text-url");
			}
		});
		userText.addEventListener('click', function() {
			var text = userText.value.toLowerCase();
			if (text.indexOf("http://") == 0 || text.indexOf("https://") == 0) {
				window.open(userText.value);
			}
		});

		// Handle text change
		document.getElementById("generate-button").addEventListener('click', function() {
			generateCode(userText.value);
		});

		// Handle text scan
		var codeScanned = function(code) {
			document.getElementById("outdiv").style.visibility = "hidden";
			document.getElementById("video-stream").style.visibility = "hidden";
			document.getElementById("qr-code").style.visibility = "visible";
			document.getElementById("loading-spinner").style.visibility = "hidden";
			document.getElementById("photo-button").classList.remove("active");
			userText.value = code;
			generateCode(code);
		};

		// Export as PNG image
		document.getElementById("png-button").addEventListener('click', function() {
			var mimetype = 'image/png';
			var inputData = document.getElementById("qr-code").childNodes[1].src;
			var metadata = {
				mimetype: mimetype,
				title: "Image QR Code",
				activity: "org.olpcfrance.MediaViewerActivity",
				timestamp: new Date().getTime(),
				creation_time: new Date().getTime(),
				file_size: 0
			};
			datastore.create(metadata, function() {
				humane.log(webL10n.get('QRCodeSaved'))
				console.log("export done.")
			}, inputData);
		});

		// Full screen
		document.getElementById("fullscreen-button").addEventListener('click', function() {
			if (document.getElementById("photo-button").classList.contains('active')) {
				return;
			}
			document.getElementById("main-toolbar").style.opacity = 0;
			document.getElementById("input-box").style.opacity = 0;
			document.getElementById("canvas").style.top = "0px";
			document.getElementById("unfullscreen-button").style.visibility = "visible";
		});
		document.getElementById("unfullscreen-button").addEventListener('click', function() {
			document.getElementById("main-toolbar").style.opacity = 1;
			document.getElementById("input-box").style.opacity = 1;
			document.getElementById("canvas").style.top = "55px";
			document.getElementById("unfullscreen-button").style.visibility = "hidden";
		});

		// Capture photo
		var photoButton = document.getElementById("photo-button");
		var photoInitialized = false;
		var oldUserText = "";
		photoButton.addEventListener('click', function() {
			// Handle mobile capture
			if (isMobile) {
				document.getElementById("main-toolbar").style.opacity = 0;
				document.getElementById("canvas").style.opacity = 0;
				document.getElementById("close-button").style.visibility = "visible";
				document.getElementById("switchcamera-button").style.visibility = "visible";
				QRScanner.prepare(function(err, status) {
					if (err) {
						console.log("Error "+err);
					} else {
						console.log(status);
						QRScanner.scan(function(err, code) {
							if (err) {
								console.log("Error "+err);
							} else {
								userText.value = code;
								generateCode(code);
							}
							document.getElementById("main-toolbar").style.opacity = 1;
							document.getElementById("canvas").style.opacity = 1;
							document.getElementById("close-button").style.visibility = "hidden";
							document.getElementById("switchcamera-button").style.visibility = "hidden";
						});
						QRScanner.show(function(status) {});
					}
				});
				return;
			}

			// Handle HTML5 capture
			var outdiv = document.getElementById("outdiv");
			var videostream = document.getElementById("video-stream");
			if (!photoButton.classList.contains('active')) {
				document.getElementById("qr-code").style.visibility = "hidden";
				document.getElementById("loading-spinner").style.visibility = "visible";
				oldUserText = userText.value;
				userText.value = "";
				if (!photoInitialized) {
					outdiv.innerHTML = '<video id="video-window" autoplay></video>';
					load(qrSize, margin, codeScanned);
					photoInitialized = true;
				} else {
					outdiv.style.visibility = "visible";
					videostream.style.visibility = "visible";
				}
			} else {
				outdiv.style.visibility = "hidden";
				videostream.style.visibility = "hidden";
				document.getElementById("loading-spinner").style.visibility = "hidden";
				document.getElementById("qr-code").style.visibility = "visible";
				userText.value = oldUserText;
				generateCode(userText.value);
			}
			photoButton.classList.toggle("active");
		});

		// Mobile buttons: Cancel capture, Switch camera
		document.getElementById("close-button").addEventListener('click', function (event) {
			if (isMobile) {
				QRScanner.cancelScan(function(status){});
			}
		});
		document.getElementById("switchcamera-button").addEventListener('click', function (event) {
			if (isMobile) {
				currentCamera = (currentCamera + 1) % 2;
				QRScanner.useCamera(currentCamera, function(err, status){});
			}
		});

		// Add entry to history
		function addToHistory(text) {
			history.push(text);
			updateHistory();
		}

		// Update dropdown with user history
		function updateHistory() {
			var mhtml = '';
			var index = history.length;
			while(index--) {
				mhtml += '<option value="'+(history[index])+'">'+(history[index])+'</option>';
			}
			document.getElementById("qrtextdropdown").innerHTML = mhtml;
		}

		// QR history dropdown change
		document.getElementById("qrtextdropdown").addEventListener('change', function () {
			document.getElementById("user-text").value = document.getElementById("qrtextdropdown").value;
			generateCode(document.getElementById("qrtextdropdown").value);
		});

		// Handle graph save/world
		var stopButton = document.getElementById("stop-button");
		stopButton.addEventListener('click', function (event) {
			console.log("writing...");
			var data = { userText: (photoButton.classList.contains('active') ? oldUserText : userText.value), uHistory: history };
			var jsonData = JSON.stringify(data);
			activity.getDatastoreObject().setDataAsText(jsonData);
			activity.getDatastoreObject().save(function (error) {
				if (error === null) {
					console.log("write done.");
				} else {
					console.log("write failed.");
				}
			});
		});

		// Initialize code
		env.getEnvironment(function(err, environment) {
			if (!environment.objectId) {
				console.log("New instance");
				userText.value = environment.user.name;
				generateCode(environment.user.name);
			} else {
				activity.getDatastoreObject().loadAsText(function(error, metadata, data) {
					if (error==null && data!=null) {
						data = JSON.parse(data);
						userText.value = data.userText;
						history = data.uHistory;
						generateCode(data.userText);
					}
				});
			}
		});
	});
});
<commit_msg>Fixed log redundancy in qr activity<commit_after>

define(["sugar-web/activity/activity","sugar-web/datastore", "sugar-web/env", "webL10n"], function (activity, datastore, env, webL10n) {

	// Manipulate the DOM only when it is ready.
	requirejs(['domReady!', 'humane'], function (doc, humane) {

		// Initialize the activity.
		activity.setup();
		var isMobile = (/Android/i.test(navigator.userAgent) || /iPad/i.test(navigator.userAgent) || /iPhone/i.test(navigator.userAgent)) && document.location.protocol.substr(0,4) != "http";
		var currentCamera = 0;
		var history = [];
		env.getEnvironment(function(err, environment) {
			var defaultLanguage = (typeof chrome != 'undefined' && chrome.app && chrome.app.runtime) ? chrome.i18n.getUILanguage() : navigator.language;
			var language = environment.user ? environment.user.language : defaultLanguage;
			webL10n.language.code = language;
		});

		// Compute size of QR Code
		var headerSize = 55 + 40;
		var marginPercent = 20;
		var qrSize = document.getElementById("canvas").parentNode.offsetHeight - headerSize;
		qrSize -= (marginPercent*qrSize)/100;

		// Init QR Code object
		var qrCode = new QRCode("qr-code", {
			width: qrSize,
			height: qrSize,
			colorDark : "#000000",
			colorLight : "#ffffff",
			correctLevel : QRCode.CorrectLevel.H
		});
		var margin = (document.getElementById("canvas").parentNode.offsetWidth - qrSize) / 2;
		document.getElementById("qr-code").style.marginLeft = margin + "px";
		document.getElementById("qr-code").style.marginTop = "20px";

		// Generate Code
		var generateCode = function(text) {
			qrCode.clear();
			qrCode.makeCode(text);
			var text = userText.value.toLowerCase();
			if (text.indexOf("http://") == 0 || text.indexOf("https://") == 0) {
				document.getElementById("user-text").classList.add("text-url");
			} else {
				document.getElementById("user-text").classList.remove("text-url");
			}
			addToHistory(text);
		}

		// Process Resize events
		window.addEventListener('resize', function() {
			var windowSize = document.body.clientHeight - headerSize;
			var zoom = windowSize/((qrSize*(100+marginPercent))/100);
			document.getElementById("qr-code").style.zoom = zoom;
			var useragent = navigator.userAgent.toLowerCase();
			if (useragent.indexOf('chrome') == -1) {
				document.getElementById("qr-code").style.MozTransform = "scale("+zoom+")";
				document.getElementById("qr-code").style.MozTransformOrigin = "0 0";
			}
			if (photoInitialized) {
				document.getElementById("outdiv").style.zoom = zoom;
				if (useragent.indexOf('chrome') == -1) {
					document.getElementById("outdiv").style.MozTransform = "scale("+zoom+")";
					document.getElementById("outdiv").style.MozTransformOrigin = "0 0";
				}
			}
		});

		// Get settings
		var userText = document.getElementById("user-text");
		userText.addEventListener('keyup', function() {
			var text = userText.value.toLowerCase();
			if (text.indexOf("http://") == 0 || text.indexOf("https://") == 0) {
				document.getElementById("user-text").classList.add("text-url");
			} else {
				document.getElementById("user-text").classList.remove("text-url");
			}
		});
		userText.addEventListener('click', function() {
			var text = userText.value.toLowerCase();
			if (text.indexOf("http://") == 0 || text.indexOf("https://") == 0) {
				window.open(userText.value);
			}
		});

		// Handle text change
		document.getElementById("generate-button").addEventListener('click', function() {
			generateCode(userText.value);
		});

		// Handle text scan
		var codeScanned = function(code) {
			document.getElementById("outdiv").style.visibility = "hidden";
			document.getElementById("video-stream").style.visibility = "hidden";
			document.getElementById("qr-code").style.visibility = "visible";
			document.getElementById("loading-spinner").style.visibility = "hidden";
			document.getElementById("photo-button").classList.remove("active");
			userText.value = code;
			generateCode(code);
		};

		// Export as PNG image
		document.getElementById("png-button").addEventListener('click', function() {
			var mimetype = 'image/png';
			var inputData = document.getElementById("qr-code").childNodes[1].src;
			var metadata = {
				mimetype: mimetype,
				title: "Image QR Code",
				activity: "org.olpcfrance.MediaViewerActivity",
				timestamp: new Date().getTime(),
				creation_time: new Date().getTime(),
				file_size: 0
			};
			datastore.create(metadata, function() {
				humane.log(webL10n.get('QRCodeSaved'))
				console.log("export done.")
			}, inputData);
		});

		// Full screen
		document.getElementById("fullscreen-button").addEventListener('click', function() {
			if (document.getElementById("photo-button").classList.contains('active')) {
				return;
			}
			document.getElementById("main-toolbar").style.opacity = 0;
			document.getElementById("input-box").style.opacity = 0;
			document.getElementById("canvas").style.top = "0px";
			document.getElementById("unfullscreen-button").style.visibility = "visible";
		});
		document.getElementById("unfullscreen-button").addEventListener('click', function() {
			document.getElementById("main-toolbar").style.opacity = 1;
			document.getElementById("input-box").style.opacity = 1;
			document.getElementById("canvas").style.top = "55px";
			document.getElementById("unfullscreen-button").style.visibility = "hidden";
		});

		// Capture photo
		var photoButton = document.getElementById("photo-button");
		var photoInitialized = false;
		var oldUserText = "";
		photoButton.addEventListener('click', function() {
			// Handle mobile capture
			if (isMobile) {
				document.getElementById("main-toolbar").style.opacity = 0;
				document.getElementById("canvas").style.opacity = 0;
				document.getElementById("close-button").style.visibility = "visible";
				document.getElementById("switchcamera-button").style.visibility = "visible";
				QRScanner.prepare(function(err, status) {
					if (err) {
						console.log("Error "+err);
					} else {
						console.log(status);
						QRScanner.scan(function(err, code) {
							if (err) {
								console.log("Error "+err);
							} else {
								userText.value = code;
								generateCode(code);
							}
							document.getElementById("main-toolbar").style.opacity = 1;
							document.getElementById("canvas").style.opacity = 1;
							document.getElementById("close-button").style.visibility = "hidden";
							document.getElementById("switchcamera-button").style.visibility = "hidden";
						});
						QRScanner.show(function(status) {});
					}
				});
				return;
			}

			// Handle HTML5 capture
			var outdiv = document.getElementById("outdiv");
			var videostream = document.getElementById("video-stream");
			if (!photoButton.classList.contains('active')) {
				document.getElementById("qr-code").style.visibility = "hidden";
				document.getElementById("loading-spinner").style.visibility = "visible";
				oldUserText = userText.value;
				userText.value = "";
				if (!photoInitialized) {
					outdiv.innerHTML = '<video id="video-window" autoplay></video>';
					load(qrSize, margin, codeScanned);
					photoInitialized = true;
				} else {
					outdiv.style.visibility = "visible";
					videostream.style.visibility = "visible";
				}
			} else {
				outdiv.style.visibility = "hidden";
				videostream.style.visibility = "hidden";
				document.getElementById("loading-spinner").style.visibility = "hidden";
				document.getElementById("qr-code").style.visibility = "visible";
				userText.value = oldUserText;
				generateCode(userText.value);
			}
			photoButton.classList.toggle("active");
		});

		// Mobile buttons: Cancel capture, Switch camera
		document.getElementById("close-button").addEventListener('click', function (event) {
			if (isMobile) {
				QRScanner.cancelScan(function(status){});
			}
		});
		document.getElementById("switchcamera-button").addEventListener('click', function (event) {
			if (isMobile) {
				currentCamera = (currentCamera + 1) % 2;
				QRScanner.useCamera(currentCamera, function(err, status){});
			}
		});

		// Add entry to history
		function addToHistory(text) {
			if(!history.includes(text)){
			history.push(text);
			updateHistory();
		}
		}

		// Update dropdown with user history
		function updateHistory() {
			var mhtml = '';
			var index = history.length;
			while(index--) {
				mhtml += '<option value="'+(history[index])+'">'+(history[index])+'</option>';
			}
			document.getElementById("qrtextdropdown").innerHTML = mhtml;
		}

		// QR history dropdown change
		document.getElementById("qrtextdropdown").addEventListener('change', function () {
			document.getElementById("user-text").value = document.getElementById("qrtextdropdown").value;
			generateCode(document.getElementById("qrtextdropdown").value);
		});

		// Handle graph save/world
		var stopButton = document.getElementById("stop-button");
		stopButton.addEventListener('click', function (event) {
			console.log("writing...");
			var data = { userText: (photoButton.classList.contains('active') ? oldUserText : userText.value), uHistory: history };
			var jsonData = JSON.stringify(data);
			activity.getDatastoreObject().setDataAsText(jsonData);
			activity.getDatastoreObject().save(function (error) {
				if (error === null) {
					console.log("write done.");
				} else {
					console.log("write failed.");
				}
			});
		});

		// Initialize code
		env.getEnvironment(function(err, environment) {
			if (!environment.objectId) {
				console.log("New instance");
				userText.value = environment.user.name;
				generateCode(environment.user.name);
			} else {
				activity.getDatastoreObject().loadAsText(function(error, metadata, data) {
					if (error==null && data!=null) {
						data = JSON.parse(data);
						userText.value = data.userText;
						history = data.uHistory;
						generateCode(data.userText);
					}
				});
			}
		});
	});
});
<|endoftext|>


<commit_before>var list_of_quicktests = [
  "area_animate",
  "areaRenderer",
  "dragzoom_x",
  "gridlines",
  "LayoutInvalidate",
  "line_animate",
  "scatter_animate",
  "verticalBar_animate"
]
<commit_msg>Update list_of_quicktests.js<commit_after>var list_of_quicktests = [
  "area_animate",
  "areaRenderer",
  "dragzoom_x",
  "panzoom",
  "LayoutInvalidate",
  "line_animate",
  "scatter_animate",
  "verticalBar_animate"
]
<|endoftext|>


<commit_before>Helpers=initHelpers(window,document);
function initHelpers(p,l,A){function t(b){(u=b)?l.querySelector("#HelperCSS")||(b=e("link",{href:"http://kooiinc.github.io/JSHelpers/Helpers.css",type:"text/css",rel:"stylesheet",id:"HelperCSS"}),l.querySelector("head").appendChild(b)):(b=document.querySelector("#HelperCSS"))&&document.querySelector("head").removeChild(b)}function v(b){if(p.jQuery)return b&&Object.ofType(b,Function)?b():!0;var c=e("script",{src:"//code.jquery.com/jquery-2.1.1.min.js",id:"jqloaded"});l.querySelector("head").appendChild(c);
b&&m(b,Function)&&c.addEventListener("load",b)}function w(){function b(){var a=h(arguments);return this.replace(/(\{\d+\})/g,function(d){return a[+d.substr(1,d.length-2)||0]})}function c(a){var d={sum:0};(a||this).map(function(a){this[a]=a in this?this[a]+1:1;this.sum+=1;return a},d);return d}if(x)return!0;String.Format=function(){var a=h(arguments);return b.apply(a[0],a.slice(1))};String.prototype.format=function(){return b.apply(this,arguments)};String.prototype.repeat=function(a){for(var d="";a--;)d+=
this;return d};Boolean.prototype.yn=function(){return 0==this?"no":"yes"};Number.prototype.toRange=Number.prototype.toRange||function(a,d){d=d||0;a=m(a,Function)?a:function(a,b){return b+d};return String(Array(this.valueOf())).split(",").map(a)};Number.prototype.pretty=Number.prototype.pretty||function(a,b){return B(this,a,b)};Function.prototype.andThen=function(){var a=h(arguments),b=a.slice(1);this();return a[0]&&b.length?a[0].andThen.apply(a[0],b):1==a.length?a[0]():!0};Function.prototype.partial=
Function.prototype.partial||function(){var a=[].slice.call(arguments),b=this;return function(){return b.apply(null,a.concat([].slice.call(arguments)))}};Function.prototype.partialx=function(){var a=this,b=Array.prototype.slice.call(arguments);return function(){for(var c=0,g=0;g<b.length&&c<arguments.length;g++)if(b[g]===A||null===b[g])b[g]=arguments[c++];return a.apply(this,b)}};String.prototype.charIsUpper=function(a){a=this.charAt(a);return/[A-Z]|[\u0080-\u024F]/.test(a)&&a===a.toUpperCase()};String.prototype.firstUp=
function(){return this.slice(0,1).toUpperCase()+this.slice(1).toLowerCase()};String.prototype.isValidEmail=function(){return/^[\w._-]{1,}[+]?[\w._-]{0,}@[\w.-]+\.[a-zA-Z]{2,6}$/.test(this)};Array.prototype.toRE=function(){try{return RegExp.apply(null,this)}catch(a){return/.*/}};Array.toRE=function(a){return[].toRE.call(a)};Array.prototype.toCheckboxValues=Array.prototype.toCheckboxValues||function(a){a=a||[];var b=[];this.map(function(c,g){b.push({check:-1>>0;if(!m(a,Function))throw new TypeError;for(var g=[],h=2<=arguments.length?arguments[1]:void 0,k=0;k<
c;k++)if(k in b){var e=b[k];a.call(h,e,k,b)&&g.push(e)}return g});Array.prototype.map||(Array.prototype.map=function(a){if(void 0===this||null===this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(!m(a,Function))throw new TypeError;for(var g=Array(c),e=2<=arguments.length?arguments[1]:void 0,k=0;k<c;k++)k in b&&(g[k]=a.call(e,b[k],k,b));return g});Array.prototype.uniquify=function(a){return(a||this).filter(function(a){return this[a]?!1:(this[a]=!0,!0)},{})};Array.prototype.frequencies=Array.prototype.frequencies||
c;Array.prototype.each=Array.prototype.each||function(a,b){for(var c=0;c<this.length;c++)b?this[c]=a(this[c]):a(this[c]);return this};Object.print=function(a,b){return'
'+JSON.stringify(a,null,b||"  ")+"
"};Object.format=function(a,b){return'
'+JSON.stringify(a,null,b||"  ")+"
"};Object.ofType=m;return x=!0}function y(){var b=l.querySelector("[data-linkid]");b&&!b.querySelector(".linkhover")&&b.appendChild(e("div",{className:"linkhover","data-dyn":"true"}));
v(function(){$("#helperload").fadeOut(900);$(document).on("mouseover",".solink",C);$(document).on("click","[data-link]",D)})}function C(b){if($(this).attr("data-link"))return!0;$.ajax({url:"http://www.nicon.nl/node/stackx/questionx",data:{qid:$(".solink").first().attr("data-linkid")},method:"post",success:function(b){b=b.items&&b.items[0]||b;var a=$(".solink").first();a.find(".linkhover").first().html("
Click logo to view the related question:

"+b.title+'
Asked by
'+b.owner.display_name+"
; rep "+b.owner.reputation+"; question views: "+b.view_count);a.attr("data-link",b.link)}});return!0}function D(b){b.stopPropagation();b=this.getAttribute("data-link");var c;(c=document.querySelector('a[href="'+b+'"]'))||(b=e("a",{href:b,target:"_blank",style:{display:"none"}}),document.body.appendChild(b),c=b);c.click()}function z(){var b=document.querySelector("#result")||function(){var a=e("div",{id:"result"});
document.body.appendChild(a);return a}(),c=h(arguments),a=c.slice(-1)[0],d=/clear|clrscr|direct|opts|useopts|continuous/i,a=Object.ofType(a,Object)&&Object.keys(a).filter(function(a){return d.test(a)}).length?a.opts instanceof Object?a.opts:a:{empty:1};!a.empty&&(c=c.slice(0,-1));if(a.clrscr)return b.innerHTML="";a.clear&&(b.innerHTML="");if(a.continuous)return b.innerHTML+=c.join("").replace(/\n/g,"
");var f=e("p");f.innerHTML=c.join("").replace(/\n/g,"
").replace(/`(\b[^`]*)?`/g,"$1");
b.appendChild(f);return a.direct?f.className="show":setTimeout(function(){f.className="show"},1E3*+a.timed||0)}function e(b,c){var a=l.createElement(b);if(c&&m(c,Object))for(var d in c)if(c.hasOwnProperty(d))if(/style/i.test(d))for(var f in c[d])c[d].hasOwnProperty(f)&&(a.style[f]=c[d][f]);else a[d]=c[d];return a}function h(b,c){for(var a=[];a.length<b.length;)a.push(b[a.length]);return c?a.slice(c):a}function m(b){if(!b)return!1;var c=arguments.length?h(arguments).slice(1):null,a=b.constructor;return c?
!!c.filter(function(b){return b===a}).length:a.constructor.name||(String(a).match(/^function\s*([^\s(]+)/im)||[0,"ANONYMOUS_CONSTRUCTOR"])[1]}function q(b){this.initial=h(arguments,1);this.funcp=function(){return b.partialx.apply(b,this.initial)};q.prototype.x||(q.prototype.x=function(){return this.funcp().apply(null,h(arguments))})}function r(b){function c(a,b){for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]instanceof Object?r(a[c]):a[c]);return b}return b instanceof Array?b.slice().map(function(a){return r(a)}):
b instanceof Date?new Date(b):b instanceof Object?c(b,{}):b}function B(b,c,a){function d(a,b){return String(a.toFixed(12)).split(/[.,]/)[1].substr(0,b)}var f=(""+b).split(/[.,]/),g=f[1]?f[1].length:0;b=g&&!a?d(b,g):f[1];a=c?".":",";return function(a,b){for(var c=String(a).split(""),n=-3;0<c.length+n;)c.splice(n,0,b),n-=4;return c.join("")}(f[0],c?",":".")+(f[1]?a+b:"")}var u=!1,x=w(),E={report:function(){var b=document.querySelector("#result")||function(){var a=e("div",{id:"result"});document.querySelector("body").appendChild(a);
return a}(),c=e("p");c.innerHTML=h(arguments).join("");b.appendChild(c);u&&setTimeout(function(){c.className="show"},100)},useJQ:v,useCSS:t,log2Screen:z,initSO:y,printDirect:function(){return z.apply(null,h(arguments).concat({direct:!0}))},logClear:function(){var b=l.querySelector("#result");return b&&(b.innerHTML=""),!0},Partial:q,cloneObj:r,randomID:function(){return"_"+Math.floor(1E4+1E7*Math.random()).toString(16)},extendDate:function(){Date.prototype.add||function(){function b(a){for(var b=a.length,
c={byArr:a};--b;)c[a[b]]=b;return c}function c(){this.cd=this.getDate();this.cm=this.getMonth()+1;this.cy=this.getFullYear();this.ch=this.getHours()||Number(0);this.cmin=this.getMinutes()||Number(0);this.cs=this.getSeconds()||Number(0);this.cms=this.getMilliseconds()||Number(0);this.dow=this.getDay()||Number(0)}function a(a){a=a||this;a.cy||c.call(a);var b=a.language||"EN";a={yyyy:a.cy,m:a.cm,d:a.cd,h:a.ch,min:a.cmin,s:a.cs,ms:a.cms,dow:a.dow};var b={mm:a.m.padLeft(),dd:a.d.padLeft(),hh:a.h.padLeft(),
mi:a.min.padLeft(),ss:a.s.padLeft(),mss:a.ms.padLeft(3),M:h[b].byArr[a.m-1],MM:e[b].byArr[a.m-1],wd:k[b].byArr[a.dow],WD:g[b].byArr[a.dow],WDU:g[b].byArr[a.dow].firstUp()},d=[],f;for(f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);for(f in a)a.hasOwnProperty(f)&&d.push("(\\b"+f+"\\b)");a.re=new RegExp(d.join("|"),"g");return a}function d(a){var b=this+"";return(Math.pow(10,(a||2)-b.length)+b).slice(1)}var f={d:"Date",m:"Month",y:"FullYear",h:"Hours",mi:"Minutes",s:"Seconds",ms:"MilliSeconds",get:function(a,
b){return b&&"get"+this[a]||"set"+this[a]}},g={NL:b("zondag maandag dinsdag woensdag donderdag vrijdag zaterdag".split(" ")),EN:b("sunday monday tuesday wednesday thursday friday saturday".split(" "))},e={NL:b("januari februari maart april mei juni juli augustus september oktober november december".split(" ")),EN:b("january february march april may june july august september october november december".split(" "))},k={NL:b("zo ma di wo do vr za".split(" ")),EN:b("su mo tu we th fr sa".split(" "))},
h={NL:b("jan feb mrt apr mei jun jul aug sep okt nov dec".split(" ")),EN:b("jan feb mrch apr may jun jul aug sep okt nov dec".split(" "))};Number.prototype.padLeft=Number.prototype.padLeft||d;Date.prototype.setFormat=function(a){this.strformat=a;return this};Date.prototype.changeLanguage=function(a){this.language=/^[EN][NL]$/i.test(a)&&a.toUpperCase()||"NL";return this};Date.prototype.set=function(a,b){b=Number(b);b-="m"===a&&1||0;a=f.get(a);this[a](b);c.call(this);return this};Date.prototype.add=
function(a,b){if(a instanceof Object){var d;d=void 0;if(+a)d=this.setSeconds(this.getSeconds()+ +a);else{d=d&&"UTC"||"";for(var g in a){var e=g,h=+a[g],e=e&&e.slice(0,1).toUpperCase()+e.slice(1).toLowerCase()||"",e=/Fullyear/.test(e)&&"FullYear"||e;this["set"+d+e]&&this["set"+d+e](this["get"+d+e]()+h)}d=this}return d}b=Number(b)||1;d=f.get(a);g=f.get(a,1);this[d](this[g]()+b);c.call(this);return this};Date.prototype.format=function(b){b=b||this.strformat||"yyyy/mm/dd hh:mi:ss";var c=a.call(this);
return b.replace(c.re,function(a){return c[a]}).replace(/~/g,"")}}()}};/fiddle|stacksnippets/i.test(self.location.href)&&(w(),t(!0),p.addEventListener("load",function(){l.body.insertBefore(e("div",{id:"helperload"}),l.body.firstChild)}),p.addEventListener("load",y));return E};<commit_msg>Update minified js<commit_after>Helpers=initHelpers(window,document);
function initHelpers(q,l,B){function u(c){(v=c)?l.querySelector("#HelperCSS")||(c=g("link",{href:"http://kooiinc.github.io/JSHelpers/Helpers.css",type:"text/css",rel:"stylesheet",id:"HelperCSS"}),l.querySelector("head").appendChild(c)):(c=document.querySelector("#HelperCSS"))&&document.querySelector("head").removeChild(c)}function w(c){if(q.jQuery)return c&&Object.ofType(c,Function)?c():!0;var d=g("script",{src:"//code.jquery.com/jquery-2.1.1.min.js",id:"jqloaded"});l.querySelector("head").appendChild(d);
c&&n(c,Function)&&d.addEventListener("load",c)}function x(){function c(){var a=h(arguments);return this.replace(/(\{\d+\})/g,function(e){return a[+e.substr(1,e.length-2)||0]})}function d(a){var e={sum:0};(a||this).map(function(a){this[a]=a in this?this[a]+1:1;this.sum+=1;return a},e);return e}if(y)return!0;String.Format=function(){var a=h(arguments);return c.apply(a[0],a.slice(1))};String.prototype.format=function(){return c.apply(this,arguments)};String.prototype.repeat=function(a){for(var e="";a--;)e+=
this;return e};Boolean.prototype.yn=function(){return 0==this?"no":"yes"};Number.prototype.toRange=Number.prototype.toRange||function(a,e){e=e||0;a=n(a,Function)?a:function(a,c){return c+e};return String(Array(this.valueOf())).split(",").map(a)};Number.prototype.pretty=Number.prototype.pretty||function(a,e){return C(this,a,e)};Function.prototype.andThen=function(){var a=h(arguments),e=a.slice(1);this();return a[0]&&e.length?a[0].andThen.apply(a[0],e):1==a.length?a[0]():!0};Function.prototype.partial=
Function.prototype.partial||function(){var a=[].slice.call(arguments),e=this;return function(){return e.apply(null,a.concat([].slice.call(arguments)))}};Function.prototype.partialx=function(){var a=this,e=Array.prototype.slice.call(arguments);return function(){for(var c=0,d=0;d<e.length&&c<arguments.length;d++)if(e[d]===B||null===e[d])e[d]=arguments[c++];return a.apply(this,e)}};String.prototype.charIsUpper=function(a){a=this.charAt(a);return/[A-Z]|[\u0080-\u024F]/.test(a)&&a===a.toUpperCase()};String.prototype.firstUp=
function(){return this.slice(0,1).toUpperCase()+this.slice(1).toLowerCase()};String.prototype.isValidEmail=function(){return/^[\w._-]{1,}[+]?[\w._-]{0,}@[\w.-]+\.[a-zA-Z]{2,6}$/.test(this)};Array.prototype.toRE=function(){try{return RegExp.apply(null,this)}catch(a){return/.*/}};Array.toRE=function(a){return[].toRE.call(a)};Array.prototype.toCheckboxValues=Array.prototype.toCheckboxValues||function(a){a=a||[];var c=[];this.map(function(d,m){c.push({check:-1>>0;if(!n(a,Function))throw new TypeError;for(var m=[],h=2<=arguments.length?arguments[1]:void 0,k=0;k<
d;k++)if(k in c){var g=c[k];a.call(h,g,k,c)&&m.push(g)}return m});Array.prototype.map||(Array.prototype.map=function(a){if(void 0===this||null===this)throw new TypeError;var c=Object(this),d=c.length>>>0;if(!n(a,Function))throw new TypeError;for(var m=Array(d),g=2<=arguments.length?arguments[1]:void 0,k=0;k<d;k++)k in c&&(m[k]=a.call(g,c[k],k,c));return m});Array.prototype.uniquify=function(a){return(a||this).filter(function(a){return this[a]?!1:(this[a]=!0,!0)},{})};Array.prototype.frequencies=Array.prototype.frequencies||
d;Array.prototype.each=Array.prototype.each||function(a,c){for(var d=0;d<this.length;d++)c?this[d]=a(this[d]):a(this[d]);return this};Object.extend=function(a,c){for(var d in c)b.hasOwnProperty(d)&&(a[d]=c[d]);return a};Object.print=function(a,c){return'
'+JSON.stringify(a,null,c||"  ")+"
"};Object.format=function(a,c){return'
'+JSON.stringify(a,null,c||"  ")+"
"};Object.ofType=n;return y=!0}function z(){var c=l.querySelector("[data-linkid]");c&&!c.querySelector(".linkhover")&&
c.appendChild(g("div",{className:"linkhover","data-dyn":"true"}));w(function(){$("#helperload").fadeOut(900);$(document).on("mouseover",".solink",D);$(document).on("click","[data-link]",E)})}function D(c){if($(this).attr("data-link"))return!0;$.ajax({url:"http://www.nicon.nl/node/stackx/questionx",data:{qid:$(".solink").first().attr("data-linkid")},method:"post",success:function(c){c=c.items&&c.items[0]||c;var a=$(".solink").first();a.find(".linkhover").first().html("
Click logo to view the related question:

"+
c.title+'
Asked by
'+c.owner.display_name+"
; rep "+c.owner.reputation+"; question views: "+c.view_count);a.attr("data-link",c.link)}});return!0}function E(c){c.stopPropagation();c=this.getAttribute("data-link");var d;(d=document.querySelector('a[href="'+c+'"]'))||(c=g("a",{href:c,target:"_blank",style:{display:"none"}}),document.body.appendChild(c),d=c);d.click()}function A(){var c=document.querySelector("#result")||
function(){var a=g("div",{id:"result"});document.body.appendChild(a);return a}(),d=h(arguments),a=d.slice(-1)[0],e=/clear|clrscr|direct|opts|useopts|continuous/i,a=Object.ofType(a,Object)&&Object.keys(a).filter(function(a){return e.test(a)}).length?a.opts instanceof Object?a.opts:a:{empty:1};!a.empty&&(d=d.slice(0,-1));if(a.clrscr)return c.innerHTML="";a.clear&&(c.innerHTML="");if(a.continuous)return c.innerHTML+=d.join("").replace(/\n/g,"
");var f=g("p");f.innerHTML=d.join("").replace(/\n/g,"
").replace(/`(\b[^`]*)?`/g,
"$1");c.appendChild(f);return a.direct?f.className="show":setTimeout(function(){f.className="show"},1E3*+a.timed||0)}function g(c,d){var a=l.createElement(c);if(d&&n(d,Object))for(var e in d)if(d.hasOwnProperty(e))if(/style/i.test(e))for(var f in d[e])d[e].hasOwnProperty(f)&&(a.style[f]=d[e][f]);else a[e]=d[e];return a}function h(c,d){for(var a=[];a.length<c.length;)a.push(c[a.length]);return d?a.slice(d):a}function n(c){if(!c)return!1;var d=arguments.length?h(arguments).slice(1):null,
a=c.constructor;return d?!!d.filter(function(c){return c===a}).length:a.constructor.name||(String(a).match(/^function\s*([^\s(]+)/im)||[0,"ANONYMOUS_CONSTRUCTOR"])[1]}function r(c){this.initial=h(arguments,1);this.funcp=function(){return c.partialx.apply(c,this.initial)};r.prototype.x||(r.prototype.x=function(){return this.funcp().apply(null,h(arguments))})}function t(c){function d(a,c){for(var d in a)a.hasOwnProperty(d)&&(c[d]=a[d]instanceof Object?t(a[d]):a[d]);return c}return c instanceof Array?
c.slice().map(function(a){return t(a)}):c instanceof Date?new Date(c):c instanceof Object?d(c,{}):c}function C(c,d,a){function e(a,c){return String(a.toFixed(12)).split(/[.,]/)[1].substr(0,c)}var f=(""+c).split(/[.,]/),g=f[1]?f[1].length:0;c=g&&!a?e(c,g):f[1];a=d?".":",";return function(a,c){for(var d=String(a).split(""),p=-3;0<d.length+p;)d.splice(p,0,c),p-=4;return d.join("")}(f[0],d?",":".")+(f[1]?a+c:"")}var v=!1,y=x(),F={report:function(){var c=document.querySelector("#result")||function(){var a=
g("div",{id:"result"});document.querySelector("body").appendChild(a);return a}(),d=g("p");d.innerHTML=h(arguments).join("");c.appendChild(d);v&&setTimeout(function(){d.className="show"},100)},useJQ:w,useCSS:u,log2Screen:A,initSO:z,printDirect:function(){return A.apply(null,h(arguments).concat({direct:!0}))},logClear:function(){var c=l.querySelector("#result");return c&&(c.innerHTML=""),!0},Partial:r,cloneObj:t,randomID:function(){return"_"+Math.floor(1E4+1E7*Math.random()).toString(16)},extendDate:function(){Date.prototype.add||
function(){function c(a){for(var c=a.length,d={byArr:a};--c;)d[a[c]]=c;return d}function d(){this.cd=this.getDate();this.cm=this.getMonth()+1;this.cy=this.getFullYear();this.ch=this.getHours()||Number(0);this.cmin=this.getMinutes()||Number(0);this.cs=this.getSeconds()||Number(0);this.cms=this.getMilliseconds()||Number(0);this.dow=this.getDay()||Number(0)}function a(a){a=a||this;a.cy||d.call(a);var c=a.language||"EN";a={yyyy:a.cy,m:a.cm,d:a.cd,h:a.ch,min:a.cmin,s:a.cs,ms:a.cms,dow:a.dow};var c={mm:a.m.padLeft(),
dd:a.d.padLeft(),hh:a.h.padLeft(),mi:a.min.padLeft(),ss:a.s.padLeft(),mss:a.ms.padLeft(3),M:l[c].byArr[a.m-1],MM:h[c].byArr[a.m-1],wd:k[c].byArr[a.dow],WD:g[c].byArr[a.dow],WDU:g[c].byArr[a.dow].firstUp()},e=[],f;for(f in c)c.hasOwnProperty(f)&&(a[f]=c[f]);for(f in a)a.hasOwnProperty(f)&&e.push("(\\b"+f+"\\b)");a.re=new RegExp(e.join("|"),"g");return a}function e(a){var c=this+"";return(Math.pow(10,(a||2)-c.length)+c).slice(1)}var f={d:"Date",m:"Month",y:"FullYear",h:"Hours",mi:"Minutes",s:"Seconds",
ms:"MilliSeconds",get:function(a,c){return c&&"get"+this[a]||"set"+this[a]}},g={NL:c("zondag maandag dinsdag woensdag donderdag vrijdag zaterdag".split(" ")),EN:c("sunday monday tuesday wednesday thursday friday saturday".split(" "))},h={NL:c("januari februari maart april mei juni juli augustus september oktober november december".split(" ")),EN:c("january february march april may june july august september october november december".split(" "))},k={NL:c("zo ma di wo do vr za".split(" ")),EN:c("su mo tu we th fr sa".split(" "))},
l={NL:c("jan feb mrt apr mei jun jul aug sep okt nov dec".split(" ")),EN:c("jan feb mrch apr may jun jul aug sep okt nov dec".split(" "))};Number.prototype.padLeft=Number.prototype.padLeft||e;Date.prototype.setFormat=function(a){this.strformat=a;return this};Date.prototype.changeLanguage=function(a){this.language=/^[EN][NL]$/i.test(a)&&a.toUpperCase()||"NL";return this};Date.prototype.set=function(a,c){c=Number(c);c-="m"===a&&1||0;a=f.get(a);this[a](c);d.call(this);return this};Date.prototype.add=
function(a,c){if(a instanceof Object){var e;e=void 0;if(+a)e=this.setSeconds(this.getSeconds()+ +a);else{e=e&&"UTC"||"";for(var g in a){var h=g,k=+a[g],h=h&&h.slice(0,1).toUpperCase()+h.slice(1).toLowerCase()||"",h=/Fullyear/.test(h)&&"FullYear"||h;this["set"+e+h]&&this["set"+e+h](this["get"+e+h]()+k)}e=this}return e}c=Number(c)||1;e=f.get(a);g=f.get(a,1);this[e](this[g]()+c);d.call(this);return this};Date.prototype.format=function(c){c=c||this.strformat||"yyyy/mm/dd hh:mi:ss";var d=a.call(this);
return c.replace(d.re,function(a){return d[a]}).replace(/~/g,"")}}()}};/fiddle|stacksnippets/i.test(self.location.href)&&(x(),u(!0),q.addEventListener("load",function(){l.body.insertBefore(g("div",{id:"helperload"}),l.body.firstChild)}),q.addEventListener("load",z));return F};<|endoftext|>


<commit_before>import Analyzer from 'parser/core/Analyzer';
import EventEmitter from 'parser/core/modules/EventEmitter';
import EventFilter from 'parser/core/EventFilter';

const debug = false;

const APPLY = 'apply';
const REMOVE = 'remove';

class Entities extends Analyzer {
  static dependencies = {
    eventEmitter: EventEmitter,
  };

  static get changedebuffstack() {
    return new EventFilter('changedebuffstack');
  }

  static get changebuffstack() {
    return new EventFilter('changebuffstack');
  }

  getEntities() {
    throw new Error('Not implemented');
  }
  getEntity(event) {
    throw new Error('Not implemented');
  }

  on_applybuff(event) {
    this.applyBuff(event);
  }
  // TODO: Add a sanity check to the `refreshbuff` event that checks if a buff that's being refreshed was applied, if it wasn't it might be a broken pre-combat applied buff not shown in the combatantinfo event
  // We don't store/use durations, so refreshing buff is useless. Removing the buff actually interferes with the `minimalActiveTime` parameter of `getBuff`.
  // on_refreshbuff(event) {
  //   this.removeActiveBuff(event);
  //   this.applyActiveBuff(event);
  // }
  on_applybuffstack(event) {
    this.updateBuffStack(event);
  }
  on_removebuffstack(event) {
    this.updateBuffStack(event);
  }
  on_removebuff(event) {
    this.removeBuff(event);
  }

  on_applydebuff(event) {
    this.applyBuff(event, true);
  }
  on_applydebuffstack(event) {
    this.updateBuffStack(event, true);
  }
  on_removedebuffstack(event) {
    this.updateBuffStack(event, true);
  }
  on_removedebuff(event) {
    this.removeBuff(event, true);
  }

  applyBuff(event, isDebuff) {
    if (!this.owner.byPlayer(event) && !this.owner.toPlayer(event)) {
      // We don't need to know about debuffs on bosses or buffs on other players not caused by us, but we do want to know about our outgoing buffs, and other people's buffs on us
      return;
    }
    const entity = this.getEntity(event);
    if (!entity) {
      return;
    }

    debug && this.log(`Apply buff ${event.ability.name} to ${entity.name}`);

    const buff = {
      ...event,
      start: event.timestamp,
      end: null,
      stackHistory: [{ stacks: 1, timestamp: event.timestamp }],
      isDebuff,
    };

    // The initial buff counts as 1 stack, to make the `changebuffstack` event complete it's fired for all applybuff events, including buffs that aren't actually stackable.
    buff.stacks = 1;
    this._triggerChangeBuffStack(buff, event.timestamp, 0, 1);

    entity.buffs.push(buff);
  }

  updateBuffStack(event) {
    if (!this.owner.byPlayer(event) && !this.owner.toPlayer(event)) {
      // We don't need to know about debuffs on bosses or buffs on other players not caused by us, but we do want to know about our outgoing buffs, and other people's buffs on us
      return;
    }
    const entity = this.getEntity(event);
    if (!entity) {
      return;
    }

    debug && this.log(`Apply buff stack ${event.ability.name} to ${entity.name}`);

    const existingBuff = entity.buffs.find(item => item.ability.guid === event.ability.guid && item.end === null);
    if (existingBuff) {
      const oldStacks = existingBuff.stacks || 1; // the original spell counts as 1 stack
      existingBuff.stacks = event.stack;
      existingBuff.stackHistory.push({ stacks: event.stack, timestamp: event.timestamp });

      this._triggerChangeBuffStack(existingBuff, event.timestamp, oldStacks, existingBuff.stacks);
    } else {
      console.error('Buff stack updated while active buff wasn\'t known. Was this buff applied pre-combat? Maybe we should register the buff with start time as fight start when this happens, but it might also be a basic case of erroneous combatlog ordering.');
    }
  }

  removeBuff(event, isDebuff) {
    if (!this.owner.byPlayer(event) && !this.owner.toPlayer(event)) {
      // We don't need to know about debuffs on bosses or buffs on other players not caused by us, but we do want to know about our outgoing buffs, and other people's buffs on us
      return;
    }
    const entity = this.getEntity(event);
    if (!entity) {
      return;
    }

    debug && this.log(`Remove buff ${event.ability.name} from ${entity.name}`);

    const existingBuff = entity.buffs.find(item => item.ability.guid === event.ability.guid && item.end === null);
    if (existingBuff) {
      existingBuff.end = event.timestamp;
      existingBuff.stackHistory.push({ stacks: 0, timestamp: event.timestamp });

      this._triggerChangeBuffStack(existingBuff, event.timestamp, existingBuff.stacks, 0);
    } else {
      const buff = {
        ...event,
        start: this.owner.fight.start_time,
        end: event.timestamp,
        stackHistory: [{ stacks: 1, timestamp: this.owner.fight.start_time }, { stacks: 0, timestamp: event.timestamp }],
        isDebuff,
      };
      entity.buffs.push(buff);

      this._triggerChangeBuffStack(buff, event.timestamp, 1, 0);
    }
  }

  /**
   * Trigger a custom `changebuffstack` event that provides all information available about the buff and its stacks, most notably `oldStacks`, `newStacks` and `stacksGained`.
   * This event is also fired for `applybuff` where `oldStacks` will be 0 and `newStacks` will be 1. NOTE: This event is usually fired before the `applybuff` event!
   * This event is also fired for `removebuff` where `oldStacks` will be either the old stacks (if there were multiple) or 1 and `newStacks` will be 0. NOTE: This event is usually fired before the `removebuff` event!
   */
  _triggerChangeBuffStack(buff, timestamp, oldStacks, newStacks) {
    this.eventEmitter.fabricateEvent({
      ...buff,
      type: buff.isDebuff ? 'changedebuffstack' : 'changebuffstack',
      timestamp,
      oldStacks,
      newStacks,
      stacksGained: newStacks - oldStacks,
      stack: undefined,
    }, buff);
  }

  // Surely this can be done with a couple less loops???
  getBuffUptime(spellId, sourceID = this.owner.playerId) {
    const events = [];

    const entities = this.getEntities();
    Object.values(entities)
      .forEach(enemy => {
        enemy.getBuffHistory(spellId, sourceID)
          .forEach(buff => {
            events.push({
              timestamp: buff.start,
              type: APPLY,
              buff,
            });
            events.push({
              timestamp: buff.end !== null ? buff.end : this.owner.currentTimestamp, // buff end is null if it's still active, it can also be 0 if buff ended at pull
              type: REMOVE,
              buff,
            });
          });
      });

    let active = 0;
    let start = null;
    return events
      .sort((a, b) => a.timestamp - b.timestamp)
      .reduce((uptime, event) => {
        if (event.type === APPLY) {
          if (active === 0) {
            start = event.timestamp;
          }
          active += 1;
        }
        if (event.type === REMOVE) {
          active -= 1;
          if (active === 0) {
            uptime += event.timestamp - start;
          }
        }
        return uptime;
      }, 0);
  }
}

export default Entities;
<commit_msg>Removed new event filters<commit_after>import Analyzer from 'parser/core/Analyzer';
import EventEmitter from 'parser/core/modules/EventEmitter';

const debug = false;

const APPLY = 'apply';
const REMOVE = 'remove';

class Entities extends Analyzer {
  static dependencies = {
    eventEmitter: EventEmitter,
  };

  getEntities() {
    throw new Error('Not implemented');
  }
  getEntity(event) {
    throw new Error('Not implemented');
  }

  on_applybuff(event) {
    this.applyBuff(event);
  }
  // TODO: Add a sanity check to the `refreshbuff` event that checks if a buff that's being refreshed was applied, if it wasn't it might be a broken pre-combat applied buff not shown in the combatantinfo event
  // We don't store/use durations, so refreshing buff is useless. Removing the buff actually interferes with the `minimalActiveTime` parameter of `getBuff`.
  // on_refreshbuff(event) {
  //   this.removeActiveBuff(event);
  //   this.applyActiveBuff(event);
  // }
  on_applybuffstack(event) {
    this.updateBuffStack(event);
  }
  on_removebuffstack(event) {
    this.updateBuffStack(event);
  }
  on_removebuff(event) {
    this.removeBuff(event);
  }

  on_applydebuff(event) {
    this.applyBuff(event, true);
  }
  on_applydebuffstack(event) {
    this.updateBuffStack(event, true);
  }
  on_removedebuffstack(event) {
    this.updateBuffStack(event, true);
  }
  on_removedebuff(event) {
    this.removeBuff(event, true);
  }

  applyBuff(event, isDebuff) {
    if (!this.owner.byPlayer(event) && !this.owner.toPlayer(event)) {
      // We don't need to know about debuffs on bosses or buffs on other players not caused by us, but we do want to know about our outgoing buffs, and other people's buffs on us
      return;
    }
    const entity = this.getEntity(event);
    if (!entity) {
      return;
    }

    debug && this.log(`Apply buff ${event.ability.name} to ${entity.name}`);

    const buff = {
      ...event,
      start: event.timestamp,
      end: null,
      stackHistory: [{ stacks: 1, timestamp: event.timestamp }],
      isDebuff,
    };

    // The initial buff counts as 1 stack, to make the `changebuffstack` event complete it's fired for all applybuff events, including buffs that aren't actually stackable.
    buff.stacks = 1;
    this._triggerChangeBuffStack(buff, event.timestamp, 0, 1);

    entity.buffs.push(buff);
  }

  updateBuffStack(event) {
    if (!this.owner.byPlayer(event) && !this.owner.toPlayer(event)) {
      // We don't need to know about debuffs on bosses or buffs on other players not caused by us, but we do want to know about our outgoing buffs, and other people's buffs on us
      return;
    }
    const entity = this.getEntity(event);
    if (!entity) {
      return;
    }

    debug && this.log(`Apply buff stack ${event.ability.name} to ${entity.name}`);

    const existingBuff = entity.buffs.find(item => item.ability.guid === event.ability.guid && item.end === null);
    if (existingBuff) {
      const oldStacks = existingBuff.stacks || 1; // the original spell counts as 1 stack
      existingBuff.stacks = event.stack;
      existingBuff.stackHistory.push({ stacks: event.stack, timestamp: event.timestamp });

      this._triggerChangeBuffStack(existingBuff, event.timestamp, oldStacks, existingBuff.stacks);
    } else {
      console.error('Buff stack updated while active buff wasn\'t known. Was this buff applied pre-combat? Maybe we should register the buff with start time as fight start when this happens, but it might also be a basic case of erroneous combatlog ordering.');
    }
  }

  removeBuff(event, isDebuff) {
    if (!this.owner.byPlayer(event) && !this.owner.toPlayer(event)) {
      // We don't need to know about debuffs on bosses or buffs on other players not caused by us, but we do want to know about our outgoing buffs, and other people's buffs on us
      return;
    }
    const entity = this.getEntity(event);
    if (!entity) {
      return;
    }

    debug && this.log(`Remove buff ${event.ability.name} from ${entity.name}`);

    const existingBuff = entity.buffs.find(item => item.ability.guid === event.ability.guid && item.end === null);
    if (existingBuff) {
      existingBuff.end = event.timestamp;
      existingBuff.stackHistory.push({ stacks: 0, timestamp: event.timestamp });

      this._triggerChangeBuffStack(existingBuff, event.timestamp, existingBuff.stacks, 0);
    } else {
      const buff = {
        ...event,
        start: this.owner.fight.start_time,
        end: event.timestamp,
        stackHistory: [{ stacks: 1, timestamp: this.owner.fight.start_time }, { stacks: 0, timestamp: event.timestamp }],
        isDebuff,
      };
      entity.buffs.push(buff);

      this._triggerChangeBuffStack(buff, event.timestamp, 1, 0);
    }
  }

  /**
   * Trigger a custom `changebuffstack` event that provides all information available about the buff and its stacks, most notably `oldStacks`, `newStacks` and `stacksGained`.
   * This event is also fired for `applybuff` where `oldStacks` will be 0 and `newStacks` will be 1. NOTE: This event is usually fired before the `applybuff` event!
   * This event is also fired for `removebuff` where `oldStacks` will be either the old stacks (if there were multiple) or 1 and `newStacks` will be 0. NOTE: This event is usually fired before the `removebuff` event!
   */
  _triggerChangeBuffStack(buff, timestamp, oldStacks, newStacks) {
    this.eventEmitter.fabricateEvent({
      ...buff,
      type: buff.isDebuff ? 'changedebuffstack' : 'changebuffstack',
      timestamp,
      oldStacks,
      newStacks,
      stacksGained: newStacks - oldStacks,
      stack: undefined,
    }, buff);
  }

  // Surely this can be done with a couple less loops???
  getBuffUptime(spellId, sourceID = this.owner.playerId) {
    const events = [];

    const entities = this.getEntities();
    Object.values(entities)
      .forEach(enemy => {
        enemy.getBuffHistory(spellId, sourceID)
          .forEach(buff => {
            events.push({
              timestamp: buff.start,
              type: APPLY,
              buff,
            });
            events.push({
              timestamp: buff.end !== null ? buff.end : this.owner.currentTimestamp, // buff end is null if it's still active, it can also be 0 if buff ended at pull
              type: REMOVE,
              buff,
            });
          });
      });

    let active = 0;
    let start = null;
    return events
      .sort((a, b) => a.timestamp - b.timestamp)
      .reduce((uptime, event) => {
        if (event.type === APPLY) {
          if (active === 0) {
            start = event.timestamp;
          }
          active += 1;
        }
        if (event.type === REMOVE) {
          active -= 1;
          if (active === 0) {
            uptime += event.timestamp - start;
          }
        }
        return uptime;
      }, 0);
  }
}

export default Entities;
<|endoftext|>