<commit_before>"use strict";

var Promise = require('bluebird');

var substitutions = require("./substitutions");
var rp = require('request-promise');

function BridgedRoom(main, opts) {
    this._main = main;

    if (!opts.inbound_id) {
        throw new Error("BridgedRoom requires an inbound ID");
    }
    if (!opts.matrix_room_id) {
        throw new Error("BridgedRoom requires an Matrix Room ID");
    }

    this._matrix_room_id = opts.matrix_room_id;
    this._inbound_id = opts.inbound_id;
    this._slack_channel_name = opts.slack_channel_name;
    this._slack_channel_id = opts.slack_channel_id;
    this._slack_webhook_uri = opts.slack_webhook_uri;
    this._slack_bot_token = opts.slack_bot_token;
    this._slack_user_token = opts.slack_user_token;
    this._slack_team_domain = opts.slack_team_domain;
    this._slack_team_id = opts.slack_team_id;
    this._slack_user_id = opts.slack_user_id;
    this._slack_bot_id =  opts.slack_bot_id;
    this._access_token = opts.access_token;
    this._access_scopes = opts.access_scopes;

    this._slackAtime = null;  // last activity time in epoch seconds
    this._matrixAtime = null;

    this._dirty = true;
};

BridgedRoom.prototype.getStatus = function() {
    if (!this._slack_webhook_uri && !this._slack_bot_token) {
        return "pending-params";
    }
    if (!this._slack_channel_name) {
        return "pending-name";
    }
    if (!this._access_token && !this._slack_bot_token) {
        return "ready-no-token";
    }
    return "ready";
}

// Returns true if this instance has changed from the version last read/written
// to the RoomStore.
BridgedRoom.prototype.isDirty = function() {
    return this._dirty;
};

BridgedRoom.prototype.getInboundId = function() {
    return this._inbound_id;
};

BridgedRoom.prototype.getSlackChannelId = function() {
    return this._slack_channel_id;
};

BridgedRoom.prototype.getSlackChannelName = function() {
    return this._slack_channel_name;
};

BridgedRoom.prototype.getSlackWebhookUri = function() {
    return this._slack_webhook_uri;
};

BridgedRoom.prototype.getAccessToken = function() {
    return this._access_token || this._slack_bot_token;
};

BridgedRoom.prototype.getMatrixRoomId = function() {
    return this._matrix_room_id;
};

BridgedRoom.prototype.getSlackTeamDomain = function() {
  return this._slack_team_domain;
};

BridgedRoom.prototype.getSlackTeamId = function() {
    return this._slack_team_id;
};

BridgedRoom.prototype.getSlackBotId = function() {
    return this._slack_bot_id;
};

BridgedRoom.prototype.getSlackUserToken = function() {
    return this._slack_user_token;
};

BridgedRoom.prototype.updateInboundId = function(inbound_id) {
    if (this._inbound_id !== inbound_id) this._dirty = true;
    this._inbound_id = inbound_id;
};

BridgedRoom.prototype.updateSlackChannelId = function(channel_id) {
    if (this._slack_channel_id !== channel_id) this._dirty = true;
    this._slack_channel_id = channel_id;
};

BridgedRoom.prototype.updateSlackChannelName = function(channel_name) {
    if (this._slack_channel_name !== channel_name) this._dirty = true;
    this._slack_channel_name = channel_name;
};

BridgedRoom.prototype.updateSlackWebhookUri = function(slack_webhook_uri) {
    if (this._slack_webhook_uri !== slack_webhook_uri) this._dirty = true;
    this._slack_webhook_uri = slack_webhook_uri;
};

BridgedRoom.prototype.updateSlackBotToken = function(slack_bot_token) {
  if (this._slack_bot_token !== slack_bot_token) this._dirty = true;
  this._slack_bot_token = slack_bot_token;
};

BridgedRoom.prototype.updateSlackUserToken = function(slack_user_token) {
    if (this._slack_user_token !== slack_user_token) this._dirty = true;
    this._slack_user_token = slack_user_token;
};

BridgedRoom.prototype.updateSlackTeamDomain = function(domain) {
  if (this._slack_team_domain !== domain) this._dirty = true;
  this._slack_team_domain = domain;
};

BridgedRoom.prototype.updateAccessToken = function(token, scopes) {
  console.log('updateAccessToken ->', token, scopes);
    if (this._access_token === token &&
        this._access_scopes.sort().join(",") === scopes.sort().join(",")) return;

    this._access_token = token;
    this._access_scopes = scopes;
    this._dirty = true;
};

BridgedRoom.fromEntry = function(main, entry) {
    var opts = {
        inbound_id: entry.remote_id,
        matrix_room_id: entry.matrix_id,
        slack_channel_id: entry.remote.id,
        slack_channel_name: entry.remote.name,
        slack_webhook_uri: entry.remote.webhook_uri,
        slack_bot_token: entry.remote.slack_bot_token,
        slack_user_token: entry.remote.slack_user_token,
        slack_team_domain: entry.remote.slack_team_domain,
        slack_team_id: entry.remote.slack_team_id,
        slack_user_id: entry.remote.slack_user_id,
        slack_bot_id: entry.remote.slack_bot_id,
        access_token: entry.remote.access_token,
        access_scopes: entry.remote.access_scopes,
    };

    return new BridgedRoom(main, opts);
};

// Returns data to write to the RoomStore
// As a side-effect will also clear the isDirty() flag
BridgedRoom.prototype.toEntry = function() {
    var entry = {
        remote: {
            id: this._slack_channel_id,
            name: this._slack_channel_name,
            webhook_uri: this._slack_webhook_uri,
            slack_bot_token: this._slack_bot_token,
            slack_user_token: this._slack_user_token,
            slack_team_domain: this._slack_team_domain,
            slack_team_id: this._slack_team_id,
            slack_user_id: this._slack_user_id,
            slack_bot_id: this._slack_bot_id,
            access_token: this._access_token,
            access_scopes: this._access_scopes,
        },
    };

    entry.id = "INTEG-" + this._inbound_id;
    entry.matrix_id = this._matrix_room_id;
    entry.remote_id = this._inbound_id;

    this._dirty = false;
    return entry;
};

BridgedRoom.prototype.onMatrixMessage = function(message) {
    if (!this._slack_webhook_uri && !this._slack_bot_token) return Promise.resolve();

    return this._main.getOrCreateMatrixUser(message.user_id).then((user) => {
        var body = substitutions.matrixToSlack(message, this._main);

        var uri = (this._slack_bot_token) ? "https://slack.com/api/chat.postMessage" : this._slack_webhook_uri;

        var sendMessageParams = {
            method: "POST",
            json: true,
            uri: uri,
            body: body,
        };

        if (this._slack_bot_token) {
            sendMessageParams.headers = {
                Authorization: 'Bearer ' + this._slack_bot_token
            };
            sendMessageParams.body.as_user = false;
            sendMessageParams.body.channel = this._slack_channel_id;
        }

        sendMessageParams.body.username = user.getDisplaynameForRoom(message.room_id);

        var avatar_url = user.getAvatarUrlForRoom(message.room_id);

        if (avatar_url && avatar_url.indexOf("mxc://") === 0) {
            sendMessageParams.body.icon_url = this._main.getUrlForMxc(avatar_url);
        }

        user.bumpATime();
        this._matrixAtime = Date.now() / 1000;

        return rp(sendMessageParams).then((res) => {
            this._main.incCounter("sent_messages", {side: "remote"});
            if (!res || (this._slack_bot_token && !res.ok)) {
                console.log("HTTP Error: ", res);
            }
        });
    });
};

BridgedRoom.prototype.onSlackMessage = function(message) {
    return this._main.getGhostForSlackMessage(message).then((ghost) => {
        return ghost.update(message, this).then(() => {
            return this._handleSlackMessage(message, ghost);
        });
    });
};

BridgedRoom.prototype._handleSlackMessage = function(message, ghost) {
    var roomID = this.getMatrixRoomId();

    ghost.bumpATime();
    this._slackAtime = Date.now() / 1000;

    var subtype = message.subtype;

    if (!subtype || subtype === "bot_message") {
        var text = substitutions.slackToMatrix(message.text);
        return ghost.sendText(roomID, text);
    }
    else if (subtype === "me_message") {
        var message = {
            msgtype: "m.emote",
            body: substitutions.slackToMatrix(message.text)
        };
        return ghost.sendMessage(roomID, message);
    }
    else if (subtype === "file_comment") {
        var text = substitutions.slackToMatrix(message.text, message.file);
        return ghost.sendText(roomID, text);
    }
    else if (subtype === "file_share") {
        if (!message.file) {
            console.log("Ignoring missing file message: " + message);
            return;
        }
        if (message.file._content) {
            // TODO: Currently Matrix lacks a way to upload a "captioned image",
            //   so we just send a separate `m.image` and `m.text` message
            // See https://github.com/matrix-org/matrix-doc/issues/906

            // upload to media repo; get media repo URL back
            return ghost.uploadContent(message.file).then((content_uri) => {
                if(undefined == content_uri) {
                    // no URL returned from media repo; abort
                   return undefined;
                }
                var matrixMessage = slackFileToMatrixMessage(message.file, content_uri);
                return ghost.sendMessage(roomID, matrixMessage);
            }).finally(() => {
                if (message.file.initial_comment) {
                    var text = substitutions.slackToMatrix(
                        message.file.initial_comment.comment
                    );
                    return ghost.sendText(roomID, text);
                }
            });
        } else {
            // post a msg with the link
            var text = substitutions.slackToMatrix(message.text, message.file);
            return ghost.sendText(roomID, text);
        }
    }
    else {
        console.log("Ignoring message with subtype: " + subtype);
    }
};

BridgedRoom.prototype.leaveGhosts = function(ghosts) {
    var roomID = this.getMatrixRoomId();
    var main = this._main;

    return Promise.each(ghosts,
        (ghost) => {
            var intent = main._bridge.getIntent(ghost);
            return intent.leave(roomID);
        }
    )
};

/**
 * Converts a slack image attachment to a matrix image event.
 *
 * @param {Object} file The slack image attachment file object.
 * @param {string} file.url URL of the file.
 * @param {string} file.title alt-text for the file.
 * @param {string} file.mimetype mime-type of the file.
 * @param {?integer} file.size size of the file in bytes.
 * @param {?integer} file.original_w width of the file if an image, in pixels.
 * @param {?integer} file.original_h height of the file if an image, in pixels.
 * @param {?string} file.thumb_360 URL of a 360 pixel wide thumbnail of the
 *     file, if an image.
 * @param {?integer} file.thumb_360_w width of the thumbnail of the 360 pixel
 *     wide thumbnail of the file, if an image.
 * @param {?integer} file.thumb_360_h height of the thumbnail of the 36 pixel
 *     wide thumbnail of the file, if an image.
 * @return {Object} Matrix event content, as per https://matrix.org/docs/spec/#m-image
 */
