<commit_before>/*global FormplayerFrontend, Util */

FormplayerFrontend.module("Menus", function (Menus, FormplayerFrontend, Backbone, Marionette, $) {

    Menus.Controller = {
        selectMenu: function (options) {

            options.preview = FormplayerFrontend.currentUser.displayOptions.singleAppMode;

            var fetchingNextMenu = FormplayerFrontend.request("app:select:menus", options);

            /*
             Determine the next screen to display.  Could be
             a list of commands (modules and/or forms)
             a list of entities (cases) and their details
             */
            $.when(fetchingNextMenu).done(function (menuResponse) {
                // show any notifications from Formplayer
                if (menuResponse.notification && !_.isNull(menuResponse.notification.message)) {
                    FormplayerFrontend.request("handleNotification", menuResponse.notification);
                }

                // If redirect was set, clear and go home.
                if (menuResponse.clearSession) {
                    FormplayerFrontend.trigger("apps:currentApp");
                    return;
                }

                var urlObject = Util.currentUrlToObject();
                // If we don't have an appId in the URL (usually due to form preview)
                // then parse the appId from the response.
                if (urlObject.appId === undefined || urlObject.appId === null) {
                    if (menuResponse.appId === null || menuResponse.appId === undefined) {
                        FormplayerFrontend.trigger('showError', "Response did not contain appId even though it was" +
                            "required. If this persists, please report an issue to CommCareHQ");
                        FormplayerFrontend.trigger("apps:list");
                        return;
                    }
                    urlObject.appId = menuResponse.appId;
                    Util.setUrlToObject(urlObject);
                }

                Menus.Controller.showMenu(menuResponse);
                // If menuResponse is an entities type, make set search bar continues to show search
                if (menuResponse.type == "entities") {
                    $('#searchText').val(urlObject.search);
                }

                if (menuResponse.shouldRequestLocation) {
                    Menus.Util.handleLocationRequest(options);
                }
                Menus.Util.startOrStopLocationWatching(menuResponse.shouldWatchLocation);
            }).fail(function () {
                // if it didn't go through, then it displayed an error message.
                // the right thing to do is then to just stay in the same place.
            });
        },

        selectDetail: function (caseId, detailIndex, isPersistent) {
            var urlObject = Util.currentUrlToObject();
            if (!isPersistent) {
                urlObject.addStep(caseId);
            }
            var fetchingDetails = FormplayerFrontend.request("entity:get:details", urlObject, isPersistent);
            $.when(fetchingDetails).done(function (detailResponse) {
                Menus.Controller.showDetail(detailResponse, detailIndex, caseId);
            }).fail(function () {
                FormplayerFrontend.trigger('navigateHome');
            });
        },

        showMenu: function (menuResponse) {
            var menuListView = Menus.Util.getMenuView(menuResponse);

            if (menuListView) {
                FormplayerFrontend.regions.main.show(menuListView.render());
            }
            if (menuResponse.persistentCaseTile && !FormplayerFrontend.currentUser.displayOptions.singleAppMode) {
                Menus.Controller.showPersistentCaseTile(menuResponse.persistentCaseTile);
            } else {
                FormplayerFrontend.regions.persistentCaseTile.empty();
            }

            if (menuResponse.breadcrumbs) {
                Menus.Util.showBreadcrumbs(menuResponse.breadcrumbs);
            } else {
                FormplayerFrontend.regions.breadcrumb.empty();
            }
            if (menuResponse.appVersion) {
                FormplayerFrontend.trigger('setVersionInfo', menuResponse.appVersion);
            }
        },

        showPersistentCaseTile: function (persistentCaseTile) {
            var detailView = Menus.Controller.getCaseTile(persistentCaseTile);
            FormplayerFrontend.regions.persistentCaseTile.show(detailView.render());
        },

        showDetail: function (model, detailTabIndex, caseId) {
            var self = this;
            var detailObjects = model.models;
            // If we have no details, just select the entity
            if (detailObjects === null || detailObjects === undefined || detailObjects.length === 0) {
                FormplayerFrontend.trigger("menu:select", caseId);
                return;
            }
            var detailObject = detailObjects[detailTabIndex];
            var menuListView = Menus.Controller.getDetailList(detailObject);

            var tabModels = _.map(detailObjects, function (detail, index) {
                return {title: detail.get('title'), id: index, active: index === detailTabIndex};
            });
            var tabCollection = new Backbone.Collection();
            tabCollection.reset(tabModels);

            var tabListView = new Menus.Views.DetailTabListView({
                collection: tabCollection,
                showDetail: function (detailTabIndex) {
                    self.showDetail(model, detailTabIndex, caseId);
                },
            });

            $('#select-case').off('click').click(function () {
                FormplayerFrontend.trigger("menu:select", caseId);
            });
            $('#case-detail-modal').find('.js-detail-tabs').html(tabListView.render().el);
            $('#case-detail-modal').find('.js-detail-content').html(menuListView.render().el);
            $('#case-detail-modal').modal('show');

            if (model.isPersistentDetail) {
                $('#case-detail-modal').find('#select-case').hide();
            } else {
                $('#case-detail-modal').find('#select-case').show();
            }
        },

        getDetailList: function (detailObject) {

            if (detailObject.get('entities')) {
                var entities = detailObject.get('entities');
                var listModel = [];
                // we need to map the details and headers JSON to a list for a Backbone Collection
                for (var i = 0; i < entities.length; i++) {
                    var listObj = {};
                    listObj.data = entities[i].data;
                    listObj.id = i;
                    listModel.push(listObj);
                }
                var listCollection = new Backbone.Collection();
                listCollection.reset(listModel);
                var menuData = {
                    collection: listCollection,
                    headers: detailObject.get('headers'),
                    styles: detailObject.get('styles'),
                    title: detailObject.get('title'),
                };
                return new Menus.Views.CaseListDetailView(menuData);
            }

            var headers = detailObject.get('headers');
            var details = detailObject.get('details');
            var styles = detailObject.get('styles');
            var detailModel = [];
            // we need to map the details and headers JSON to a list for a Backbone Collection
            for (var i = 0; i < headers.length; i++) {
                var obj = {};
                obj.data = details[i];
                obj.header = headers[i];
                obj.style = styles[i];
                obj.id = i;
                detailModel.push(obj);
            }
            var detailCollection = new Backbone.Collection();
            detailCollection.reset(detailModel);
            return new Menus.Views.DetailListView({
                collection: detailCollection,
            });
        },

        // return a case tile from a detail object (for persistent case tile)
        getCaseTile: function (detailObject) {
            var detailModel = new Backbone.Model({
                data: detailObject.details,
                id: 0,
            });
            var numEntitiesPerRow = detailObject.numEntitiesPerRow || 1;
            var numRows = detailObject.maxHeight;
            var numColumns = detailObject.maxWidth;
            var useUniformUnits = detailObject.useUniformUnits || false;
            var caseTileStyles = Menus.Views.buildCaseTileStyles(detailObject.tiles, numRows, numColumns,
                numEntitiesPerRow, useUniformUnits, 'persistent');
            // Style the positioning of the elements within a tile (IE element 1 at grid position 1 / 2 / 4 / 3
            $("#persistent-cell-layout-style").html(caseTileStyles[0]).data("css-polyfilled", false);
            // Style the grid (IE each tile has 6 rows, 12 columns)
            $("#persistent-cell-grid-style").html(caseTileStyles[1]).data("css-polyfilled", false);
            return new Menus.Views.PersistentCaseTileView({
                model: detailModel,
                styles: detailObject.styles,
                tiles: detailObject.tiles,
                maxWidth: detailObject.maxWidth,
                maxHeight: detailObject.maxHeight,
                prefix: 'persistent',
                hasInlineTile: detailObject.hasInlineTile,
            });
        },
    };

    Menus.Util = {
        handleLocationRequest: function (optionsFromLastRequest) {
            var success = function (position) {
                FormplayerFrontend.regions.loadingProgress.empty();
                Menus.Util.recordPosition(position);
                Menus.Controller.selectMenu(optionsFromLastRequest);
            };

            var error = function (err) {
                FormplayerFrontend.regions.loadingProgress.empty();
                FormplayerFrontend.trigger('showError',
                    getErrorMessage(err) +
                    "Without access to your location, computations that rely on the here() function will show up blank.");
            };

            var getErrorMessage = function (err) {
                switch (err.code) {
                    case err.PERMISSION_DENIED:
                        return "You denied CommCare HQ permission to read your browser's current location. ";
                    case err.TIMEOUT:
                        return "Your connection was not strong enough to acquire your location. Please try again later. ";
                    case err.POSITION_UNAVAILABLE:
                    default:
                        return "Your browser location could not be determined. ";
                }
            };

            if (navigator.geolocation) {
                var progressView = new FormplayerFrontend.Layout.Views.ProgressView({
                    progressMessage: "Fetching your location...",
                });
                FormplayerFrontend.regions.loadingProgress.show(progressView.render());
                navigator.geolocation.getCurrentPosition(success, error, {timeout: 10000});
            }
        },

        startOrStopLocationWatching: function (shouldWatchLocation) {
            if (navigator.geolocation) {
                var watching = Boolean(sessionStorage.lastLocationWatchId);
                if (!watching && shouldWatchLocation) {
                    sessionStorage.lastLocationWatchId = navigator.geolocation.watchPosition(Menus.Util.recordPosition);
                } else if (watching && !shouldWatchLocation) {
                    navigator.geolocation.clearWatch(sessionStorage.lastLocationWatchId);
                    sessionStorage.lastLocationWatchId = '';
                }
            }
        },

        recordPosition: function (position) {
            sessionStorage.locationLat = position.coords.latitude;
            sessionStorage.locationLon = position.coords.longitude;
            sessionStorage.locationAltitude = position.coords.altitude;
            sessionStorage.locationAccuracy = position.coords.accuracy;
        },

        showBreadcrumbs: function (breadcrumbs) {
            var detailCollection,
                breadcrumbModels;

            breadcrumbModels = _.map(breadcrumbs, function (breadcrumb, idx) {
                return {
                    data: breadcrumb,
                    id: idx,
                };
            });

            detailCollection = new Backbone.Collection(breadcrumbModels);
            var breadcrumbView = new Menus.Views.BreadcrumbListView({
                collection: detailCollection,
            });
            FormplayerFrontend.regions.breadcrumb.show(breadcrumbView.render());
        },

        getMenuView: function (menuResponse) {
            var menuData = {
                collection: menuResponse,
                title: menuResponse.title,
                headers: menuResponse.headers,
                widthHints: menuResponse.widthHints,
                actions: menuResponse.actions,
                pageCount: menuResponse.pageCount,
                currentPage: menuResponse.currentPage,
                styles: menuResponse.styles,
                type: menuResponse.type,
                sessionId: menuResponse.sessionId,
                tiles: menuResponse.tiles,
                numEntitiesPerRow: menuResponse.numEntitiesPerRow,
                maxHeight: menuResponse.maxHeight,
                maxWidth: menuResponse.maxWidth,
                useUniformUnits: menuResponse.useUniformUnits,
                isPersistentDetail: menuResponse.isPersistentDetail,
                sortIndices: menuResponse.sortIndices,
            };
            if (menuResponse.type === "commands") {
                return new Menus.Views.MenuListView(menuData);
            } else if (menuResponse.type === "query") {
                return new Menus.Views.QueryListView(menuData);
            }
            else if (menuResponse.type === "entities") {
                if (menuResponse.tiles === null || menuResponse.tiles === undefined) {
                    return new Menus.Views.CaseListView(menuData);
                } else {
                    if (menuResponse.numEntitiesPerRow > 1) {
                        return new Menus.Views.GridCaseTileListView(menuData);
                    } else {
                        return new Menus.Views.CaseTileListView(menuData);
                    }
                }
            }
        },
    };
});
<commit_msg>change conditional for keeping search criteria in search bar such that it depends on the urlObject.search and not the type of menuResponse<commit_after>/*global FormplayerFrontend, Util */

