<commit_before>/*global tarteaucitron, ga, Shareaholic, stLight*/

// addthis
tarteaucitron.services.addthis = {
    "key": "addthis",
    "type": "social",
    "name": "AddThis",
    "uri": "http://www.addthis.com/privacy/privacy-policy#publisher-visitors",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.addthisPubId === undefined) {
            return;
        }
        tarteaucitron.fallback(['addthis_sharing_toolbox'], '');
        tarteaucitron.addScript('//s7.addthis.com/js/300/addthis_widget.js#pubid=' + tarteaucitron.user.addthisPubId);
    },
    "fallback": function () {
        "use strict";
        var cookies = ['__atuvc'],
            id = 'addthis';

        tarteaucitron.cookie.purge(cookies);
        tarteaucitron.fallback(['addthis_sharing_toolbox'], tarteaucitron.engage(id));
    }
};

// clicky
tarteaucitron.services.clicky = {
    "key": "clicky",
    "type": "analytics",
    "name": "Clicky",
    "uri": "https://clicky.com/terms",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.clickyId === undefined) {
            return;
        }
        var clicky_site_ids = clicky_site_ids || [];
        clicky_site_ids.push(tarteaucitron.user.clickyId);
        tarteaucitron.addScript('//static.getclicky.com/js', '', function () {
            if (typeof tarteaucitron.user.clickyMore === 'function') {
                tarteaucitron.user.clickyMore();
            }
        });
    },
    "fallback": function () {
        "use strict";
        var cookies = ['_jsuid', '_eventqueue', '_referrer_og', '_utm_og', '_first_pageview', 'clicky_olark'];
        tarteaucitron.cookie.purge(cookies);
    }
};

// disqus
tarteaucitron.services.disqus = {
    "key": "disqus",
    "type": "social",
    "name": "Disqus",
    "uri": "https://help.disqus.com/customer/portal/articles/466259-privacy-policy",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.disqusShortname === undefined) {
            return;
        }
        tarteaucitron.addScript('//' + tarteaucitron.user.disqusShortname + '.disqus.com/embed.js');
        tarteaucitron.addScript('//' + tarteaucitron.user.disqusShortname + '.disqus.com/count.js');
    },
    "fallback": function () {
        "use strict";
        var id = 'disqus';

        if (document.getElementById('disqus_thread')) {
            document.getElementById('disqus_thread').innerHTML = tarteaucitron.engage(id);
        }
    }
};

// facebook
tarteaucitron.services.facebook = {
    "key": "facebook",
    "type": "social",
    "name": "Facebook",
    "uri": "https://www.facebook.com/help/cookies/",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.fallback(['fb-like'], '');
        tarteaucitron.addScript('//connect.facebook.net/fr_FR/sdk.js#xfbml=1&version=v2.0', 'facebook-jssdk');
    },
    "fallback": function () {
        "use strict";
        tarteaucitron.fallback(['fb-post', 'fb-follow', 'fb-comments', 'fb-activity', 'fb-like-box', 'fb-send', 'fb-share-button', 'fb-like'], 'Facebook');
    }
};

// ferank
tarteaucitron.services.ferank = {
    "key": "ferank",
    "type": "analytics",
    "name": "FERank",
    "uri": "https://www.ferank.fr/respect-vie-privee/#mesureaudience",
    "needConsent": false,
    "js": function () {
        "use strict";
        tarteaucitron.addScript('//static.ferank.fr/pixel.js', '', function () {
            if (typeof tarteaucitron.user.ferankMore === 'function') {
                tarteaucitron.user.ferankMore();
            }
        });
    }
};

// google+
tarteaucitron.services.gplus = {
    "key": "gplus",
    "type": "social",
    "name": "Google+",
    "uri": "http://www.google.fr/intl/policies/privacy/",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.addScript('https://apis.google.com/js/platform.js');
    },
    "fallback": function () {
        "use strict";
        tarteaucitron.fallback(['g-page', 'g-plus', 'g-plusone'], 'Google+');
    }
};

// google adsense
tarteaucitron.services.adsense = {
    "key": "adsense",
    "type": "ads",
    "name": "Adsense (Google)",
    "uri": "http://www.google.com/ads/preferences/",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.addScript('//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js');
    },
    "fallback": function () {
        "use strict";
        var id = 'adsense';

        tarteaucitron.fallback(['adsbygoogle'], tarteaucitron.engage(id));
    }
};

// google analytics
tarteaucitron.services.analytics = {
    "key": "analytics",
    "type": "analytics",
    "name": "Analytics (Google)",
    "uri": "https://support.google.com/analytics/answer/6004245",
    "needConsent": true,
    "js": function () {
        "use strict";
        window.GoogleAnalyticsObject = 'ga';
        window.ga = window.ga || function () {
            window.ga.q = window.ga.q || [];
            window.ga.q.push(arguments);
        };
        window.ga.l = new Date();

        tarteaucitron.addScript('//www.google-analytics.com/analytics.js', '', function () {
            ga('create', tarteaucitron.user.analyticsUa, {'cookieExpires': 34128000});
            ga('send', 'pageview');
            if (typeof tarteaucitron.user.analyticsMore === 'function') {
                tarteaucitron.user.analyticsMore();
            }
        });
    },
    "grayJs": function () {
        "use strict";
        window.GoogleAnalyticsObject = 'ga';
        window.ga = window.ga || function () {
            window.ga.q = window.ga.q || [];
            window.ga.q.push(arguments);
        };
        window.ga.l = new Date();

        tarteaucitron.addScript('//www.google-analytics.com/analytics.js', '', function () {
            ga('create', tarteaucitron.user.analyticsUa, {'cookieExpires': 86400});
            ga('set', 'anonymizeIp', true);
            ga('set', 'forceSSL', true);
            ga('send', 'pageview');
            if (typeof tarteaucitron.user.analyticsMore === 'function') {
                tarteaucitron.user.analyticsMore();
            }
        });
    },
    "fallback": function () {
        "use strict";
        var cookies = ['_ga', '_gat', '__utma', '__utmb', '__utmc', '__utmt', '__utmz'];
        tarteaucitron.cookie.purge(cookies);
    }
};

// linkedin
tarteaucitron.services.linkedin = {
    "key": "linkedin",
    "type": "social",
    "name": "Linkedin",
    "uri": "https://www.linkedin.com/legal/cookie_policy",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.fallback(['tacLinkedin'], '');
        tarteaucitron.addScript('//platform.linkedin.com/in.js');
    },
    "fallback": function () {
        "use strict";
        tarteaucitron.fallback(['tacLinkedin'], 'Linkedin');
    }
};

// pinterest
tarteaucitron.services.pinterest = {
    "key": "pinterest",
    "type": "social",
    "name": "Pinterest",
    "uri": "https://about.pinterest.com/privacy-policy",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.addScript('//assets.pinterest.com/js/pinit.js');
    }
};

// shareaholic
tarteaucitron.services.shareaholic = {
    "key": "shareaholic",
    "type": "social",
    "name": "Shareaholic",
    "uri": "https://shareaholic.com/privacy/choices",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.shareaholicSiteId === undefined) {
            return;
        }

        tarteaucitron.fallback(['shareaholic-canvas'], '');
        tarteaucitron.addScript('//dsms0mj1bbhn4.cloudfront.net/assets/pub/shareaholic.js', '', function () {
            try {
                Shareaholic.init(tarteaucitron.user.shareaholicSiteId);
            } catch (e) {}
        });
    },
    "fallback": function () {
        "use strict";
        var cookies = ['__utma', '__utmb', '__utmc', '__utmz'],
            id = 'shareaholic';

        tarteaucitron.cookie.purge(cookies);
        tarteaucitron.fallback(['shareaholic-canvas'], tarteaucitron.engage(id));
    }
};

// sharethis
tarteaucitron.services.sharethis = {
    "key": "sharethis",
    "type": "social",
    "name": "ShareThis",
    "uri": "http://www.sharethis.com/legal/privacy/",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.sharethisPublisher === undefined) {
            return;
        }
        var switchTo5x = true,
            uri = ('https:' === document.location.protocol ? 'https://ws' : 'http://w') + '.sharethis.com/button/buttons.js';

        tarteaucitron.fallback(['tacSharethis'], '');
        tarteaucitron.addScript(uri, '', function () {
            stLight.options({publisher: tarteaucitron.user.sharethisPublisher, doNotHash: false, doNotCopy: false, hashAddressBar: false});
        });
    },
    "fallback": function () {
        "use strict";
        var cookies = ['__unam'],
            id = 'sharethis';

        tarteaucitron.cookie.purge(cookies);
        tarteaucitron.fallback(['tacSharethis'], tarteaucitron.engage(id));
    }
};

// twitter
tarteaucitron.services.twitter = {
    "key": "twitter",
    "type": "social",
    "name": "Twitter",
    "uri": "https://support.twitter.com/articles/20170514",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.fallback(['tacTwitter'], '');
        tarteaucitron.addScript('//platform.twitter.com/widgets.js', 'twitter-wjs');
    },
    "fallback": function () {
        "use strict";
        tarteaucitron.fallback(['tacTwitter'], 'Twitter');
    }
};

// xiti
tarteaucitron.services.xiti = {
    "key": "xiti",
    "type": "analytics",
    "name": "Xiti",
    "uri": "http://www.atinternet.com/politique-du-respect-de-la-vie-privee/",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.xitiId === undefined || document.getElementById('xitiTac') === undefined) {
            return;
        }
        var Xt_param = 's=' + tarteaucitron.user.xitiId + '&p=',
            Xt_r,
            Xt_h,
            Xt_i,
            Xt_s;
        try {
            Xt_r = top.document.referrer;
        } catch (e) {
            Xt_r = document.referrer;
        }
        Xt_h = new Date();
        Xt_i = '= 4) {
            Xt_s = screen;
            Xt_i += '&r=' + Xt_s.width + 'x' + Xt_s.height + 'x' + Xt_s.pixelDepth + 'x' + Xt_s.colorDepth;
        }
        document.getElementById('xitiTac').innerHTML = Xt_i + '&ref=' + Xt_r.replace(/[<>"]/g, '').replace(/&/g, '$') + '" title="Internet Audience">';

        if (typeof tarteaucitron.user.xitiMore === 'function') {
            tarteaucitron.user.xitiMore();
        }
    },
    "fallback": function () {
        "use strict";
        var cookies = [''];
        tarteaucitron.cookie.purge(cookies);
    }
};
<commit_msg>Updating the clicky marker<commit_after>/*global tarteaucitron, ga, Shareaholic, stLight*/

// addthis
tarteaucitron.services.addthis = {
    "key": "addthis",
    "type": "social",
    "name": "AddThis",
    "uri": "http://www.addthis.com/privacy/privacy-policy#publisher-visitors",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.addthisPubId === undefined) {
            return;
        }
        tarteaucitron.fallback(['addthis_sharing_toolbox'], '');
        tarteaucitron.addScript('//s7.addthis.com/js/300/addthis_widget.js#pubid=' + tarteaucitron.user.addthisPubId);
    },
    "fallback": function () {
        "use strict";
        var cookies = ['__atuvc'],
            id = 'addthis';

        tarteaucitron.cookie.purge(cookies);
        tarteaucitron.fallback(['addthis_sharing_toolbox'], tarteaucitron.engage(id));
    }
};

// clicky
tarteaucitron.services.clicky = {
    "key": "clicky",
    "type": "analytics",
    "name": "Clicky",
    "uri": "https://clicky.com/terms",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.clickyId === undefined) {
            return;
        }
        tarteaucitron.addScript('//static.getclicky.com/js', '', function () {
            if (typeof clicky.init === 'function') {
                clicky.init(tarteaucitron.user.clickyId);
            }
            if (typeof tarteaucitron.user.clickyMore === 'function') {
                tarteaucitron.user.clickyMore();
            }
        });
    },
    "fallback": function () {
        "use strict";
        var cookies = ['_jsuid', '_eventqueue', '_referrer_og', '_utm_og', '_first_pageview', 'clicky_olark'];
        tarteaucitron.cookie.purge(cookies);
    }
};