var slackImageToMatrixImage = function(file, url) {
    var message = {
        msgtype: "m.image",
        url: url,
        body: file.title,
        info: {
            mimetype: file.mimetype
        }
    };
    if (file.original_w) {
        message.info.w = file.original_w;
    }
    if (file.original_h) {
        message.info.h = file.original_h;
    }
    if (file.size) {
        message.info.size = file.size;
    }
    if (false && file.thumb_360) {
        message.thumbnail_url = file.thumb_360;
        message.thumbnail_info = {};
        if (file.thumb_360_w) {
            message.thumbnail_info.w = file.thumb_360_w;
        }
        if (file.thumb_360_h) {
            message.thumbnail_info.h = file.thumb_360_h;
        }
    }
    return message;
};

/**
 * Converts a slack file upload to a matrix file upload event.
 *
 * @param {Object} file The slack file object.
 * @return {Object} Matrix event content, as per https://matrix.org/docs/spec/#m-file
 */
var slackFileToMatrixMessage = function(file, url) {
    if (file.mimetype && file.mimetype.indexOf("image/") === 0) {
        return slackImageToMatrixImage(file, url);
    }

    var message = {
        msgtype: "m.file",
        url: url,
        body: file.title,
        info: {
            mimetype: file.mimetype
        }
    };
    if (file.size) {
        message.info.size = file.size;
    }
    return message;
};

BridgedRoom.prototype.getRemoteATime = function() {
    return this._slackAtime;
};

BridgedRoom.prototype.getMatrixATime = function() {
    return this._matrixAtime;
};

BridgedRoom.prototype.lookupAndSetTeamInfo = function() {
    if (!this._slack_bot_token) return Promise.resolve();

    return rp({
        uri: 'https://slack.com/api/team.info',
        qs: {
            token: this._slack_bot_token
        },
        json: true,
    }).then((response) => {
        if (!response.team) return;

        if (this._slack_team_domain !== response.team.domain) {
            this._slack_team_domain = response.team.domain;
            this._dirty = true;
        }

        if (this._slack_team_id !== response.team.id) {
            this._slack_team_id = response.team.id;
            this._dirty = true;
        }
    });
};

BridgedRoom.prototype.lookupAndSetUserInfo = function() {
    if (!this._slack_bot_token) return Promise.resolve();

    return rp({
        uri: 'https://slack.com/api/auth.test',
        qs: {
            token: this._slack_bot_token
        },
        json: true,
    }).then((response) => {
        console.log(response);
        if (!response.user_id) return;

        if (this._slack_user_id !== response.user_id) {
            this._slack_user_id = response.user_id;
            this._dirty = true;
        }

        return rp({
            uri: 'https://slack.com/api/users.info',
            qs: {
                token: this._slack_bot_token,
                user: response.user_id,
            },
            json: true,
        });
    }).then((response) => {
        if (!response.user || !response.user.profile) return;

        if (this._slack_bot_id !== response.user.profile.bot_id) {
            this._slack_bot_id = response.user.profile.bot_id;
            this._dirty = true;
        }
    });
};

module.exports = BridgedRoom;
<commit_msg>Can't imagine only webhooks are allowed to be !ok<commit_after>"use strict";

var Promise = require('bluebird');

var substitutions = require("./substitutions");
var rp = require('request-promise');

function BridgedRoom(main, opts) {
    this._main = main;

    if (!opts.inbound_id) {
        throw new Error("BridgedRoom requires an inbound ID");
    }
    if (!opts.matrix_room_id) {
        throw new Error("BridgedRoom requires an Matrix Room ID");
    }

    this._matrix_room_id = opts.matrix_room_id;
    this._inbound_id = opts.inbound_id;
    this._slack_channel_name = opts.slack_channel_name;
    this._slack_channel_id = opts.slack_channel_id;
    this._slack_webhook_uri = opts.slack_webhook_uri;
    this._slack_bot_token = opts.slack_bot_token;
    this._slack_user_token = opts.slack_user_token;
    this._slack_team_domain = opts.slack_team_domain;
    this._slack_team_id = opts.slack_team_id;
    this._slack_user_id = opts.slack_user_id;
    this._slack_bot_id =  opts.slack_bot_id;
    this._access_token = opts.access_token;
    this._access_scopes = opts.access_scopes;

    this._slackAtime = null;  // last activity time in epoch seconds
    this._matrixAtime = null;

    this._dirty = true;
};

BridgedRoom.prototype.getStatus = function() {
    if (!this._slack_webhook_uri && !this._slack_bot_token) {
        return "pending-params";
    }
    if (!this._slack_channel_name) {
        return "pending-name";
    }
    if (!this._access_token && !this._slack_bot_token) {
        return "ready-no-token";
    }
    return "ready";
}

// Returns true if this instance has changed from the version last read/written
// to the RoomStore.
BridgedRoom.prototype.isDirty = function() {
    return this._dirty;
};

BridgedRoom.prototype.getInboundId = function() {
    return this._inbound_id;
};

BridgedRoom.prototype.getSlackChannelId = function() {
    return this._slack_channel_id;
};

BridgedRoom.prototype.getSlackChannelName = function() {
    return this._slack_channel_name;
};

BridgedRoom.prototype.getSlackWebhookUri = function() {
    return this._slack_webhook_uri;
};

BridgedRoom.prototype.getAccessToken = function() {
    return this._access_token || this._slack_bot_token;
};

BridgedRoom.prototype.getMatrixRoomId = function() {
    return this._matrix_room_id;
};

BridgedRoom.prototype.getSlackTeamDomain = function() {
  return this._slack_team_domain;
};

BridgedRoom.prototype.getSlackTeamId = function() {
    return this._slack_team_id;
};

BridgedRoom.prototype.getSlackBotId = function() {
    return this._slack_bot_id;
};

BridgedRoom.prototype.getSlackUserToken = function() {
    return this._slack_user_token;
};

BridgedRoom.prototype.updateInboundId = function(inbound_id) {
    if (this._inbound_id !== inbound_id) this._dirty = true;
    this._inbound_id = inbound_id;
};

BridgedRoom.prototype.updateSlackChannelId = function(channel_id) {
    if (this._slack_channel_id !== channel_id) this._dirty = true;
    this._slack_channel_id = channel_id;
};

BridgedRoom.prototype.updateSlackChannelName = function(channel_name) {
    if (this._slack_channel_name !== channel_name) this._dirty = true;
    this._slack_channel_name = channel_name;
};

BridgedRoom.prototype.updateSlackWebhookUri = function(slack_webhook_uri) {
    if (this._slack_webhook_uri !== slack_webhook_uri) this._dirty = true;
    this._slack_webhook_uri = slack_webhook_uri;
};

BridgedRoom.prototype.updateSlackBotToken = function(slack_bot_token) {
  if (this._slack_bot_token !== slack_bot_token) this._dirty = true;
  this._slack_bot_token = slack_bot_token;
};

BridgedRoom.prototype.updateSlackUserToken = function(slack_user_token) {
    if (this._slack_user_token !== slack_user_token) this._dirty = true;
    this._slack_user_token = slack_user_token;
};

BridgedRoom.prototype.updateSlackTeamDomain = function(domain) {
  if (this._slack_team_domain !== domain) this._dirty = true;
  this._slack_team_domain = domain;
};

BridgedRoom.prototype.updateAccessToken = function(token, scopes) {
  console.log('updateAccessToken ->', token, scopes);
    if (this._access_token === token &&
        this._access_scopes.sort().join(",") === scopes.sort().join(",")) return;

    this._access_token = token;
    this._access_scopes = scopes;
    this._dirty = true;
};

BridgedRoom.fromEntry = function(main, entry) {
    var opts = {
        inbound_id: entry.remote_id,
        matrix_room_id: entry.matrix_id,
        slack_channel_id: entry.remote.id,
        slack_channel_name: entry.remote.name,
        slack_webhook_uri: entry.remote.webhook_uri,
        slack_bot_token: entry.remote.slack_bot_token,
        slack_user_token: entry.remote.slack_user_token,
        slack_team_domain: entry.remote.slack_team_domain,
        slack_team_id: entry.remote.slack_team_id,
        slack_user_id: entry.remote.slack_user_id,
        slack_bot_id: entry.remote.slack_bot_id,
        access_token: entry.remote.access_token,
        access_scopes: entry.remote.access_scopes,
    };

    return new BridgedRoom(main, opts);
};

// Returns data to write to the RoomStore
// As a side-effect will also clear the isDirty() flag
BridgedRoom.prototype.toEntry = function() {
    var entry = {
        remote: {
            id: this._slack_channel_id,
            name: this._slack_channel_name,
            webhook_uri: this._slack_webhook_uri,
            slack_bot_token: this._slack_bot_token,
            slack_user_token: this._slack_user_token,
            slack_team_domain: this._slack_team_domain,
            slack_team_id: this._slack_team_id,
            slack_user_id: this._slack_user_id,
            slack_bot_id: this._slack_bot_id,
            access_token: this._access_token,
            access_scopes: this._access_scopes,
        },
    };

    entry.id = "INTEG-" + this._inbound_id;
    entry.matrix_id = this._matrix_room_id;
    entry.remote_id = this._inbound_id;

    this._dirty = false;
    return entry;
};

BridgedRoom.prototype.onMatrixMessage = function(message) {
    if (!this._slack_webhook_uri && !this._slack_bot_token) return Promise.resolve();

    return this._main.getOrCreateMatrixUser(message.user_id).then((user) => {
        var body = substitutions.matrixToSlack(message, this._main);

        var uri = (this._slack_bot_token) ? "https://slack.com/api/chat.postMessage" : this._slack_webhook_uri;

        var sendMessageParams = {
            method: "POST",
            json: true,
            uri: uri,
            body: body,
        };
        if (this._slack_bot_token) {
            sendMessageParams.headers = {
                Authorization: 'Bearer ' + this._slack_bot_token
            };
            // See https://api.slack.com/methods/chat.postMessage#authorship
            sendMessageParams.body.as_user = false;
            sendMessageParams.body.channel = this._slack_channel_id;
        }

        sendMessageParams.body.username = user.getDisplaynameForRoom(message.room_id);

        var avatar_url = user.getAvatarUrlForRoom(message.room_id);

        if (avatar_url && avatar_url.indexOf("mxc://") === 0) {
            sendMessageParams.body.icon_url = this._main.getUrlForMxc(avatar_url);
        }

        user.bumpATime();
        this._matrixAtime = Date.now() / 1000;

        return rp(sendMessageParams).then((res) => {
            this._main.incCounter("sent_messages", {side: "remote"});
            if (!res || !res.ok) {
                console.log("HTTP Error: ", res);
            }
        });
    });
};

BridgedRoom.prototype.onSlackMessage = function(message) {
    return this._main.getGhostForSlackMessage(message).then((ghost) => {
        return ghost.update(message, this).then(() => {
            return this._handleSlackMessage(message, ghost);
        });
    });
};