FormplayerFrontend.module("Menus", function (Menus, FormplayerFrontend, Backbone, Marionette, $) {

    Menus.Controller = {
        selectMenu: function (options) {

            options.preview = FormplayerFrontend.currentUser.displayOptions.singleAppMode;

            var fetchingNextMenu = FormplayerFrontend.request("app:select:menus", options);

            /*
             Determine the next screen to display.  Could be
             a list of commands (modules and/or forms)
             a list of entities (cases) and their details
             */
            $.when(fetchingNextMenu).done(function (menuResponse) {
                // show any notifications from Formplayer
                if (menuResponse.notification && !_.isNull(menuResponse.notification.message)) {
                    FormplayerFrontend.request("handleNotification", menuResponse.notification);
                }

                // If redirect was set, clear and go home.
                if (menuResponse.clearSession) {
                    FormplayerFrontend.trigger("apps:currentApp");
                    return;
                }

                var urlObject = Util.currentUrlToObject();
                // If we don't have an appId in the URL (usually due to form preview)
                // then parse the appId from the response.
                if (urlObject.appId === undefined || urlObject.appId === null) {
                    if (menuResponse.appId === null || menuResponse.appId === undefined) {
                        FormplayerFrontend.trigger('showError', "Response did not contain appId even though it was" +
                            "required. If this persists, please report an issue to CommCareHQ");
                        FormplayerFrontend.trigger("apps:list");
                        return;
                    }
                    urlObject.appId = menuResponse.appId;
                    Util.setUrlToObject(urlObject);
                }

                Menus.Controller.showMenu(menuResponse);
                // If a search exists in urlObject, make set search bar continues to show search
                if (urlObject.search !== null) {
                    $('#searchText').val(urlObject.search);
                }

                if (menuResponse.shouldRequestLocation) {
                    Menus.Util.handleLocationRequest(options);
                }
                Menus.Util.startOrStopLocationWatching(menuResponse.shouldWatchLocation);
            }).fail(function () {
                // if it didn't go through, then it displayed an error message.
                // the right thing to do is then to just stay in the same place.
            });
        },

        selectDetail: function (caseId, detailIndex, isPersistent) {
            var urlObject = Util.currentUrlToObject();
            if (!isPersistent) {
                urlObject.addStep(caseId);
            }
            var fetchingDetails = FormplayerFrontend.request("entity:get:details", urlObject, isPersistent);
            $.when(fetchingDetails).done(function (detailResponse) {
                Menus.Controller.showDetail(detailResponse, detailIndex, caseId);
            }).fail(function () {
                FormplayerFrontend.trigger('navigateHome');
            });
        },

        showMenu: function (menuResponse) {
            var menuListView = Menus.Util.getMenuView(menuResponse);

            if (menuListView) {
                FormplayerFrontend.regions.main.show(menuListView.render());
            }
            if (menuResponse.persistentCaseTile && !FormplayerFrontend.currentUser.displayOptions.singleAppMode) {
                Menus.Controller.showPersistentCaseTile(menuResponse.persistentCaseTile);
            } else {
                FormplayerFrontend.regions.persistentCaseTile.empty();
            }

            if (menuResponse.breadcrumbs) {
                Menus.Util.showBreadcrumbs(menuResponse.breadcrumbs);
            } else {
                FormplayerFrontend.regions.breadcrumb.empty();
            }
            if (menuResponse.appVersion) {
                FormplayerFrontend.trigger('setVersionInfo', menuResponse.appVersion);
            }
        },

        showPersistentCaseTile: function (persistentCaseTile) {
            var detailView = Menus.Controller.getCaseTile(persistentCaseTile);
            FormplayerFrontend.regions.persistentCaseTile.show(detailView.render());
        },

        showDetail: function (model, detailTabIndex, caseId) {
            var self = this;
            var detailObjects = model.models;
            // If we have no details, just select the entity
            if (detailObjects === null || detailObjects === undefined || detailObjects.length === 0) {
                FormplayerFrontend.trigger("menu:select", caseId);
                return;
            }
            var detailObject = detailObjects[detailTabIndex];
            var menuListView = Menus.Controller.getDetailList(detailObject);

            var tabModels = _.map(detailObjects, function (detail, index) {
                return {title: detail.get('title'), id: index, active: index === detailTabIndex};
            });
            var tabCollection = new Backbone.Collection();
            tabCollection.reset(tabModels);

            var tabListView = new Menus.Views.DetailTabListView({
                collection: tabCollection,
                showDetail: function (detailTabIndex) {
                    self.showDetail(model, detailTabIndex, caseId);
                },
            });

            $('#select-case').off('click').click(function () {
                FormplayerFrontend.trigger("menu:select", caseId);
            });
            $('#case-detail-modal').find('.js-detail-tabs').html(tabListView.render().el);
            $('#case-detail-modal').find('.js-detail-content').html(menuListView.render().el);
            $('#case-detail-modal').modal('show');

            if (model.isPersistentDetail) {
                $('#case-detail-modal').find('#select-case').hide();
            } else {
                $('#case-detail-modal').find('#select-case').show();
            }
        },

        getDetailList: function (detailObject) {

            if (detailObject.get('entities')) {
                var entities = detailObject.get('entities');
                var listModel = [];
                // we need to map the details and headers JSON to a list for a Backbone Collection
                for (var i = 0; i < entities.length; i++) {
                    var listObj = {};
                    listObj.data = entities[i].data;
                    listObj.id = i;
                    listModel.push(listObj);
                }
                var listCollection = new Backbone.Collection();
                listCollection.reset(listModel);
                var menuData = {
                    collection: listCollection,
                    headers: detailObject.get('headers'),
                    styles: detailObject.get('styles'),
                    title: detailObject.get('title'),
                };
                return new Menus.Views.CaseListDetailView(menuData);
            }

            var headers = detailObject.get('headers');
            var details = detailObject.get('details');
            var styles = detailObject.get('styles');
            var detailModel = [];
            // we need to map the details and headers JSON to a list for a Backbone Collection
            for (var i = 0; i < headers.length; i++) {
                var obj = {};
                obj.data = details[i];
                obj.header = headers[i];
                obj.style = styles[i];
                obj.id = i;
                detailModel.push(obj);
            }
            var detailCollection = new Backbone.Collection();
            detailCollection.reset(detailModel);
            return new Menus.Views.DetailListView({
                collection: detailCollection,
            });
        },

        // return a case tile from a detail object (for persistent case tile)
        getCaseTile: function (detailObject) {
            var detailModel = new Backbone.Model({
                data: detailObject.details,
                id: 0,
            });
            var numEntitiesPerRow = detailObject.numEntitiesPerRow || 1;
            var numRows = detailObject.maxHeight;
            var numColumns = detailObject.maxWidth;
            var useUniformUnits = detailObject.useUniformUnits || false;
            var caseTileStyles = Menus.Views.buildCaseTileStyles(detailObject.tiles, numRows, numColumns,
                numEntitiesPerRow, useUniformUnits, 'persistent');
            // Style the positioning of the elements within a tile (IE element 1 at grid position 1 / 2 / 4 / 3
            $("#persistent-cell-layout-style").html(caseTileStyles[0]).data("css-polyfilled", false);
            // Style the grid (IE each tile has 6 rows, 12 columns)
            $("#persistent-cell-grid-style").html(caseTileStyles[1]).data("css-polyfilled", false);
            return new Menus.Views.PersistentCaseTileView({
                model: detailModel,
                styles: detailObject.styles,
                tiles: detailObject.tiles,
                maxWidth: detailObject.maxWidth,
                maxHeight: detailObject.maxHeight,
                prefix: 'persistent',
                hasInlineTile: detailObject.hasInlineTile,
            });
        },
    };

    Menus.Util = {
        handleLocationRequest: function (optionsFromLastRequest) {
            var success = function (position) {
                FormplayerFrontend.regions.loadingProgress.empty();
                Menus.Util.recordPosition(position);
                Menus.Controller.selectMenu(optionsFromLastRequest);
            };

            var error = function (err) {
                FormplayerFrontend.regions.loadingProgress.empty();
                FormplayerFrontend.trigger('showError',
                    getErrorMessage(err) +
                    "Without access to your location, computations that rely on the here() function will show up blank.");
            };

            var getErrorMessage = function (err) {
                switch (err.code) {
                    case err.PERMISSION_DENIED:
                        return "You denied CommCare HQ permission to read your browser's current location. ";
                    case err.TIMEOUT:
                        return "Your connection was not strong enough to acquire your location. Please try again later. ";
                    case err.POSITION_UNAVAILABLE:
                    default:
                        return "Your browser location could not be determined. ";
                }
            };

            if (navigator.geolocation) {
                var progressView = new FormplayerFrontend.Layout.Views.ProgressView({
                    progressMessage: "Fetching your location...",
                });
                FormplayerFrontend.regions.loadingProgress.show(progressView.render());
                navigator.geolocation.getCurrentPosition(success, error, {timeout: 10000});
            }
        },

        startOrStopLocationWatching: function (shouldWatchLocation) {
            if (navigator.geolocation) {
                var watching = Boolean(sessionStorage.lastLocationWatchId);
                if (!watching && shouldWatchLocation) {
                    sessionStorage.lastLocationWatchId = navigator.geolocation.watchPosition(Menus.Util.recordPosition);
                } else if (watching && !shouldWatchLocation) {
                    navigator.geolocation.clearWatch(sessionStorage.lastLocationWatchId);
                    sessionStorage.lastLocationWatchId = '';
                }
            }
        },

        recordPosition: function (position) {
            sessionStorage.locationLat = position.coords.latitude;
            sessionStorage.locationLon = position.coords.longitude;
            sessionStorage.locationAltitude = position.coords.altitude;
            sessionStorage.locationAccuracy = position.coords.accuracy;
        },

        showBreadcrumbs: function (breadcrumbs) {
            var detailCollection,
                breadcrumbModels;

            breadcrumbModels = _.map(breadcrumbs, function (breadcrumb, idx) {
                return {
                    data: breadcrumb,
                    id: idx,
                };
            });

            detailCollection = new Backbone.Collection(breadcrumbModels);
            var breadcrumbView = new Menus.Views.BreadcrumbListView({
                collection: detailCollection,
            });
            FormplayerFrontend.regions.breadcrumb.show(breadcrumbView.render());
        },

        getMenuView: function (menuResponse) {
            var menuData = {
                collection: menuResponse,
                title: menuResponse.title,
                headers: menuResponse.headers,
                widthHints: menuResponse.widthHints,
                actions: menuResponse.actions,
                pageCount: menuResponse.pageCount,
                currentPage: menuResponse.currentPage,
                styles: menuResponse.styles,
                type: menuResponse.type,
                sessionId: menuResponse.sessionId,
                tiles: menuResponse.tiles,
                numEntitiesPerRow: menuResponse.numEntitiesPerRow,
                maxHeight: menuResponse.maxHeight,
                maxWidth: menuResponse.maxWidth,
                useUniformUnits: menuResponse.useUniformUnits,
                isPersistentDetail: menuResponse.isPersistentDetail,
                sortIndices: menuResponse.sortIndices,
            };
            if (menuResponse.type === "commands") {
                return new Menus.Views.MenuListView(menuData);
            } else if (menuResponse.type === "query") {
                return new Menus.Views.QueryListView(menuData);
            }
            else if (menuResponse.type === "entities") {
                if (menuResponse.tiles === null || menuResponse.tiles === undefined) {
                    return new Menus.Views.CaseListView(menuData);
                } else {
                    if (menuResponse.numEntitiesPerRow > 1) {
                        return new Menus.Views.GridCaseTileListView(menuData);
                    } else {
                        return new Menus.Views.CaseTileListView(menuData);
                    }
                }
            }
        },
    };
});
<|endoftext|>


<commit_before>"use strict";

const Telegraf = require("telegraf");
const { Telegram } = require("telegraf");
const { json } = require("req");
const cowsay = require("cowsay");
// const cron = require("node-cron");
const key = process.argv[2];
const readline = require("readline");
const Danbooru = require("danbooru");
// const fs = require("fs");

const args = text => text.split(" ").slice(1);
const argstring = text => args(text).join(" ").trim();

const bot = new Telegraf(key);
bot.telegram.getMe().then(data =>
	bot.options.username = data.username);

const rl = readline.createInterface({
	input: process.stdin,
	output: process.stdout,
	prompt: "chat: "
});

const tg = new Telegram(key);

const booru = new Danbooru();

const feature = ({ reply }) =>
	reply("This feature is either under construction " +
	"or I'm too retarded to implement it");