// disqus
tarteaucitron.services.disqus = {
    "key": "disqus",
    "type": "social",
    "name": "Disqus",
    "uri": "https://help.disqus.com/customer/portal/articles/466259-privacy-policy",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.disqusShortname === undefined) {
            return;
        }
        tarteaucitron.addScript('//' + tarteaucitron.user.disqusShortname + '.disqus.com/embed.js');
        tarteaucitron.addScript('//' + tarteaucitron.user.disqusShortname + '.disqus.com/count.js');
    },
    "fallback": function () {
        "use strict";
        var id = 'disqus';

        if (document.getElementById('disqus_thread')) {
            document.getElementById('disqus_thread').innerHTML = tarteaucitron.engage(id);
        }
    }
};

// facebook
tarteaucitron.services.facebook = {
    "key": "facebook",
    "type": "social",
    "name": "Facebook",
    "uri": "https://www.facebook.com/help/cookies/",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.fallback(['fb-like'], '');
        tarteaucitron.addScript('//connect.facebook.net/fr_FR/sdk.js#xfbml=1&version=v2.0', 'facebook-jssdk');
    },
    "fallback": function () {
        "use strict";
        tarteaucitron.fallback(['fb-post', 'fb-follow', 'fb-comments', 'fb-activity', 'fb-like-box', 'fb-send', 'fb-share-button', 'fb-like'], 'Facebook');
    }
};

// ferank
tarteaucitron.services.ferank = {
    "key": "ferank",
    "type": "analytics",
    "name": "FERank",
    "uri": "https://www.ferank.fr/respect-vie-privee/#mesureaudience",
    "needConsent": false,
    "js": function () {
        "use strict";
        tarteaucitron.addScript('//static.ferank.fr/pixel.js', '', function () {
            if (typeof tarteaucitron.user.ferankMore === 'function') {
                tarteaucitron.user.ferankMore();
            }
        });
    }
};

// google+
tarteaucitron.services.gplus = {
    "key": "gplus",
    "type": "social",
    "name": "Google+",
    "uri": "http://www.google.fr/intl/policies/privacy/",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.addScript('https://apis.google.com/js/platform.js');
    },
    "fallback": function () {
        "use strict";
        tarteaucitron.fallback(['g-page', 'g-plus', 'g-plusone'], 'Google+');
    }
};

// google adsense
tarteaucitron.services.adsense = {
    "key": "adsense",
    "type": "ads",
    "name": "Adsense (Google)",
    "uri": "http://www.google.com/ads/preferences/",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.addScript('//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js');
    },
    "fallback": function () {
        "use strict";
        var id = 'adsense';

        tarteaucitron.fallback(['adsbygoogle'], tarteaucitron.engage(id));
    }
};

// google analytics
tarteaucitron.services.analytics = {
    "key": "analytics",
    "type": "analytics",
    "name": "Analytics (Google)",
    "uri": "https://support.google.com/analytics/answer/6004245",
    "needConsent": true,
    "js": function () {
        "use strict";
        window.GoogleAnalyticsObject = 'ga';
        window.ga = window.ga || function () {
            window.ga.q = window.ga.q || [];
            window.ga.q.push(arguments);
        };
        window.ga.l = new Date();

        tarteaucitron.addScript('//www.google-analytics.com/analytics.js', '', function () {
            ga('create', tarteaucitron.user.analyticsUa, {'cookieExpires': 34128000});
            ga('send', 'pageview');
            if (typeof tarteaucitron.user.analyticsMore === 'function') {
                tarteaucitron.user.analyticsMore();
            }
        });
    },
    "grayJs": function () {
        "use strict";
        window.GoogleAnalyticsObject = 'ga';
        window.ga = window.ga || function () {
            window.ga.q = window.ga.q || [];
            window.ga.q.push(arguments);
        };
        window.ga.l = new Date();

        tarteaucitron.addScript('//www.google-analytics.com/analytics.js', '', function () {
            ga('create', tarteaucitron.user.analyticsUa, {'cookieExpires': 86400});
            ga('set', 'anonymizeIp', true);
            ga('set', 'forceSSL', true);
            ga('send', 'pageview');
            if (typeof tarteaucitron.user.analyticsMore === 'function') {
                tarteaucitron.user.analyticsMore();
            }
        });
    },
    "fallback": function () {
        "use strict";
        var cookies = ['_ga', '_gat', '__utma', '__utmb', '__utmc', '__utmt', '__utmz'];
        tarteaucitron.cookie.purge(cookies);
    }
};

// linkedin
tarteaucitron.services.linkedin = {
    "key": "linkedin",
    "type": "social",
    "name": "Linkedin",
    "uri": "https://www.linkedin.com/legal/cookie_policy",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.fallback(['tacLinkedin'], '');
        tarteaucitron.addScript('//platform.linkedin.com/in.js');
    },
    "fallback": function () {
        "use strict";
        tarteaucitron.fallback(['tacLinkedin'], 'Linkedin');
    }
};

// pinterest
tarteaucitron.services.pinterest = {
    "key": "pinterest",
    "type": "social",
    "name": "Pinterest",
    "uri": "https://about.pinterest.com/privacy-policy",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.addScript('//assets.pinterest.com/js/pinit.js');
    }
};

// shareaholic
tarteaucitron.services.shareaholic = {
    "key": "shareaholic",
    "type": "social",
    "name": "Shareaholic",
    "uri": "https://shareaholic.com/privacy/choices",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.shareaholicSiteId === undefined) {
            return;
        }

        tarteaucitron.fallback(['shareaholic-canvas'], '');
        tarteaucitron.addScript('//dsms0mj1bbhn4.cloudfront.net/assets/pub/shareaholic.js', '', function () {
            try {
                Shareaholic.init(tarteaucitron.user.shareaholicSiteId);
            } catch (e) {}
        });
    },
    "fallback": function () {
        "use strict";
        var cookies = ['__utma', '__utmb', '__utmc', '__utmz'],
            id = 'shareaholic';

        tarteaucitron.cookie.purge(cookies);
        tarteaucitron.fallback(['shareaholic-canvas'], tarteaucitron.engage(id));
    }
};

// sharethis
tarteaucitron.services.sharethis = {
    "key": "sharethis",
    "type": "social",
    "name": "ShareThis",
    "uri": "http://www.sharethis.com/legal/privacy/",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.sharethisPublisher === undefined) {
            return;
        }
        var switchTo5x = true,
            uri = ('https:' === document.location.protocol ? 'https://ws' : 'http://w') + '.sharethis.com/button/buttons.js';

        tarteaucitron.fallback(['tacSharethis'], '');
        tarteaucitron.addScript(uri, '', function () {
            stLight.options({publisher: tarteaucitron.user.sharethisPublisher, doNotHash: false, doNotCopy: false, hashAddressBar: false});
        });
    },
    "fallback": function () {
        "use strict";
        var cookies = ['__unam'],
            id = 'sharethis';

        tarteaucitron.cookie.purge(cookies);
        tarteaucitron.fallback(['tacSharethis'], tarteaucitron.engage(id));
    }
};

// twitter
tarteaucitron.services.twitter = {
    "key": "twitter",
    "type": "social",
    "name": "Twitter",
    "uri": "https://support.twitter.com/articles/20170514",
    "needConsent": true,
    "js": function () {
        "use strict";
        tarteaucitron.fallback(['tacTwitter'], '');
        tarteaucitron.addScript('//platform.twitter.com/widgets.js', 'twitter-wjs');
    },
    "fallback": function () {
        "use strict";
        tarteaucitron.fallback(['tacTwitter'], 'Twitter');
    }
};