BridgedRoom.prototype._handleSlackMessage = function(message, ghost) {
    var roomID = this.getMatrixRoomId();

    ghost.bumpATime();
    this._slackAtime = Date.now() / 1000;

    var subtype = message.subtype;

    if (!subtype || subtype === "bot_message") {
        var text = substitutions.slackToMatrix(message.text);
        return ghost.sendText(roomID, text);
    }
    else if (subtype === "me_message") {
        var message = {
            msgtype: "m.emote",
            body: substitutions.slackToMatrix(message.text)
        };
        return ghost.sendMessage(roomID, message);
    }
    else if (subtype === "file_comment") {
        var text = substitutions.slackToMatrix(message.text, message.file);
        return ghost.sendText(roomID, text);
    }
    else if (subtype === "file_share") {
        if (!message.file) {
            console.log("Ignoring missing file message: " + message);
            return;
        }
        if (message.file._content) {
            // TODO: Currently Matrix lacks a way to upload a "captioned image",
            //   so we just send a separate `m.image` and `m.text` message
            // See https://github.com/matrix-org/matrix-doc/issues/906

            // upload to media repo; get media repo URL back
            return ghost.uploadContent(message.file).then((content_uri) => {
                if(undefined == content_uri) {
                    // no URL returned from media repo; abort
                   return undefined;
                }
                var matrixMessage = slackFileToMatrixMessage(message.file, content_uri);
                return ghost.sendMessage(roomID, matrixMessage);
            }).finally(() => {
                if (message.file.initial_comment) {
                    var text = substitutions.slackToMatrix(
                        message.file.initial_comment.comment
                    );
                    return ghost.sendText(roomID, text);
                }
            });
        } else {
            // post a msg with the link
            var text = substitutions.slackToMatrix(message.text, message.file);
            return ghost.sendText(roomID, text);
        }
    }
    else {
        console.log("Ignoring message with subtype: " + subtype);
    }
};

BridgedRoom.prototype.leaveGhosts = function(ghosts) {
    var roomID = this.getMatrixRoomId();
    var main = this._main;

    return Promise.each(ghosts,
        (ghost) => {
            var intent = main._bridge.getIntent(ghost);
            return intent.leave(roomID);
        }
    )
};

/**
 * Converts a slack image attachment to a matrix image event.
 *
 * @param {Object} file The slack image attachment file object.
 * @param {string} file.url URL of the file.
 * @param {string} file.title alt-text for the file.
 * @param {string} file.mimetype mime-type of the file.
 * @param {?integer} file.size size of the file in bytes.
 * @param {?integer} file.original_w width of the file if an image, in pixels.
 * @param {?integer} file.original_h height of the file if an image, in pixels.
 * @param {?string} file.thumb_360 URL of a 360 pixel wide thumbnail of the
 *     file, if an image.
 * @param {?integer} file.thumb_360_w width of the thumbnail of the 360 pixel
 *     wide thumbnail of the file, if an image.
 * @param {?integer} file.thumb_360_h height of the thumbnail of the 36 pixel
 *     wide thumbnail of the file, if an image.
 * @return {Object} Matrix event content, as per https://matrix.org/docs/spec/#m-image
 */
var slackImageToMatrixImage = function(file, url) {
    var message = {
        msgtype: "m.image",
        url: url,
        body: file.title,
        info: {
            mimetype: file.mimetype
        }
    };
    if (file.original_w) {
        message.info.w = file.original_w;
    }
    if (file.original_h) {
        message.info.h = file.original_h;
    }
    if (file.size) {
        message.info.size = file.size;
    }
    if (false && file.thumb_360) {
        message.thumbnail_url = file.thumb_360;
        message.thumbnail_info = {};
        if (file.thumb_360_w) {
            message.thumbnail_info.w = file.thumb_360_w;
        }
        if (file.thumb_360_h) {
            message.thumbnail_info.h = file.thumb_360_h;
        }
    }
    return message;
};

/**
 * Converts a slack file upload to a matrix file upload event.
 *
 * @param {Object} file The slack file object.
 * @return {Object} Matrix event content, as per https://matrix.org/docs/spec/#m-file
 */
var slackFileToMatrixMessage = function(file, url) {
    if (file.mimetype && file.mimetype.indexOf("image/") === 0) {
        return slackImageToMatrixImage(file, url);
    }

    var message = {
        msgtype: "m.file",
        url: url,
        body: file.title,
        info: {
            mimetype: file.mimetype
        }
    };
    if (file.size) {
        message.info.size = file.size;
    }
    return message;
};

BridgedRoom.prototype.getRemoteATime = function() {
    return this._slackAtime;
};

BridgedRoom.prototype.getMatrixATime = function() {
    return this._matrixAtime;
};

BridgedRoom.prototype.lookupAndSetTeamInfo = function() {
    if (!this._slack_bot_token) return Promise.resolve();

    return rp({
        uri: 'https://slack.com/api/team.info',
        qs: {
            token: this._slack_bot_token
        },
        json: true,
    }).then((response) => {
        if (!response.team) return;

        if (this._slack_team_domain !== response.team.domain) {
            this._slack_team_domain = response.team.domain;
            this._dirty = true;
        }

        if (this._slack_team_id !== response.team.id) {
            this._slack_team_id = response.team.id;
            this._dirty = true;
        }
    });
};

BridgedRoom.prototype.lookupAndSetUserInfo = function() {
    if (!this._slack_bot_token) return Promise.resolve();

    return rp({
        uri: 'https://slack.com/api/auth.test',
        qs: {
            token: this._slack_bot_token
        },
        json: true,
    }).then((response) => {
        console.log(response);
        if (!response.user_id) return;

        if (this._slack_user_id !== response.user_id) {
            this._slack_user_id = response.user_id;
            this._dirty = true;
        }

        return rp({
            uri: 'https://slack.com/api/users.info',
            qs: {
                token: this._slack_bot_token,
                user: response.user_id,
            },
            json: true,
        });
    }).then((response) => {
        if (!response.user || !response.user.profile) return;

        if (this._slack_bot_id !== response.user.profile.bot_id) {
            this._slack_bot_id = response.user.profile.bot_id;
            this._dirty = true;
        }
    });
};

module.exports = BridgedRoom;
<|endoftext|>


<commit_before>var vows    = require('vows')
  , request = require('request')
  , assert  = require('assert')
  , static  = require('../../lib/node-static');

var fileServer = new(static.Server)(__dirname + '/../fixtures', {serverInfo: 'custom-server-name'});

var suite = vows.describe('node-static');

var TEST_PORT = 8080;
var TEST_SERVER = 'http://localhost:' + TEST_PORT;
var server;

suite.addBatch({
  'once an http server is listening': {
    topic: function () {
      server = require('http').createServer(function (request, response) {
        request.addListener('end', function () {
          fileServer.serve(request, response);
        });
      }).listen(TEST_PORT, this.callback)
    },
    'should be listening' : function(){
      /* This test is necessary to ensure the topic execution.
       * A topic without tests will be not executed */
      assert.isTrue(true);
    }
  }
}).addBatch({
    'requesting a file not found': {
      topic : function(){
        request.get(TEST_SERVER + '/not-found', this.callback);
      },
      'should respond with 404' : function(error, response, body){
        assert.equal(response.statusCode, 404);
      }
    }
}).addBatch({
  'serving hello.txt': {
    topic : function(){
      request.get(TEST_SERVER + '/hello.txt', this.callback);
    },
    'should respond with 200' : function(error, response, body){
      assert.equal(response.statusCode, 200);
    },
    'should respond with text/plain': function(error, response, body){
      assert.equal(response.headers['content-type'], 'text/plain');
    },
    'should respond with hello world': function(error, response, body){
      assert.equal(body, 'hello world');
    }
  }
}).addBatch({
  'serving directory index': {
    topic : function(){
      request.get(TEST_SERVER, this.callback);
    },
    'should respond with 200' : function(error, response, body){
      assert.equal(response.statusCode, 200);
    },
    'should respond with text/html': function(error, response, body){
      assert.equal(response.headers['content-type'], 'text/html');
    }
  }
}).addBatch({
  'serving index.html from the cache': {
    topic : function(){
      request.get(TEST_SERVER + '/index.html', this.callback);
    },
    'should respond with 200' : function(error, response, body){
      assert.equal(response.statusCode, 200);
    },
    'should respond with text/html': function(error, response, body){
      assert.equal(response.headers['content-type'], 'text/html');
    }
  }
}).addBatch({
  'requesting with If-None-Match': {
    topic : function(){
      var _this = this;
      request.get(TEST_SERVER + '/index.html', function(error, response, body){
        request({
          method: 'GET',
          uri: TEST_SERVER + '/index.html',
          headers: {'if-none-match': response.headers['etag']}
        },
        _this.callback);
      });
    },
    'should respond with 304' : function(error, response, body){
      assert.equal(response.statusCode, 304);
    }
  },
  'requesting with If-None-Match and If-Modified-Since': {
    topic : function(){
      var _this = this;
      request.get(TEST_SERVER + '/index.html', function(error, response, body){
        var modified = Date.parse(response.headers['last-modified']);
        var oneDayLater = new Date(modified + (24 * 60 * 60 * 1000)).toUTCString();
        var nonMatchingEtag = '1111222233334444';
        request({
          method: 'GET',
          uri: TEST_SERVER + '/index.html',
          headers: {
            'if-none-match': nonMatchingEtag,
            'if-modified-since': oneDayLater
          }
        },
        _this.callback);
      });
    },
    'should respond with a 200': function(error, response, body){
      assert.equal(response.statusCode, 200);
    }
  }
}).addBatch({
  'requesting HEAD': {
    topic : function(){
      request.head(TEST_SERVER + '/index.html', this.callback);
    },
    'should respond with 200' : function(error, response, body){
      assert.equal(response.statusCode, 200);
    },
    'head must has no body' : function(error, response, body){
      assert.isUndefined(body);
    }
  }
}).addBatch({
  'requesting headers': {
    topic : function(){
      request.head(TEST_SERVER + '/index.html', this.callback);
    },
    'should respond with node-static/0.6.0' : function(error, response, body){
      assert.equal(response.headers['server'], 'custom-server-name');
    }
  }
}).export(module);
<commit_msg>404 streaming test<commit_after>var vows    = require('vows')
  , request = require('request')
  , assert  = require('assert')
  , static  = require('../../lib/node-static');

var fileServer = new(static.Server)(__dirname + '/../fixtures', {serverInfo: 'custom-server-name'});

var suite = vows.describe('node-static');

var TEST_PORT = 8080;
var TEST_SERVER = 'http://localhost:' + TEST_PORT;
var server;
var callback;