const cow = `

         (__)
         (oo)
   /------\\/
  / |    ||
 *  /\\---/\\
    ~~   ~~
...."Have you mooed today?"...
`;

const cows = ["beavis.zen", "bong", "bud-frogs", "bunny", "cheese" +
"cower", "daemon", "default", "doge", "dragon-and-cow", "dragon", +
"elephant-in-snake", "elephant", "eyes", "flaming-sheep", +
"ghostbusters", "goat", "head-in", "hedgehog", "hellokitty", "kiss", +
"kitty", "koala", "kosh", "luke-koala", "mech-and-cow", "meow", "milk", +
"moofasa", "moose", "mutilated", "ren", "satanic", "sheep", "skeleton", +
"small", "sodomized", "squirrel", "stegosaurus", "stimpy", "supermilker", +
"surgery", "telebears", "turkey", "turtle", "tux", "vader-koala", "vader", "whale", "www"];

bot.command("start", ({ reply }) =>
	reply("fuck off"));

bot.command("moo", ({ reply }) =>
	reply(cow, { parse_mode: "HTML" }));

bot.command("test", feature);

bot.command("rogue", feature);

bot.command("info", feature);

bot.command("danbooru", ({ message, reply, replyWithPhoto }) => {
	const tags = args(message.text);
	let errors = "Following errors occured:\n";
	if(tags == "")
		reply("you forgot to specify tags retard");
	else booru.posts(tags)
		.then(posts => posts[Math.floor(Math.random()*posts.length)])
		.then(post => booru.posts.get(post))
		.catch(function(e) {
			errors += e + "\n";
		})
		.then(postInfo => {
			const file = postInfo.file;
			if(!("request" in file))
				reply("image unavailable");
			else replyWithPhoto(`https://danbooru.donmai.us/data/${file.name}`)
				.then(reply(`Post ID: ${postInfo.id}\n` +
				`Artist: ${postInfo.tags.artist}\n` +
				`Characters: ${postInfo.tags.character}\n` +
				`Copyright info: ${postInfo.tags.copyright}`))
				.catch(function(e) {
					errors += e + " (image unavailable)\n";
					reply(errors);
				});
		})
		.catch(function(e) {
			errors += e + "\n";
			reply(errors + "You propably used nonexisting tags.");
		});
});

bot.command("price", ({ message, reply }) => {
	if(args(message.text)[0] == undefined)
		reply("input a valid ticker symbol retard");
	else json("https://api.coinmarketcap.com/v1/ticker/")
		.then(crap => crap.find(obj =>
			obj.symbol === args(message.text)[0].toUpperCase()))
		.then(balls => {
			if(balls == undefined)
				reply("input a valid ticker symbol retard");
			else balls.percent_change_24h.includes("-") //as long as it works
				? reply(balls.name + ": " + balls.price_usd + "$ " + balls.percent_change_24h + "% 📉")
				: reply(balls.name + ": " + balls.price_usd + "$ +" + balls.percent_change_24h + "% 📈");
		});
});

bot.command("weather", ({ message, reply }) => {
	const K = 273.15;
	const icons = {
		"01d": "☀", "01n": "🌕",
		"02d": "🌤", "02n": "🌤",
		"03d": "⛅", "03n": "⛅",
		"04d": "☁", "04n": "☁",
		"09d": "🌧", "09n": "🌧",
		"10d": "🌦", "10n": "🌦",
		"11d": "🌩", "11n": "🌩",
		"13d": "🌨", "13n": "🌨",
		"50d": "🌫", "50n": "🌫"
	};
	return json("http://api.openweathermap.org/data/2.5/weather?q=" +
		encodeURIComponent(argstring(message.text)) +
		"&APPID=1566ed87c9944f0df94332da29ee817c").then(data =>
		data.cod !== 200
			? reply(`Error:\n${data.cod}: ${data.message}`)
			: reply(
				"Weather in " +
				data.name + ", " +
				data.sys.country + ": " +
				Math.floor(data.main.temp - K) + "°C, " +
				data.weather[0].description + " " +
				(icons[data.weather[0].icon] || "") + "\n" +
				" Humidity: " + Math.floor(data.main.humidity) + "%\n" +
				" Air pressure: " + Math.floor(data.main.pressure) + " hPa"));
});

bot.command("skelet", ({ reply }) => {
	let skelets = "";
	for (let i = 0; i < Math.floor((Math.random() * 20) + 1 ); i++)
		skelets += Math.random() < 0.5 ? "💀" : "☠";
	return reply(skelets);
});

bot.command("cowsay", ({ message, reply }) => {
	const arg = args(message.text);
	let text = arg.slice();
	let notCow = false;
	text.splice(0,1);
	if(arg == undefined || message.text == undefined || arg[0] == undefined) {
		reply("specify animal and/or text");
	} else for(let i = 0; i < cows.length; i++){
		if(arg[0].includes(cows[i])) {
			reply("```" + cowsay.say({
				text : text.join(" ") || "I'm too dumb to type some text",
				f : arg[0]
			}) + "```", { parse_mode: "Markdown" });
			notCow = true; //fucking genius
			break;
		}
	} if(arg[0].includes("list") && notCow === false) {
		reply(cows);
	} else if(notCow === false) {
		reply("```" + cowsay.say({
			text : arg.join(" ") || "Have you mooed today?",
		}) + "```", { parse_mode: "Markdown" });
	}
});

bot.on("text", ({ message, replyWithSticker, reply, tg }) => {
	let msg = message.from.username + ": " + message.text;
	if (message.from.username == undefined)
		msg = message.from.first_name + " " + message.from.last_name + ": " + message.text;
	if(message.chat.id != "-1001064029829" && message.chat.id != "-1001138989974" && message.chat.id != "-1001144567507")
		tg.sendMessage("@skeletlog", msg);
	const text = message.text.toLowerCase();
	if(message.from.id == 353196474 && text.includes("nice") || message.from.id == 128432371 && text.includes("nice"))
		replyWithSticker("CAADBAADPwADulkNFYeAzy5ClSxjAg");
	else if(text == undefined)
		reply("unknown error");
});

bot.on("photo", ({ message, tg }) => {
	let caption = message.from.username + ": " + message.caption;
	if (message.from.username == undefined)
		caption = message.from.first_name + " " + message.from.last_name + ": " + message.caption;
	if(message.chat.id != "-1001064029829" && message.chat.id != "-1001138989974" && message.chat.id != "-1001144567507"){
		tg.sendPhoto("@skeletlog", message.photo[0].file_id, [caption]);
		tg.sendMessage("@skeletlog", caption);
	}
});

let id = "@skeletlog";
rl.prompt();
rl.on("line", (line) => {
	switch (line.trim()) {
	case "/setchat":
		rl.question("set chat id: ", (setid) => {
			id = setid;
			rl.prompt();
		});
		break;
	default:
		tg.sendMessage(id, `${line.trim()}`);
		break;
	}
	rl.prompt();
});

bot.startPolling();
<commit_msg>danbooru fix for cached images<commit_after>"use strict";

const Telegraf = require("telegraf");
const { Telegram } = require("telegraf");
const { json } = require("req");
const cowsay = require("cowsay");
// const cron = require("node-cron");
const key = process.argv[2];
const readline = require("readline");
const Danbooru = require("danbooru");
// const fs = require("fs");

const args = text => text.split(" ").slice(1);
const argstring = text => args(text).join(" ").trim();

const bot = new Telegraf(key);
bot.telegram.getMe().then(data =>
	bot.options.username = data.username);

const rl = readline.createInterface({
	input: process.stdin,
	output: process.stdout,
	prompt: "chat: "
});

const tg = new Telegram(key);

const booru = new Danbooru();

const feature = ({ reply }) =>
	reply("This feature is either under construction " +
	"or I'm too retarded to implement it");

const cow = `

         (__)
         (oo)
   /------\\/
  / |    ||
 *  /\\---/\\
    ~~   ~~
...."Have you mooed today?"...
`;

const cows = ["beavis.zen", "bong", "bud-frogs", "bunny", "cheese" +
"cower", "daemon", "default", "doge", "dragon-and-cow", "dragon", +
"elephant-in-snake", "elephant", "eyes", "flaming-sheep", +
"ghostbusters", "goat", "head-in", "hedgehog", "hellokitty", "kiss", +
"kitty", "koala", "kosh", "luke-koala", "mech-and-cow", "meow", "milk", +
"moofasa", "moose", "mutilated", "ren", "satanic", "sheep", "skeleton", +
"small", "sodomized", "squirrel", "stegosaurus", "stimpy", "supermilker", +
"surgery", "telebears", "turkey", "turtle", "tux", "vader-koala", "vader", "whale", "www"];

bot.command("start", ({ reply }) =>
	reply("fuck off"));

bot.command("moo", ({ reply }) =>
	reply(cow, { parse_mode: "HTML" }));

bot.command("test", feature);

bot.command("rogue", feature);

bot.command("info", feature);

bot.command("danbooru", ({ message, reply, replyWithPhoto }) => {
	const tags = args(message.text);
	let errors = "Following errors occured:\n";
	if(tags == "")
		reply("you forgot to specify tags retard");
	else booru.posts(tags)
		.then(posts => posts[Math.floor(Math.random()*posts.length)])
		.then(post => booru.posts.get(post))
		.catch(function(e) {
			errors += e + "\n";
		})
		.then(postInfo => {
			const file = postInfo.file;
			if(!("request" in file))
				reply("image unavailable");
			else replyWithPhoto(`https://danbooru.donmai.us/data/${file.name}`)
				.catch(function(e) {
					if(e.toString().includes("400"))
						replyWithPhoto(`https://danbooru.donmai.us/cached/data/${file.name}`);
					else {
						errors += e + " (image unavailable)\n";
						reply(errors);
					}
				})
				.then(reply(`Post ID: ${postInfo.id}\n` +
				`Artist: ${postInfo.tags.artist}\n` +
				`Characters: ${postInfo.tags.character}\n` +
				`Copyright info: ${postInfo.tags.copyright}`));
		})
		.catch(function(e) {
			errors += e + "\n";
			reply(errors + "You propably used nonexisting tags.");
		});
});

bot.command("price", ({ message, reply }) => {
	if(args(message.text)[0] == undefined)
		reply("input a valid ticker symbol retard");
	else json("https://api.coinmarketcap.com/v1/ticker/")
		.then(crap => crap.find(obj =>
			obj.symbol === args(message.text)[0].toUpperCase()))
		.then(balls => {
			if(balls == undefined)
				reply("input a valid ticker symbol retard");
			else balls.percent_change_24h.includes("-") //as long as it works
				? reply(balls.name + ": " + balls.price_usd + "$ " + balls.percent_change_24h + "% 📉")
				: reply(balls.name + ": " + balls.price_usd + "$ +" + balls.percent_change_24h + "% 📈");
		});
});

bot.command("weather", ({ message, reply }) => {
	const K = 273.15;
	const icons = {
		"01d": "☀", "01n": "🌕",
		"02d": "🌤", "02n": "🌤",
		"03d": "⛅", "03n": "⛅",
		"04d": "☁", "04n": "☁",
		"09d": "🌧", "09n": "🌧",
		"10d": "🌦", "10n": "🌦",
		"11d": "🌩", "11n": "🌩",
		"13d": "🌨", "13n": "🌨",
		"50d": "🌫", "50n": "🌫"
	};
	return json("http://api.openweathermap.org/data/2.5/weather?q=" +
		encodeURIComponent(argstring(message.text)) +
		"&APPID=1566ed87c9944f0df94332da29ee817c").then(data =>
		data.cod !== 200
			? reply(`Error:\n${data.cod}: ${data.message}`)
			: reply(
				"Weather in " +
				data.name + ", " +
				data.sys.country + ": " +
				Math.floor(data.main.temp - K) + "°C, " +
				data.weather[0].description + " " +
				(icons[data.weather[0].icon] || "") + "\n" +
				" Humidity: " + Math.floor(data.main.humidity) + "%\n" +
				" Air pressure: " + Math.floor(data.main.pressure) + " hPa"));
});

bot.command("skelet", ({ reply }) => {
	let skelets = "";
	for (let i = 0; i < Math.floor((Math.random() * 20) + 1 ); i++)
		skelets += Math.random() < 0.5 ? "💀" : "☠";
	return reply(skelets);
});