// xiti
tarteaucitron.services.xiti = {
    "key": "xiti",
    "type": "analytics",
    "name": "Xiti",
    "uri": "http://www.atinternet.com/politique-du-respect-de-la-vie-privee/",
    "needConsent": true,
    "js": function () {
        "use strict";
        if (tarteaucitron.user.xitiId === undefined || document.getElementById('xitiTac') === undefined) {
            return;
        }
        var Xt_param = 's=' + tarteaucitron.user.xitiId + '&p=',
            Xt_r,
            Xt_h,
            Xt_i,
            Xt_s;
        try {
            Xt_r = top.document.referrer;
        } catch (e) {
            Xt_r = document.referrer;
        }
        Xt_h = new Date();
        Xt_i = '= 4) {
            Xt_s = screen;
            Xt_i += '&r=' + Xt_s.width + 'x' + Xt_s.height + 'x' + Xt_s.pixelDepth + 'x' + Xt_s.colorDepth;
        }
        document.getElementById('xitiTac').innerHTML = Xt_i + '&ref=' + Xt_r.replace(/[<>"]/g, '').replace(/&/g, '$') + '" title="Internet Audience">';

        if (typeof tarteaucitron.user.xitiMore === 'function') {
            tarteaucitron.user.xitiMore();
        }
    },
    "fallback": function () {
        "use strict";
        var cookies = [''];
        tarteaucitron.cookie.purge(cookies);
    }
};
<|endoftext|>


<commit_before>// ==UserScript==
// @name         ProgrammersForum Detect Bots
// @namespace    programmersforum.ru
// @version      1.9.2
// @description  adds detectBots function that loads the list of online users and counts bots, and logUsers/startLogDaemon functions to save users into IndexedDB
// @author       Alex P
// @include      *programmersforum.ru/*
// @require      https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js
// @require      https://unpkg.com/dexie@2.0.4/dist/dexie.js
// @require      https://cdn.jsdelivr.net/npm/file-saver@2.0.2/dist/FileSaver.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js
// @require      https://unpkg.com/papaparse@5.1.0/papaparse.min.js
// @require      https://cdn.jsdelivr.net/npm/ua-parser-js@0/dist/ua-parser.min.js
// @grant        none
// @downloadURL  https://github.com/AlexP11223/ProgForumRuUserscripts/raw/master/pf_detect_bots.user.js
// ==/UserScript==

(function () {
    'use strict';

    const OUTPUT_TIMEZONE = 3;

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function isBot(user) {
        return user.detections && user.detections.length;
    }

    function identify(users) {
        const botUaParts = [
            'bot', 'crawl', 'spider', 'batch', 'bing',
            'share', 'preview', 'facebook', 'vk.com',
            'curl', 'indy', 'http',
            'media', 'metrics', '(compatible)',
            'zh-cn', 'zh_cn', 'mb2345', 'liebao', 'micromessenger', 'kinza', // chinese https://www.johnlarge.co.uk/blocking-aggressive-chinese-crawlers-scrapers-bots/
        ];

        const ipCounts = _.countBy(users, 'ip');
        const subnet3Counts = _.countBy(users, u => u.subnet(3));
        const subnet2Counts = _.countBy(users, u => u.subnet(2));

        const detectors = [
            user => user.useragent.length < 20 || botUaParts.some(s => user.useragent.includes(s)) ? 'ua' : null,
            user => ipCounts[user.ip] > 2 ? 'ip' : null,
            user => subnet3Counts[user.subnet(3)] > 5 ? 'subnet3' : null,
            user => subnet2Counts[user.subnet(2)] > 20 ? 'subnet2' : null,
        ];

        return users.map(user => {
            const detections = detectors.map(d => d(user)).filter(Boolean);
            return Object.assign(user, {
                detections,
                isBot: function () {
                    return isBot(this);
                },
            })
        });
    }

    async function loadOnlineUsers(url = '/online.php?s=&sortfield=time&sortorder=desc&who=all&ua=1&pp=200') {
        console.log(`Loading ${url}`);
        const html = await $.get(url);

        const users = $(html).find('[id^=resolveip]').toArray()
            .map(el => ({
                ip: $(el).text().trim(),
                useragent: $(el).parent().text().trim().split('\n').slice(-1)[0].toLowerCase().trim(),
                subnet: function (n) {
                    return this.ip.split('.').slice(0, n).join('.');
                },
            }));

        const nextPageLinks = $(html).find('a[href^=online][rel=next]');
        if (!nextPageLinks.length) {
            return users;
        }

        const nextPageUrl = nextPageLinks[0].href;
        return users.concat(await loadOnlineUsers(nextPageUrl));
    }

    window.detectBots = async function () {
        const users = identify(await loadOnlineUsers());

        const bots = users.filter(u => u.isBot());

        const mapToOutput = u => ({ ip: u.ip, useragent: u.useragent, detections: u.detections.join(', ') });
        console.log(`${bots.length} bots, ${users.length - bots.length} normal users`);
        console.log('Bots:');
        console.log(bots.map(mapToOutput));
        console.log('Normal users:');
        console.log(users.filter(u => !u.isBot()).map(mapToOutput));

        window.onlineUsers = users;
    };

    let _db = null;

    function db() {
        if (!_db) {
            _db = new Dexie('UserLogDatabase');
            _db.version(1).stores({ users: '++, date, ip, useragent, *detections' });
        }
        return _db;
    }

    window.userLogsDb = db;

    window.logUsers = async function () {
        const users = identify(await loadOnlineUsers());

        await db().users.bulkPut(users.map(u => ({
            date: new Date(),
            ip: u.ip,
            useragent: u.useragent,
            detections: u.detections,
        })));
        console.log(`Saved ${users.length} users to db`);
    };

    window.startLogDaemon = async function () {
        while (true) {
            try {
                await logUsers();
            } catch (e) {
                console.log(e);
            }

            await sleep(20 * 60 * 1000);
        }
    };

    window.FILTER_IS_BOT = isBot;
    window.FILTER_IS_NORMAL_USER = user => !isBot(user);

    window.filterUserLogs = async function (filter = () => true, startDate = '2019-10-30 00:00:00+03', endDate = new Date()) {
        return await db().users
            .where('date').between(moment(startDate).toDate(), moment(endDate).toDate())
            .and(filter)
            .toArray();
    };

    window.exportToCsv = function (records, header = [], fileName = 'data.csv') {
        const csv = Papa.unparse([header].concat(records), {
            skipEmptyLines: true,
        });

        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8'});
        saveAs(blob, fileName);
    };

    window.exportUsersToCsv = function (users) {
        const header = ['Date/Time', 'IP', 'User-Agent', 'Detections'];
        const records = users.map(u => [
            moment(u.date).utcOffset(OUTPUT_TIMEZONE).format('YYYY-MM-DD HH:mm:ss'),
            u.ip,
            u.useragent,
            u.detections.join(', '),
        ]);

        exportToCsv(records, header, 'users.csv');
    };

    window.uniqueUsers = function (users) {
        return _.uniqBy(users, 'ip');
    };

    window.countVisitorsByTime = function (users) {
        return _.countBy(users, u => moment(u.date).utcOffset(OUTPUT_TIMEZONE).seconds(0).milliseconds(0).format('YYYY-MM-DD HH:mm'));
    };

    // some user-agents are truncated
    function isYandexBrowser(ua) {
        return ua.split(' ').pop().indexOf('Ya') === 0;
    }

    function isChrome(ua) {
        return ua.split(' ').pop().indexOf('Ch') === 0;
    }

    function getOsNameVersion(uaData) {
        const name = uaData.os.name;
        const version = uaData.os.version;
        const allowedVersions = ['XP', 'Vista', '7', '8', '8.1', '10'];
        return [name, allowedVersions.includes(version) ? version : ''].join(' ').trim();
    }

    function getBrowserName(uaData) {
        const name = uaData.browser.name;
        if (name === 'Chrome' && isYandexBrowser(uaData.ua)) {
            return 'Yandex';
        }
        return name;
    }

    window.countUsersOS = function (users) {
        return _.countBy(users, u => getOsNameVersion(UAParser(u.useragent)));
    };

    window.countUsersBrowsers = function (users) {
        return _.countBy(users, u => getBrowserName(UAParser(u.useragent)));
    };
})();
<commit_msg>lowercase browser<commit_after>// ==UserScript==
// @name         ProgrammersForum Detect Bots
// @namespace    programmersforum.ru
// @version      1.9.3
// @description  adds detectBots function that loads the list of online users and counts bots, and logUsers/startLogDaemon functions to save users into IndexedDB
// @author       Alex P
// @include      *programmersforum.ru/*
// @require      https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js
// @require      https://unpkg.com/dexie@2.0.4/dist/dexie.js
// @require      https://cdn.jsdelivr.net/npm/file-saver@2.0.2/dist/FileSaver.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js
// @require      https://unpkg.com/papaparse@5.1.0/papaparse.min.js
// @require      https://cdn.jsdelivr.net/npm/ua-parser-js@0/dist/ua-parser.min.js
// @grant        none
// @downloadURL  https://github.com/AlexP11223/ProgForumRuUserscripts/raw/master/pf_detect_bots.user.js
// ==/UserScript==

(function () {
    'use strict';

    const OUTPUT_TIMEZONE = 3;

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function isBot(user) {
        return user.detections && user.detections.length;
    }

    function identify(users) {
        const botUaParts = [
            'bot', 'crawl', 'spider', 'batch', 'bing',
            'share', 'preview', 'facebook', 'vk.com',
            'curl', 'indy', 'http',
            'media', 'metrics', '(compatible)',
            'zh-cn', 'zh_cn', 'mb2345', 'liebao', 'micromessenger', 'kinza', // chinese https://www.johnlarge.co.uk/blocking-aggressive-chinese-crawlers-scrapers-bots/
        ];

        const ipCounts = _.countBy(users, 'ip');
        const subnet3Counts = _.countBy(users, u => u.subnet(3));
        const subnet2Counts = _.countBy(users, u => u.subnet(2));

        const detectors = [
            user => user.useragent.length < 20 || botUaParts.some(s => user.useragent.includes(s)) ? 'ua' : null,
            user => ipCounts[user.ip] > 2 ? 'ip' : null,
            user => subnet3Counts[user.subnet(3)] > 5 ? 'subnet3' : null,
            user => subnet2Counts[user.subnet(2)] > 20 ? 'subnet2' : null,
        ];

        return users.map(user => {
            const detections = detectors.map(d => d(user)).filter(Boolean);
            return Object.assign(user, {
                detections,
                isBot: function () {
                    return isBot(this);
                },
            })
        });
    }

    async function loadOnlineUsers(url = '/online.php?s=&sortfield=time&sortorder=desc&who=all&ua=1&pp=200') {
        console.log(`Loading ${url}`);
        const html = await $.get(url);

        const users = $(html).find('[id^=resolveip]').toArray()
            .map(el => ({
                ip: $(el).text().trim(),
                useragent: $(el).parent().text().trim().split('\n').slice(-1)[0].toLowerCase().trim(),
                subnet: function (n) {
                    return this.ip.split('.').slice(0, n).join('.');
                },
            }));

        const nextPageLinks = $(html).find('a[href^=online][rel=next]');
        if (!nextPageLinks.length) {
            return users;
        }

        const nextPageUrl = nextPageLinks[0].href;
        return users.concat(await loadOnlineUsers(nextPageUrl));
    }

    window.detectBots = async function () {
        const users = identify(await loadOnlineUsers());

        const bots = users.filter(u => u.isBot());

        const mapToOutput = u => ({ ip: u.ip, useragent: u.useragent, detections: u.detections.join(', ') });
        console.log(`${bots.length} bots, ${users.length - bots.length} normal users`);
        console.log('Bots:');
        console.log(bots.map(mapToOutput));
        console.log('Normal users:');
        console.log(users.filter(u => !u.isBot()).map(mapToOutput));

        window.onlineUsers = users;
    };

    let _db = null;

    function db() {
        if (!_db) {
            _db = new Dexie('UserLogDatabase');
            _db.version(1).stores({ users: '++, date, ip, useragent, *detections' });
        }
        return _db;
    }

    window.userLogsDb = db;

    window.logUsers = async function () {
        const users = identify(await loadOnlineUsers());

        await db().users.bulkPut(users.map(u => ({
            date: new Date(),
            ip: u.ip,
            useragent: u.useragent,
            detections: u.detections,
        })));
        console.log(`Saved ${users.length} users to db`);
    };

    window.startLogDaemon = async function () {
        while (true) {
            try {
                await logUsers();
            } catch (e) {
                console.log(e);
            }

            await sleep(20 * 60 * 1000);
        }
    };

    window.FILTER_IS_BOT = isBot;
    window.FILTER_IS_NORMAL_USER = user => !isBot(user);

    window.filterUserLogs = async function (filter = () => true, startDate = '2019-10-30 00:00:00+03', endDate = new Date()) {
        return await db().users
            .where('date').between(moment(startDate).toDate(), moment(endDate).toDate())
            .and(filter)
            .toArray();
    };

    window.exportToCsv = function (records, header = [], fileName = 'data.csv') {
        const csv = Papa.unparse([header].concat(records), {
            skipEmptyLines: true,
        });

        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8'});
        saveAs(blob, fileName);
    };

    window.exportUsersToCsv = function (users) {
        const header = ['Date/Time', 'IP', 'User-Agent', 'Detections'];
        const records = users.map(u => [
            moment(u.date).utcOffset(OUTPUT_TIMEZONE).format('YYYY-MM-DD HH:mm:ss'),
            u.ip,
            u.useragent,
            u.detections.join(', '),
        ]);

        exportToCsv(records, header, 'users.csv');
    };

    window.uniqueUsers = function (users) {
        return _.uniqBy(users, 'ip');
    };

    window.countVisitorsByTime = function (users) {
        return _.countBy(users, u => moment(u.date).utcOffset(OUTPUT_TIMEZONE).seconds(0).milliseconds(0).format('YYYY-MM-DD HH:mm'));
    };

    // some user-agents are truncated
    function isYandexBrowser(ua) {
        return ua.split(' ').pop().indexOf('Ya') === 0;
    }

    function isChrome(ua) {
        return ua.split(' ').pop().indexOf('Ch') === 0;
    }

    function getOsNameVersion(uaData) {
        const name = uaData.os.name;
        const version = uaData.os.version;
        const allowedVersions = ['XP', 'Vista', '7', '8', '8.1', '10'];
        return [name, allowedVersions.includes(version) ? version : ''].join(' ').trim();
    }

    function getBrowserName(uaData) {
        const name = uaData.browser.name.toLowerCase();
        if (name === 'chrome' && isYandexBrowser(uaData.ua)) {
            return 'yandex';
        }
        return name;
    }

    window.countUsersOS = function (users) {
        return _.countBy(users, u => getOsNameVersion(UAParser(u.useragent)));
    };

    window.countUsersBrowsers = function (users) {
        return _.countBy(users, u => getBrowserName(UAParser(u.useragent)));
    };
})();
<|endoftext|>


<commit_before>'use strict';

var debug = require('debug')('base:cli:init');
var utils = require('./utils');

module.exports = function(app, options, cb) {
  if (typeof app.questions === 'undefined') {
    cb(new Error('expected base-questions plugin to be defined'));
    return;
  }

  if (typeof options === 'function') {
    cb = options;
    options = {};
  }

  options = utils.extend({}, options, app.options);
  app.use(askSeries(options));

  app.questions.disable('save')
    .set('global.preferences', 'Would you like to use global preferences?', {
      type: 'confirm',
      next: function(key, answers, next) {
        debug('global preferences');

        var tableized = utils.tableize(answers);
        var answer = tableized[key];

        if (answer === true && app.store.has('preferences')) {
          debug('using global preferences');

          var opts = utils.extend({}, options, {
            answers: answers,
            config: app.store.get('preferences')
          });

          // when `--init` is passed, `force` is set to true by default
          // but when `--config=init` is passed, you also need to pass
          // `--force` to re-ask questions
          if (opts.force === true) {
            app.askSeries('config', opts, next);
          } else {
            next(null, key, {config: app.store.get('preferences')});
          }

        } else {
          debug('asking config questions');
          var opts = utils.extend({}, options, {answers: answers});
          app.askSeries('config', opts, next);
        }
      }
    })
    .set('config.layout', 'What layout would you like to use?', {
      default: 'default'
    })
    .set('config.toc', 'Do you want to add a Table of Contents to README.md?', {
      default: false
    })
    .set('config.plugins', 'What plugins do you want to use?', {
      default: ['gulp-format-md']
    })
    .set('config.tasks', 'What tasks or generators do you want to run on this project?', {
      default: ['readme']
    });

  app.askSeries('global.preferences', function(err, key, answers) {
    if (err) return cb(err);

    debug('finished with global.preferences "%j"', answers);
    var tableized = utils.tableize(answers);

    var answer = utils.get(answers, 'global.preferences');
    if (answer === true) {
      app.store.set('preferences', answers[key]);
    }

    cb(null, answers);
  });
};

function askSeries(config) {
  return function(app) {

    this.define('askSeries', function(key, options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      var opts = utils.extend({}, config, app.options, options);

      debug('askSeries: %s', key);
      var question = app.questions.get(key);
      if (!question) {
        cb(null, key, opts);
        return;
      }

      app.ask(key, opts, function(err, answers) {
        if (err) return cb(err);

        answers = utils.extend({}, opts.answers, answers);
        if (Array.isArray(question)) {
          cb.call(this, null, key, answers);
          return;
        }

        var next = question.options.next;
        var answer = answers[key];

        if (typeof next === 'function') {
          next.call(question, key, answers, cb);
          return;
        }

        if (utils.isAffirmative(answer) && typeof next === 'string') {
          app.askSeries(next, options, cb);
          return;
        }

        if (app.questions.has(answer)) {
          app.askSeries(answer, options, cb);
          return;
        }

        cb.call(this, null, key, answers);
      }.bind(this));
    });
  };
}
<commit_msg>adds `run` and `lint.reflinks` questions to init<commit_after>'use strict';

var debug = require('debug')('base:cli:init');
var utils = require('./utils');

module.exports = function(app, options, cb) {
  if (typeof app.questions === 'undefined') {
    cb(new Error('expected base-questions plugin to be defined'));
    return;
  }

  if (typeof options === 'function') {
    cb = options;
    options = {};
  }

  options = utils.extend({}, options, app.options);
  app.use(askSeries(options));

  app.questions.disable('save')
    .set('global.preferences', 'Would you like to use global preferences?', {
      type: 'confirm',
      next: function(key, answers, next) {
        debug('global preferences');

        var tableized = utils.tableize(answers);
        var answer = tableized[key];

        if (answer === true && app.store.has('preferences')) {
          debug('using global preferences');

          var opts = utils.extend({}, options, {
            answers: answers,
            config: app.store.get('preferences')
          });

          // when `--init` is passed, `force` is set to true by default
          // but when `--config=init` is passed, you also need to pass
          // `--force` to re-ask questions
          if (opts.force === true) {
            app.askSeries('config', opts, next);
          } else {
            next(null, key, {config: app.store.get('preferences')});
          }

        } else {
          debug('asking config questions');
          var opts = utils.extend({}, options, {answers: answers});
          app.askSeries('config', opts, next);
        }
      }
    })
    .set('config.layout', 'What layout would you like to use?', {
      default: 'default'
    })
    .set('config.toc', 'Do you want to add a Table of Contents to README.md?', {
      default: false
    })
    .set('config.plugins', 'What plugins do you want to use?', {
      default: ['gulp-format-md']
    })
    .set('config.tasks', 'What tasks or generators do you want to run on this project?', {
      default: ['readme']
    })
    .set('config.run', 'Do you want to run tasks anyway when only non-task flags are passed?', {
      default: true
    })
    .set('config.lint.reflinks', 'Do you want to lint for missing reflinks and add them to verb config?', {
      default: true
    });

  app.askSeries('global.preferences', function(err, key, answers) {
    if (err) return cb(err);

    debug('finished with global.preferences "%j"', answers);
    var answer = utils.get(answers, 'global.preferences');
    if (answer === true) {
      app.store.set('preferences', answers[key]);
    }

    cb(null, answers);
  });
};

function askSeries(config) {
  return function(app) {

    this.define('askSeries', function(key, options, cb) {
      if (typeof options === 'function') {
        cb = options;
        options = {};
      }

      var opts = utils.extend({}, config, app.options, options);

      debug('askSeries: %s', key);
      var question = app.questions.get(key);
      if (!question) {
        cb(null, key, opts);
        return;
      }

      app.ask(key, opts, function(err, answers) {
        if (err) return cb(err);

        answers = utils.extend({}, opts.answers, answers);
        if (Array.isArray(question)) {
          cb.call(this, null, key, answers);
          return;
        }

        var next = question.options.next;
        var answer = answers[key];

        if (typeof next === 'function') {
          next.call(question, key, answers, cb);
          return;
        }

        if (utils.isAffirmative(answer) && typeof next === 'string') {
          app.askSeries(next, options, cb);
          return;
        }

        if (app.questions.has(answer)) {
          app.askSeries(answer, options, cb);
          return;
        }

        cb.call(this, null, key, answers);
      }.bind(this));
    });
  };
}
<|endoftext|>


<commit_before>(function () {
    /**
     * The CompositeImageManager is in charge of actually downloading and
     * caching raw image and compositing data for a given VisualizationModel.
     * Usage example:
     *
     *    var mgr = new cinema.utilities.CompositeImageManager({
     *        visModel: myVisualizationModel
     *    };
     *    mgr.on('c:data.ready', function (payload) {
     *        // compute composited image and draw it ...
     *    }, this).on('c:error', function (e) {
     *        console.log('Error: ' + e.message);
     *    });
     *
     *    mgr.updateFields(t, phi, theta);
     *
     * Further compositing computation and drawing of the composited image is
     * left to other components.
     */
    cinema.utilities.CompositeImageManager = function (params) {
        _.extend(this, Backbone.Events);

        this.visModel = params.visModel;
        this._cache = {};

        if (this.visModel.get('metadata').type !== 'composite-image-stack') {
            throw new Error('Unsupported file format');
        }

        return this;
    };

    var prototype = cinema.utilities.CompositeImageManager.prototype;

    /**
     * Returns the image strip file name from the vis model.
     * The method looks for the following in order:
     *   1. arguments.filename.default
     *   2. arguments.files.image
     * Falls back on rgb.jpg.
     */
    prototype._imageFileName = function () {
        var image, args;

        image = 'rgb.jpg';
        args = this.visModel.get('arguments');
        if (args.files && args.files.image) {
            image = args.files.image;
        } else if (args.filename && args.filename['default']) {
            image = args.filename['default'];
        }
        return image;
    };

    /**
     * Returns the composite info file name from the vis model.
     */
    prototype._compositeInfoFileName = function () {
        var composite, args;

        composite = 'composite.json';
        args = this.visModel.get('arguments');
        if (args.files && args.files.composite) {
            composite = args.files.composite;
        }
        return composite;
    };

    /**
     * Helper method to transform phi, theta, and time into a path.
     */
    prototype._getDataPath = function (time, phi, theta) {
        return this.visModel.get('name_pattern').replace('{time}', time)
                                                .replace('{phi}', phi)
                                                .replace('{theta}', theta);
    };

    /**
     * Downloads the image data asynchronously and stores it in the cache for
     * the given key, storing it in the "image" key in the cache entry.
     */
    prototype._downloadImage = function (key) {
        var url = this.visModel.url.substring(0, this.visModel.url.lastIndexOf('/')) +
            '/' + key.replace('{filename}', this._imageFileName()),
            img = new Image();

        img.onload = _.bind(function () {
            this._cache[key].image = img;
            if (_.has(this._cache[key], 'json')) {
                this.trigger('c:data.ready', this._cache[key]);
            }
        }, this);

        img.onerror = _.bind(function () {
            this.trigger('c:error', {
                'message': 'Error loading image ' + url + ' for key ' + key
            });
        }, this);

        img.src = url;
        if (img.complete) {
            img.onload();
        }
    };

    /**
     * Downloads the composite info file that sits alongside the image,
     * storing its contents in the "json" key in the cache entry.
     */
    prototype._downloadCompositeInfo = function (key) {
        var url = this.visModel.url.substring(0, this.visModel.url.lastIndexOf('/')) +
                  '/' + key.replace('{filename}', this._compositeInfoFileName());

        $.getJSON(url, _.bind(function (data) {
            this._cache[key].json = data;
            if (_.has(this._cache[key], 'image')) {
                this.trigger('c:data.ready', this._cache[key]);
            }
        }, this)).fail(_.bind(function () {
            this.trigger('c:error', {
                'message': 'Error loading composite JSON file from ' + url
            });
        }, this));
    };

    prototype.updateViewpoint = function (viewpoint) {
        var key = this._getDataPath(viewpoint.time, viewpoint.phi, viewpoint.theta);

        if (_.has(this._cache, key)) {
            this.trigger('c:data.ready', this._cache[key]);
        }
        else {
            this._cache[key] = {key: key};
            this._downloadImage(key);
            this._downloadCompositeInfo(key);
        }
    };
}) ();
<commit_msg>Fixing file name search order in docstring.<commit_after>(function () {
    /**
     * The CompositeImageManager is in charge of actually downloading and
     * caching raw image and compositing data for a given VisualizationModel.
     * Usage example:
     *
     *    var mgr = new cinema.utilities.CompositeImageManager({
     *        visModel: myVisualizationModel
     *    };
     *    mgr.on('c:data.ready', function (payload) {
     *        // compute composited image and draw it ...
     *    }, this).on('c:error', function (e) {
     *        console.log('Error: ' + e.message);
     *    });
     *
     *    mgr.updateFields(t, phi, theta);
     *
     * Further compositing computation and drawing of the composited image is
     * left to other components.
     */
    cinema.utilities.CompositeImageManager = function (params) {
        _.extend(this, Backbone.Events);

        this.visModel = params.visModel;
        this._cache = {};

        if (this.visModel.get('metadata').type !== 'composite-image-stack') {
            throw new Error('Unsupported file format');
        }

        return this;
    };

    var prototype = cinema.utilities.CompositeImageManager.prototype;

    /**
     * Returns the image strip file name from the vis model.
     * The method looks for the following in order:
     *   1. arguments.files.image
     *   2. arguments.filename.default
     * Falls back on rgb.jpg.
     */
    prototype._imageFileName = function () {
        var image, args;

        image = 'rgb.jpg';
        args = this.visModel.get('arguments');
        if (args.files && args.files.image) {
            image = args.files.image;
        } else if (args.filename && args.filename['default']) {
            image = args.filename['default'];
        }
        return image;
    };

    /**
     * Returns the composite info file name from the vis model.
     */
    prototype._compositeInfoFileName = function () {
        var composite, args;

        composite = 'composite.json';
        args = this.visModel.get('arguments');
        if (args.files && args.files.composite) {
            composite = args.files.composite;
        }
        return composite;
    };

    /**
     * Helper method to transform phi, theta, and time into a path.
     */
    prototype._getDataPath = function (time, phi, theta) {
        return this.visModel.get('name_pattern').replace('{time}', time)
                                                .replace('{phi}', phi)
                                                .replace('{theta}', theta);
    };

    /**
     * Downloads the image data asynchronously and stores it in the cache for
     * the given key, storing it in the "image" key in the cache entry.
     */
    prototype._downloadImage = function (key) {
        var url = this.visModel.url.substring(0, this.visModel.url.lastIndexOf('/')) +
            '/' + key.replace('{filename}', this._imageFileName()),
            img = new Image();

        img.onload = _.bind(function () {
            this._cache[key].image = img;
            if (_.has(this._cache[key], 'json')) {
                this.trigger('c:data.ready', this._cache[key]);
            }
        }, this);

        img.onerror = _.bind(function () {
            this.trigger('c:error', {
                'message': 'Error loading image ' + url + ' for key ' + key
            });
        }, this);

        img.src = url;
        if (img.complete) {
            img.onload();
        }
    };

    /**
     * Downloads the composite info file that sits alongside the image,
     * storing its contents in the "json" key in the cache entry.
     */
    prototype._downloadCompositeInfo = function (key) {
        var url = this.visModel.url.substring(0, this.visModel.url.lastIndexOf('/')) +
                  '/' + key.replace('{filename}', this._compositeInfoFileName());

        $.getJSON(url, _.bind(function (data) {
            this._cache[key].json = data;
            if (_.has(this._cache[key], 'image')) {
                this.trigger('c:data.ready', this._cache[key]);
            }
        }, this)).fail(_.bind(function () {
            this.trigger('c:error', {
                'message': 'Error loading composite JSON file from ' + url
            });
        }, this));
    };

    prototype.updateViewpoint = function (viewpoint) {
        var key = this._getDataPath(viewpoint.time, viewpoint.phi, viewpoint.theta);

        if (_.has(this._cache, key)) {
            this.trigger('c:data.ready', this._cache[key]);
        }
        else {
            this._cache[key] = {key: key};
            this._downloadImage(key);
            this._downloadCompositeInfo(key);
        }
    };
}) ();
<|endoftext|>


<commit_before>/******************************************************************************
 *	Copyright (c) 2004 Actuate Corporation and others.
 *	All rights reserved. This program and the accompanying materials
 *	are made available under the terms of the Eclipse Public License v1.0
 *	which accompanies this distribution, and is available at
 *		http://www.eclipse.org/legal/epl-v10.html
 *
 *	Contributors:
 *		Actuate Corporation - Initial implementation.
 *****************************************************************************/

/**
 *	BirtParameterDialog
 *	...
 */
BirtParameterDialog = Class.create( );

BirtParameterDialog.prototype = Object.extend( new AbstractParameterDialog( ),
{
	/**
	 *	Parameter dialog working state. Whether embedded inside
	 *	designer dialog.
	 */
	__mode : 'frameset',

	/**
	 *	Identify the parameter is null.
	 */
	__isnull : '__isnull',

	/**
	 *	Initialization routine required by "ProtoType" lib.
	 *	@return, void
	 */
	initialize : function( id, mode )
	{
		this.initializeBase( id );
		this.__mode = mode;

		if ( this.__mode == 'parameter' )
		{
			// Hide dialog title bar if embedded in designer.
			var paramDialogTitleBar = $( id + 'dialogTitleBar' );
			paramDialogTitleBar.style.display = 'none';
		}
	},

	/**
	 *	Binding data to the dialog UI. Data includes zoom scaling factor.
	 *	@data, data DOM tree (schema TBD)
	 *	@return, void
	 */
	__bind : function( data )
	{
		if ( !data )
		{
			return;
		}

		var cascadeParamObj = data.getElementsByTagName( 'CascadeParameter' );
		var confirmObj = data.getElementsByTagName( 'Confirmation' );
		if ( cascadeParamObj.length > 0 )
		{
			this.__propogateCascadeParameter( data );
		}
		else if ( confirmObj.length > 0 )
		{
			this.__close( );
		}
	},

	/**
	 *	Collect parameters, include five cases appear in Birt1.0 Viewer.
	 *
	 *	@return, void
	 */
	collect_parameter : function( )
	{
		// Clear parameter array
		this.__parameter = new Array( );

		var k = 0;
		//oTRC[i] is

 section
		var oTRC = document.getElementById( "parameter_table" ).getElementsByTagName( "TR" );
		for( var i = 0; i < oTRC.length; i++ )
		{
			if( !this.__parameter[k] )
			{
				this.__parameter[k] = { };
			}

			//input element collection
			var oIEC = oTRC[i].getElementsByTagName( "input" );
			//select element collection
			var oSEC = oTRC[i].getElementsByTagName( "select" );
			//avoid group parameter
			var oTable = oTRC[i].getElementsByTagName( "table" );
			if( oTable.length > 0 || ( oSEC.length == 0 && oIEC.length == 0 ) || ( oIEC.length == 1 && oIEC[0].type == 'submit' ) )
			{
				continue;
			}

			if( oSEC.length == 1 && oIEC.length <= 1 )
			{
				// deal with "select" parameter
				if( oIEC.length == 1 )
				{
					this.__parameter[k].name = oIEC[0].name;
				}
				else
				{
					this.__parameter[k].name = oSEC[0].name;
				}
				this.__parameter[k].value = oSEC[0].options[oSEC[0].selectedIndex].value;
				k++;
			}

			if( oSEC.length == 0 && ( oIEC.length == 1 || oIEC.length == 2 ) )
			{
				var temp = {};
				var tempDef;
				if( oIEC.length == 1 )
				{
					temp = oIEC[0]
				}
				else if( oIEC[0].type == 'hidden' )
				{
					tempDef = oIEC[0]
					temp = oIEC[1];
				}
				else
				{
					continue;
				}

				if( temp.type == 'text' || temp.type == 'password' )
				{
					// deal with "text" parameter
					this.__parameter[k].name = temp.name;
					// if the parameter neither has a value nor a default value, error
					if( temp.value == "" )
					{
						if( tempDef && tempDef.defaultValue == "" )
						{
							temp.focus( );
							alert( temp.name + " should have a value" );
							return false;
						}
						if( tempDef )
						{
							this.__parameter[k].value = tempDef.defaultValue;
						}
						else
						{
							this.__parameter[k].value = "";
						}
					}
					else
					{
						this.__parameter[k].value = temp.value;
					}
					k++;
				}
				else if( temp.type == 'checkbox' )
				{
					// deal with checkbox
					this.__parameter[k].name = temp.value;
					temp.checked?this.__parameter[k].value = 'true':this.__parameter[k].value = 'false';
					k++;
				}
				else
				{
					//handle more cases
				}
			}
			else if( oSEC.length <= 1 && oIEC.length > 2 )
			{
				for( var j = 0; j < oIEC.length; j++ )
				{
					// deal with radio
					if( oIEC[j].type == 'radio' && oIEC[j].checked )
					{
						if( oIEC[j+1] && ( oIEC[j+1].type == 'text' || oIEC[j+1].type == 'password' ) )
						{
							//Check if allow blank
							var temp = oIEC[j+2];
							if ( temp && temp.value == 'true' )
							{
								if ( oIEC[j+1].value == '' )
								{
									oIEC[j+1].focus( );
									alert( temp.name + " should have a value" );
									return false;
								}
							}

							// deal with radio box with textarea or password area
							if( oIEC[j+1].name && oIEC[j+1].value )
							{
								this.__parameter[k].name = oIEC[j+1].name
								this.__parameter[k].value = oIEC[j+1].value;
								k++;
							}
							else
							{
								this.__parameter[k].name = oIEC[j].value
								this.__parameter[k].value = oIEC[j+1].value;
								oIEC[j+1].value = "";
								k++;
							}
						}
						else if( oSEC[0] )
						{
							// deal with "select" parameter
							if ( oSEC[0].selectedIndex == -1 )
							{
								oSEC[0].focus( );
								alert( oIEC[j].value + " should have a value" );
								return false;
							}

							// Check if select 'Null Value' option
							var temp = oSEC[0].options[oSEC[0].selectedIndex].text;
							if( temp && temp != 'Null Value' )
							{
								this.__parameter[k].name = oIEC[j].value;
								this.__parameter[k].value = oSEC[0].options[oSEC[0].selectedIndex].value;
								k++;
							}
							else
							{
								this.__parameter[k].name = this.__isnull;
								this.__parameter[k].value = oIEC[j].value;
								k++;
							}
						}
						else if( !oIEC[j+1] && !oIEC[j].name )
						{
							//deal with common radio with null value
							this.__parameter[k].name = this.__isnull;
							this.__parameter[k].value = oIEC[j-1].name;
							k++;
						}
						else
						{
							//deal with common radio
							this.__parameter[k].name = oIEC[j].name;
							this.__parameter[k].value = oIEC[j].value;
							k++;
						}
					}
				}
			}
		}
		return true;
	},

	/**
	 *	Handle clicking on okRun.
	 *
	 *	@event, incoming browser native event
	 *	@return, void
	 */
	__okPress : function( )
	{
		if( birtParameterDialog.collect_parameter( ) )
		{
			if ( this.__mode == 'parameter' )
			{
				birtEventDispatcher.broadcastEvent( birtEvent.__E_CACHE_PARAMETER );
			}
			else if ( this.__mode == 'run' )
			{
				this.__doSubmit( );
			}
			else
			{
				birtEventDispatcher.broadcastEvent( birtEvent.__E_CHANGE_PARAMETER );
				this.__l_hide( );
			}
		}
	},

	/**
	 *	Override cancel button click.
	 */
	__neh_cancel : function( )
	{
		if ( this.__mode == 'parameter' )
		{
			this.__cancel();
		}
		else
		{
			this.__l_hide( );
		}
	},

	/**
	 *	Handle submit form with current parameters.
	 *
	 *	@return, void
	 */
	__doSubmit : function( )
	{
		var divObj = document.createElement( "DIV" );
		document.body.appendChild( divObj );
		divObj.style.display = "none";

		var formObj = document.createElement( "FORM" );
		divObj.appendChild( formObj );
		formObj.action = window.location.href;
		formObj.method = "post";

		if ( this.__parameter != null )
		{
			for( var i = 0; i < this.__parameter.length; i++ )
			{
				var param = document.createElement( "INPUT" );
				formObj.appendChild( param );
				param.TYPE = "HIDDEN";
				param.name = this.__parameter[i].name;
				param.value = this.__parameter[i].value;
			}
		}

		this.__l_hide( );
		formObj.submit( );
	},

	/**
	 *	Caching parameters success, close window.
	 *
	 *	@return, void
	 */
	__close : function( )
	{
		window.status = "close";
		//window.opener = null;
		//window.close( );
	},

	/**
	 *	Click 'Cancel', close window.
	 *
	 *	@return, void
	 */
	__cancel : function( )
	{
		window.status = "cancel";
	}
}
);<commit_msg>Fix Bugzilla Bug 141201<commit_after>/******************************************************************************
 *	Copyright (c) 2004 Actuate Corporation and others.
 *	All rights reserved. This program and the accompanying materials
 *	are made available under the terms of the Eclipse Public License v1.0
 *	which accompanies this distribution, and is available at
 *		http://www.eclipse.org/legal/epl-v10.html
 *
 *	Contributors:
 *		Actuate Corporation - Initial implementation.
 *****************************************************************************/

/**
 *	BirtParameterDialog
 *	...
 */
BirtParameterDialog = Class.create( );

BirtParameterDialog.prototype = Object.extend( new AbstractParameterDialog( ),
{
	/**
	 *	Parameter dialog working state. Whether embedded inside
	 *	designer dialog.
	 */
	__mode : 'frameset',

	/**
	 *	Identify the parameter is null.
	 */
	__isnull : '__isnull',

	/**
	 *	Initialization routine required by "ProtoType" lib.
	 *	@return, void
	 */
	initialize : function( id, mode )
	{
		this.initializeBase( id );
		this.__mode = mode;

		if ( this.__mode == 'parameter' )
		{
			// Hide dialog title bar if embedded in designer.
			var paramDialogTitleBar = $( id + 'dialogTitleBar' );
			paramDialogTitleBar.style.display = 'none';
		}
	},

	/**
	 *	Binding data to the dialog UI. Data includes zoom scaling factor.
	 *	@data, data DOM tree (schema TBD)
	 *	@return, void
	 */
	__bind : function( data )
	{
		if ( !data )
		{
			return;
		}

		var cascadeParamObj = data.getElementsByTagName( 'CascadeParameter' );
		var confirmObj = data.getElementsByTagName( 'Confirmation' );
		if ( cascadeParamObj.length > 0 )
		{
			this.__propogateCascadeParameter( data );
		}
		else if ( confirmObj.length > 0 )
		{
			this.__close( );
		}
	},

	/**
	 *	Collect parameters, include five cases appear in Birt1.0 Viewer.
	 *
	 *	@return, void
	 */
	collect_parameter : function( )
	{
		// Clear parameter array
		this.__parameter = new Array( );

		var k = 0;
		//oTRC[i] is

 section
		var oTRC = document.getElementById( "parameter_table" ).getElementsByTagName( "TR" );
		for( var i = 0; i < oTRC.length; i++ )
		{
			if( !this.__parameter[k] )
			{
				this.__parameter[k] = { };
			}

			//input element collection
			var oIEC = oTRC[i].getElementsByTagName( "input" );
			//select element collection
			var oSEC = oTRC[i].getElementsByTagName( "select" );
			//avoid group parameter
			var oTable = oTRC[i].getElementsByTagName( "table" );
			if( oTable.length > 0 || ( oSEC.length == 0 && oIEC.length == 0 ) || ( oIEC.length == 1 && oIEC[0].type == 'submit' ) )
			{
				continue;
			}

			if( oSEC.length == 1 && oIEC.length <= 1 )
			{
				// deal with "select" parameter
				if( oIEC.length == 1 )
				{
					this.__parameter[k].name = oIEC[0].name;
				}
				else
				{
					this.__parameter[k].name = oSEC[0].name;
				}
				this.__parameter[k].value = oSEC[0].options[oSEC[0].selectedIndex].value;
				k++;
			}

			if( oSEC.length == 0 && ( oIEC.length == 1 || oIEC.length == 2 ) )
			{
				var temp = {};
				var tempDef;
				if( oIEC.length == 1 )
				{
					temp = oIEC[0]
				}
				else if( oIEC[0].type == 'hidden' )
				{
					tempDef = oIEC[0]
					temp = oIEC[1];
				}
				else
				{
					continue;
				}

				if( temp.type == 'text' || temp.type == 'password' )
				{
					// deal with "text" parameter
					this.__parameter[k].name = temp.name;
					// if the parameter neither has a value nor a default value, error
					if( temp.value == "" )
					{
						if( tempDef )
						{
							temp.focus( );
							alert( temp.name + " should have a value" );
							return false;
						}
						else
						{
							this.__parameter[k].value = "";
						}
					}
					else
					{
						this.__parameter[k].value = temp.value;
					}
					k++;
				}
				else if( temp.type == 'checkbox' )
				{
					// deal with checkbox
					this.__parameter[k].name = temp.value;
					temp.checked?this.__parameter[k].value = 'true':this.__parameter[k].value = 'false';
					k++;
				}
				else
				{
					//handle more cases
				}
			}
			else if( oSEC.length <= 1 && oIEC.length > 2 )
			{
				for( var j = 0; j < oIEC.length; j++ )
				{
					// deal with radio
					if( oIEC[j].type == 'radio' && oIEC[j].checked )
					{
						if( oIEC[j+1] && ( oIEC[j+1].type == 'text' || oIEC[j+1].type == 'password' ) )
						{
							//Check if allow blank
							var temp = oIEC[j+2];
							if ( temp && temp.value == 'true' )
							{
								if ( oIEC[j+1].value == '' )
								{
									oIEC[j+1].focus( );
									alert( temp.name + " should have a value" );
									return false;
								}
							}

							// deal with radio box with textarea or password area
							if( oIEC[j+1].name && oIEC[j+1].value )
							{
								this.__parameter[k].name = oIEC[j+1].name
								this.__parameter[k].value = oIEC[j+1].value;
								k++;
							}
							else
							{
								this.__parameter[k].name = oIEC[j].value
								this.__parameter[k].value = oIEC[j+1].value;
								oIEC[j+1].value = "";
								k++;
							}
						}
						else if( oSEC[0] )
						{
							// deal with "select" parameter
							if ( oSEC[0].selectedIndex == -1 )
							{
								oSEC[0].focus( );
								alert( oIEC[j].value + " should have a value" );
								return false;
							}

							// Check if select 'Null Value' option
							var temp = oSEC[0].options[oSEC[0].selectedIndex].text;
							if( temp && temp != 'Null Value' )
							{
								this.__parameter[k].name = oIEC[j].value;
								this.__parameter[k].value = oSEC[0].options[oSEC[0].selectedIndex].value;
								k++;
							}
							else
							{
								this.__parameter[k].name = this.__isnull;
								this.__parameter[k].value = oIEC[j].value;
								k++;
							}
						}
						else if( !oIEC[j+1] && !oIEC[j].name )
						{
							//deal with common radio with null value
							this.__parameter[k].name = this.__isnull;
							this.__parameter[k].value = oIEC[j-1].name;
							k++;
						}
						else
						{
							//deal with common radio
							this.__parameter[k].name = oIEC[j].name;
							this.__parameter[k].value = oIEC[j].value;
							k++;
						}
					}
				}
			}
		}
		return true;
	},

	/**
	 *	Handle clicking on okRun.
	 *
	 *	@event, incoming browser native event
	 *	@return, void
	 */
	__okPress : function( )
	{
		if( birtParameterDialog.collect_parameter( ) )
		{
			if ( this.__mode == 'parameter' )
			{
				birtEventDispatcher.broadcastEvent( birtEvent.__E_CACHE_PARAMETER );
			}
			else if ( this.__mode == 'run' )
			{
				this.__doSubmit( );
			}
			else
			{
				birtEventDispatcher.broadcastEvent( birtEvent.__E_CHANGE_PARAMETER );
				this.__l_hide( );
			}
		}
	},

	/**
	 *	Override cancel button click.
	 */
	__neh_cancel : function( )
	{
		if ( this.__mode == 'parameter' )
		{
			this.__cancel();
		}
		else
		{
			this.__l_hide( );
		}
	},

	/**
	 *	Handle submit form with current parameters.
	 *
	 *	@return, void
	 */
	__doSubmit : function( )
	{
		var divObj = document.createElement( "DIV" );
		document.body.appendChild( divObj );
		divObj.style.display = "none";

		var formObj = document.createElement( "FORM" );
		divObj.appendChild( formObj );
		formObj.action = window.location.href;
		formObj.method = "post";

		if ( this.__parameter != null )
		{
			for( var i = 0; i < this.__parameter.length; i++ )
			{
				var param = document.createElement( "INPUT" );
				formObj.appendChild( param );
				param.TYPE = "HIDDEN";
				param.name = this.__parameter[i].name;
				param.value = this.__parameter[i].value;
			}
		}

		this.__l_hide( );
		formObj.submit( );
	},

	/**
	 *	Caching parameters success, close window.
	 *
	 *	@return, void
	 */
	__close : function( )
	{
		window.status = "close";
		//window.opener = null;
		//window.close( );
	},

	/**
	 *	Click 'Cancel', close window.
	 *
	 *	@return, void
	 */
	__cancel : function( )
	{
		window.status = "cancel";
	}
}
);<|endoftext|>


<commit_before>var expect = require('chai').expect
  , define = require('cli-define')
  , Program = define.Program
  , Command = define.Command
  , Option = define.Option
  , Flag = define.Flag
  , compiler = require('../..').compiler
  , mock = require('../util/mock');

function converter(func) {
  expect(func).to.be.a('function');
  expect(func.name).to.eql('mockOptionConverter');
}

function sections(secs) {
  expect(secs).to.be.an('object');
  expect(secs['mock top-level section'])
    .to.be.an('object');
}

describe('cli-compiler:', function() {

  it('should compile simple program', function(done) {
    var opts = mock.opts.simple;
    compiler(opts, function(err, req) {
      //console.dir(err);
      expect(err).to.eql(undefined);
      expect(req).to.be.an('object');

      var program = req.program;

      expect(program).to.be.an('object');
      expect(program.options()).to.be.an('object');
      expect(program.commands()).to.be.an('object');

      // basic test on the compiled program (in-memory)
      var options = program.options();
      var mockOption = options.mockOption;
      expect(mockOption).to.be.an('object');
      expect(mockOption.key()).to.eql('mockOption');
      expect(mockOption.names()).to.eql(['-o', '--mock-option']);

      converter(mockOption.converter());
      sections(program.sections());

      // run an empty program against the compiled
      // closure loaded from disc
      mock.run(opts, function(err, prg) {

        expect(err).to.eql(null);
        expect(prg).to.be.an('object');
        expect(prg).to.be.an.instanceof(Program);
        expect(prg.name()).to.eql('simple-mock-program');
        expect(prg.version()).to.eql('1.0.0');

        var des = prg.description();
        expect(des).to.be.an('object');
        expect(des.txt).to.eql('Simple program.');
        expect(des.md).to.eql('*Simple* program.');

        var det = prg.detail();
        expect(det).to.be.an('object');
        expect(det.txt).to.eql('Mock detail about the program.');
        expect(det.md).to.eql('Mock `detail` about the program.');

        // loaded sections information
        sections(prg.sections());

        var opts = prg.options()
          , cmds = prg.commands();

        expect(opts).to.be.an('object');
        expect(cmds).to.be.an('object');

        expect(Object.keys(opts)).to.eql(['mockOption', 'mockFlag']);
        expect(Object.keys(cmds)).to.eql(['mockCommand']);

        expect(cmds.mockCommand).to.be.instanceof(Command);
        expect(opts.mockOption).to.be.instanceof(Option);
        expect(opts.mockFlag).to.be.instanceof(Flag);

        converter(opts.mockOption.converter());

        done();
      });
    });
  });
})
<commit_msg>Assert on parsed subcommand.<commit_after>var expect = require('chai').expect
  , define = require('cli-define')
  , Program = define.Program
  , Command = define.Command
  , Option = define.Option
  , Flag = define.Flag
  , compiler = require('../..').compiler
  , mock = require('../util/mock');

function converter(func) {
  expect(func).to.be.a('function');
  expect(func.name).to.eql('mockOptionConverter');
}

function sections(secs) {
  expect(secs).to.be.an('object');
  expect(secs['mock top-level section'])
    .to.be.an('object');
}

function subcommand(sub) {
  expect(sub).to.be.instanceof(Command);
  expect(sub.name()).to.eql('mock-sub-command, msc');
  expect(sub.key()).to.eql('mockSubCommand');
}

describe('cli-compiler:', function() {

  it('should compile simple program', function(done) {
    var opts = mock.opts.simple;
    compiler(opts, function(err, req) {
      //console.dir(err);
      expect(err).to.eql(undefined);
      expect(req).to.be.an('object');

      var program = req.program;

      expect(program).to.be.an('object');
      expect(program.options()).to.be.an('object');
      expect(program.commands()).to.be.an('object');

      // basic test on the compiled program (in-memory)
      var mockOption = program.options().mockOption;
      expect(mockOption).to.be.an('object');
      expect(mockOption.key()).to.eql('mockOption');
      expect(mockOption.names()).to.eql(['-o', '--mock-option']);

      converter(mockOption.converter());
      sections(program.sections());

      var mockCommand = program.commands().mockCommand;
      var mockSubCommand = mockCommand.commands().mockSubCommand;
      subcommand(mockSubCommand);

      // run an empty program against the compiled
      // closure loaded from disc
      mock.run(opts, function(err, prg) {

        expect(err).to.eql(null);
        expect(prg).to.be.an('object');
        expect(prg).to.be.an.instanceof(Program);
        expect(prg.name()).to.eql('simple-mock-program');
        expect(prg.version()).to.eql('1.0.0');

        var des = prg.description();
        expect(des).to.be.an('object');
        expect(des.txt).to.eql('Simple program.');
        expect(des.md).to.eql('*Simple* program.');

        var det = prg.detail();
        expect(det).to.be.an('object');
        expect(det.txt).to.eql('Mock detail about the program.');
        expect(det.md).to.eql('Mock `detail` about the program.');

        // loaded sections information
        sections(prg.sections());

        var opts = prg.options()
          , cmds = prg.commands();

        expect(opts).to.be.an('object');
        expect(cmds).to.be.an('object');

        expect(Object.keys(opts)).to.eql(['mockOption', 'mockFlag']);
        expect(Object.keys(cmds)).to.eql(['mockCommand']);

        expect(cmds.mockCommand).to.be.instanceof(Command);
        expect(opts.mockOption).to.be.instanceof(Option);
        expect(opts.mockFlag).to.be.instanceof(Flag);

        converter(opts.mockOption.converter());

        done();
      });
    });
  });
})
<|endoftext|>


<commit_before>/**
 * http://usejsdoc.org/
 */
String.prototype.capitalize = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
}
const styles = {
		won: {
			emoji: ":moneybag:",
			color: "#43C35E"
		},
		lost: {
			emoji: ":money_with_wings:",
			color: "#F0ACAC"
		},
		open: {
			emoji: "",
			color: ""
		}
}
class Messenger {
	linkChoices(deals, searchTerm, baseUrl) {
		var attachments = []
		deals.forEach((deal) => {
			var status = deal.details.status
			var stage = deal.details.stage_name
			var attachment = {
		         "title": deal.title,
				 "title_link": `${baseUrl}deal/${deal.id}`,
		         "fallback": deal.title,
		         "callback_id": "link",
		         "color": styles[status].color,
				 "fields": [
		                {
		                    "title": "Status",
		                    "value": `${styles[status].emoji}${status.capitalize()}`,
		                    "short": true
		                },
						{
							"title": "Stage",
							"value": stage,
							"short": true
						}
				 ],
		         "actions":[
		            {
		               "name":"choice",
		               "text": "Link to Channel",
		               "type": "button",
					   "style": "primary",
		               "value": deal.id
		            }
		         ]
		      }
			if (status === "won" || status === "lost") {
				attachment.actions[0].confirm = {
						title: "Are you sure?",
						text: `The deal you selected is already ${status}, are you sure you want to link it to this channel?`,
						ok_text: "Yes"
				}
			}
			attachments.push(attachment)
		})
		return {
    	    text: `I found ${deals.length} deals matching _${searchTerm}_, which one would you like to link to this channel?`,
    	    attachments: attachments
		}
	}