suite.addBatch({
  'once an http server is listening': {
    topic: function () {
      server = require('http').createServer(function (request, response) {
        request.addListener('end', function () {
          fileServer.serve(request, response, function(err, result) {
            if (callback)
              callback(request, response, err, result);
          });
        });
      }).listen(TEST_PORT, this.callback)
    },
    'should be listening' : function(){
      /* This test is necessary to ensure the topic execution.
       * A topic without tests will be not executed */
      assert.isTrue(true);
    }
  },
}).addBatch({
    'requesting a file not found': {
      topic : function(){
        request.get(TEST_SERVER + '/not-found', this.callback);
      },
      'should respond with 404' : function(error, response, body){
        assert.equal(response.statusCode, 404);
      }
    },
    'streaming a 404 page': {
      topic: function(){
        callback = function(request, response, err, result) {
          if (err) {
            response.writeHead(err.status, err.headers);
            setTimeout(function() {
              response.end('Custom 404 Stream.')
            }, 100);
          }
        }
        request.get(TEST_SERVER + '/not-found', this.callback);
      },
      'should respond with the streamed content': function(error, response, body){
        callback = null;
        assert.equal(body, 'Custom 404 Stream.');
      }
    }
}).addBatch({
  'serving hello.txt': {
    topic : function(){
      request.get(TEST_SERVER + '/hello.txt', this.callback);
    },
    'should respond with 200' : function(error, response, body){
      assert.equal(response.statusCode, 200);
    },
    'should respond with text/plain': function(error, response, body){
      assert.equal(response.headers['content-type'], 'text/plain');
    },
    'should respond with hello world': function(error, response, body){
      assert.equal(body, 'hello world');
    }
  }
}).addBatch({
  'serving directory index': {
    topic : function(){
      request.get(TEST_SERVER, this.callback);
    },
    'should respond with 200' : function(error, response, body){
      assert.equal(response.statusCode, 200);
    },
    'should respond with text/html': function(error, response, body){
      assert.equal(response.headers['content-type'], 'text/html');
    }
  }
}).addBatch({
  'serving index.html from the cache': {
    topic : function(){
      request.get(TEST_SERVER + '/index.html', this.callback);
    },
    'should respond with 200' : function(error, response, body){
      assert.equal(response.statusCode, 200);
    },
    'should respond with text/html': function(error, response, body){
      assert.equal(response.headers['content-type'], 'text/html');
    }
  }
}).addBatch({
  'requesting with If-None-Match': {
    topic : function(){
      var _this = this;
      request.get(TEST_SERVER + '/index.html', function(error, response, body){
        request({
          method: 'GET',
          uri: TEST_SERVER + '/index.html',
          headers: {'if-none-match': response.headers['etag']}
        },
        _this.callback);
      });
    },
    'should respond with 304' : function(error, response, body){
      assert.equal(response.statusCode, 304);
    }
  },
  'requesting with If-None-Match and If-Modified-Since': {
    topic : function(){
      var _this = this;
      request.get(TEST_SERVER + '/index.html', function(error, response, body){
        var modified = Date.parse(response.headers['last-modified']);
        var oneDayLater = new Date(modified + (24 * 60 * 60 * 1000)).toUTCString();
        var nonMatchingEtag = '1111222233334444';
        request({
          method: 'GET',
          uri: TEST_SERVER + '/index.html',
          headers: {
            'if-none-match': nonMatchingEtag,
            'if-modified-since': oneDayLater
          }
        },
        _this.callback);
      });
    },
    'should respond with a 200': function(error, response, body){
      assert.equal(response.statusCode, 200);
    }
  }
}).addBatch({
  'requesting HEAD': {
    topic : function(){
      request.head(TEST_SERVER + '/index.html', this.callback);
    },
    'should respond with 200' : function(error, response, body){
      assert.equal(response.statusCode, 200);
    },
    'head must has no body' : function(error, response, body){
      assert.isUndefined(body);
    }
  }
}).addBatch({
  'requesting headers': {
    topic : function(){
      request.head(TEST_SERVER + '/index.html', this.callback);
    },
    'should respond with node-static/0.6.0' : function(error, response, body){
      assert.equal(response.headers['server'], 'custom-server-name');
    }
  }
}).export(module);
<|endoftext|>


<commit_before>var args = arguments[0] || {};
var mod_InventoryProd = Alloy.createCollection('product_inventory');
COMMON.construct($);
var details = mod_InventoryProd.getProductList();
COMMON.showLoading();
setTimeout(function(){
	displayProduct(details);
}, 1000);

function displayProduct(products){

	var TheTable = Titanium.UI.createTableView({
		width:'100%',
		backgroundImage: "/images/bg.jpg",
		separatorColor: '#375540'
	});

	var data=[];
		//hide loading bar
		COMMON.hideLoading();

   		var counter = 0;

   		if(products.length < 1){
			var noRecord = Ti.UI.createLabel({
			    text: "No record found",
			    color: '#375540',
			    textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
			    font:{fontSize:14,fontStyle:'italic'},
			    top: 15,
			    width: "100%"
			 });
			$.productView.add(noRecord);
		}else{

	   		products.forEach(function(entry) {

	   			var row = Titanium.UI.createTableViewRow({
			    touchEnabled: true,
			    source: entry.code,
			    selectedBackgroundColor: "#ECFFF9",
				backgroundGradient: {
				      type: 'linear',
				      colors: ['#fff','#F7F7F6'],
				      startPoint: {x:0,y:0},
				      endPoint:{x:"100%",y:0},
				      backFillStart:false
				    },
			    });

				var row_view = Ti.UI.createView({
					left: 5,
			    	top: 5,
			    	right: 5,
			    	bottom: 5,
			    	height: 80,
			    	width: Ti.UI.FILL,
			    	layout: "horizontal",
				});

				var tblView = Ti.UI.createView({
					layout: "vertical",
					height:"80",
					width:"auto",
				});

				var leftImage =  Titanium.UI.createImageView({
					image:entry.image,
					source: entry.id,
					width: 112,
					right: 10,
					height: 80,
				});

				var popUpTitle = Titanium.UI.createLabel({
					text:entry.name,
					font:{fontSize:14, fontWeight:'bold'},
					source: entry.id,
					color: "#848484",
					width: Ti.UI.FILL,
					height: Ti.UI.SIZE,
					textAlign:Titanium.UI.TEXT_ALIGNMENT_LEFT,

				});

				var category =  Titanium.UI.createLabel({
					text:entry.category,
					source: entry.id,
					font:{fontSize:11},
					color: "#848484",
					textAlign: Titanium.UI.TEXT_ALIGNMENT_LEFT,
					width: Ti.UI.FILL,
					height: Ti.UI.SIZE,
				});

				var distance =  Titanium.UI.createLabel({
					text:entry.code,
					source: entry.id,
					font:{fontSize:11},
					color: "#848484",
					textAlign: Titanium.UI.TEXT_ALIGNMENT_LEFT,
					width: Ti.UI.FILL,
					height: Ti.UI.SIZE,
				});

				var quantity_view = Ti.UI.createView({
					layout: "horizontal",
					width: Ti.UI.FILL,
					height: Ti.UI.SIZE,
					top: 5,
				});

				var quantity_text_view = Ti.UI.createView({
					width: Ti.UI.SIZE,
					height: Ti.UI.SIZE,
					backgroundColor: "red",
				});

				var quantity_text =  Titanium.UI.createLabel({
					text: "QUANTITY",
					source: entry.id,
					font:{fontSize:14},
					left: 5,
					right: 5,
					color: "#ffffff",
					textAlign: Titanium.UI.TEXT_ALIGNMENT_LEFT,
					width: Ti.UI.SIZE,
					height: 20,
				});

				var quantity_text_input = Titanium.UI.createTextField({
					editable: false,
					borderColor: "red",
					height: 20,
					width: Ti.UI.FILL,
					value: 1000,
					textAlign: Titanium.UI.TEXT_ALIGNMENT_CENTER,
				});

				quantity_text_view.add(quantity_text);
				quantity_view.add(quantity_text_view);
				quantity_view.add(quantity_text_input);

				row.addEventListener('touchend', function(e) {
				 	viewDetails(e);
				});

				tblView.add(popUpTitle);
				tblView.add(category);
			 	tblView.add(distance);
			 	tblView.add(quantity_view);

			 	row_view.add(leftImage);
			 	row_view.add(tblView);
			 	row.add(row_view);
			 	data.push(row);
	   		});

	   		TheTable.setData(data);
			$.productView.add(TheTable);
		}
};

function refreshTableList(){
	removeAllChildren($.productView);
	displayProduct(products);
}

//products and resources tab
function goSlide(event){
	var index = event.source.mod;
	var arrViews = $.scrollableView.getViews();

	switch(index){
		case "0":
			$.lbl_products.backgroundColor = "#FFFFFF";
			$.lbl_products.color = "#494949";
			$.lbl_products.borderColor = "#FFFFFF";
			$.lbl_resources.backgroundColor = "#D1D1D1";
			$.lbl_resources.color = "#838383";
			$.lbl_resources.borderColor = "#ADADAD";
			break;

		case "1":
			$.lbl_products.backgroundColor = "#D1D1D1";
			$.lbl_products.color = "#838383";
			$.lbl_products.borderColor = "#ADADAD";
			$.lbl_resources.backgroundColor = "#FFFFFF";
			$.lbl_resources.color = "#494949";
			$.lbl_resources.borderColor = "#FFFFFF";
			break;
	}
	$.scrollableView.scrollToView(arrViews[index]);
}
/***SEARCH PRODUCTS***/
$.searchProduct.addEventListener('focus', function(e){
	$.searchProduct.showCancel =  true;
	$.productView.opacity = 1;
	$.productView.height = "auto";
});

$.searchProduct.addEventListener('blur', function(e){
	$.searchProduct.showCancel =  false;
});

$.searchProduct.addEventListener('cancel', function(e){
	$.searchProduct.blur();
	var str = $.searchProduct.getValue();
	if(str == ""){
		removeAllChildren($.productView);
		displayProduct(details);
	}

});

var searchResult = function(){
	COMMON.showLoading();
	$.searchProduct.blur();
	var str = $.searchProduct.getValue();

	var searchResult = mod_InventoryProd.searchProducts(str);
	removeAllChildren($.productView);
	displayProduct(searchResult);
};
$.searchProduct.addEventListener("return", searchResult);

Ti.App.addEventListener('refreshTableList' , refreshTableList);
$.productView.addEventListener('touchend', function(e){
    $.searchProduct.blur();
});<commit_msg>go_inventory sync<commit_after>var args = arguments[0] || {};
var mod_InventoryProd = Alloy.createCollection('product_inventory');
COMMON.construct($);
var details = mod_InventoryProd.getProductList();
COMMON.showLoading();
setTimeout(function(){
	displayProduct(details);
}, 1000);