bot.command("cowsay", ({ message, reply }) => {
	const arg = args(message.text);
	let text = arg.slice();
	let notCow = false;
	text.splice(0,1);
	if(arg == undefined || message.text == undefined || arg[0] == undefined) {
		reply("specify animal and/or text");
	} else for(let i = 0; i < cows.length; i++){
		if(arg[0].includes(cows[i])) {
			reply("```" + cowsay.say({
				text : text.join(" ") || "I'm too dumb to type some text",
				f : arg[0]
			}) + "```", { parse_mode: "Markdown" });
			notCow = true; //fucking genius
			break;
		}
	} if(arg[0].includes("list") && notCow === false) {
		reply(cows);
	} else if(notCow === false) {
		reply("```" + cowsay.say({
			text : arg.join(" ") || "Have you mooed today?",
		}) + "```", { parse_mode: "Markdown" });
	}
});

bot.on("text", ({ message, replyWithSticker, reply, tg }) => {
	let msg = message.from.username + ": " + message.text;
	if (message.from.username == undefined)
		msg = message.from.first_name + " " + message.from.last_name + ": " + message.text;
	if(message.chat.id != "-1001064029829" && message.chat.id != "-1001138989974" && message.chat.id != "-1001144567507")
		tg.sendMessage("@skeletlog", msg);
	const text = message.text.toLowerCase();
	if(message.from.id == 353196474 && text.includes("nice") || message.from.id == 128432371 && text.includes("nice"))
		replyWithSticker("CAADBAADPwADulkNFYeAzy5ClSxjAg");
	else if(text == undefined)
		reply("unknown error");
});

bot.on("photo", ({ message, tg }) => {
	let caption = message.from.username + ": " + message.caption;
	if (message.from.username == undefined)
		caption = message.from.first_name + " " + message.from.last_name + ": " + message.caption;
	if(message.chat.id != "-1001064029829" && message.chat.id != "-1001138989974" && message.chat.id != "-1001144567507"){
		tg.sendPhoto("@skeletlog", message.photo[0].file_id, [caption]);
		tg.sendMessage("@skeletlog", caption);
	}
});

let id = "@skeletlog";
rl.prompt();
rl.on("line", (line) => {
	switch (line.trim()) {
	case "/setchat":
		rl.question("set chat id: ", (setid) => {
			id = setid;
			rl.prompt();
		});
		break;
	default:
		tg.sendMessage(id, `${line.trim()}`);
		break;
	}
	rl.prompt();
});

bot.startPolling();
<|endoftext|>


<commit_before>/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Class PuppetWeb Events
 *
 * use to control wechat in web browser.
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */

/**************************************
 *
 * Class PuppetWeb
 *
 ***************************************/
const util  = require('util')
const fs    = require('fs')
const co    = require('co')

const log = require('../npmlog-env')
const Contact = require('../contact')
const Message = require('../message')
const MediaMessage = require('../message-media')

const PuppetWebEvent = {
  onBrowserDead

  , onServerLogin
  , onServerLogout

  , onServerConnection
  , onServerDisconnect

  , onServerDing
  , onServerScan
  , onServerUnload
  , onServerLog

  , onServerMessage
}

function onBrowserDead(e) {
  log.verbose('PuppetWebEvent', 'onBrowserDead(%s)', e)
  // because this function is async, so maybe entry more than one times.
  // guard by variable: isBrowserBirthing to prevent the 2nd time entrance.
  if (this.isBrowserBirthing) {
    if (this.isBrowserBirthing === true) {
      log.verbose('PuppetWebEvent', 'onBrowserDead() Im busy, dont call me again before I return. this time will return and do nothing')
      return
    } else {
      log.warn('PuppetWebEvent', 'onBrowserDead() Im FAKE busy? isBrowserBirthing is not boolean true!')
    }
  }

  return co.call(this, function* () {
    log.warn('PuppetWebEvent', 'onBrowserDead() co() set isBrowserBirthing true')
    this.isBrowserBirthing = true

    const TIMEOUT = 180000 // 180s / 3m
    // this.watchDog(`onBrowserDead() set a timeout of ${Math.floor(TIMEOUT / 1000)} seconds to prevent unknown state change`, {timeout: TIMEOUT})
    this.emit('watchdog', {
      data: `onBrowserDead() set a timeout of ${Math.floor(TIMEOUT / 1000)} seconds to prevent unknown state change`
      , timeout: TIMEOUT
    })

    log.verbose('PuppetWebEvent', 'onBrowserDead(%s)', e.message || e)
    if (!this.browser || !this.bridge) {
      log.error('PuppetWebEvent', 'onBrowserDead() browser or bridge not found. do nothing')
      throw new Error('no browser or no bridge')
    }

    log.verbose('PuppetWebEvent', 'onBrowserDead() try to reborn browser')

    yield this.browser.quit()
    .catch(e => { // fail safe
      log.warn('PuppetWebEvent', 'browser.quit() exception: %s', e.message)
    })
    log.verbose('PuppetWebEvent', 'old browser quited')

    this.browser = yield this.initBrowser()
    log.verbose('PuppetWebEvent', 'new browser inited')

    // this.bridge = yield this.bridge.init()
    this.bridge = yield this.initBridge()
    log.verbose('PuppetWebEvent', 'bridge re-inited')

    const dong = yield this.ding()
    if (/dong/i.test(dong)) {
      log.verbose('PuppetWebEvent', 'ding() works well after reset')
    } else {
      log.warn('PuppetWebEvent', 'ding() get error return after reset: ' + dong)
    }
  })
  .catch(e => { // Exception
    log.error('PuppetWebEvent', 'onBrowserDead() exception: %s', e.message)

    log.warn('PuppetWebEvent', 'onBrowserDead() try to re-init PuppetWeb itself')
    return this.quit().then(_ => this.init())
  })
  .then(() => { // Finally
    log.verbose('PuppetWebEvent', 'onBrowserDead() new browser borned')
    this.isBrowserBirthing = false
  })
}

function onServerDing(data) {
  log.silly('PuppetWebEvent', 'onServerDing(%s)', data)
  // this.watchDog(data)
  this.emit('watchdog', { data })
}

function onServerScan(data) {
  log.verbose('PuppetWebEvent', 'onServerScan(%d)', data && data.code)

  /**
   * When wx.qq.com push a new QRCode to Scan, there will be cookie updates(?)
   */
  this.browser.saveSession()
      .catch(() => {/* fail safe */})

  if (this.userId) {
    log.verbose('PuppetWebEvent', 'onServerScan() there has userId when got a scan event. emit logout and set userId to null')
    this.emit('logout', this.user || this.userId)
    this.userId = this.user = null
  }

  // feed watchDog a `scan` type of food
  // this.watchDog(data, {type: 'scan'})
  this.emit('watchdog', { data, type: 'SCAN' })

  this.emit('scan', data)
}

function onServerConnection(data) {
  log.verbose('PuppetWebEvent', 'onServerConnection: %s', typeof data)
}

function onServerDisconnect(data) {
  log.verbose('PuppetWebEvent', 'onServerDisconnect: %s', data)

  if (this.userId) {
    log.verbose('PuppetWebEvent', 'onServerDisconnect() there has userId set. emit a logout event and set userId to null')
    this.emit('logout', this.user || this.userId ) //'onServerDisconnect(' + data + ')')
    this.userId = null
    this.user = null
  }

  /**
   * conditions:
   * 1. browser crash(i.e.: be killed)
   * 2. quiting
   */
  if (!this.browser) {                // no browser, quiting?
    log.verbose('PuppetWebEvent', 'onServerDisconnect() no browser. maybe Im quiting, do nothing')
    return
  } else if (this.browser.dead()) {   // browser is dead
    log.verbose('PuppetWebEvent', 'onServerDisconnect() found dead browser. wait it to restore')
    return
  } else if (!this.bridge) {          // no bridge, quiting???
    log.verbose('PuppetWebEvent', 'onServerDisconnect() no bridge. maybe Im quiting, do nothing')
    return
  }

  this.browser.readyLive()
  .then(r => {  // browser is alive, and we have a bridge to it
    log.verbose('PuppetWebEvent', 'onServerDisconnect() re-initing bridge')
    // must use setTimeout to wait a while.
    // because the browser has just refreshed, need some time to re-init to ready.
    // if the browser is not ready, bridge init will fail,
    // caused browser dead and have to be restarted. 2016/6/12
    setTimeout(_ => {
      this.bridge.init()
      .then(r  => log.verbose('PuppetWebEvent', 'onServerDisconnect() bridge re-inited: %s', r))
      .catch(e => log.error('PuppetWebEvent', 'onServerDisconnect() exception: [%s]', e))
    }, 1000) // 1 second instead of 10 seconds? try. (should be enough to wait)
    return
  })
  .catch(e => { // browser is in indeed dead, or almost dead. readyLive() will auto recover itself.
    log.verbose('PuppetWebEvent', 'onServerDisconnect() browser dead, waiting it recover itself: %s', e.message)
    return
  })
}

/**
 * `unload` event is sent from js@browser to webserver via socketio
 * after received `unload`, we should fix bridge by re-inject the Wechaty js code into browser.
 * possible conditions:
 * 1. browser refresh
 * 2. browser navigated to a new url
 * 3. browser quit(crash?)
 * 4. ...
 */
function onServerUnload(data) {
  log.warn('PuppetWebEvent', 'onServerUnload(%s)', typeof data)
  // onServerLogout.call(this, data) // XXX: should emit event[logout] from browser

  if (!this.browser) {
    log.warn('PuppetWebEvent', 'onServerUnload() found browser gone, should be quiting now')
    return
  } else if (!this.bridge) {
    log.warn('PuppetWebEvent', 'onServerUnload() found bridge gone, should be quiting now')
    return
  } else if (this.browser.dead()) {
    log.error('PuppetWebEvent', 'onServerUnload() found browser dead. wait it to restore itself')
    return
  }
  // re-init bridge after 1 second XXX: better method to confirm unload/reload finished?
  return setTimeout(() => {
    this.bridge.init()
    .then(r  => log.verbose('PuppetWebEvent', 'onServerUnload() bridge.init() done: %s', r))
    .catch(e => log.error('PuppetWebEvent', 'onServerUnload() bridge.init() exceptoin: %s', e.message))
  }, 1000)
}

function onServerLog(data) {
  log.verbose('PuppetWebEvent', 'onServerLog(%s)', data)
}

function onServerLogin(data, attempt = 0) {
  log.verbose('PuppetWebEvent', 'onServerLogin(%s, %d)', data, attempt)

  if (this.userId) {
    log.verbose('PuppetWebEvent', 'onServerLogin() be called but with userId set?')
  }

  co.call(this, function* () {
    // co.call to make `this` context work inside generator.
    // See also: https://github.com/tj/co/issues/274

    /**
     * save login user id to this.userId
     */
    this.userId = yield this.bridge.getUserName()

    if (!this.userId) {
      log.verbose('PuppetWebEvent', 'onServerLogin: browser not full loaded(%d), retry later', attempt)
      setTimeout(onServerLogin.bind(this, data, ++attempt), 500)
      return
    }

    log.verbose('PuppetWebEvent', 'bridge.getUserName: %s', this.userId)
    this.user = yield Contact.load(this.userId).ready()
    log.verbose('PuppetWebEvent', `onServerLogin() user ${this.user.name()} logined`)
    this.emit('login', this.user)

    yield this.browser.saveSession()
              .catch(e => { // fail safe
                log.warn('PuppetWebEvent', 'onServerLogin() browser.saveSession exception: %s', e.message)
              })
  }).catch(e => {
    log.error('PuppetWebEvent', 'onServerLogin() exception: %s', e.message)
  })
}

function onServerLogout(data) {
  if (this.user) {
    this.emit('logout', this.user)
  } else if (this.userId) {
    this.emit('logout', this.userId)
  } else { log.verbose('PuppetWebEvent', 'onServerLogout() without this.user or userId initialized') }

  this.userId = null
  this.user = null

  // this.browser.cleanSession()
  // .catch(e => { /* fail safe */
  //   log.verbose('PuppetWebEvent', 'onServerLogout() browser.cleanSession() exception: %s', e.message)
  // })
}