	unauthorized() {
		return {
		    "response_type": "ephemeral",
		    "text": "I'm sorry, you are not authorized to do this",
		}
	}

	relinkConfirmation(deal, baseUrl) {
		console.log(JSON.stringify(deal))
		return {
    	    text: `It looks like you already linked this channel to a deal in pipedrive`,
    	    attachments: [{
    	    	"title": deal.title,
    	    	"title_link": `${baseUrl}deal/${deal.id}`,
    	    	"text": "Would you like to keep this deal linked or select a new deal?",
	            "fallback": "Relink channel?",
	            "callback_id": "relink",
	            "color": "#3AA3E3",
	            "fields":[
	            	 {
		                    "title": "Status",
		                    "value": `${styles[deal.status].emoji}${deal.status.capitalize()}`,
		                    "short": true
		                },
						{
							"title": "Stage",
							"value": deal.stage_name,
							"short": true
						}
	            ],
	            "attachment_type": "default",
	            "actions": [
	                {
	                    "name": "answer",
	                    "text": "Keep current deal",
	                    "type": "button",
	                    "value": "keep",
	                    "style": "primary",
	                },
	                {
	                    "name": "answer",
	                    "text": "Change deal",
	                    "type": "button",
	                    "value": "change"
	                }
	            ]
	    	}]
		}
	}