function displayProduct(products){

	var TheTable = Titanium.UI.createTableView({
		width:'100%',
		backgroundImage: "/images/bg.jpg",
		separatorColor: '#375540'
	});

	var data=[];
		//hide loading bar
		COMMON.hideLoading();

   		var counter = 0;

   		if(products.length < 1){
			var noRecord = Ti.UI.createLabel({
			    text: "No record found",
			    color: '#375540',
			    textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
			    font:{fontSize:14,fontStyle:'italic'},
			    top: 15,
			    width: "100%"
			 });
			$.productView.add(noRecord);
		}else{

	   		products.forEach(function(entry) {

	   			var row = Titanium.UI.createTableViewRow({
			    touchEnabled: true,
			    source: entry.code,
			    selectedBackgroundColor: "#ECFFF9",
				backgroundGradient: {
				      type: 'linear',
				      colors: ['#fff','#F7F7F6'],
				      startPoint: {x:0,y:0},
				      endPoint:{x:"100%",y:0},
				      backFillStart:false
				    },
			    });

				var row_view = Ti.UI.createView({
					left: 5,
			    	top: 5,
			    	right: 5,
			    	bottom: 5,
			    	height: Ti.UI.SIZE,
			    	width: Ti.UI.FILL,
			    	layout: "horizontal",
				});

				var tblView = Ti.UI.createView({
					layout: "vertical",
					height: Ti.UI.SIZE,
					width:"auto",
				});

				var leftImage =  Titanium.UI.createImageView({
					image:entry.image,
					source: entry.id,
					width: 112,
					right: 10,
					height: 80,
				});

				var popUpTitle = Titanium.UI.createLabel({
					text:entry.name,
					font:{fontSize:14, fontWeight:'bold'},
					source: entry.id,
					color: "#848484",
					width: Ti.UI.FILL,
					height: Ti.UI.SIZE,
					textAlign:Titanium.UI.TEXT_ALIGNMENT_LEFT,
					wordwrap: false,
					ellipsize: true
				});

				var category =  Titanium.UI.createLabel({
					text:entry.category,
					source: entry.id,
					font:{fontSize:11},
					color: "#848484",
					textAlign: Titanium.UI.TEXT_ALIGNMENT_LEFT,
					width: Ti.UI.FILL,
					height: Ti.UI.SIZE,
				});

				var distance =  Titanium.UI.createLabel({
					text:entry.code,
					source: entry.id,
					font:{fontSize:11},
					color: "#848484",
					textAlign: Titanium.UI.TEXT_ALIGNMENT_LEFT,
					width: Ti.UI.FILL,
					height: 14,
					ellipsize: true
				});

				var quantity_view = Ti.UI.createView({
					layout: "horizontal",
					width: Ti.UI.FILL,
					height: Ti.UI.SIZE,
					top: 5,
				});

				var quantity_text_view = Ti.UI.createView({
					width: Ti.UI.SIZE,
					height: Ti.UI.SIZE,
					backgroundColor: "red",
				});

				var quantity_text =  Titanium.UI.createLabel({
					text: "QUANTITY",
					source: entry.id,
					font:{fontSize:14},
					left: 5,
					right: 5,
					color: "#ffffff",
					textAlign: Titanium.UI.TEXT_ALIGNMENT_LEFT,
					width: Ti.UI.SIZE,
					height: 20,
				});

				var quantity_text_input = Titanium.UI.createTextField({
					editable: false,
					borderColor: "red",
					height: 20,
					width: Ti.UI.FILL,
					value: 1000,
					textAlign: Titanium.UI.TEXT_ALIGNMENT_CENTER,
				});

				quantity_text_view.add(quantity_text);
				quantity_view.add(quantity_text_view);
				quantity_view.add(quantity_text_input);

				row.addEventListener('touchend', function(e) {
				 	viewDetails(e);
				});

				tblView.add(popUpTitle);
				tblView.add(category);
			 	tblView.add(distance);
			 	tblView.add(quantity_view);

			 	row_view.add(leftImage);
			 	row_view.add(tblView);
			 	row.add(row_view);
			 	data.push(row);
	   		});

	   		TheTable.setData(data);
			$.productView.add(TheTable);
		}
};

function refreshTableList(){
	removeAllChildren($.productView);
	displayProduct(products);
}

//products and resources tab
function goSlide(event){
	var index = event.source.mod;
	var arrViews = $.scrollableView.getViews();

	switch(index){
		case "0":
			$.lbl_products.backgroundColor = "#FFFFFF";
			$.lbl_products.color = "#494949";
			$.lbl_products.borderColor = "#FFFFFF";
			$.lbl_resources.backgroundColor = "#D1D1D1";
			$.lbl_resources.color = "#838383";
			$.lbl_resources.borderColor = "#ADADAD";
			break;

		case "1":
			$.lbl_products.backgroundColor = "#D1D1D1";
			$.lbl_products.color = "#838383";
			$.lbl_products.borderColor = "#ADADAD";
			$.lbl_resources.backgroundColor = "#FFFFFF";
			$.lbl_resources.color = "#494949";
			$.lbl_resources.borderColor = "#FFFFFF";
			break;
	}
	$.scrollableView.scrollToView(arrViews[index]);
}
/***SEARCH PRODUCTS***/
$.searchProduct.addEventListener('focus', function(e){
	$.searchProduct.showCancel =  true;
	$.productView.opacity = 1;
	$.productView.height = "auto";
});

$.searchProduct.addEventListener('blur', function(e){
	$.searchProduct.showCancel =  false;
});

$.searchProduct.addEventListener('cancel', function(e){
	$.searchProduct.blur();
	var str = $.searchProduct.getValue();
	if(str == ""){
		removeAllChildren($.productView);
		displayProduct(details);
	}

});

var searchResult = function(){
	COMMON.showLoading();
	$.searchProduct.blur();
	var str = $.searchProduct.getValue();

	var searchResult = mod_InventoryProd.searchProducts(str);
	removeAllChildren($.productView);
	displayProduct(searchResult);
};
$.searchProduct.addEventListener("return", searchResult);

Ti.App.addEventListener('refreshTableList' , refreshTableList);
$.productView.addEventListener('touchend', function(e){
    $.searchProduct.blur();
});<|endoftext|>


<commit_before>import template from './templates/PopupStack.hbs';

const classes = {
    POPUP_REGION: 'js-popup-region-',
    POPUP_FADE: 'popup-fade'
};

const POPUP_ID_PREFIX = 'popup-region-';