function onServerMessage(data) {
  let m
  // log.warn('PuppetWebEvent', 'MsgType: %s', data.MsgType)
  switch (data.MsgType) {
    case Message.Type.IMAGE:
      // log.verbose('PuppetWebEvent', 'onServerMessage() IMAGE message')
      m = new MediaMessage(data)
      break

    case 'TEXT':
    default:
      m = new Message(data)
      break
  }

  // To Be Deleted: set self...
  if (this.userId) {
    m.set('self', this.userId)
  } else {
    log.warn('PuppetWebEvent', 'onServerMessage() without this.userId')
  }

  m.ready() // TODO: EventEmitter2 for video/audio/app/sys....
  .then(() => this.emit('message', m))
  .catch(e => {
    log.error('PuppetWebEvent', 'onServerMessage() message ready exception: %s', e)
    /**
     * FIXME: add retry here...
     * setTimeout(onServerMessage.bind(this, data, ++attempt), 1000)
     */
  })
}

module.exports = PuppetWebEvent
<commit_msg>check this.bridge in timeout<commit_after>/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Class PuppetWeb Events
 *
 * use to control wechat in web browser.
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */

/**************************************
 *
 * Class PuppetWeb
 *
 ***************************************/
const util  = require('util')
const fs    = require('fs')
const co    = require('co')

const log = require('../npmlog-env')
const Contact = require('../contact')
const Message = require('../message')
const MediaMessage = require('../message-media')

const PuppetWebEvent = {
  onBrowserDead

  , onServerLogin
  , onServerLogout

  , onServerConnection
  , onServerDisconnect

  , onServerDing
  , onServerScan
  , onServerUnload
  , onServerLog

  , onServerMessage
}

function onBrowserDead(e) {
  log.verbose('PuppetWebEvent', 'onBrowserDead(%s)', e)
  // because this function is async, so maybe entry more than one times.
  // guard by variable: isBrowserBirthing to prevent the 2nd time entrance.
  if (this.isBrowserBirthing) {
    if (this.isBrowserBirthing === true) {
      log.verbose('PuppetWebEvent', 'onBrowserDead() Im busy, dont call me again before I return. this time will return and do nothing')
      return
    } else {
      log.warn('PuppetWebEvent', 'onBrowserDead() Im FAKE busy? isBrowserBirthing is not boolean true!')
    }
  }

  return co.call(this, function* () {
    log.warn('PuppetWebEvent', 'onBrowserDead() co() set isBrowserBirthing true')
    this.isBrowserBirthing = true

    const TIMEOUT = 180000 // 180s / 3m
    // this.watchDog(`onBrowserDead() set a timeout of ${Math.floor(TIMEOUT / 1000)} seconds to prevent unknown state change`, {timeout: TIMEOUT})
    this.emit('watchdog', {
      data: `onBrowserDead() set a timeout of ${Math.floor(TIMEOUT / 1000)} seconds to prevent unknown state change`
      , timeout: TIMEOUT
    })

    log.verbose('PuppetWebEvent', 'onBrowserDead(%s)', e.message || e)
    if (!this.browser || !this.bridge) {
      log.error('PuppetWebEvent', 'onBrowserDead() browser or bridge not found. do nothing')
      throw new Error('no browser or no bridge')
    }

    log.verbose('PuppetWebEvent', 'onBrowserDead() try to reborn browser')

    yield this.browser.quit()
    .catch(e => { // fail safe
      log.warn('PuppetWebEvent', 'browser.quit() exception: %s', e.message)
    })
    log.verbose('PuppetWebEvent', 'old browser quited')

    this.browser = yield this.initBrowser()
    log.verbose('PuppetWebEvent', 'new browser inited')

    // this.bridge = yield this.bridge.init()
    this.bridge = yield this.initBridge()
    log.verbose('PuppetWebEvent', 'bridge re-inited')

    const dong = yield this.ding()
    if (/dong/i.test(dong)) {
      log.verbose('PuppetWebEvent', 'ding() works well after reset')
    } else {
      log.warn('PuppetWebEvent', 'ding() get error return after reset: ' + dong)
    }
  })
  .catch(e => { // Exception
    log.error('PuppetWebEvent', 'onBrowserDead() exception: %s', e.message)

    log.warn('PuppetWebEvent', 'onBrowserDead() try to re-init PuppetWeb itself')
    return this.quit().then(_ => this.init())
  })
  .then(() => { // Finally
    log.verbose('PuppetWebEvent', 'onBrowserDead() new browser borned')
    this.isBrowserBirthing = false
  })
}

function onServerDing(data) {
  log.silly('PuppetWebEvent', 'onServerDing(%s)', data)
  // this.watchDog(data)
  this.emit('watchdog', { data })
}

function onServerScan(data) {
  log.verbose('PuppetWebEvent', 'onServerScan(%d)', data && data.code)

  /**
   * When wx.qq.com push a new QRCode to Scan, there will be cookie updates(?)
   */
  this.browser.saveSession()
      .catch(() => {/* fail safe */})

  if (this.userId) {
    log.verbose('PuppetWebEvent', 'onServerScan() there has userId when got a scan event. emit logout and set userId to null')
    this.emit('logout', this.user || this.userId)
    this.userId = this.user = null
  }

  // feed watchDog a `scan` type of food
  // this.watchDog(data, {type: 'scan'})
  this.emit('watchdog', { data, type: 'SCAN' })

  this.emit('scan', data)
}

function onServerConnection(data) {
  log.verbose('PuppetWebEvent', 'onServerConnection: %s', typeof data)
}

function onServerDisconnect(data) {
  log.verbose('PuppetWebEvent', 'onServerDisconnect: %s', data)

  if (this.userId) {
    log.verbose('PuppetWebEvent', 'onServerDisconnect() there has userId set. emit a logout event and set userId to null')
    this.emit('logout', this.user || this.userId ) //'onServerDisconnect(' + data + ')')
    this.userId = null
    this.user = null
  }

  /**
   * conditions:
   * 1. browser crash(i.e.: be killed)
   * 2. quiting
   */
  if (!this.browser) {                // no browser, quiting?
    log.verbose('PuppetWebEvent', 'onServerDisconnect() no browser. maybe Im quiting, do nothing')
    return
  } else if (this.browser.dead()) {   // browser is dead
    log.verbose('PuppetWebEvent', 'onServerDisconnect() found dead browser. wait it to restore')
    return
  } else if (!this.bridge) {          // no bridge, quiting???
    log.verbose('PuppetWebEvent', 'onServerDisconnect() no bridge. maybe Im quiting, do nothing')
    return
  }

  this.browser.readyLive()
  .then(r => {  // browser is alive, and we have a bridge to it
    log.verbose('PuppetWebEvent', 'onServerDisconnect() re-initing bridge')
    // must use setTimeout to wait a while.
    // because the browser has just refreshed, need some time to re-init to ready.
    // if the browser is not ready, bridge init will fail,
    // caused browser dead and have to be restarted. 2016/6/12
    setTimeout(_ => {
      if (!this.bridge) {
        return // XXX: sometimes this.bridge gone in this timeout. why? what's happend between the last if(!this.bridge) check and the timeout call?
      }
      this.bridge.init()
      .then(r  => log.verbose('PuppetWebEvent', 'onServerDisconnect() bridge re-inited: %s', r))
      .catch(e => log.error('PuppetWebEvent', 'onServerDisconnect() exception: [%s]', e))
    }, 1000) // 1 second instead of 10 seconds? try. (should be enough to wait)
    return
  })
  .catch(e => { // browser is in indeed dead, or almost dead. readyLive() will auto recover itself.
    log.verbose('PuppetWebEvent', 'onServerDisconnect() browser dead, waiting it recover itself: %s', e.message)
    return
  })
}

/**
 * `unload` event is sent from js@browser to webserver via socketio
 * after received `unload`, we should fix bridge by re-inject the Wechaty js code into browser.
 * possible conditions:
 * 1. browser refresh
 * 2. browser navigated to a new url
 * 3. browser quit(crash?)
 * 4. ...
 */
function onServerUnload(data) {
  log.warn('PuppetWebEvent', 'onServerUnload(%s)', typeof data)
  // onServerLogout.call(this, data) // XXX: should emit event[logout] from browser

  if (!this.browser) {
    log.warn('PuppetWebEvent', 'onServerUnload() found browser gone, should be quiting now')
    return
  } else if (!this.bridge) {
    log.warn('PuppetWebEvent', 'onServerUnload() found bridge gone, should be quiting now')
    return
  } else if (this.browser.dead()) {
    log.error('PuppetWebEvent', 'onServerUnload() found browser dead. wait it to restore itself')
    return
  }
  // re-init bridge after 1 second XXX: better method to confirm unload/reload finished?
  return setTimeout(() => {
    this.bridge.init()
    .then(r  => log.verbose('PuppetWebEvent', 'onServerUnload() bridge.init() done: %s', r))
    .catch(e => log.error('PuppetWebEvent', 'onServerUnload() bridge.init() exceptoin: %s', e.message))
  }, 1000)
}

function onServerLog(data) {
  log.verbose('PuppetWebEvent', 'onServerLog(%s)', data)
}

function onServerLogin(data, attempt = 0) {
  log.verbose('PuppetWebEvent', 'onServerLogin(%s, %d)', data, attempt)

  if (this.userId) {
    log.verbose('PuppetWebEvent', 'onServerLogin() be called but with userId set?')
  }

  co.call(this, function* () {
    // co.call to make `this` context work inside generator.
    // See also: https://github.com/tj/co/issues/274

    /**
     * save login user id to this.userId
     */
    this.userId = yield this.bridge.getUserName()

    if (!this.userId) {
      log.verbose('PuppetWebEvent', 'onServerLogin: browser not full loaded(%d), retry later', attempt)
      setTimeout(onServerLogin.bind(this, data, ++attempt), 500)
      return
    }

    log.verbose('PuppetWebEvent', 'bridge.getUserName: %s', this.userId)
    this.user = yield Contact.load(this.userId).ready()
    log.verbose('PuppetWebEvent', `onServerLogin() user ${this.user.name()} logined`)
    this.emit('login', this.user)

    yield this.browser.saveSession()
              .catch(e => { // fail safe
                log.warn('PuppetWebEvent', 'onServerLogin() browser.saveSession exception: %s', e.message)
              })
  }).catch(e => {
    log.error('PuppetWebEvent', 'onServerLogin() exception: %s', e.message)
  })
}

function onServerLogout(data) {
  if (this.user) {
    this.emit('logout', this.user)
  } else if (this.userId) {
    this.emit('logout', this.userId)
  } else { log.verbose('PuppetWebEvent', 'onServerLogout() without this.user or userId initialized') }

  this.userId = null
  this.user = null

  // this.browser.cleanSession()
  // .catch(e => { /* fail safe */
  //   log.verbose('PuppetWebEvent', 'onServerLogout() browser.cleanSession() exception: %s', e.message)
  // })
}

function onServerMessage(data) {
  let m
  // log.warn('PuppetWebEvent', 'MsgType: %s', data.MsgType)
  switch (data.MsgType) {
    case Message.Type.IMAGE:
      // log.verbose('PuppetWebEvent', 'onServerMessage() IMAGE message')
      m = new MediaMessage(data)
      break

    case 'TEXT':
    default:
      m = new Message(data)
      break
  }

  // To Be Deleted: set self...
  if (this.userId) {
    m.set('self', this.userId)
  } else {
    log.warn('PuppetWebEvent', 'onServerMessage() without this.userId')
  }

  m.ready() // TODO: EventEmitter2 for video/audio/app/sys....
  .then(() => this.emit('message', m))
  .catch(e => {
    log.error('PuppetWebEvent', 'onServerMessage() message ready exception: %s', e)
    /**
     * FIXME: add retry here...
     * setTimeout(onServerMessage.bind(this, data, ++attempt), 1000)
     */
  })
}

module.exports = PuppetWebEvent
<|endoftext|>


<commit_before>/**
 * Created by Jade on 2014/04/11.
 */