	noDealFound(searchTerm) {
		return `I couldn't find any deals that match _${searchTerm}_. If you want me to search for a deal with another name, just type \`/pipedrive [deal name]\``
	}

	channelGreeting() {
		return "Hey! Thanks for inviting me to this channel. I'll quickly check which Pipedrive deal this channel might be about..."
	}

	channelLinked(originalMsg, chosenAttachment, botUserId) {
		chosenAttachment.actions = [
            	{
	               "name": "void",
	               "text": "✔ Deal Linked",
	               "type": "button",
				   "style": "primary"
	            }
	         ]
		chosenAttachment.text = `:link: Deal linked to this channel. You can now mention <@${botUserId}> in any message to create a note in this deal.`
		originalMsg.attachments = [chosenAttachment]
		return originalMsg
	}

	dealKept(originalMsg) {
		var chosenAttachment = originalMsg.attachments[0]
		chosenAttachment.actions = []
		chosenAttachment.text = ":white_check_mark: Keeping this deal linked. You can always change it by calling /pipedrive [deal name]"
		originalMsg.attachments = [chosenAttachment]
		return originalMsg
	}
}

module.exports = new Messenger()<commit_msg>fixed formatting in attachments<commit_after>/**
 * http://usejsdoc.org/
 */
String.prototype.capitalize = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
}
const styles = {
		won: {
			emoji: ":moneybag:",
			color: "#43C35E"
		},
		lost: {
			emoji: ":money_with_wings:",
			color: "#F0ACAC"
		},
		open: {
			emoji: "",
			color: ""
		}
}
class Messenger {
	linkChoices(deals, searchTerm, baseUrl) {
		var attachments = []
		deals.forEach((deal) => {
			var status = deal.details.status
			var stage = deal.details.stage_name
			var attachment = {
		         "title": deal.title,
				 "title_link": `${baseUrl}deal/${deal.id}`,
		         "fallback": deal.title,
		         "callback_id": "link",
		         "color": styles[status].color,
				 "fields": [
		                {
		                    "title": "Status",
		                    "value": `${styles[status].emoji}${status.capitalize()}`,
		                    "short": true
		                },
						{
							"title": "Stage",
							"value": stage,
							"short": true
						}
				 ],
		         "actions":[
		            {
		               "name":"choice",
		               "text": "Link to Channel",
		               "type": "button",
					   "style": "primary",
		               "value": deal.id
		            }
		         ]
		      }
			if (status === "won" || status === "lost") {
				attachment.actions[0].confirm = {
						title: "Are you sure?",
						text: `The deal you selected is already ${status}, are you sure you want to link it to this channel?`,
						ok_text: "Yes"
				}
			}
			attachments.push(attachment)
		})
		var lastAttachment = {
			 "pretext": "The deal you're looking for isn't here? Try typing `/pipedrive [deal name]` to look for another deal",
			 "mrkdwn_in": ["pretext"]
	      }
		attachments.push(lastAttachment)
		return {
    	    text: `I found ${deals.length} deals matching _${searchTerm}_, which one would you like to link to this channel?`,
    	    attachments: attachments
		}
	}