export default Marionette.View.extend({
    initialize() {
        this.__stack = [];
        this.__forceFadeBackground = false;
    },

    template: Handlebars.compile(template),

    ui: {
        fadingPanel: '.js-fading-panel'
    },

    showPopup(view, options) {
        const { fadeBackground, transient, hostEl } = options;

        if (!transient) {
            this.__removeTransientPopups();
        }

        const popupId = _.uniqueId(POPUP_ID_PREFIX);
        const regionEl = document.createElement('div');
        regionEl.setAttribute('data-popup-id', popupId);
        regionEl.classList.add('js-core-ui__global-popup-region');
        regionEl.classList.add('core-ui__global-popup-region');

        let parentPopup;
        let parentPopupId = null;

        if (hostEl) {
            parentPopup = hostEl.closest && hostEl.closest('.js-core-ui__global-popup-region');
            parentPopupId = parentPopup ? parentPopup.getAttribute('popup-id') : null;
        }
        const config = {
            view,
            options,
            regionEl,
            popupId,
            parentPopupId
        };

        if (parentPopupId) {
            // If there is a child popup, it must be closed:
            // 1. There might be nested dropdowns
            // 2. There can't be dropdowns opened on the same level
            const childPopupDef = this.__stack.find(x => x.parentPopupId === parentPopupId);
            if (childPopupDef) {
                this.closePopup(childPopupDef.popupId);
            }
        }

        this.$el.append(regionEl);
        this.addRegion(popupId, {
            el: regionEl
        });

        view.once('attach', () =>
            requestAnimationFrame(() => {
                view.el.classList.add('presented-modal-window');
            })
        );
        this.getRegion(popupId).show(view);

        if (fadeBackground) {
            let lastIndex = -1;
            this.__stack.forEach((x, i) => (lastIndex = x.options.fadeBackground ? i : lastIndex));

            if (lastIndex !== -1) {
                this.__stack[lastIndex].regionEl.classList.remove(classes.POPUP_FADE);
            } else {
                this.__toggleFadedBackground(true);
            }
            regionEl.classList.add(classes.POPUP_FADE);
            this.topPopupId = popupId;
        }

        this.__stack.push(config);
        view.popupId = popupId;

        return popupId;
    },

    showElInPopup(view, options) {
        const { fadeBackground, transient } = options;

        if (!transient) {
            this.__removeTransientPopups();
        }

        const popupId = _.uniqueId(POPUP_ID_PREFIX);
        const regionEl = document.createElement('div');
        regionEl.setAttribute('data-popup-id', popupId);
        regionEl.classList.add('js-core-ui__global-popup-region');

        const config = {
            view,
            options,
            regionEl,
            popupId,
            parentPopupId: null
        };

        this.$el.append(regionEl);
        this.addRegion(popupId, {
            el: regionEl
        });
        this.prevSublingOfContentEl = view.prev();
        if (this.prevSublingOfContentEl.length === 0) {
            delete this.prevSublingOfContentEl;
            this.parentOfContentEl = view.parent();
        }
        this.getRegion(popupId).$el.append('

');

        view.appendTo(this.getRegion(popupId).$el.children('.modal-window-wrapper'));

        if (fadeBackground) {
            let lastIndex = -1;
            this.__stack.forEach((x, i) => (lastIndex = x.options.fadeBackground ? i : lastIndex));

            if (lastIndex !== -1) {
                this.__stack[lastIndex].regionEl.classList.remove(classes.POPUP_FADE);
            } else {
                this.__toggleFadedBackground(true);
            }
            regionEl.classList.add(classes.POPUP_FADE);
            this.topPopupId = popupId;
        }

        this.__stack.push(config);
        view.popupId = popupId;

        return popupId;
    },

    closePopup(popupId = null, immediate = false) {
        if (this.__stack.length === 0) {
            return;
        }

        let targets = [];
        const popupDef = this.__stack.find(x => x.popupId === popupId);
        if (popupDef) {
            if (!popupDef.options.transient) {
                this.__removeTransientPopups(immediate);
            }
            // All the children of the popup will also be closed
            // Important: we collect only logical children because another popup might have been opened at the same level already.
            // e.g.: focus-blur events (usually focus comes first) - one popup is opened on focus and the previous one is closed on blur.
            if (this.__stack.includes(popupDef)) {
                targets = [popupDef];
                const handleChildren = pId => {
                    const children = this.__stack.filter(x => x.parentPopupId === pId);
                    targets.push(...children);
                    children.forEach(c => handleChildren(c.popupId));
                };
                handleChildren(popupId);
            } else {
                targets = [];
            }
        } else if (popupId) {
            // If we don't find the popup, it must have been closed so the job is done
            targets = [];
        } else {
            // Close all transient popups and the top-most non-transient
            this.__removeTransientPopups(immediate);
            const topMostNonTransient = this.__stack[this.__stack.length - 1];
            if (topMostNonTransient) {
                targets = [topMostNonTransient];
            }
        }
        targets.reverse().forEach(pd => {
            this.__removePopup(pd, immediate);
        });

        const filteredStackList = this.__stack.filter(x => x.options.fadeBackground);
        const lastElement = filteredStackList && filteredStackList[filteredStackList.length - 1];

        if (lastElement) {
            lastElement.regionEl.classList.add(classes.POPUP_FADE);
            this.topPopupId = lastElement.popupId;
        } else {
            this.topPopupId = undefined;
            this.__toggleFadedBackground(this.__forceFadeBackground);
        }
    },

    closeElPopup(popupId = null, immediate = false) {
        if (this.__stack.length === 0) {
            return;
        }

        let targets = [];
        const popupDef = this.__stack.find(x => x.popupId === popupId);
        if (popupDef) {
            if (!popupDef.options.transient) {
                this.__removeTransientPopups(immediate);
            }
            // All the children of the popup will also be closed
            // Important: we collect only logical children because another popup might have been opened at the same level already.
            // e.g.: focus-blur events (usually focus comes first) - one popup is opened on focus and the previous one is closed on blur.
            if (this.__stack.includes(popupDef)) {
                targets = [popupDef];
                const handleChildren = pId => {
                    const children = this.__stack.filter(x => x.parentPopupId === pId);
                    targets.push(...children);
                    children.forEach(c => handleChildren(c.popupId));
                };
                handleChildren(popupId);
            } else {
                targets = [];
            }
        } else if (popupId) {
            // If we don't find the popup, it must have been closed so the job is done
            targets = [];
        } else {
            // Close all transient popups and the top-most non-transient
            this.__removeTransientPopups(immediate);
            const topMostNonTransient = this.__stack[this.__stack.length - 1];
            if (topMostNonTransient) {
                targets = [topMostNonTransient];
            }
        }
        if (this.prevSublingOfContentEl) {
            popupDef.view.insertAfter(this.prevSublingOfContentEl);
            delete this.prevSublingOfContentEl;
        } else {
            popupDef.view.prependTo(this.parentOfContentEl);
            delete this.parentOfContentEl;
        }

        targets.reverse().forEach(pd => this.__removePopup(pd, immediate));

        const filteredStackList = this.__stack.filter(x => x.options.fadeBackground);
        const lastElement = filteredStackList && filteredStackList[filteredStackList.length - 1];

        if (lastElement) {
            lastElement.regionEl.classList.add(classes.POPUP_FADE);
            this.topPopupId = lastElement.popupId;
        } else {
            this.topPopupId = undefined;
            this.__toggleFadedBackground(this.__forceFadeBackground);
        }
    },

    isPopupOnTop(popupId) {
        return this.topPopupId === popupId;
    },

    toggleFadeBackground(isToggleForced) {
        return this.fadeBackground(isToggleForced);
    },

    async closeTopPopup() {
        if (this.__stack.length === 0) {
            return;
        }
        const config = this.__stack[this.__stack.length - 1];
        if (!config.view.__isNeedToPrevent || !config.view.__isNeedToPrevent()) {
            if (config.view.close) {
                const results = await config.view.close();
                if (results) {
                    config.options.showedInEl ? this.closeElPopup(config.popupId) : this.closePopup(config.popupId);
                }
            } else {
                config.options.showedInEl ? this.closeElPopup(config.popupId) : this.closePopup(config.popupId);
            }
        }
    },

    __removeTransientPopups(immediate) {
        this.__stack
            .filter(x => x.options.transient)
            .reverse()
            .forEach(popupDef => {
                this.__removePopup(popupDef, immediate);
            });
    },

    __removePopup(popupDef, immediate) {
        const popRemove = () => {
            this.removeRegion(popupDef.popupId);
            this.el.removeChild(popupDef.regionEl);
            this.trigger('popup:close', popupDef.popupId);
        };
        if (immediate) {
            popRemove();
        } else {
            popupDef.view.el.addEventListener(
                'transitionend',
                () => {
                    popRemove();
                },
                { once: true }
            );
        }

        popupDef.view.el?.classList.remove('presented-modal-window');
        this.__stack.splice(this.__stack.indexOf(popupDef), 1);
    },

    get(popupId) {
        const index = this.__stack.findIndex(x => x.popupId === popupId);
        if (index === -1) {
            return [];
        }
        return this.__stack.slice(index).map(x => x.view);
    },

    getStack() {
        return this.__stack;
    },

    fadeBackground(fade) {
        this.__forceFadeBackground = fade;
        this.__toggleFadedBackground(this.__forceFadeBackground || this.__stack.find(x => x.options.fadeBackground));
    },

    __toggleFadedBackground(fade) {
        this.ui.fadingPanel.toggleClass('fadingPanel_open', fade);
    }
});
<commit_msg>[#434865] PopupStack: fix close popup without transition.<commit_after>import template from './templates/PopupStack.hbs';

const classes = {
    POPUP_REGION: 'js-popup-region-',
    POPUP_FADE: 'popup-fade'
};

const POPUP_ID_PREFIX = 'popup-region-';

export default Marionette.View.extend({
    initialize() {
        this.__stack = [];
        this.__forceFadeBackground = false;
    },

    template: Handlebars.compile(template),

    ui: {
        fadingPanel: '.js-fading-panel'
    },

    showPopup(view, options) {
        const { fadeBackground, transient, hostEl } = options;

        if (!transient) {
            this.__removeTransientPopups();
        }

        const popupId = _.uniqueId(POPUP_ID_PREFIX);
        const regionEl = document.createElement('div');
        regionEl.setAttribute('data-popup-id', popupId);
        regionEl.classList.add('js-core-ui__global-popup-region');
        regionEl.classList.add('core-ui__global-popup-region');

        let parentPopup;
        let parentPopupId = null;

        if (hostEl) {
            parentPopup = hostEl.closest && hostEl.closest('.js-core-ui__global-popup-region');
            parentPopupId = parentPopup ? parentPopup.getAttribute('popup-id') : null;
        }
        const config = {
            view,
            options,
            regionEl,
            popupId,
            parentPopupId
        };

        if (parentPopupId) {
            // If there is a child popup, it must be closed:
            // 1. There might be nested dropdowns
            // 2. There can't be dropdowns opened on the same level
            const childPopupDef = this.__stack.find(x => x.parentPopupId === parentPopupId);
            if (childPopupDef) {
                this.closePopup(childPopupDef.popupId);
            }
        }

        this.$el.append(regionEl);
        this.addRegion(popupId, {
            el: regionEl
        });

        view.once('attach', () =>
            requestAnimationFrame(() => {
                view.el.classList.add('presented-modal-window');
            })
        );
        this.getRegion(popupId).show(view);

        if (fadeBackground) {
            let lastIndex = -1;
            this.__stack.forEach((x, i) => (lastIndex = x.options.fadeBackground ? i : lastIndex));

            if (lastIndex !== -1) {
                this.__stack[lastIndex].regionEl.classList.remove(classes.POPUP_FADE);
            } else {
                this.__toggleFadedBackground(true);
            }
            regionEl.classList.add(classes.POPUP_FADE);
            this.topPopupId = popupId;
        }

        this.__stack.push(config);
        view.popupId = popupId;

        return popupId;
    },

    showElInPopup(view, options) {
        const { fadeBackground, transient } = options;

        if (!transient) {
            this.__removeTransientPopups();
        }

        const popupId = _.uniqueId(POPUP_ID_PREFIX);
        const regionEl = document.createElement('div');
        regionEl.setAttribute('data-popup-id', popupId);
        regionEl.classList.add('js-core-ui__global-popup-region');

        const config = {
            view,
            options,
            regionEl,
            popupId,
            parentPopupId: null
        };

        this.$el.append(regionEl);
        this.addRegion(popupId, {
            el: regionEl
        });
        this.prevSublingOfContentEl = view.prev();
        if (this.prevSublingOfContentEl.length === 0) {
            delete this.prevSublingOfContentEl;
            this.parentOfContentEl = view.parent();
        }
        this.getRegion(popupId).$el.append('

');

        view.appendTo(this.getRegion(popupId).$el.children('.modal-window-wrapper'));

        if (fadeBackground) {
            let lastIndex = -1;
            this.__stack.forEach((x, i) => (lastIndex = x.options.fadeBackground ? i : lastIndex));

            if (lastIndex !== -1) {
                this.__stack[lastIndex].regionEl.classList.remove(classes.POPUP_FADE);
            } else {
                this.__toggleFadedBackground(true);
            }
            regionEl.classList.add(classes.POPUP_FADE);
            this.topPopupId = popupId;
        }

        this.__stack.push(config);
        view.popupId = popupId;

        return popupId;
    },

    closePopup(popupId = null, immediate = false) {
        if (this.__stack.length === 0) {
            return;
        }

        let targets = [];
        const popupDef = this.__stack.find(x => x.popupId === popupId);
        if (popupDef) {
            if (!popupDef.options.transient) {
                this.__removeTransientPopups(immediate);
            }
            // All the children of the popup will also be closed
            // Important: we collect only logical children because another popup might have been opened at the same level already.
            // e.g.: focus-blur events (usually focus comes first) - one popup is opened on focus and the previous one is closed on blur.
            if (this.__stack.includes(popupDef)) {
                targets = [popupDef];
                const handleChildren = pId => {
                    const children = this.__stack.filter(x => x.parentPopupId === pId);
                    targets.push(...children);
                    children.forEach(c => handleChildren(c.popupId));
                };
                handleChildren(popupId);
            } else {
                targets = [];
            }
        } else if (popupId) {
            // If we don't find the popup, it must have been closed so the job is done
            targets = [];
        } else {
            // Close all transient popups and the top-most non-transient
            this.__removeTransientPopups(immediate);
            const topMostNonTransient = this.__stack[this.__stack.length - 1];
            if (topMostNonTransient) {
                targets = [topMostNonTransient];
            }
        }
        targets.reverse().forEach(pd => {
            this.__removePopup(pd, immediate);
        });

        const filteredStackList = this.__stack.filter(x => x.options.fadeBackground);
        const lastElement = filteredStackList && filteredStackList[filteredStackList.length - 1];

        if (lastElement) {
            lastElement.regionEl.classList.add(classes.POPUP_FADE);
            this.topPopupId = lastElement.popupId;
        } else {
            this.topPopupId = undefined;
            this.__toggleFadedBackground(this.__forceFadeBackground);
        }
    },

    closeElPopup(popupId = null, immediate = false) {
        if (this.__stack.length === 0) {
            return;
        }

        let targets = [];
        const popupDef = this.__stack.find(x => x.popupId === popupId);
        if (popupDef) {
            if (!popupDef.options.transient) {
                this.__removeTransientPopups(immediate);
            }
            // All the children of the popup will also be closed
            // Important: we collect only logical children because another popup might have been opened at the same level already.
            // e.g.: focus-blur events (usually focus comes first) - one popup is opened on focus and the previous one is closed on blur.
            if (this.__stack.includes(popupDef)) {
                targets = [popupDef];
                const handleChildren = pId => {
                    const children = this.__stack.filter(x => x.parentPopupId === pId);
                    targets.push(...children);
                    children.forEach(c => handleChildren(c.popupId));
                };
                handleChildren(popupId);
            } else {
                targets = [];
            }
        } else if (popupId) {
            // If we don't find the popup, it must have been closed so the job is done
            targets = [];
        } else {
            // Close all transient popups and the top-most non-transient
            this.__removeTransientPopups(immediate);
            const topMostNonTransient = this.__stack[this.__stack.length - 1];
            if (topMostNonTransient) {
                targets = [topMostNonTransient];
            }
        }
        if (this.prevSublingOfContentEl) {
            popupDef.view.insertAfter(this.prevSublingOfContentEl);
            delete this.prevSublingOfContentEl;
        } else {
            popupDef.view.prependTo(this.parentOfContentEl);
            delete this.parentOfContentEl;
        }

        targets.reverse().forEach(pd => this.__removePopup(pd, immediate));

        const filteredStackList = this.__stack.filter(x => x.options.fadeBackground);
        const lastElement = filteredStackList && filteredStackList[filteredStackList.length - 1];

        if (lastElement) {
            lastElement.regionEl.classList.add(classes.POPUP_FADE);
            this.topPopupId = lastElement.popupId;
        } else {
            this.topPopupId = undefined;
            this.__toggleFadedBackground(this.__forceFadeBackground);
        }
    },

    isPopupOnTop(popupId) {
        return this.topPopupId === popupId;
    },

    toggleFadeBackground(isToggleForced) {
        return this.fadeBackground(isToggleForced);
    },

    async closeTopPopup() {
        if (this.__stack.length === 0) {
            return;
        }
        const config = this.__stack[this.__stack.length - 1];
        if (!config.view.__isNeedToPrevent || !config.view.__isNeedToPrevent()) {
            if (config.view.close) {
                const results = await config.view.close();
                if (results) {
                    config.options.showedInEl ? this.closeElPopup(config.popupId) : this.closePopup(config.popupId);
                }
            } else {
                config.options.showedInEl ? this.closeElPopup(config.popupId) : this.closePopup(config.popupId);
            }
        }
    },

    __removeTransientPopups(immediate) {
        this.__stack
            .filter(x => x.options.transient)
            .reverse()
            .forEach(popupDef => {
                this.__removePopup(popupDef, immediate);
            });
    },

    __removePopup(popupDef, immediate) {
        const popRemove = () => {
            this.removeRegion(popupDef.popupId);
            this.el.removeChild(popupDef.regionEl);
            this.trigger('popup:close', popupDef.popupId);
        };

        if (immediate) {
            popRemove();
        } else {
            let transitionStarted = false;

            popupDef.view.el.addEventListener(
                'transitionstart',
                () => {
                    transitionStarted = true;
                },
                { once: true }
            );

            popupDef.view.el.addEventListener(
                'transitionend',
                () => {
                    popRemove();
                },
                { once: true }
            );

            setTimeout(() => {
                if (!transitionStarted) {
                    popRemove();
                }
            }, 100);
        }

        popupDef.view.el?.classList.remove('presented-modal-window');
        this.__stack.splice(this.__stack.indexOf(popupDef), 1);
    },

    get(popupId) {
        const index = this.__stack.findIndex(x => x.popupId === popupId);
        if (index === -1) {
            return [];
        }
        return this.__stack.slice(index).map(x => x.view);
    },

    getStack() {
        return this.__stack;
    },

    fadeBackground(fade) {
        this.__forceFadeBackground = fade;
        this.__toggleFadedBackground(this.__forceFadeBackground || this.__stack.find(x => x.options.fadeBackground));
    },

    __toggleFadedBackground(fade) {
        this.ui.fadingPanel.toggleClass('fadingPanel_open', fade);
    }
});
<|endoftext|>


<commit_before>import { readOnly } from '@ember/object/computed';
import Controller from '@ember/controller';
import { computed } from '@ember/object';

import PaginationMixin from '../../mixins/pagination';

// TODO: reduce duplicatoin with controllers/crates

export default Controller.extend(PaginationMixin, {
    queryParams: ['page', 'per_page', 'sort'],
    page: '1',
    per_page: 10,
    sort: 'alpha',

    totalItems: readOnly('model.meta.total'),

    currentSortBy: computed('sort', function() {
        return (this.get('sort') === 'downloads') ? 'Downloads' : 'Alphabetical';
    }),
});
<commit_msg>Ensure active sorting is correctly identified<commit_after>import { readOnly } from '@ember/object/computed';
import Controller from '@ember/controller';
import { computed } from '@ember/object';

import PaginationMixin from '../../mixins/pagination';

// TODO: reduce duplicatoin with controllers/crates

export default Controller.extend(PaginationMixin, {
    queryParams: ['page', 'per_page', 'sort'],
    page: '1',
    per_page: 10,
    sort: 'alpha',

    totalItems: readOnly('model.meta.total'),

    currentSortBy: computed('sort', function() {
        if (this.get('sort') === 'downloads') {
            return 'All-Time Downloads';
        } else if (this.get('sort') === 'recent-downloads') {
            return 'Recent Downloads';
        } else {
            return 'Alphabetical';
        }
    }),
});
<|endoftext|>


<commit_before>import Ember from 'ember';

const { computed, inject } = Ember;

export default Ember.Controller.extend({
  currentAccount: inject.service(),
  loggedIn: computed.readOnly('currentAccount.loggedIn')
});
<commit_msg>Add basic documentation for not-found controller<commit_after>import Ember from 'ember';

const { computed, inject } = Ember;

/**
 * A controller for our 404 page to give us access to the current user session.
 *
 * @class CanvasWeb.NotFoundController
 * @extends Ember.Controller
 */
export default Ember.Controller.extend({
  /**
   * @member {CanvasWeb.CurrentAccountService} A service exposing the current
   *   user account
   */
  currentAccount: inject.service(),

  /**
   * @member {boolean} Whether the current user is logged in
   */
  loggedIn: computed.readOnly('currentAccount.loggedIn')
});
<|endoftext|>


<commit_before>const fs = require('fs');
const path = require('path');
const {promisify} = require('util');
const schedule = require('node-schedule');
const {sortBy} = require('lodash');
const moment = require('moment');
const {unlock} = require('../achievements');

class Counter {
    constructor(name) {
        this.name = name;
        this.map = new Map();
        this.load();
    }

    add(key, cnt = 1) {
        if (this.map.has(key)) {
            this.map.set(key, this.map.get(key) + cnt);
        } else {
            this.map.set(key, cnt);
        }

        this.save();
    }

    clear() {
        this.map = new Map();
        this.save();
    }

    entries() {
        const keys = Array.from(this.map.keys());
        const sortedKeys = sortBy(keys, (key) => this.map.get(key)).reverse();
        return sortedKeys.map((key) => [key, this.map.get(key)]);
    }

    async save() {
        const filePath = path.join(__dirname, `${this.name}.json`);
        await promisify(fs.writeFile)(filePath, JSON.stringify(Array.from(this.map.entries())));
    }

    async load() {
        const filePath = path.join(__dirname, `${this.name}.json`);

        const exists = await new Promise((resolve) => {
            fs.access(filePath, fs.constants.F_OK, (error) => {
                resolve(!Boolean(error));
            });
        });

        if (exists) {
            const data = await promisify(fs.readFile)(filePath);
            this.map = new Map(JSON.parse(data));
        } else {
            this.map = new Map();
        }
    }
}

function count(haystack, needle) {
    return haystack.split(needle).length - 1;
}

function numToEmoji(num) {
    switch(num) {
        case 0:
            return 'zero';
        case 1:
            return 'one';
        case 2:
            return 'two';
        case 3:
            return 'three';
        case 4:
            return 'four';
        case 5:
            return 'five';
        case 6:
            return 'six';
        case 7:
            return 'seven';
        case 8:
            return 'eight';
        case 9:
            return 'nine';
        case 10:
            return 'keycap_ten';
        default:
            return 'vsonline';
    }
}

module.exports = (clients) => {
    const { rtmClient: rtm, webClient: slack } = clients;

    const sushiCounter = new Counter('sushi');
    const suspendCounter = new Counter('suspend');

    rtm.on('message', async (message) => {
        const { channel, text, user, ts: timestamp } = message;
        if (!text) {
            return;
        }

        if (message.channel.startsWith('D')) {
            const postDM = (text) => (
                slack.chat.postMessage({
                    channel: message.channel,
                    text,
                    username: 'sushi-bot',
                    // eslint-disable-next-line camelcase
                    icon_emoji: ':sushi:',
                })
            );

            const tokens = text.trim().split(/\s+/);

            if (tokens[0] === '寿司ランキング' && tokens[1] === '確認') {
                let currentRank = 1;
                for (let entry of entries()) {
                    if (entry.user !== user) continue;
                    return postDM(`あなたのすし数は${entry.count}個、現在の順位は${currentRank}位`);
                    currentRank++;
                }
            }

            let rtext = text;
            rtext = rtext.
                replace(/鮨/g, 'すし').
                replace(/(su|zu|ス|ズ|ず|寿|壽)/gi, 'す').
                replace(/(sh?i|ci|し|シ|司|\u{0328})/giu, 'し');

            rtext = rtext.
                replace(/(ca|(ke|け|ケ)(i|ぃ|い|ｨ|ィ|ｲ|イ|e|ぇ|え|ｪ|ェ|ｴ|エ|-|ー))(ki|ke|き|キ)/gi, 'ケーキ');

            rtext = rtext.
            replace(/akouryyy/gi, 'akkoury').
            replace(/akouryy/gi, '').
            replace(/kk/gi, 'k').
            replace(/rr/gi, 'r').
            replace(/y/gi, 'yy');

        if (count(rtext, 'すし')) {
            const cnt = count(rtext, 'すし');
            await slack.reactions.add({name: 'sushi', channel, timestamp});
            if (channel.startsWith('C')) {
                unlock(user, 'get-sushi');
                if (moment().utcOffset(9).day() === 3) {
                    unlock(user, 'wednesday-sushi');
                }
            }
            if(cnt >= 2) {
                if (channel.startsWith('C')) {
                    unlock(user, 'get-multiple-sushi');
                    if (cnt > 10) {
                        unlock(user, 'get-infinite-sushi');
                    }
                }
                await slack.reactions.add({name: 'x', channel, timestamp});
                await slack.reactions.add({name: numToEmoji(cnt), channel, timestamp});
            }
            sushiCounter.add(user, cnt);
        }
        if (rtext.includes("ケーキ")) {
            slack.reactions.add({name: 'cake', channel, timestamp});
        }
        if (rtext.includes("殺") || rtext.includes("死")|| rtext.includes(":koroすzo:")) {
            const cnt = count(rtext, '殺') + count(rtext, '死') + count(rtext, ':koroすzo:');
            if (channel.startsWith('C')) {
                unlock(user, 'freezing');
            }
            await slack.reactions.add({name: 'no_good', channel, timestamp});
            await slack.reactions.add({name: 'cookies146', channel, timestamp});
            if(cnt >= 2) {
                await slack.reactions.add({name: 'x', channel, timestamp});
                await slack.reactions.add({name: numToEmoji(cnt), channel, timestamp});
            }
            suspendCounter.add(user, cnt);
        }
        if (rtext.includes("akouryy")) {
            slack.reactions.add({name: 'no_good', channel, timestamp});
            slack.reactions.add({name: 'akouryy', channel, timestamp});
        }

        const stars = ["欲し", "干し", "ほし", "星", "★", "☆"];
        for(const star of stars) {
            if (text.includes(star)) {
                slack.reactions.add({name: 'grapes', channel, timestamp});
                break;
            }
        }
    });

    schedule.scheduleJob('0 19 * * 0', async () => {
        const {members} = await slack.users.list();

        await slack.chat.postMessage({
            channel: process.env.CHANNEL_SANDBOX,
            username: 'sushi-bot',
            text: '今週の凍結ランキング',
            icon_emoji: ':cookies146:',
            attachments: suspendCounter.entries().map(([user, count], index) => {
                const member = members.find(({id}) => id === user);
                if (!member) {
                    return null;
                }
                const name = member.profile.display_name || member.name;
                if (index === 0) {
                    unlock(user, 'freezing-master');
                }

                return {
                    author_name: `${index + 1}位: ${name} (${count}回)`,
                    author_icon: member.profile.image_24,
                };
            }).filter((attachment) => attachment !== null),
        });

        suspendCounter.clear();

        await slack.chat.postMessage({
            channel: process.env.CHANNEL_SANDBOX,
            username: 'sushi-bot',
            text: '今週の寿司ランキング',
            icon_emoji: ':sushi:',
            attachments: sushiCounter.entries().map(([user, count], index) => {
                const member = members.find(({id}) => id === user);
                if (!member) {
                    return null;
                }
                const name = member.profile.display_name || member.name;

                return {
                    author_name: `${index + 1}位: ${name} (${count}回)`,
                    author_icon: member.profile.image_24,
                };
            }).filter((attachment) => attachment !== null),
        });

        sushiCounter.clear();
    });
};
<commit_msg>Update: small fixes for Requests but test cannot be passed<commit_after>const fs = require('fs');
const path = require('path');
const {promisify} = require('util');
const schedule = require('node-schedule');
const {sortBy} = require('lodash');
const moment = require('moment');
const {unlock} = require('../achievements');

class Counter {
    constructor(name) {
        this.name = name;
        this.map = new Map();
        this.load();
    }

    add(key, cnt = 1) {
        if (this.map.has(key)) {
            this.map.set(key, this.map.get(key) + cnt);
        } else {
            this.map.set(key, cnt);
        }

        this.save();
    }

    clear() {
        this.map = new Map();
        this.save();
    }

    entries() {
        const keys = Array.from(this.map.keys());
        const sortedKeys = sortBy(keys, (key) => this.map.get(key)).reverse();
        return sortedKeys.map((key) => [key, this.map.get(key)]);
    }

    async save() {
        const filePath = path.join(__dirname, `${this.name}.json`);
        await promisify(fs.writeFile)(filePath, JSON.stringify(Array.from(this.map.entries())));
    }

    async load() {
        const filePath = path.join(__dirname, `${this.name}.json`);

        const exists = await new Promise((resolve) => {
            fs.access(filePath, fs.constants.F_OK, (error) => {
                resolve(!Boolean(error));
            });
        });

        if (exists) {
            const data = await promisify(fs.readFile)(filePath);
            this.map = new Map(JSON.parse(data));
        } else {
            this.map = new Map();
        }
    }
}

function count(haystack, needle) {
    return haystack.split(needle).length - 1;
}

function numToEmoji(num) {
    switch(num) {
        case 0:
            return 'zero';
        case 1:
            return 'one';
        case 2:
            return 'two';
        case 3:
            return 'three';
        case 4:
            return 'four';
        case 5:
            return 'five';
        case 6:
            return 'six';
        case 7:
            return 'seven';
        case 8:
            return 'eight';
        case 9:
            return 'nine';
        case 10:
            return 'keycap_ten';
        default:
            return 'vsonline';
    }
}

module.exports = (clients) => {
    const { rtmClient: rtm, webClient: slack } = clients;

    const sushiCounter = new Counter('sushi');
    const suspendCounter = new Counter('suspend');

    rtm.on('message', async (message) => {
        const { channel, text, user, ts: timestamp } = message;
        if (!text) {
            return;
        }

        if (message.channel.startsWith('D')) {
            const postDM = (text) => (
                slack.chat.postMessage({
                    channel: message.channel,
                    text,
                    username: 'sushi-bot',
                    // eslint-disable-next-line camelcase
                    icon_emoji: ':sushi:',
                })
            );

            const tokens = text.trim().split(/\s+/);

            if (tokens[0] === '寿司ランキング' && tokens[1] === '確認') {
                let currentRank = 1;
                for (let entry of sushiCounter.entries()) {
                    if (entry.user !== user) continue;
                    return postDM(`あなたのすし数は${entry.count}個、現在の順位は${currentRank}位`);
                    currentRank++;
                }
            }
        }

            let rtext = text;
            rtext = rtext.
                replace(/鮨/g, 'すし').
                replace(/(su|zu|ス|ズ|ず|寿|壽)/gi, 'す').
                replace(/(sh?i|ci|し|シ|司|\u{0328})/giu, 'し');

            rtext = rtext.
                replace(/(ca|(ke|け|ケ)(i|ぃ|い|ｨ|ィ|ｲ|イ|e|ぇ|え|ｪ|ェ|ｴ|エ|-|ー))(ki|ke|き|キ)/gi, 'ケーキ');

            rtext = rtext.
            replace(/akouryyy/gi, 'akkoury').
            replace(/akouryy/gi, '').
            replace(/kk/gi, 'k').
            replace(/rr/gi, 'r').
            replace(/y/gi, 'yy');

        if (count(rtext, 'すし')) {
            const cnt = count(rtext, 'すし');
            await slack.reactions.add({name: 'sushi', channel, timestamp});
            if (channel.startsWith('C')) {
                unlock(user, 'get-sushi');
                if (moment().utcOffset(9).day() === 3) {
                    unlock(user, 'wednesday-sushi');
                }
            }
            if(cnt >= 2) {
                if (channel.startsWith('C')) {
                    unlock(user, 'get-multiple-sushi');
                    if (cnt > 10) {
                        unlock(user, 'get-infinite-sushi');
                    }
                }
                await slack.reactions.add({name: 'x', channel, timestamp});
                await slack.reactions.add({name: numToEmoji(cnt), channel, timestamp});
            }
            sushiCounter.add(user, cnt);
        }
        if (rtext.includes("ケーキ")) {
            slack.reactions.add({name: 'cake', channel, timestamp});
        }
        if (rtext.includes("殺") || rtext.includes("死")|| rtext.includes(":koroすzo:")) {
            const cnt = count(rtext, '殺') + count(rtext, '死') + count(rtext, ':koroすzo:');
            if (channel.startsWith('C')) {
                unlock(user, 'freezing');
            }
            await slack.reactions.add({name: 'no_good', channel, timestamp});
            await slack.reactions.add({name: 'cookies146', channel, timestamp});
            if(cnt >= 2) {
                await slack.reactions.add({name: 'x', channel, timestamp});
                await slack.reactions.add({name: numToEmoji(cnt), channel, timestamp});
            }
            suspendCounter.add(user, cnt);
        }
        if (rtext.includes("akouryy")) {
            slack.reactions.add({name: 'no_good', channel, timestamp});
            slack.reactions.add({name: 'akouryy', channel, timestamp});
        }

        const stars = ["欲し", "干し", "ほし", "星", "★", "☆"];
        for(const star of stars) {
            if (text.includes(star)) {
                slack.reactions.add({name: 'grapes', channel, timestamp});
                break;
            }
        }
    });

    schedule.scheduleJob('0 19 * * 0', async () => {
        const {members} = await slack.users.list();

        await slack.chat.postMessage({
            channel: process.env.CHANNEL_SANDBOX,
            username: 'sushi-bot',
            text: '今週の凍結ランキング',
            icon_emoji: ':cookies146:',
            attachments: suspendCounter.entries().map(([user, count], index) => {
                const member = members.find(({id}) => id === user);
                if (!member) {
                    return null;
                }
                const name = member.profile.display_name || member.name;
                if (index === 0) {
                    unlock(user, 'freezing-master');
                }

                return {
                    author_name: `${index + 1}位: ${name} (${count}回)`,
                    author_icon: member.profile.image_24,
                };
            }).filter((attachment) => attachment !== null),
        });

        suspendCounter.clear();

        await slack.chat.postMessage({
            channel: process.env.CHANNEL_SANDBOX,
            username: 'sushi-bot',
            text: '今週の寿司ランキング',
            icon_emoji: ':sushi:',
            attachments: sushiCounter.entries().map(([user, count], index) => {
                const member = members.find(({id}) => id === user);
                if (!member) {
                    return null;
                }
                const name = member.profile.display_name || member.name;

                return {
                    author_name: `${index + 1}位: ${name} (${count}回)`,
                    author_icon: member.profile.image_24,
                };
            }).filter((attachment) => attachment !== null),
        });

        sushiCounter.clear();
    });
};
<|endoftext|>


<commit_before>'use strict';

const child_process = require('mz/child_process');
const fs = require('mz/fs');
const inquirer = require('inquirer');
const path = require('path');

module.exports = function *generateDoc(publish) {
    const hasDoc = yield fs.exists('doc');
    let wantsDoc = true;
    if (!hasDoc) {
        console.log('This project has no doc folder');
        wantsDoc = (yield inquirer.prompt({
            type: 'confirm',
            name: 'c',
            message: 'Do you want to create it',
            default: true
        })).c;
    }
    if (wantsDoc) {
        const documentationExecPath = path.resolve(__dirname, '../node_modules/.bin/documentation');
        yield child_process.exec(`${documentationExecPath} build --github --output doc --format html`);
        if (publish) {
            yield child_process.exec('git add doc');
            yield child_process.exec('git commit -m "doc: rebuild doc"');
            yield child_process.exec('git push origin master');
            yield child_process.exec('git subtree push --prefix doc origin gh-pages');
        }
    }
};<commit_msg>feat: default choice for doc generation is false<commit_after>'use strict';

const child_process = require('mz/child_process');
const fs = require('mz/fs');
const inquirer = require('inquirer');
const path = require('path');

module.exports = function *generateDoc(publish) {
    const hasDoc = yield fs.exists('doc');
    let wantsDoc = true;
    if (!hasDoc) {
        console.log('This project has no doc folder');
        wantsDoc = (yield inquirer.prompt({
            type: 'confirm',
            name: 'c',
            message: 'Do you want to create it',
            default: false
        })).c;
    }
    if (wantsDoc) {
        const documentationExecPath = path.resolve(__dirname, '../node_modules/.bin/documentation');
        yield child_process.exec(`${documentationExecPath} build --github --output doc --format html`);
        if (publish) {
            yield child_process.exec('git add doc');
            yield child_process.exec('git commit -m "doc: rebuild doc"');
            yield child_process.exec('git push origin master');
            yield child_process.exec('git subtree push --prefix doc origin gh-pages');
        }
    }
};
<|endoftext|>