module.exports = function($) {
    return {
        panel_questions: [
            {
                end_user: $('panel_question_1_end_user'),
                incentive: $('panel_question_1_incentive'),
                reverse_billed: $('panel_question_1_reverse_billed')
            },
            {
                end_user: $('panel_question_2_end_user'),
                incentive: $('panel_question_2_incentive'),
                reverse_billed: $('panel_question_2_reverse_billed')
            },
            {
                end_user: $('panel_question_3_end_user'),
                incentive: $('panel_question_3_incentive'),
                reverse_billed: $('panel_question_3_reverse_billed')
            },
            {
                end_user: $('panel_question_4_end_user'),
                incentive: $('panel_question_4_incentive'),
                reverse_billed: $('panel_question_4_reverse_billed')
            },
            {
                end_user: $('panel_question_5_end_user'),
                incentive: $('panel_question_5_incentive'),
                reverse_billed: $('panel_question_5_reverse_billed')
            },
            {
                end_user: $('panel_question_6_end_user'),
                incentive: $('panel_question_6_incentive'),
                reverse_billed: $('panel_question_6_reverse_billed')
            }
        ]
    };
};<commit_msg>Added thermometer questions<commit_after>/**
 * Created by Jade on 2014/04/11.
 */
module.exports = function($) {
    return {
        panel_questions: [
            {
                end_user: $('panel_question_1_end_user'),
                incentive: $('panel_question_1_incentive'),
                reverse_billed: $('panel_question_1_reverse_billed')
            },
            {
                end_user: $('panel_question_2_end_user'),
                incentive: $('panel_question_2_incentive'),
                reverse_billed: $('panel_question_2_reverse_billed')
            },
            {
                end_user: $('panel_question_3_end_user'),
                incentive: $('panel_question_3_incentive'),
                reverse_billed: $('panel_question_3_reverse_billed')
            },
            {
                end_user: $('panel_question_4_end_user'),
                incentive: $('panel_question_4_incentive'),
                reverse_billed: $('panel_question_4_reverse_billed')
            },
            {
                end_user: $('panel_question_5_end_user'),
                incentive: $('panel_question_5_incentive'),
                reverse_billed: $('panel_question_5_reverse_billed')
            },
            {
                end_user: $('panel_question_6_end_user'),
                incentive: $('panel_question_6_incentive'),
                reverse_billed: $('panel_question_6_reverse_billed')
            }
        ],
        thermometer_questions: [
            {
                end_user: $('thermometer_question_1_end_user'),
                incentive: $('thermometer_question_1_incentive'),
                reverse_billed: $('thermometer_question_1_reverse_billed')
            },
            {
                end_user: $('thermometer_question_2_end_user'),
                incentive: $('thermometer_question_2_incentive'),
                reverse_billed: $('thermometer_question_2_reverse_billed')
            },
            {
                end_user: $('thermometer_question_3_end_user'),
                incentive: $('thermometer_question_3_incentive'),
                reverse_billed: $('thermometer_question_3_reverse_billed')
            },
            {
                end_user: $('thermometer_question_4_end_user'),
                incentive: $('thermometer_question_4_incentive'),
                reverse_billed: $('thermometer_question_4_reverse_billed')
            },
            {
                end_user: $('thermometer_question_5_end_user'),
                incentive: $('thermometer_question_5_incentive'),
                reverse_billed: $('thermometer_question_5_reverse_billed')
            },
        ]
    };
};<|endoftext|>


<commit_before>// ==UserScript==
// @name           futaba ID+IP popup
// @namespace      https://github.com/himuro-majika
// @description    同じIDやIPのレスをポップアップしちゃう
// @author         himuro_majika
// @include        http://*.2chan.net/*/res/*.htm
// @include        https://*.2chan.net/*/res/*.htm
// @version        1.2.6
// @run-at      document-idle
// @license        MIT
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAPUExURYv4i2PQYy2aLUe0R////zorx9oAAAAFdFJOU/////8A+7YOUwAAAElJREFUeNqUj1EOwDAIQoHn/c88bX+2fq0kRsAoUXVAfwzCttWsDWzw0kNVWd2tZ5K9gqmMZB8libt4pSg6YlO3RnTzyxePAAMAzqMDgTX8hYYAAAAASUVORK5CYII=
// ==/UserScript==
(() => {
	const USE_COUNTER = true;	// IDカウンターを表示する
	const USE_COUNTER_CURRENT = true;	// IDカウンターに現在の出現数を表示
	const scriptName = "GM_FIP";

	let Start = new Date().getTime();//count parsing time
	let saba = location.host.replace(".2chan.net","") + location.pathname.replace("futaba.htm","");
	let timer_show, timer_hide;
	let isIDIPThread = false;

	init();
	console.log(scriptName + " Parsing " + saba + ": " + ((new Date()).getTime() - Start) + "msec");//log parsing time

	function init() {
		checkThreadMail();
		setClassAndNameThread();
		setClassAndNameRes();
		createCounter();
		observeInserted();
	}

	// ID表示・IP表示スレかどうか
	function checkThreadMail() {
		let mail = document.querySelector(".cnm") ?
			document.querySelector(".cnm a") :
			document.querySelector("html > body > form font > b > a");
		isIDIPThread = mail !== null && mail.href.match(/^mailto:i[dp]/i) !== null;
	}

	// ID/IPにclass,nameを設定する
	// 本文
	function setClassAndNameThread() {
		let cnw = document.querySelectorAll(".thre .cnw");
		if (!cnw) return;
		setClassAndName(cnw);
	}
	// レス
	function setClassAndNameRes(node) {
		let resIdNode = arguments.length ? node.querySelectorAll(".rtd .cnw") : document.querySelectorAll(".rtd .cnw");
		if (!resIdNode) return;
		setClassAndName(resIdNode);
	}

	function setClassAndName(node) {
		if(!node.length) return;

		node.forEach((item) => {
			let matchText = item.firstChild.nodeValue.match(/(.+)(I[DP]:\S+)/);
			if (!matchText) return;
			let dateEle = document.createTextNode(matchText[1]);
			let idText = matchText[2];
			let idEle = document.createElement("a");
			idEle.textContent = idText;
			idEle.classList.add("GM_fip_name");
			idEle.setAttribute("name", idText);
			idEle.style.color = "#F00";
			idEle.addEventListener("mouseover", openPopup, true);
			idEle.addEventListener("mouseout", closePopup, true);
			item.innerText = ""
			item.appendChild(dateEle);
			item.appendChild(idEle);
		})
	}
	// 出現数の表示
	function createCounter() {
		if (!USE_COUNTER) return;
		let a = document.getElementsByClassName("GM_fip_name");
		let ids = {};
		for (let i = 0; i < a.length; i++) {
			let node = a[i];
			let id = node.name;
			if (USE_COUNTER_CURRENT) {
				if (ids[id]) {
					ids[id]++;
				} else {
					ids[id] = 1;
				}
			}
			let name = document.getElementsByName(id);
			let span;
			if (node.childNodes[1]) {
				span = node.childNodes[1];
			} else {
				span = document.createElement("span");
				span.classList.add("GM_fip_counter");
				span.style.margin = "0 5px";
				node.appendChild(span);
			}
			if (USE_COUNTER_CURRENT) {
				span.textContent = "[" + ids[id] + "/" + name.length + "]";
			} else {
				span.textContent = "[" + name.length + "]";
			}
		}
	}
	// ポップアップを表示する
	function openPopup(event) {
		clearTimeout(timer_show);
		delPopup();
		timer_show = setTimeout(() => {
			let name = this.name;
			let popup = makePopupContainer();
			let divThread = document.createElement("div");
			let table = document.createElement("table");
			let tbody = document.createElement("tbody");
			let tda = document.getElementsByName(name);
			for (let i = 0; i < tda.length; i++) {
				if (tda[i].parentNode.parentNode.className === "thre") {
					// スレ
					let form;
					if (document.querySelector(".cnw")) {
						form = tda[i].parentNode.parentNode.cloneNode(true);
					} else {
						form = tda[i].parentNode.cloneNode(true);
					}
					for (let j = 0; j < form.childNodes.length; j++) {
						// 広告
						if (form.childNodes[j].className !== "tue") {
							divThread.appendChild(form.childNodes[j].cloneNode(true));
						}
						if (form.childNodes[j].tagName == "BLOCKQUOTE") {
							break;
						}
					}
				} else {
					// レス
					let tr;
					if (document.querySelector(".cnw")) {
						tr =  tda[i].parentNode.parentNode.parentNode.cloneNode(true);
					} else {
						tr = tda[i].parentNode.parentNode.cloneNode(true);
					}
					tbody.appendChild(tr);
				}
			}
			popup.appendChild(divThread);
			popup.appendChild(table);
			table.appendChild(tbody);
			this.parentNode.appendChild(popup);
			document.querySelectorAll("#GM_fip_pop .GM_fip_name").forEach((item) => {
				item.className = ("GM_fip_name_pop");
			})
			let wX = event.clientX + 10;	//ポップアップ表示位置X
			let wY = window.scrollY + event.clientY - popup.clientHeight - 10;	//ポップアップ表示位置Y
			if ( wY < 0 ) {	//ポップアップが上に見きれる時は下に表示
				wY = window.scrollY + event.clientY;
			}
			popup.style.top = wY + "px";
			popup.style.left = wX + "px";
		}, 100);
	}

	function makePopupContainer() {
		let container = document.createElement("div");
		container.id = "GM_fip_pop";
		container.classList.add("GM_fip_pop");
		container.style.position = "absolute";
		container.style.zIndex = 350;
		container.style.backgroundColor = "#eeaa88";
		container.style.fontSize = "0.85em";
		container.addEventListener("mouseover",() => {
			clearTimeout(timer_hide);
		}, true);
		container.addEventListener("mouseout",closePopup,true);

		return container;
	}
	// ポップアップを消す
	function closePopup() {
		clearTimeout(timer_show);
		clearTimeout(timer_hide);
		timer_hide = setTimeout(delPopup, 300);
	}

	function delPopup() {
		clearTimeout(timer_hide);
		let doc_pop = document.getElementsByClassName("GM_fip_pop");
		if ( doc_pop ) {
			for (let i = 0; i < doc_pop.length; i++) {
				doc_pop[i].remove();
			}
		}
	}
	// 続きを読むで追加されるレスを監視
	function observeInserted() {
		let target = document.querySelector(".thre");
		let timer_reload;
		let observer = new MutationObserver(function(mutations) {
			mutations.forEach((mutation) => {
				if (!mutation.addedNodes.length) return;
				let nodes = mutation.addedNodes[0];
				if (isIDIPThread && nodes.tagName == "TABLE" && nodes.id !== "akahuku_bottom_container") {
					setClassAndNameRes(nodes);
					clearTimeout(timer_reload);
					timer_reload = setTimeout(rel, 50);
				};
			});
		});
		observer.observe(target, { childList: true });
		function rel() {
			if (!isIDIPThread) {
				setClassAndNameThread();
				setClassAndNameRes();
			}
			createCounter();
		}
	}
})();
<commit_msg>Add res no jump feature.<commit_after>// ==UserScript==
// @name           futaba ID+IP popup
// @namespace      https://github.com/himuro-majika
// @description    同じIDやIPのレスをポップアップしちゃう
// @author         himuro_majika
// @include        http://*.2chan.net/*/res/*.htm
// @include        https://*.2chan.net/*/res/*.htm
// @version        1.2.6
// @license        MIT
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAPUExURYv4i2PQYy2aLUe0R////zorx9oAAAAFdFJOU/////8A+7YOUwAAAElJREFUeNqUj1EOwDAIQoHn/c88bX+2fq0kRsAoUXVAfwzCttWsDWzw0kNVWd2tZ5K9gqmMZB8libt4pSg6YlO3RnTzyxePAAMAzqMDgTX8hYYAAAAASUVORK5CYII=
// ==/UserScript==
(() => {
	const USE_COUNTER = true;	// IDカウンターを表示する
	const USE_COUNTER_CURRENT = true;	// IDカウンターに現在の出現数を表示
	const scriptName = "GM_FIP";

	let Start = new Date().getTime();//count parsing time
	let saba = location.host.replace(".2chan.net","") + location.pathname.replace("futaba.htm","");
	let timer_show, timer_hide;
	let isIDIPThread = false;

	init();
	console.log(scriptName + " Parsing " + saba + ": " + ((new Date()).getTime() - Start) + "msec");//log parsing time

	function init() {
		checkThreadMail();
		setClassAndNameThread();
		setClassAndNameRes();
		createCounter();
		observeInserted();
	}

	// ID表示・IP表示スレかどうか
	function checkThreadMail() {
		let mail = document.querySelector(".cnm") ?
			document.querySelector(".cnm a") :
			document.querySelector("html > body > form font > b > a");
		isIDIPThread = mail !== null && mail.href.match(/^mailto:i[dp]/i) !== null;
	}

	// ID/IPにclass,nameを設定する
	// 本文
	function setClassAndNameThread() {
		let cnw = document.querySelectorAll(".thre .cnw");
		if (!cnw) return;
		setClassAndName(cnw);
	}
	// レス
	function setClassAndNameRes(node) {
		let resIdNode = arguments.length ? node.querySelectorAll(".rtd .cnw") : document.querySelectorAll(".rtd .cnw");
		if (!resIdNode) return;
		setClassAndName(resIdNode);
	}

	function setClassAndName(node) {
		if(!node.length) return;

		node.forEach((item) => {
			let matchText = item.firstChild.nodeValue.match(/(.+)(I[DP]:\S+)/);
			if (!matchText) return;
			let dateEle = document.createTextNode(matchText[1]);
			let idText = matchText[2];
			let idEle = document.createElement("a");
			idEle.textContent = idText;
			idEle.classList.add("GM_fip_name");
			idEle.setAttribute("name", idText);
			idEle.style.color = "#F00";
			idEle.addEventListener("mouseover", openPopup, true);
			idEle.addEventListener("mouseout", closePopup, true);
			item.innerText = ""
			item.appendChild(dateEle);
			item.appendChild(idEle);
		})
	}
	// 出現数の表示
	function createCounter() {
		if (!USE_COUNTER) return;
		let a = document.getElementsByClassName("GM_fip_name");
		let ids = {};
		for (let i = 0; i < a.length; i++) {
			let node = a[i];
			let id = node.name;
			if (USE_COUNTER_CURRENT) {
				if (ids[id]) {
					ids[id]++;
				} else {
					ids[id] = 1;
				}
			}
			let name = document.getElementsByName(id);
			let span;
			if (node.childNodes[1]) {
				span = node.childNodes[1];
			} else {
				span = document.createElement("span");
				span.classList.add("GM_fip_counter");
				span.style.margin = "0 5px";
				node.appendChild(span);
			}
			if (USE_COUNTER_CURRENT) {
				span.textContent = "[" + ids[id] + "/" + name.length + "]";
			} else {
				span.textContent = "[" + name.length + "]";
			}
		}
	}
	// ポップアップを表示する
	function openPopup(event) {
		clearTimeout(timer_show);
		delPopup();
		timer_show = setTimeout(() => {
			let name = this.name;
			let popup = makePopupContainer();
			let divThread = document.createElement("div");
			let table = document.createElement("table");
			let tbody = document.createElement("tbody");
			let tda = document.getElementsByName(name);
			for (let i = 0; i < tda.length; i++) {
				if (tda[i].parentNode.parentNode.className === "thre") {
					// スレ
					let form;
					if (document.querySelector(".cnw")) {
						form = tda[i].parentNode.parentNode.cloneNode(true);
					} else {
						form = tda[i].parentNode.cloneNode(true);
					}
					for (let j = 0; j < form.childNodes.length; j++) {
						// 広告
						if (form.childNodes[j].className !== "tue") {
							divThread.appendChild(form.childNodes[j].cloneNode(true));
						}
						if (form.childNodes[j].tagName == "BLOCKQUOTE") {
							break;
						}
					}
				} else {
					// レス
					let tr;
					if (document.querySelector(".cnw")) {
						tr =  tda[i].parentNode.parentNode.parentNode.cloneNode(true);
					} else {
						tr = tda[i].parentNode.parentNode.cloneNode(true);
					}
					setQtJump(tr);
					tbody.appendChild(tr);
				}
			}
			popup.appendChild(divThread);
			popup.appendChild(table);
			table.appendChild(tbody);
			this.parentNode.appendChild(popup);
			document.querySelectorAll("#GM_fip_pop .GM_fip_name").forEach((item) => {
				item.className = ("GM_fip_name_pop");
			})
			let wX = event.clientX + 10;	//ポップアップ表示位置X
			let wY = window.scrollY + event.clientY - popup.clientHeight - 10;	//ポップアップ表示位置Y
			if ( wY < 0 ) {	//ポップアップが上に見きれる時は下に表示
				wY = window.scrollY + event.clientY;
			}
			popup.style.top = wY + "px";
			popup.style.left = wX + "px";
		}, 100);
		//ポップアップ内レス番号クリックでジャンプ
		function setQtJump(qt) {
			let rsc = qt.querySelector(".rsc");
			rsc.classList.add("qtjmp");
			let jumpid = rsc.id;
			rsc.removeAttribute("id");
			rsc.addEventListener("click", () => {
				let jumptarget = document.getElementById(jumpid).parentNode;
				window.scroll(0, jumptarget.getBoundingClientRect().top + window.pageYOffset);
				delPopup();
			});
		}
	}

	function makePopupContainer() {
		let container = document.createElement("div");
		container.id = "GM_fip_pop";
		container.classList.add("GM_fip_pop");
		container.style.position = "absolute";
		container.style.zIndex = 350;
		container.style.backgroundColor = "#eeaa88";
		container.style.fontSize = "0.85em";
		container.addEventListener("mouseover",() => {
			clearTimeout(timer_hide);
		}, true);
		container.addEventListener("mouseout",closePopup,true);

		return container;
	}
	// ポップアップを消す
	function closePopup() {
		clearTimeout(timer_show);
		clearTimeout(timer_hide);
		timer_hide = setTimeout(delPopup, 300);
	}

	function delPopup() {
		clearTimeout(timer_hide);
		let doc_pop = document.getElementsByClassName("GM_fip_pop");
		if ( doc_pop ) {
			for (let i = 0; i < doc_pop.length; i++) {
				doc_pop[i].remove();
			}
		}
	}
	// 続きを読むで追加されるレスを監視
	function observeInserted() {
		let target = document.querySelector(".thre");
		let timer_reload;
		let observer = new MutationObserver(function(mutations) {
			mutations.forEach((mutation) => {
				if (!mutation.addedNodes.length) return;
				let nodes = mutation.addedNodes[0];
				if (isIDIPThread && nodes.tagName == "TABLE" && nodes.id !== "akahuku_bottom_container") {
					setClassAndNameRes(nodes);
					clearTimeout(timer_reload);
					timer_reload = setTimeout(rel, 50);
				};
			});
		});
		observer.observe(target, { childList: true });
		function rel() {
			if (!isIDIPThread) {
				setClassAndNameThread();
				setClassAndNameRes();
			}
			createCounter();
		}
	}
})();
<|endoftext|>