	unauthorized() {
		return {
		    "response_type": "ephemeral",
		    "text": "I'm sorry, you are not authorized to do this",
		}
	}

	relinkConfirmation(deal, baseUrl) {
		console.log(JSON.stringify(deal))
		return {
    	    text: `It looks like you already linked this channel to a deal in pipedrive`,
    	    attachments: [{
    	    	"title": deal.title,
    	    	"title_link": `${baseUrl}deal/${deal.id}`,
    	    	"text": "Would you like to keep this deal linked or select a new deal?",
	            "fallback": "Relink channel?",
	            "callback_id": "relink",
	            "color": "#3AA3E3",
	            "fields":[
	            	 {
		                    "title": "Status",
		                    "value": `${styles[deal.status].emoji}${deal.status.capitalize()}`,
		                    "short": true
		                },
						{
							"title": "Stage",
							"value": deal.stage_name,
							"short": true
						}
	            ],
	            "attachment_type": "default",
	            "actions": [
	                {
	                    "name": "answer",
	                    "text": "Keep current deal",
	                    "type": "button",
	                    "value": "keep",
	                    "style": "primary",
	                },
	                {
	                    "name": "answer",
	                    "text": "Change deal",
	                    "type": "button",
	                    "value": "change"
	                }
	            ]
	    	}]
		}
	}

	noDealFound(searchTerm) {
		return `I couldn't find any deals that match _${searchTerm}_. If you want me to search for a deal with another name, just type \`/pipedrive [deal name]\``
	}

	channelGreeting() {
		return "Hey! Thanks for inviting me to this channel. I'll quickly check which Pipedrive deal this channel might be about..."
	}

	channelLinked(originalMsg, chosenAttachment, botUserId) {
		chosenAttachment.actions = [
            	{
	               "name": "void",
	               "text": "✔ Deal Linked",
	               "type": "button",
				   "style": "primary"
	            }
	         ]
		chosenAttachment.text = `:link: Deal linked to this channel. You can now mention <@${botUserId}> in any message to create a note in this deal.`
		originalMsg.attachments = [chosenAttachment]
		return originalMsg
	}

	dealKept(originalMsg) {
		var chosenAttachment = originalMsg.attachments[0]
		chosenAttachment.actions = []
		chosenAttachment.text = ":white_check_mark: Keeping this deal linked. You can always change it by calling `/pipedrive [deal name]`"
		chosenAttachment.mrkdwn_in = ["text"]
		originalMsg.attachments = [chosenAttachment]
		return originalMsg
	}
}

module.exports = new Messenger()<|endoftext|>


<commit_before>export default createIndex
export const ddoc = {
  _id: '_design/entry_index',
  views: {
    by_from: {
      map: mapFrom.toString()
    }
  }
}

function createIndex (dbName) {
  return dbName.get(ddoc._id)
  .then(doc => {
    let newddoc = ddoc
    newddoc._rev = doc._rev
    return dbName.put(newddoc)
  }, err => {
    return dbName.put(ddoc)
  })
}

function mapFrom (doc) {
  emit(doc.fromArr)
}

<commit_msg>add by_version view<commit_after>export default createIndex
export const ddoc = {
  _id: '_design/entry_index',
  views: {
    by_from: {
      map: mapFrom.toString()
    },
    by_version: {
      map: mapVersion.toString()
    }
  }
}

function createIndex (dbName) {
  return dbName.get(ddoc._id)
  .then(doc => {
    let newddoc = ddoc
    newddoc._rev = doc._rev
    return dbName.put(newddoc)
  }, err => {
    return dbName.put(ddoc)
  })
}

function mapFrom (doc) {
  emit(doc.fromArr)
}

function mapVersion (doc) {
  var version = 0
  if (doc.version) {
    version = doc.version
  }

  emit(version)
}

<|endoftext|>


<commit_before>/**
 * @file Defines the provider settings.
 *
 * Will set the path to Mongo, and applications id
 * Most of the configuration can be done using system environment variables.
 */

// Load environment variables from .env file
var dotenv = require('dotenv');
dotenv.load();

// node_env can either be "development" or "production"
var node_env = process.env.NODE_ENV || "development";

// Port to run the app on. 8000 for development
// (Vagrant syncs this port)
// 80 for production
var default_port = 8000;
if(node_env === "production") {
  default_port = 80;
}