<commit_before>"use strict";

function UFOs(container){

	//scene elements, I wouldn't mess with these if you are trying to work with this
	var scene, camera, renderer, cube;
	//Mess with these if you don't want a full screen visualization
	var WIDTH  = window.innerWidth;
	var HEIGHT = window.innerHeight;

	var MAXWIDTH = 928, MAXHEIGHT = 592;
	var jsonloader = new THREE.JSONLoader();
	//SECTION INITIALIZATION
	function init(container) {
		scene = new THREE.Scene();
		initCamera();
		initRenderer();
		initCube();
		container.body.appendChild(renderer.domElement);
		addMouseHandler(renderer.domElement);
		render();
	}
	function resize(){
		WIDTH  = window.innerWidth;
		HEIGHT = window.innerHeight
		renderer.setSize(WIDTH, HEIGHT);
	}
	function initCamera() {
		camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT, 1, 10000);
		camera.position.set(0,-500,500);
		camera.lookAt(scene.position);
	}

	function initRenderer() {
		renderer = new THREE.WebGLRenderer({ antialias: true });
		renderer.setSize(WIDTH, HEIGHT);
		window.renderer = renderer;
	}
	function initCube() {
                var loader = new THREE.TextureLoader();

                var texture = loader.load("usmap.gif");
		var material = new THREE.MeshPhongMaterial({map:texture});
		material.bumpMap =texture;

		material.bumpScale = 22;
		cube = new THREE.Mesh(new THREE.CubeGeometry(MAXWIDTH,MAXHEIGHT, 2), material);
		var light = new THREE.DirectionalLight( 0xffffff );
		light.position.set( 1, 1, 1).normalize();
		scene.add(light);
		scene.add(cube);
	}
	var ufos = [];
	var counter = 0;
      var ufo_geometry = new THREE.TorusGeometry(25,5,25,50);

	function addUFO(x,y){
			counter++;
      var ufo = new THREE.Mesh(ufo_geometry,ufo_material);
			var ufo_material = new THREE.MeshBasicMaterial({color: Math.random()%0xffffff,opacity:.6});
			ufo.name = counter.toString();
			cube.add(ufo);
		  ufo.position.set(-928/2 +x,592/2 -y,400);
			ufos.push(ufo);
	}
	function animateUFO(ufo){
			ufo.completelyAnimated = false;
			ufo.ival = setInterval(function(){
				ufo.position.z-=3;
				if(ufo.position.z <=75)
				{
					clearInterval(ufo.ival);
				}
			},20);
	}
	this.startAnimation = function(){
			ufos.forEach(function(ufo){
					animateUFO(ufo);
			});
	}
	function addUFOPercent(x,y){
			addUFO(x*MAXWIDTH, y*MAXHEIGHT);
	}
	function remove(ufo){
			ufo.ival2 = setInterval(function(){
					ufo.z+=3;
					if(ufo.z >=300){
						clearInterval(ufo.ival2);
						scene.remove(ufo);
					}
			},30);
	}
	function clearUFOs()
	{
			ufos.forEach(function(ufo){
				remove(ufo);
			});
			ufos = [];
	}

	//END INITIALIZATION

	function render(){
                renderer.render(scene, camera);
                requestAnimationFrame(render);
	}

	//Function Calls

	init(container);
	this.addUFO = addUFO;
	this.clearUFOs = clearUFOs;
	this.resize = resize;
	this.addUFOPercent = addUFOPercent;

	this.destroy = function(){
			renderer.domElement.parentNode.removeChild(renderer.domElement);
	}

	var mouseDown = false,mouseX,mouseY;
	// kept all of these at the end so they're out of the way.
	function onMouseMove(e) {

        e.preventDefault();

        var deltaX = e.clientX - mouseX,
            deltaY = e.clientY - mouseY;
        mouseX = e.clientX;
        mouseY = e.clientY;

        if (mouseDown) {
			rotateScene(deltaX,deltaY);
			moveScene(deltaX, deltaY);
        }
    }
    function onMouseUp(evt) {
        evt.preventDefault();

        mouseDown = false;
    }
	function onMouseDown(evt){
		evt.preventDefault();
		mouseDown = true;
	}

    function addMouseHandler(canvas) {
    canvas.addEventListener('mousemove', function (e) {
        onMouseMove(e);
    }, false);
    canvas.addEventListener('mousedown', function (e) {
        onMouseDown(e);
    }, false);
    canvas.addEventListener('mouseup', function (e) {
        onMouseUp(e);
    }, false);
}
	function rotateScene(deltaX,deltaY){
		cube.rotation.z -= deltaX/100;
	}
    function moveScene(deltaX, deltaY) {
					camera.position.z += deltaY;
					camera.rotation.x-=deltaY/360;

	}
}
<commit_msg>UFOs animation is complete<commit_after>"use strict";