// Exports configuration for use by app.js
module.exports = {
  env: node_env,
  port: process.env.PORT || default_port,
  maxSize: process.env.MAX_SIZE || 50,

  mongoUrl: process.env.MONGO_URL || process.env.MONGOLAB_URI,
  redisUrl: process.env.REDIS_URL || process.env.REDISCLOUD_URL,

  dropboxId: process.env.DROPBOX_API_ID,
  dropboxSecret: process.env.DROPBOX_API_SECRET,

  providerUrl: process.env.PROVIDER_URL,

  appId: process.env.ANYFETCH_API_ID,
  appSecret: process.env.ANYFETCH_API_SECRET,

  concurrency: process.env.DROPBOX_CONCURRENCY || 5,

  testTokens: {
    oauth_token_secret: process.env.DROPBOX_TEST_OAUTH_TOKEN_SECRET,
    oauth_token: process.env.DROPBOX_TEST_OAUTH_TOKEN,
    uid: process.env.DROPBOX_TEST_UID,
  },
  testImagePath: process.env.DROPBOX_TEST_IMAGE_PATH, // Path to an image in the dropbox test account
  testCursor: process.env.DROPBOX_TEST_CURSOR
};
<commit_msg>Add opbeat tokens to config<commit_after>/**
 * @file Defines the provider settings.
 *
 * Will set the path to Mongo, and applications id
 * Most of the configuration can be done using system environment variables.
 */

// Load environment variables from .env file
var dotenv = require('dotenv');
dotenv.load();

// node_env can either be "development" or "production"
var node_env = process.env.NODE_ENV || "development";

// Port to run the app on. 8000 for development
// (Vagrant syncs this port)
// 80 for production
var default_port = 8000;
if(node_env === "production") {
  default_port = 80;
}

// Exports configuration for use by app.js
module.exports = {
  env: node_env,
  port: process.env.PORT || default_port,
  maxSize: process.env.MAX_SIZE || 50,

  mongoUrl: process.env.MONGO_URL || process.env.MONGOLAB_URI,
  redisUrl: process.env.REDIS_URL || process.env.REDISCLOUD_URL,

  dropboxId: process.env.DROPBOX_API_ID,
  dropboxSecret: process.env.DROPBOX_API_SECRET,

  providerUrl: process.env.PROVIDER_URL,

  appId: process.env.ANYFETCH_API_ID,
  appSecret: process.env.ANYFETCH_API_SECRET,

  concurrency: process.env.DROPBOX_CONCURRENCY || 5,

  testTokens: {
    oauth_token_secret: process.env.DROPBOX_TEST_OAUTH_TOKEN_SECRET,
    oauth_token: process.env.DROPBOX_TEST_OAUTH_TOKEN,
    uid: process.env.DROPBOX_TEST_UID,
  },
  testImagePath: process.env.DROPBOX_TEST_IMAGE_PATH, // Path to an image in the dropbox test account
  testCursor: process.env.DROPBOX_TEST_CURSOR,

  opbeat: {
    organization_id: process.env.OPBEAT_ORGANIZATION_ID,
    app_id: process.env.OPBEAT_APP_ID,
    secret_token: process.env.OPBEAT_SECRET_TOKEN,
    silent: true
  }
};
<|endoftext|>


<commit_before>Template.editable.created = function() {
	this.changed = new ReactiveVar(false);
}

Template.editable.rendered = function() {
	var self = this;
	var editable = this.$('.editable');
	new MediumEditor(editable); /* { disableReturn: true, disableToolbar: true } */
	editable.on('input', function() {
		self.changed.set(true);
	});
}

Template.editable.helpers({
	changed: function() { return Template.instance().changed.get() }
});

Template.editable.events({
	'click .editable-store': function(event, instance) {
		instance.data.store(instance.$('.editable').html(), function() { instance.changed.set(false); });
		//instance.$('.editable').html('')
	},
	'click .editable-cancel': function(event, instance) {
		instance.$('.editable').html(instance.data.text);
		instance.changed.set(false);
	}
});
<commit_msg>remove content to avoid duplicate after reactive update<commit_after>Template.editable.created = function() {
	this.changed = new ReactiveVar(false);
}

Template.editable.rendered = function() {
	var self = this;
	var editable = this.$('.editable');
	self.editor = new MediumEditor(editable); /* { disableReturn: true, disableToolbar: true } */
	editable.on('input', function() {
		self.changed.set(true);
	});
}

Template.editable.helpers({
	changed: function() { return Template.instance().changed.get() }
});

Template.editable.events({
	'click .editable-store': function(event, instance) {
		var editable = instance.$('.editable')
		instance.data.store(editable.html(), function() { instance.changed.set(false); });

		// ugly hack to have the field empty before it is reactively updated
		// otherwise new tags in the edited HTML would get added as duplicates
		editable.html('')
	},
	'click .editable-cancel': function(event, instance) {
		instance.$('.editable').html(instance.data.text);
		instance.changed.set(false);
	}
});
<|endoftext|>


<commit_before>/**
 * Sitespeed.io - How speedy is your site? (http://www.sitespeed.io)
 * Copyright (c) 2014, Peter Hedenskog, Tobias Lidskog
 * and other contributors
 * Released under the Apache 2.0 License
 */

var path = require('path'),
  config = require('./../conf'),
  util = require('../util'),
  fs = require('fs'),
  WebPageTest = require('webpagetest'),
  log = require('winston'),
  async = require('async');

module.exports = {
  analyze: function(urls, callback) {

    var wptResultDir = path.join(config.run.absResultDir, config.dataDir, 'webpagetest');
    fs.mkdirSync(wptResultDir, function(err) {

      if (err) {
        log.log('error', 'Could not create the WPT result dir: ' + wptResultDir + ' ' + err);
        callback(err, {
          'type': 'webpagetest',
          'data': {},
          'errors': {}
        });
      } else {
        var queue = async.queue(analyzeUrl, config.threads);
        var errors = {};
        var pageData = {};

        var wpt = ((config.webpagetestKey) ? new WebPageTest(config.webpagetestUrl, config.webpagetestKey) :
          new WebPageTest(config.webpagetestUrl));

        urls.forEach(function(u) {
          queue.push({
            "url": u,
            "wpt": wpt
          }, function(data, err) {
            if (err) {
              log.log('error', 'Error running WebPageTest: ' + err);
              errors[u] = err;
            } else
              pageData[u] = data;
          });
        });

        queue.drain = function() {
          callback(undefined, {
            'type': 'webpagetest',
            'data': pageData,
            'errors': errors
          });
        };
      }

    });
  }
};

function analyzeUrl(args, asyncDoneCallback) {
  var url = args.url;
  var wpt = args.wpt;

  var wptOptions = ({
    pollResults: 10,
    timeout: 500,
    firstViewOnly: false,
    runs: config.no,
    private: true,
    aftRenderingTime: true,
    location: config.webpagetest,
    video: true
  });

  log.log('info', 'Running WebPageTest ' + url);

  wpt.runTest(url, wptOptions, function(err, data) {

    var id = data.response.data.testId;

    /*
  console.log('Test id:' + data.response.data.testId);
  waterfallOptions = {
        dataURI:false,
        colorByMime:true
      };
      wpt.getWaterfallImage(id, waterfallOptions, function(err,data,info) {

    var wstream = fs.createWriteStream(path.join(config.run.absResultDir, config.dataDir,
    'webpagetest', util.getUrlHash(url) + '-webpagetest.png'));

    wstream.on('finish', function () {
        console.log('Wrote waterfall');
      });
    wstream.write(data);
    wstream.end();
  });

    wpt.getHARData(id, {}, function(err,data) {
      console.log("har:" + JSON.stringify(data));
    });
    */

    // TODO check for err
    if (err) {
      log.log('error', "WebPageTest couldn't fetch info for url " + url + '(' +
        JSON.stringify(err) + ')');
      asyncDoneCallback(undefined, err);
      return;
    }
    var jsonPath = path.join(config.run.absResultDir, config.dataDir,
      'webpagetest',
      util.getUrlHash(url) + '-webpagetest.json');

    fs.writeFile(jsonPath, JSON.stringify(data), function(err) {
      if (err) {
        log.log('error', "WebPageTest couldn't store file for url " + url + '(' +
          err + ')');
        asyncDoneCallback(undefined, err);

      } else asyncDoneCallback(data, undefined);
    });

  });
}
<commit_msg>create the wpt data dir async<commit_after>/**
 * Sitespeed.io - How speedy is your site? (http://www.sitespeed.io)
 * Copyright (c) 2014, Peter Hedenskog, Tobias Lidskog
 * and other contributors
 * Released under the Apache 2.0 License
 */

var path = require('path'),
  config = require('./../conf'),
  util = require('../util'),
  fs = require('fs'),
  WebPageTest = require('webpagetest'),
  log = require('winston'),
  async = require('async');

module.exports = {
  analyze: function(urls, callback) {

    var wptResultDir = path.join(config.run.absResultDir, config.dataDir, 'webpagetest');
    fs.mkdir(wptResultDir, function(err) {

      if (err) {
        log.log('error', 'Could not create the WPT result dir: ' + wptResultDir + ' ' + err);
        callback(err, {
          'type': 'webpagetest',
          'data': {},
          'errors': {}
        });
      } else {
        var queue = async.queue(analyzeUrl, config.threads);
        var errors = {};
        var pageData = {};

        var wpt = ((config.webpagetestKey) ? new WebPageTest(config.webpagetestUrl, config.webpagetestKey) :
          new WebPageTest(config.webpagetestUrl));

        urls.forEach(function(u) {
          queue.push({
            "url": u,
            "wpt": wpt
          }, function(data, err) {
            if (err) {
              log.log('error', 'Error running WebPageTest: ' + err);
              errors[u] = err;
            } else
              pageData[u] = data;
          });
        });

        queue.drain = function() {
          callback(undefined, {
            'type': 'webpagetest',
            'data': pageData,
            'errors': errors
          });
        };
      }

    });
  }
};

function analyzeUrl(args, asyncDoneCallback) {
  var url = args.url;
  var wpt = args.wpt;

  var wptOptions = ({
    pollResults: 10,
    timeout: 500,
    firstViewOnly: false,
    runs: config.no,
    private: true,
    aftRenderingTime: true,
    location: config.webpagetest,
    video: true
  });

  log.log('info', 'Running WebPageTest ' + url);

  wpt.runTest(url, wptOptions, function(err, data) {

    var id = data.response.data.testId;

    /*
  console.log('Test id:' + data.response.data.testId);
  waterfallOptions = {
        dataURI:false,
        colorByMime:true
      };
      wpt.getWaterfallImage(id, waterfallOptions, function(err,data,info) {

    var wstream = fs.createWriteStream(path.join(config.run.absResultDir, config.dataDir,
    'webpagetest', util.getUrlHash(url) + '-webpagetest.png'));

    wstream.on('finish', function () {
        console.log('Wrote waterfall');
      });
    wstream.write(data);
    wstream.end();
  });

    wpt.getHARData(id, {}, function(err,data) {
      console.log("har:" + JSON.stringify(data));
    });
    */

    // TODO check for err
    if (err) {
      log.log('error', "WebPageTest couldn't fetch info for url " + url + '(' +
        JSON.stringify(err) + ')');
      asyncDoneCallback(undefined, err);
      return;
    }
    var jsonPath = path.join(config.run.absResultDir, config.dataDir,
      'webpagetest',
      util.getUrlHash(url) + '-webpagetest.json');

    fs.writeFile(jsonPath, JSON.stringify(data), function(err) {
      if (err) {
        log.log('error', "WebPageTest couldn't store file for url " + url + '(' +
          err + ')');
        asyncDoneCallback(undefined, err);

      } else asyncDoneCallback(data, undefined);
    });

  });
}
<|endoftext|>