function UFOs(container){

	//scene elements, I wouldn't mess with these if you are trying to work with this
	var scene, camera, renderer, cube;
	//Mess with these if you don't want a full screen visualization
	var WIDTH  = window.innerWidth;
	var HEIGHT = window.innerHeight;

	var MAXWIDTH = 928, MAXHEIGHT = 592;
	var jsonloader = new THREE.JSONLoader();
	//SECTION INITIALIZATION
	function init(container) {
		scene = new THREE.Scene();
		initCamera();
		initRenderer();
		initCube();
		container.body.appendChild(renderer.domElement);
		addMouseHandler(renderer.domElement);
		render();
	}
	function resize(){
		WIDTH  = window.innerWidth;
		HEIGHT = window.innerHeight
		renderer.setSize(WIDTH, HEIGHT);
	}
	function initCamera() {
		camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT, 1, 10000);
		camera.position.set(0,-500,500);
		camera.lookAt(scene.position);
	}

	function initRenderer() {
		renderer = new THREE.WebGLRenderer({ antialias: true });
		renderer.setSize(WIDTH, HEIGHT);
		window.renderer = renderer;
	}
	function initCube() {
                var loader = new THREE.TextureLoader();

                var texture = loader.load("usmap.gif");
		var material = new THREE.MeshPhongMaterial({map:texture});
		material.bumpMap =texture;

		material.bumpScale = 22;
		cube = new THREE.Mesh(new THREE.CubeGeometry(MAXWIDTH,MAXHEIGHT, 2), material);
		var light = new THREE.DirectionalLight( 0xffffff );
		light.position.set( 1, 1, 1).normalize();
		scene.add(light);
		scene.add(cube);
	}
	var ufos = [];
	var counter = 0;
      var ufo_geometry = new THREE.TorusGeometry(25,5,25,50);

	function addUFO(x,y){
			counter++;
      var ufo = new THREE.Mesh(ufo_geometry,ufo_material);
			var ufo_material = new THREE.MeshBasicMaterial({color: Math.random()%0xffffff,opacity:.6});
			ufo.name = counter.toString();
			cube.add(ufo);
		  ufo.position.set(-928/2 +x,592/2 -y,400);
			ufos.push(ufo);
	}
	function animateUFO(ufo){
			ufo.completelyAnimated = false;
			ufo.ival = setInterval(function(){
				ufo.position.z-=3;
				if(ufo.position.z <=75)
				{
					clearInterval(ufo.ival);
				}
			},20);
	}
	this.startAnimation = function(){
			ufos.forEach(function(ufo){
					animateUFO(ufo);
			});
	}
	function addUFOPercent(x,y){
			addUFO(x*MAXWIDTH, y*MAXHEIGHT);
	}
	function remove(ufo){
			ufo.ival2 = setInterval(function(){
					ufo.position.z+=5;
					if(ufo.position.z >=500){
						clearInterval(ufo.ival2);
						cube.remove(ufo);
					}
			},30);
	}
	function clearUFOs()
	{
			ufos.forEach(function(ufo){
				remove(ufo);
			});
			ufos.splice(0,ufos.length);
	}

	//END INITIALIZATION

	function render(){
                renderer.render(scene, camera);
                requestAnimationFrame(render);
	}

	//Function Calls

	init(container);
	this.addUFO = addUFO;
	this.clearUFOs = clearUFOs;
	this.resize = resize;
	this.addUFOPercent = addUFOPercent;

	this.destroy = function(){
			renderer.domElement.parentNode.removeChild(renderer.domElement);
	}

	var mouseDown = false,mouseX,mouseY;
	// kept all of these at the end so they're out of the way.
	function onMouseMove(e) {

        e.preventDefault();

        var deltaX = e.clientX - mouseX,
            deltaY = e.clientY - mouseY;
        mouseX = e.clientX;
        mouseY = e.clientY;

        if (mouseDown) {
			rotateScene(deltaX,deltaY);
			moveScene(deltaX, deltaY);
        }
    }
    function onMouseUp(evt) {
        evt.preventDefault();

        mouseDown = false;
    }
	function onMouseDown(evt){
		evt.preventDefault();
		mouseDown = true;
	}

    function addMouseHandler(canvas) {
    canvas.addEventListener('mousemove', function (e) {
        onMouseMove(e);
    }, false);
    canvas.addEventListener('mousedown', function (e) {
        onMouseDown(e);
    }, false);
    canvas.addEventListener('mouseup', function (e) {
        onMouseUp(e);
    }, false);
}
	function rotateScene(deltaX,deltaY){
		cube.rotation.z -= deltaX/100;
	}
    function moveScene(deltaX, deltaY) {
					camera.position.z += deltaY;
					camera.rotation.x-=deltaY/360;

	}
}
<|endoftext|>


<commit_before>// Requires ====================================================================
var pkg = require ('./package.json');
var fs = require ('fs');
var express = require ('express');
var WebSocketServer = require ('ws').Server;

// Constants ===================================================================
var VERSION         = pkg.version;
var LOG_FILE        = 'ugly.log';
var VIEWER_PORT     = 3333;
var VIEWER_ADDR     = 'localhost:' + VIEWER_PORT;
var SOCKET_PORT     = 4444;
var SOCKET_ADDR     = 'ws://localhost:' + SOCKET_PORT;

// Globals = ===================================================================
var server = new WebSocketServer ({ port: SOCKET_PORT});
var socket = undefined;

// Logging =====================================================================
function initLog () {
	// Clear the log
	fs.writeFileSync (LOG_FILE, "");

	// Write header data
	writeToFile ('Created with ugly v' + VERSION);
	writeToFile ('Start date: ' + (new Date ().toString ()));
	writeToFile ('===========================================================');
}

function writeToFile (msg_) {
	fs.appendFileSync (LOG_FILE, msg_ + '\n');
}

function info (msg_) {
	var time = new Date ().getTime ();

	msg_ = '[' + time + '] INFO: ' + msg_;

	console.log (msg_);
	writeToFile (msg_);
}

function error (msg_) {
	var time = new Date ().getTime ();

	msg_ = '[' + time + '] ERROR: ' + msg_;

	console.error (msg_);
	writeToFile (msg_);
	process.exit (1);
}

// Main code ==================================================================

// Start listening on stdin
function main () {
	initLog ();
	info ("Initializing...");

	serveViewer ();

	connectToViewer (function () {
		readlines (handleLine);
	});
}

function serveViewer () {
	var app = express ();

	app.use (express.static (__dirname + '/viewer'));
	app.listen (VIEWER_PORT);

	info ('Serving viewer at ' + VIEWER_ADDR);
}

function connectToViewer (callback_) {
	server.on ('connection', function (socket_) {
		info ('Viewer connected');

		socket = socket_;
		callback_ ();
	});
}

// Listen on stdin and call callback_ on each line
function readlines (callback_) {
	console.assert (typeof (callback_) === 'function');

	info ("Listening on stdin");

	var unhandledText = '';

	process.stdin.setEncoding ('utf8');
	process.stdin.on ('readable', function () {
		var chunk = process.stdin.read ();

		if (chunk === null)
			return;

		unhandledText += chunk;

		var lineBreak = unhandledText.indexOf ('\n');

		while (lineBreak >= 0) {
			callback_ (unhandledText.substring (0, lineBreak));

			unhandledText = unhandledText.slice (lineBreak + 1);
			lineBreak = unhandledText.indexOf ('\n');
		}
	});
}

function handleLine (line_) {
	console.assert (typeof (line_) === 'string');

	socket.send (line_, function (err_) {
		if (err_)
			error (err_);
	});
}

// GO!
main ();
<commit_msg>Implemented parsing according to protocol v1.0.1<commit_after>// Requires ====================================================================
var pkg = require ('./package.json');
var fs = require ('fs');
var express = require ('express');
var WebSocketServer = require ('ws').Server;

// Constants ===================================================================
var VERSION         = pkg.version;
var LOG_FILE        = 'ugly.log';
var VIEWER_PORT     = 3333;
var VIEWER_ADDR     = 'localhost:' + VIEWER_PORT;
var SOCKET_PORT     = 4444;
var SOCKET_ADDR     = 'ws://localhost:' + SOCKET_PORT;

// Globals = ===================================================================
var server = new WebSocketServer ({ port: SOCKET_PORT});
var socket;
var currentChunk;
var lineHandler;

// Logging =====================================================================
function initLog () {
	// Clear the log
	fs.writeFileSync (LOG_FILE, "");

	// Write header data
	writeToFile ('Created with ugly v' + VERSION);
	writeToFile ('Start date: ' + (new Date ().toString ()));
	writeToFile ('===========================================================');
}

function writeToFile (msg_) {
	fs.appendFileSync (LOG_FILE, msg_ + '\n');
}

function info (msg_) {
	var time = new Date ().getTime ();

	msg_ = '[' + time + '] INFO: ' + msg_;

	console.log (msg_);
	writeToFile (msg_);
}

function error (msg_) {
	var time = new Date ().getTime ();

	msg_ = '[' + time + '] ERROR: ' + msg_;

	console.error (msg_);
	writeToFile (msg_);
	process.exit (1);
}

// Main code ==================================================================

// Start listening on stdin
function main () {
	initLog ();
	info ("Initializing...");

	serveViewer ();

	connectToViewer (function () {
		readlines (handleLine);
	});
}

function serveViewer () {
	var app = express ();

	app.use (express.static (__dirname + '/viewer'));
	app.listen (VIEWER_PORT);

	info ('Serving viewer at ' + VIEWER_ADDR);
}

function connectToViewer (callback_) {
	server.on ('connection', function (socket_) {
		info ('Viewer connected');

		socket = socket_;
		callback_ ();
	});
}

// Listen on stdin and call callback_ on each line
function readlines (callback_) {
	console.assert (typeof (callback_) === 'function');

	info ("Listening on stdin");

	var unhandledText = '';

	process.stdin.setEncoding ('utf8');
	process.stdin.on ('readable', function () {
		var chunk = process.stdin.read ();

		if (chunk === null)
			return;

		unhandledText += chunk;

		var lineBreak = unhandledText.indexOf ('\n');

		while (lineBreak >= 0) {
			callback_ (unhandledText.substring (0, lineBreak));

			unhandledText = unhandledText.slice (lineBreak + 1);
			lineBreak = unhandledText.indexOf ('\n');
		}
	});
}

// Command parsing =============================================================
function startsWith (prefix_, string_) {
	console.assert (typeof (prefix_) === 'string');
	console.assert (typeof (string_) === 'string');

	return string_.indexOf (prefix_) === 0;
}

function sendData (data_) {
	console.assert (typeof (data_) === 'string');

	socket.send (data_, function (err_) {
		if (err_)
			error (err_);
	});
}

// Config validation functions -------------------------------------------------
function validateLetterboxColor (line_) {
	console.assert (typeof (line_) === 'string');
	console.assert (startsWith ('letterbox_color', line_));

	var commandArr = line_.match (/\S+/g);

	if (commandArr.length !== 2)
		error ('Invalid syntax. letterbox_color expects 1 parameter.');

	var color = commandArr[1];

	if (! color.match (/#[a-fA-F0-9]{6}/))
		error ('Invalid syntax. letterbox_color expectx a parameter of ' +
		       'the form #XXXXXX where XXXXXX is the hexadecimal ' +
		       'representation of the desired color.');
}

function parseConfigCommand (line_) {
	console.assert (typeof (line_) === 'string');

	if (startsWith ('letterbox_color', line_)) {
		validateLetterboxColor (line_);
		return;
	} else {
		error ('Unrecognized config command "' + line_ + '"');
	}
}

// Frame validation functions --------------------------------------------------
function parseFrameCommand (line_) {
	console.assert (typeof (line_) === 'string');

	error ('Unrecognized frame command "' + line_ + '"');

}

function handleLine (line_) {
	console.assert (typeof (line_) === 'string');

	if (line_.length === 0)
		return;

	// TODO: This should be moved to a global so I don't recreate it each time
	var chunkHandlers = {
		'CONFIG': parseConfigCommand,
		'FRAME': parseFrameCommand,
	};

	for (var chunkName in chunkHandlers) {
		// Chunk declaration
		if (startsWith ('$' + chunkName, line_)) {
			if (currentChunk !== undefined)
				error ('Found ' + chunkName + ' declaration before the ' +
				       'previous chunk was terminated.');

			currentChunk = chunkName;
			lineHandler = chunkHandlers[chunkName];
			break;
		// Chunk termination
		} else if (startsWith ('$END_' + chunkName, line_)) {
			if (currentChunk !== chunkName)
				error ('Found ' + chunkName + ' terminator in non-' +
				       chunkName + ' chunk.');

			lineHandler = undefined;
			currentChunk = undefined;
			break;
		}
	}

	// Only handle commands, not declarations or terminations
	if (! startsWith ('$', line_))
			lineHandler (line_);

	// We send everything, though
	sendData (line_);
}

// Entry point =================================================================
main ();
<|endoftext|>