<commit_before>/*
 * (C) Copyright 2016 Ventrom LLC (http://www.ventrom.com/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *     David Ciarletta
 *     Renan Dembogurski
 */

'use strict'

const d3 = require('d3')
const inflector = require('inflected')
const pluck = require('pluck')
const reductio = require('reductio')

function pluckFullLoc(rec) {
    return Object.keys(rec["location"])
        .filter(function(k) {return typeof(rec["location"][k]) === "string"})
        .sort().map(function(k) {return k + ":" + rec["location"][k]}).join(":")
}

function pluckMetric(name) {
    return function(r) {
        for (let m of r.metrics) {
            if (m.name === name) return m.value
        }
        return null
    }
}

function generateReducer(id, accessor, filter) {
    let full_filter = function(r) {return (accessor(r) !== null) ? true : false}
    if (filter) full_filter = function(r) {return (accessor(r) !== null) ? filter(r) : false}
    let reducer = reductio()
    reducer.value(id)
        .filter(full_filter)
        .sum(accessor)
        .valueList(accessor)
        .count(true).min(true).max(true).avg(true).median(true)
    return reducer
}

function generateGroupAccessors(id) {
    return {
        "sum": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".sum")(d) },
        "average": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".avg")(d) },
        "count": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".count")(d) },
        "min": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".min")(d) },
        "max": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".max")(d) },
        "median": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".median")(d) },
        "values": function(d) { return Array.isArray(d.value) ? d.value : pluck("value."+id+".valueList")(d) }
    }
}

function recommendFilters(dataset) {
    let result = []

    Object.keys(dataset.dimensions).forEach((tag) => {
        let d = dataset.dimensions[tag]
        // TODO handle recommendations for geo filters
        if (tag === "position" || d.dim === "time" || d.items.size <= 1) return
        let type = d.items.size > 9 ? "row" : "pie"
        d.metrics.forEach((m) => result.push({"type": type, "dimension": d, "groups": [dataset.groups[m]], "defaultGroupAccessor": "sum", "title": dataset.groups[m].title + " by " + inflector.titleize(d.key)}))
    })

    return result
}

function recommendCharts(dataset) {
    let result = []
    let timeUnits = "year"

    if (dataset.timeRange[1] !== 0) {
        let secDiff = (dataset.timeRange[1] - dataset.timeRange[0])/1000
        if (secDiff < 3600*12) { // up to 12 hours
            timeUnits = null
        } else if (secDiff < 3600*24*7) { // up to 7 days
            timeUnits = "hour"
        } else if (secDiff < 3600*24*70) { // up to 10 weeks
            timeUnits = "day"
        } else if (secDiff < 3600*24*365) { // up to a year
            timeUnits = "week"
        } else if (secDiff < 3600*24*3650) { // up to 10 years
            timeUnits = "month"
        }
    }

    Object.keys(dataset.dimensions).forEach((tag) => {
        let d = dataset.dimensions[tag]

        if (d.dim === "time" && d.key === timeUnits) {
            let compatibleUnitsFound = []
            d.metrics.forEach((m) => {
                result.push({"type": "candle", "dimension": d, "groups": [dataset.groups[m]], "defaultGroupAccessor": "values", "title": dataset.groups[m].title + " by " + inflector.titleize(d.key)})

                if (compatibleUnitsFound.indexOf(dataset.groups[m].units) < 0) {
                    let compatibleMetrics = [...d.metrics].filter((m2) => (m2 !== m && dataset.groups[m].units === dataset.groups[m2].units))
                    if (compatibleMetrics.length > 0) {
                        compatibleUnitsFound.push(dataset.groups[m].units)
                        compatibleMetrics.push(m)
                        let title = "Average " + compatibleMetrics.map((m) => dataset.groups[m].title).join(", ") + " by " + inflector.titleize(d.key)
                        result.push({"type": "line", "dimension": d, "groups": compatibleMetrics.map((m) => dataset.groups[m]), "defaultGroupAccessor": "average", "title": title})
                    }
                }

                dataset.groups[m].dimensions.forEach((tag2) => {
                    let d2 = dataset.dimensions[tag2]
                    if (d2.dim === "time" || d2.items.size < 2) return
                    let groups = [...d2.items].map((item) => {
                        let id = inflector.parameterize(item+"-"+m)
                        return {
                            title: dataset.groups[m].title,
                            label: item,
                            units: dataset.groups[m].units,
                            accessor: dataset.groups[m].accessor,
                            dimensions: new Set([...dataset.groups[m].dimensions].filter((t) => dataset.dimensions[t].dim !== d2.dim)),
                            reducer: generateReducer(id, dataset.groups[m].accessor, function(r) { return d2.accessor(r) === item}),
                            groupAccessors: generateGroupAccessors(id),
                            id: id
                        }
                    })
                    let title = dataset.groups[m].title + " by " + inflector.titleize(d.key) + " by " + inflector.titleize(d2.key)
                    result.push({"type": "sand", "dimension": d, "groups": groups, "defaultGroupAccessor": "sum", "title": title})
                })
            })
        }
    })

    return result
}

//
// prepare a set of data for use with crossfilter
// by analyzing the available dimensions and metrics
// in the data set.
//
function deduce(data) {
    // this will hold the results
    let result = {
        dimensions: {},
        groups: {},
        timeRange: [Infinity, 0],
        records: data
    }
    let timeDims = ["year", "month", "week", "day", "hour"]

    // process the information in each record
    data.forEach((rec) => {
        let recDims = []
        let recMetrics = []
        Object.keys(rec).forEach((field) => {
            // Process well-known fields
            switch (field) {
                case "location":
                    if (typeof(rec[field]["lat"]) === "number" &&
                        typeof(rec[field]["lon"]) === "number") {
                            let fullLoc = pluckFullLoc(rec)
                            result.locations[fullLoc] = {"lat": rec[field]["lat"], "lon": rec[field]["lon"]}
                            if (!result.dimensions["position"]) {
                                result.dimensions["position"] = {
                                    dim: field,
                                    key: "position",
                                    items: new Set(),
                                    accessor: pluckFullLoc,
                                    metrics: new Set()
                                }
                            }
                            result.dimensions["position"].items.add(fullLoc)
                            recDims.push(result.dimensions["position"])
                        }
                case "system":
                case "activity":
                case "organization":
                    Object.keys(rec[field]).forEach((tag) => {
                        if (typeof(rec[field][tag]) !== "string") return
                        if (!result.dimensions[tag]) {
                            result.dimensions[tag] = {
                                dim: field,
                                key: tag,
                                items: new Set(),
                                accessor: pluck(field+"."+tag),
                                metrics: new Set()
                            }
                        }
                        result.dimensions[tag].items.add(rec[field][tag])
                        recDims.push(result.dimensions[tag])
                    })
                    break;

                case "conditions":
                    // TODO when better examples exist
                    break;
                case "time":
                    // preparse the timestamp into time dimensions
                    rec.dt = new Date(rec.time)
                    rec.year = d3.time.year(rec.dt)
                    rec.month = d3.time.month(rec.dt)
                    rec.week = d3.time.week(rec.dt)
                    rec.day = d3.time.day(rec.dt)
                    // TODO add day of week?
                    rec.hour = d3.time.hour(rec.dt)
                    // TODO will probably want to add minute/second for completeness
                    if (!result.dimensions[timeDims[0]]) {
                        timeDims.forEach((tag) => {
                            result.dimensions[tag] = {
                                dim: "time",
                                key: tag,
                                accessor: pluck(tag),
                                metrics: new Set()
                            }
                        })
                    }
                    // update the overall time extent
                    result.timeRange[0] = Math.min(result.timeRange[0], rec.dt)
                    result.timeRange[1] = Math.max(result.timeRange[1], rec.dt)
                    recDims.push.apply(recDims, timeDims.map((t) => result.dimensions[t]))
                    break;
                case "metrics":
                    rec.metrics.forEach((m) => {
                        var id = inflector.parameterize(m.name)
                        if (!result.groups[id]) {
                            result.groups[id] = {
                                key: id,
                                title: inflector.titleize(m.name),
                                units: m.units,
                                accessor: pluckMetric(m.name),
                                dimensions: new Set(),
                                reducer: generateReducer(id, pluckMetric(m.name)),
                                groupAccessors: generateGroupAccessors(id)
                            }
                        }
                        recMetrics.push(result.groups[id])
                    })
                    break;
                case "rep":
                case "baseline":
                case "produced_by":
                    if (!result.dimensions[field]) {
                        result.dimensions[field] = {
                            dim: "data_source",
                            key: field,
                            items: new Set(),
                            accessor: pluck(field),
                            metrics: new Set()
                        }
                    }
                    result.dimensions[field].items.add(rec[field])
                    recDims.push(result.dimensions[field])
                    break;
            }
        })
        recDims.forEach((d) => recMetrics.forEach((m) => {
            d.metrics.add(m.key)
            m.dimensions.add(d.key)
        }))
    })

    result.filters = recommendFilters(result)
    result.charts = recommendCharts(result)
    return result
}

module.exports = deduce
<commit_msg>change id to key to be consistent with base groupSpec returned when not combined with another dimension<commit_after>/*
 * (C) Copyright 2016 Ventrom LLC (http://www.ventrom.com/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *     David Ciarletta
 *     Renan Dembogurski
 */

'use strict'

const d3 = require('d3')
const inflector = require('inflected')
const pluck = require('pluck')
const reductio = require('reductio')

function pluckFullLoc(rec) {
    return Object.keys(rec["location"])
        .filter(function(k) {return typeof(rec["location"][k]) === "string"})
        .sort().map(function(k) {return k + ":" + rec["location"][k]}).join(":")
}

function pluckMetric(name) {
    return function(r) {
        for (let m of r.metrics) {
            if (m.name === name) return m.value
        }
        return null
    }
}

function generateReducer(id, accessor, filter) {
    let full_filter = function(r) {return (accessor(r) !== null) ? true : false}
    if (filter) full_filter = function(r) {return (accessor(r) !== null) ? filter(r) : false}
    let reducer = reductio()
    reducer.value(id)
        .filter(full_filter)
        .sum(accessor)
        .valueList(accessor)
        .count(true).min(true).max(true).avg(true).median(true)
    return reducer
}

function generateGroupAccessors(id) {
    return {
        "sum": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".sum")(d) },
        "average": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".avg")(d) },
        "count": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".count")(d) },
        "min": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".min")(d) },
        "max": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".max")(d) },
        "median": function(d) { return (typeof(d.value) === "number") ? d.value : pluck("value."+id+".median")(d) },
        "values": function(d) { return Array.isArray(d.value) ? d.value : pluck("value."+id+".valueList")(d) }
    }
}

function recommendFilters(dataset) {
    let result = []

    Object.keys(dataset.dimensions).forEach((tag) => {
        let d = dataset.dimensions[tag]
        // TODO handle recommendations for geo filters
        if (tag === "position" || d.dim === "time" || d.items.size <= 1) return
        let type = d.items.size > 9 ? "row" : "pie"
        d.metrics.forEach((m) => result.push(
            {"type": type,
             "dimension": d,
             "groups": [dataset.groups[m]],
             "defaultGroupAccessor": "sum",
             "title": dataset.groups[m].title + " by " + inflector.titleize(d.key)}))
    })

    return result
}

function recommendCharts(dataset) {
    let result = []
    let timeUnits = "year"

    if (dataset.timeRange[1] !== 0) {
        let secDiff = (dataset.timeRange[1] - dataset.timeRange[0])/1000
        if (secDiff < 3600*12) { // up to 12 hours
            timeUnits = null
        } else if (secDiff < 3600*24*7) { // up to 7 days
            timeUnits = "hour"
        } else if (secDiff < 3600*24*70) { // up to 10 weeks
            timeUnits = "day"
        } else if (secDiff < 3600*24*365) { // up to a year
            timeUnits = "week"
        } else if (secDiff < 3600*24*3650) { // up to 10 years
            timeUnits = "month"
        }
    }

    Object.keys(dataset.dimensions).forEach((tag) => {
        let d = dataset.dimensions[tag]

        if (d.dim === "time" && d.key === timeUnits) {
            let compatibleUnitsFound = []
            d.metrics.forEach((m) => {
                result.push({"type": "candle", "dimension": d, "groups": [dataset.groups[m]], "defaultGroupAccessor": "values", "title": dataset.groups[m].title + " by " + inflector.titleize(d.key)})

                if (compatibleUnitsFound.indexOf(dataset.groups[m].units) < 0) {
                    let compatibleMetrics = [...d.metrics].filter((m2) => (m2 !== m && dataset.groups[m].units === dataset.groups[m2].units))
                    if (compatibleMetrics.length > 0) {
                        compatibleUnitsFound.push(dataset.groups[m].units)
                        compatibleMetrics.push(m)
                        let title = "Average " + compatibleMetrics.map((m) => dataset.groups[m].title).join(", ") + " by " + inflector.titleize(d.key)
                        result.push({"type": "line", "dimension": d, "groups": compatibleMetrics.map((m) => dataset.groups[m]), "defaultGroupAccessor": "average", "title": title})
                    }
                }

                dataset.groups[m].dimensions.forEach((tag2) => {
                    let d2 = dataset.dimensions[tag2]
                    if (d2.dim === "time" || d2.items.size < 2) return
                    let groups = [...d2.items].map((item) => {
                        let id = inflector.parameterize(item+"-"+m)
                        return {
                            title: dataset.groups[m].title,
                            label: item,
                            units: dataset.groups[m].units,
                            accessor: dataset.groups[m].accessor,
                            dimensions: new Set([...dataset.groups[m].dimensions].filter((t) => dataset.dimensions[t].dim !== d2.dim)),
                            reducer: generateReducer(id, dataset.groups[m].accessor, function(r) { return d2.accessor(r) === item}),
                            groupAccessors: generateGroupAccessors(id),
                            key: id
                        }
                    })
                    let title = dataset.groups[m].title + " by " + inflector.titleize(d.key) + " by " + inflector.titleize(d2.key)
                    result.push({"type": "sand", "dimension": d, "groups": groups, "defaultGroupAccessor": "sum", "title": title})
                })
            })
        }
    })

    return result
}

//
// prepare a set of data for use with crossfilter
// by analyzing the available dimensions and metrics
// in the data set.
//
function deduce(data) {
    // this will hold the results
    let result = {
        dimensions: {},
        groups: {},
        timeRange: [Infinity, 0],
        records: data
    }
    let timeDims = ["year", "month", "week", "day", "hour"]

    // process the information in each record
    data.forEach((rec) => {
        let recDims = []
        let recMetrics = []
        Object.keys(rec).forEach((field) => {
            // Process well-known fields
            switch (field) {
                case "location":
                    if (typeof(rec[field]["lat"]) === "number" &&
                        typeof(rec[field]["lon"]) === "number") {
                            let fullLoc = pluckFullLoc(rec)
                            result.locations[fullLoc] = {"lat": rec[field]["lat"], "lon": rec[field]["lon"]}
                            if (!result.dimensions["position"]) {
                                result.dimensions["position"] = {
                                    dim: field,
                                    key: "position",
                                    items: new Set(),
                                    accessor: pluckFullLoc,
                                    metrics: new Set()
                                }
                            }
                            result.dimensions["position"].items.add(fullLoc)
                            recDims.push(result.dimensions["position"])
                        }
                case "system":
                case "activity":
                case "organization":
                    Object.keys(rec[field]).forEach((tag) => {
                        if (typeof(rec[field][tag]) !== "string") return
                        if (!result.dimensions[tag]) {
                            result.dimensions[tag] = {
                                dim: field,
                                key: tag,
                                items: new Set(),
                                accessor: pluck(field+"."+tag),
                                metrics: new Set()
                            }
                        }
                        result.dimensions[tag].items.add(rec[field][tag])
                        recDims.push(result.dimensions[tag])
                    })
                    break;

                case "conditions":
                    // TODO when better examples exist
                    break;
                case "time":
                    // preparse the timestamp into time dimensions
                    rec.dt = new Date(rec.time)
                    rec.year = d3.time.year(rec.dt)
                    rec.month = d3.time.month(rec.dt)
                    rec.week = d3.time.week(rec.dt)
                    rec.day = d3.time.day(rec.dt)
                    // TODO add day of week?
                    rec.hour = d3.time.hour(rec.dt)
                    // TODO will probably want to add minute/second for completeness
                    if (!result.dimensions[timeDims[0]]) {
                        timeDims.forEach((tag) => {
                            result.dimensions[tag] = {
                                dim: "time",
                                key: tag,
                                accessor: pluck(tag),
                                metrics: new Set()
                            }
                        })
                    }
                    // update the overall time extent
                    result.timeRange[0] = Math.min(result.timeRange[0], rec.dt)
                    result.timeRange[1] = Math.max(result.timeRange[1], rec.dt)
                    recDims.push.apply(recDims, timeDims.map((t) => result.dimensions[t]))
                    break;
                case "metrics":
                    rec.metrics.forEach((m) => {
                        var id = inflector.parameterize(m.name)
                        if (!result.groups[id]) {
                            result.groups[id] = {
                                key: id,
                                title: inflector.titleize(m.name),
                                units: m.units,
                                accessor: pluckMetric(m.name),
                                dimensions: new Set(),
                                reducer: generateReducer(id, pluckMetric(m.name)),
                                groupAccessors: generateGroupAccessors(id)
                            }
                        }
                        recMetrics.push(result.groups[id])
                    })
                    break;
                case "rep":
                case "baseline":
                case "produced_by":
                    if (!result.dimensions[field]) {
                        result.dimensions[field] = {
                            dim: "data_source",
                            key: field,
                            items: new Set(),
                            accessor: pluck(field),
                            metrics: new Set()
                        }
                    }
                    result.dimensions[field].items.add(rec[field])
                    recDims.push(result.dimensions[field])
                    break;
            }
        })
        recDims.forEach((d) => recMetrics.forEach((m) => {
            d.metrics.add(m.key)
            m.dimensions.add(d.key)
        }))
    })

    result.filters = recommendFilters(result)
    result.charts = recommendCharts(result)
    return result
}

module.exports = deduce
<|endoftext|>


<commit_before>try {
	var Config = require('./config.json');
} catch (e){
	Config.debug = false;
	Config.respondToInvalid = false;
}

try {
	var AuthDetails = require('./auth.json');
} catch (e){
	console.log("Please create an auth.json file with account email and password"+e.stack);
	process.exit();
}

try {
	var Discord = require("discord.js");
} catch (e){
	console.log("Please run npm install discord.js"+e.stack);
	process.exit();
}

try {
	var d20 = require('d20');
} catch (e){
	console.log("Please run npm install d20"+e.stack);
}

try {
	var Stats = require("./user_stats.json");
} catch (e){
	console.log("Unable to provide stats: Missing file");
	Stats.debug = true;
}

var bot = new Discord.Client();

var commands = {
	"dam": {
		usage: "",
		description: "returns image/gif",
		process: function(bot, msg) {
			var rand = Math.floor(Math.random() * 2) + 1;
			var result;
			if (rand == 1)
				result = 'https://i.imgur.com/7lZwLKc.jpg';
			else
				result = 'https://i.imgur.com/VQLGJOL.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"happy": {
		usage: "",
		description: "returns image/gif",
		process: function(bot, msg) {
			var rand = Math.floor(Math.random() * 2) + 1;
			var result;
			if (rand == 1)
			  result = 'https://i.imgur.com/gbXzYXp.gif';
			else
			  result = 'https://i.imgur.com/OxwIC0O.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"horse": {
		usage: "",
		description: "returns horse gif",
		process: function(bot, msg) {
			var result = 'https://i.giphy.com/S4mOmgmpBQAXm.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"img": {
		usage: "<search term>",
		description: "searches imgur based on keyword",
		process: function(bot, msg, suffix) {
			var key = AuthDetails;
			var Imgur = require('imgur-search');

			var term = suffix;
			if(!term) {
				bot.sendMessage(msg.channel, "No Search Term");
				return;
			}
			new Imgur(key.imgur_key).search(term).then(function(result) {
				if (result.length == 0 || result == undefined) {
					bot.sendMessage(msg.channel, "You suck at searching");
				} else {
					var img = result[Math.floor(Math.random() * result.length)];
					bot.sendMessage(msg.channel, img.link);
				}
			});
		}
	},
	"why": {
		usage: "",
		description: "returns gif",
		process: function(bot, msg) {
			var result = 'https://media.giphy.com/media/1M9fmo1WAFVK0/giphy.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"roll": {
		usage: "[#d#]",
		description: "dice roller, with single command giving a d20, otherwise rolling specified combo",
		process: function(bot, msg, suffix) {
			if(!suffix) {
				bot.sendMessage(msg.channel, msg.author + " rolled: " + d20.roll(20));
			} else {
				var result;
				try {
					result = d20.roll(suffix);
				} catch (e){
					bot.sendMessage(msg.channel, msg.author + " Could not roll " + suffix);
					return;
				}
				if (!isNaN(result) && result != 'NaN')
					bot.sendMessage(msg.channel, msg.author + " rolled: " + d20.verboseRoll(suffix));
				else
					bot.sendMessage(msg.channel, msg.author + " Not a valid number");
			}
		}
	},
	"rolloff": {
		usage: "",
		description: "Roll off against the bot (D20)",
		process: function(bot, msg) {
			var roll_bot;
			var roll_usr;

			roll_bot = d20.roll(20);
			roll_usr = d20.roll(20);
			bot.sendMessage(msg.channel, "A CHALLENGER APPROACHES!");

			if (roll_bot > roll_usr)
				bot.sendMessage(msg.channel, "MUAHAHAHA. My " + roll_bot + " p4wned your " + roll_usr + ". Get rekt.");
			else if (roll_usr > roll_bot)
				bot.sendMessage(msg.channel, "Lucky roll... My " + roll_bot + " can't beat your " + roll_usr + ".");
			else
				bot.sendMessage(msg.channel, "Tie? TIE!? Let's call that a win for me...");
		}
	},
	"blown": {
		usage: "",
		description: "returns gif",
		process: function(bot, msg) {
			var result;
			var rand = Math.floor(Math.random() * 6);
			var gifs = [	'https://i.imgur.com/YGsTG5o.gif',
					'https://i.imgur.com/pBTk7MC.gif',
					'https://i.imgur.com/rfFWukr.gif',
					'https://media.giphy.com/media/OK27wINdQS5YQ/giphy.gif',
					'https://i.imgur.com/l4bJmRE.gif',
					'https://i.imgur.com/3WB3L.gif',
					'https://giphy.com/gifs/cheezburger-robots-screaming-NfEQd1ge1kP04']

			bot.sendMessage(msg.channel, gifs[rand]);
		}
	},
	"weekend": {
		usage: "",
		description: "returns days until weekend",
		process: function(bot, msg) {
			var date = new Date();
			var day = date.getDay();
			var result = [  "Last day, Enjoy it!",
					"4 Days Left. Got a case of the Mondays?",
					"3 Days Left",
					"2 Days Left.  Hump Day!",
					"1 Day Left",
					"FRIDAY!",
					"Less than 48 hours left..."];

			bot.sendMessage(msg.channel, result[day]);
		}
	},
	"test": {
		usage: "Admin only",
		description: "",
		process: function(bot, msg) {
			var roles = msg.channel.server.roles;
			if (msg.author.hasRole(roles[1])) {
				bot.sendMessage(msg.channel, "Good afternoon, Mr. Admin.  JSON test below");
				var tmp = Stats;
				bot.sendMessage(msg.channel, tmp);
			} else
				bot.sendMessage(msg.channel, "Frig off...")
		}
	}
};

bot.on("ready", function () {
	console.log("Ready to start. Channels: " + bot.channels.length);
});

bot.on('disconnected', function() {
	console.log('Disconnected.');
	process.exit(1);
});

bot.on("message", function (msg) {
	if(msg.author.id != bot.user.id && msg.content[0] === '!'){
		var cmdTxt = msg.content.split(" ")[0].substring(1);
        	var suffix = msg.content.substring(cmdTxt.length+2);
		var cmd = commands[cmdTxt];

        	if(cmdTxt === "help"){
			var info = "Available Commands: ";
			for(var cmd in commands) {
				info += "!" + cmd;
				var usage = commands[cmd].usage;
				if(usage){
					info += " " + usage + "; ";
				}
				else
					info += "; ";
			}
			bot.sendMessage(msg.author,info);
        	} else if(cmd) {
			try{
				cmd.process(bot,msg,suffix);
			} catch(e){
				if(Config.debug){
					bot.sendMessage(msg.channel, "command " + cmdTxt + " failed :(\n" + e.stack);
				}
			}
		} else {
			bot.sendMessage(msg.author, " Invalid command, Sucka!  Maybe you need some !help");
		}
	} else if (msg.author != bot.user && msg.isMentioned(bot.user)) {
		bot.sendMessage(msg.channel,msg.author + ", you called?");
    	}
});

exports.addCommand = function(commandName, commandObject){
    try {
        commands[commandName] = commandObject;
    } catch(err){
        console.log(err);
    }
}
exports.commandCount = function(){
    return Object.keys(commands).length;
}

bot.login(AuthDetails.email, AuthDetails.password);
<commit_msg>testing user_stats as admin<commit_after>try {
	var Config = require('./config.json');
} catch (e){
	Config.debug = false;
	Config.respondToInvalid = false;
}

try {
	var AuthDetails = require('./auth.json');
} catch (e){
	console.log("Please create an auth.json file with account email and password"+e.stack);
	process.exit();
}

try {
	var Discord = require("discord.js");
} catch (e){
	console.log("Please run npm install discord.js"+e.stack);
	process.exit();
}

try {
	var d20 = require('d20');
} catch (e){
	console.log("Please run npm install d20"+e.stack);
}

try {
	var Stats = require("./user_stats.json");
} catch (e){
	console.log("Unable to provide stats: Missing file");
	Stats.debug = true;
}

var bot = new Discord.Client();

var commands = {
	"dam": {
		usage: "",
		description: "returns image/gif",
		process: function(bot, msg) {
			var rand = Math.floor(Math.random() * 2) + 1;
			var result;
			if (rand == 1)
				result = 'https://i.imgur.com/7lZwLKc.jpg';
			else
				result = 'https://i.imgur.com/VQLGJOL.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"happy": {
		usage: "",
		description: "returns image/gif",
		process: function(bot, msg) {
			var rand = Math.floor(Math.random() * 2) + 1;
			var result;
			if (rand == 1)
			  result = 'https://i.imgur.com/gbXzYXp.gif';
			else
			  result = 'https://i.imgur.com/OxwIC0O.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"horse": {
		usage: "",
		description: "returns horse gif",
		process: function(bot, msg) {
			var result = 'https://i.giphy.com/S4mOmgmpBQAXm.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"img": {
		usage: "<search term>",
		description: "searches imgur based on keyword",
		process: function(bot, msg, suffix) {
			var key = AuthDetails;
			var Imgur = require('imgur-search');

			var term = suffix;
			if(!term) {
				bot.sendMessage(msg.channel, "No Search Term");
				return;
			}
			new Imgur(key.imgur_key).search(term).then(function(result) {
				if (result.length == 0 || result == undefined) {
					bot.sendMessage(msg.channel, "You suck at searching");
				} else {
					var img = result[Math.floor(Math.random() * result.length)];
					bot.sendMessage(msg.channel, img.link);
				}
			});
		}
	},
	"why": {
		usage: "",
		description: "returns gif",
		process: function(bot, msg) {
			var result = 'https://media.giphy.com/media/1M9fmo1WAFVK0/giphy.gif';
			bot.sendMessage(msg.channel, result);
		}
	},
	"roll": {
		usage: "[#d#]",
		description: "dice roller, with single command giving a d20, otherwise rolling specified combo",
		process: function(bot, msg, suffix) {
			if(!suffix) {
				bot.sendMessage(msg.channel, msg.author + " rolled: " + d20.roll(20));
			} else {
				var result;
				try {
					result = d20.roll(suffix);
				} catch (e){
					bot.sendMessage(msg.channel, msg.author + " Could not roll " + suffix);
					return;
				}
				if (!isNaN(result) && result != 'NaN')
					bot.sendMessage(msg.channel, msg.author + " rolled: " + d20.verboseRoll(suffix));
				else
					bot.sendMessage(msg.channel, msg.author + " Not a valid number");
			}
		}
	},
	"rolloff": {
		usage: "",
		description: "Roll off against the bot (D20)",
		process: function(bot, msg) {
			var roll_bot;
			var roll_usr;

			roll_bot = d20.roll(20);
			roll_usr = d20.roll(20);
			bot.sendMessage(msg.channel, "A CHALLENGER APPROACHES!");

			if (roll_bot > roll_usr)
				bot.sendMessage(msg.channel, "MUAHAHAHA. My " + roll_bot + " p4wned your " + roll_usr + ". Get rekt.");
			else if (roll_usr > roll_bot)
				bot.sendMessage(msg.channel, "Lucky roll... My " + roll_bot + " can't beat your " + roll_usr + ".");
			else
				bot.sendMessage(msg.channel, "Tie? TIE!? Let's call that a win for me...");
		}
	},
	"blown": {
		usage: "",
		description: "returns gif",
		process: function(bot, msg) {
			var result;
			var rand = Math.floor(Math.random() * 6);
			var gifs = [	'https://i.imgur.com/YGsTG5o.gif',
					'https://i.imgur.com/pBTk7MC.gif',
					'https://i.imgur.com/rfFWukr.gif',
					'https://media.giphy.com/media/OK27wINdQS5YQ/giphy.gif',
					'https://i.imgur.com/l4bJmRE.gif',
					'https://i.imgur.com/3WB3L.gif',
					'https://giphy.com/gifs/cheezburger-robots-screaming-NfEQd1ge1kP04']

			bot.sendMessage(msg.channel, gifs[rand]);
		}
	},
	"weekend": {
		usage: "",
		description: "returns days until weekend",
		process: function(bot, msg) {
			var date = new Date();
			var day = date.getDay();
			var result = [  "Last day, Enjoy it!",
					"4 Days Left. Got a case of the Mondays?",
					"3 Days Left",
					"2 Days Left.  Hump Day!",
					"1 Day Left",
					"FRIDAY!",
					"Less than 48 hours left..."];

			bot.sendMessage(msg.channel, result[day]);
		}
	},
	"test": {
		usage: "Admin only",
		description: "",
		process: function(bot, msg) {
			var roles = msg.channel.server.roles;
			if (msg.author.hasRole(roles[1])) {
				bot.sendMessage(msg.channel, "Good afternoon, Mr. Admin.  JSON test below");
				var tmp = Stats;
				bot.sendMessage(msg.channel, tmp.Player1);
			} else
				bot.sendMessage(msg.channel, "Frig off...")
		}
	}
};

bot.on("ready", function () {
	console.log("Ready to start. Channels: " + bot.channels.length);
});

bot.on('disconnected', function() {
	console.log('Disconnected.');
	process.exit(1);
});

bot.on("message", function (msg) {
	if(msg.author.id != bot.user.id && msg.content[0] === '!'){
		var cmdTxt = msg.content.split(" ")[0].substring(1);
        	var suffix = msg.content.substring(cmdTxt.length+2);
		var cmd = commands[cmdTxt];

        	if(cmdTxt === "help"){
			var info = "Available Commands: ";
			for(var cmd in commands) {
				info += "!" + cmd;
				var usage = commands[cmd].usage;
				if(usage){
					info += " " + usage + "; ";
				}
				else
					info += "; ";
			}
			bot.sendMessage(msg.author,info);
        	} else if(cmd) {
			try{
				cmd.process(bot,msg,suffix);
			} catch(e){
				if(Config.debug){
					bot.sendMessage(msg.channel, "command " + cmdTxt + " failed :(\n" + e.stack);
				}
			}
		} else {
			bot.sendMessage(msg.author, " Invalid command, Sucka!  Maybe you need some !help");
		}
	} else if (msg.author != bot.user && msg.isMentioned(bot.user)) {
		bot.sendMessage(msg.channel,msg.author + ", you called?");
    	}
});

exports.addCommand = function(commandName, commandObject){
    try {
        commands[commandName] = commandObject;
    } catch(err){
        console.log(err);
    }
}
exports.commandCount = function(){
    return Object.keys(commands).length;
}

bot.login(AuthDetails.email, AuthDetails.password);
<|endoftext|>


<commit_before>'use strict';

import Promise from 'bluebird'
import stampit from 'stampit'
import cuid from 'cuid'

//utility
function isFunction(obj) {
    return obj && toString.call(obj === '[object Function]')
}
/**
 * accepts `_id` as an initial id value. if an `id` function
 * exists (further up the composition chain) it does not override it;
 * otherwise, it provides its own method for `id()`
 * */
const identifiable = stampit()
    .init(function(){
        //accept id initializer value
        let id = this._id
        ;(delete this._id)
        if(!isFunction(this.id)) {
            this.id = function() {
                return (id || (id = cuid() ))
            }
        }
    })

/**
 * encapsulates behaviors for revisioning of components
 * */
const revisable = stampit()
    .init(function() {
        let revision = 1
        /**
         * either get the current revision or set the revision with `val`
         * @param {Number} val the revision to set
         * */
        this.revision = function (val) {
            if(val) {
                return (revision = val)
            }
            return revision
        }
        /**
         * gets next revision (doesnt mutate state)
         * */
        this.nextRevision = () => {
            return (this.revision() + 1)
        }
    })

/**
 * simple hashmap storage of event providers
 * */
const hashIdentityMap = stampit().init(function(){
    let providers = new Map()
    this.register = (id, provider) => {
        if(!id) {
            throw new Error('`id` is required')
        }
        if(!provider) {
            throw new Error('`provider` is required')
        }
        providers.set(id, provider)
        return provider
    }
    this.get = (id) => {
        if(!id) {
            throw new Error('`id` is required')
        }
        let provider = providers.get(id)
        if(!provider) {
            throw new Error('could not locate provider with id "' + id + '""')
        }
        return provider
    }
    this.release = () => {
        providers.clear()
    }

})

const inMemoryStorage = stampit()
    .compose(revisable)
    .init(function() {
        var envelopes = []
        this.store = (env) => {
            if(!env.revision) {
                env.revision = this.revision(this.nextRevision())
            }
            envelopes.push(env)
            return this
        }
        this.events = function*(from, to) {
            from = (from || 0)
            to = (to || Number.MAX_VALUE)
            if(from > to) {
                throw new Error('`from` must be less than or equal `to`')
            }
            if(!envelopes.length) {
                return []
            }
            for(let env of envelopes) {
                if(env.revision > to) {
                    return //we are done streaming
                }
                if(env.revision >= from) {
                    for(let ev of env.events) {
                        yield ev
                    }
                }
            }
        }
    })

const writeableUnitOfWork = stampit()
    .refs({
        storage: undefined
        , identityMap: undefined
    })
    .methods({
        envelope: function enveloper( events) {
            return {
                events: events
            }
        }
    })
    .init(function(){
        let pending = []

        this.append = (e) => {
            pending.push.apply(pending,e)
            return e
        }
        this.commit = () => {
            //move reference to array in case event arrives while flushing
            let commitable = pending.splice(0,pending.length)
            return Promise.resolve(commitable)
                .bind(this)
                .then(this.envelope)
                .bind(this.storage)
                .tap(this.storage.store)
                .bind(this.identityMap)
                .tap(this.identityMap.release)
                .return(this)
        }
        this.register = function() {
            //no op
        }
    })

const readableUnitOfWork = stampit()
    .refs({
        storage: undefined
        , identityMap: undefined
    })
    .init(function(){
        this.append = (e) => {
            //no op
            return e
        }
        this.commit = () => {
            return Promise.resolve(this)
        }
        this.register = this.identityMap.register

        const iterate = (cur, iterator) => {
            if(cur.done) {
                return Promise.resolve(this)
            }
            let event = cur.value
            let target = this.identityMap.get(event.id)
            return target.applyEvent(event)
                .bind(this)
                .then(function(){
                    return iterate(iterator.next(), iterator)
                }, function(err) {
                    iterator.throw(err)
                    return err
                })
        }
        this.restore = (root, from, to) => {
            if(!root) {
                throw new Error('`root` is required')
            }
            this.register(root.id(),root)
            let events = this.storage.events(from, to)
            return iterate(events.next(),events)
        }
    })

const unitOfWork = stampit()
    .refs({
        storage: undefined
        , identityMap: undefined
    })
    .methods({
        envelope: function enveloper( events) {
            return {
                events: events
            }
        }
    })
    .init(function(){
        let current
        let writeable = writeableUnitOfWork({
            envelope      : this.envelope
            , identityMap : this.identityMap
            , storage     : this.storage
        })

        this.append = (e) => {
            return current.append(e)
        }
        this.commit = () => {
            return current.commit()
        }
        this.register = (id, provider) => {
            return current.register(id, provider)
        }
        this.restore = (root, from, to) => {
            current = readableUnitOfWork({
                identityMap : this.identityMap
                , storage   : this.storage
            })
            return current.restore(root, from, to)
        }

        //by default we are in writeable state
        current = writeable
    })

const eventable = stampit()
    .init(function(){
        let uow = this.leo.unitOfWork()
        //decorate event(s) with critical properties
        let decorate = (arr) => {
            return arr.map((e) => {
                e.id = this.id()
                e.revision = this.nextRevision()
                return e
            })
        }

        let validateEvents = function (arr) {
            for(let e of arr) {
                if(!e || !e.event) {
                    throw new Error('`event` is required')
                }
            }
            return arr
        }
        this.raise = function (e) {
            if(!Array.isArray(e)) {
                e = [e]
            }
            validateEvents(e)
            decorate(e)

            return Promise.resolve(e)
            .bind(uow)
            .tap(uow.append)
            .bind(this)
            .then(this.applyEvent)
        }
        this.applyEvent = function(e) {
            this.revision(e.revision)
            if(Array.isArray(e)) {
                return Promise.resolve(e)
                    .bind(this)
                    .map(this.applyEvent)
                    .return(this)
            }
            return Promise.resolve(this)
                .bind(this)
                .tap(function(){
                    let fn = this['$' + e.event]
                    if(!fn) {
                        return this
                    }
                    return fn.call(this, e)
                })
                .return(this)

        }
        //register this instance on the unit of work
        uow.register(this.id(), this)
    })

export default stampit()
    .compose(identifiable)
    .init(function() {
        this.storage = (this.storage || inMemoryStorage())
        this.identityMap = (this.identityMap || hashIdentityMap())
        //default uow impl
        let uow = unitOfWork({
            storage: this.storage
            , identityMap: this.identityMap
        })
        /**
         * Expose an `stamp` that may be use for composition
         * with another stamp
         * @method eventable
         * @return {stamp} factory that may be composed to attach
         * `eventable` behaviors onto another stamp
         * */
        this.eventable = () => {
            return stampit()
                .props({leo: this})
                .compose(identifiable)
                .compose(revisable)
                .compose(eventable)
        }

        /**
         * convenience method to commit pending events to storage
         * @return {leopold}
         * */
        this.commit = () => {
            return this.unitOfWork().commit()
        }
        /**
         * convenience method to unitOfWork inside `eventable` impl
         * @return {unitOfWork}
         * */
        this.unitOfWork = () => {
            return uow
        }
        /**
         * mount an envelope having events into storage
         * useful for testing, or perhaps seeding an app from a backend
         * */
        this.mount = (envelope) => {
            return Promise.resolve(envelope)
                .bind(this.storage)
                .then(this.storage.store)
        }
        /**
         * restore to revision `to` from revision `from`
         * using `root` at the entrypoint. `to` and `from` are inclusive.
         * @param {eventable} root any `eventable` object
         * @param {Number} from lower bound revision to include
         * @param {Number} to upper bound revision to include
         * @return {Promise} resolving this leo instance
         */
        this.restore = (root, from, to) => {
            return this.unitOfWork().restore(root, from, to)
        }
    })

<commit_msg>releasing identity map after commit and restore<commit_after>'use strict';

import Promise from 'bluebird'
import stampit from 'stampit'
import cuid from 'cuid'

//utility
function isFunction(obj) {
    return obj && toString.call(obj === '[object Function]')
}
/**
 * accepts `_id` as an initial id value. if an `id` function
 * exists (further up the composition chain) it does not override it;
 * otherwise, it provides its own method for `id()`
 * */
const identifiable = stampit()
    .init(function(){
        //accept id initializer value
        let id = this._id
        ;(delete this._id)
        if(!isFunction(this.id)) {
            this.id = function() {
                return (id || (id = cuid() ))
            }
        }
    })

/**
 * encapsulates behaviors for revisioning of components
 * */
const revisable = stampit()
    .init(function() {
        let revision = 1
        /**
         * either get the current revision or set the revision with `val`
         * @param {Number} val the revision to set
         * */
        this.revision = function (val) {
            if(val) {
                return (revision = val)
            }
            return revision
        }
        /**
         * gets next revision (doesnt mutate state)
         * */
        this.nextRevision = () => {
            return (this.revision() + 1)
        }
    })

/**
 * simple hashmap storage of event providers
 * */
const hashIdentityMap = stampit().init(function(){
    let providers = new Map()
    this.register = (id, provider) => {
        if(!id) {
            throw new Error('`id` is required')
        }
        if(!provider) {
            throw new Error('`provider` is required')
        }
        providers.set(id, provider)
        return provider
    }
    this.get = (id) => {
        if(!id) {
            throw new Error('`id` is required')
        }
        let provider = providers.get(id)
        if(!provider) {
            throw new Error('could not locate provider with id "' + id + '""')
        }
        return provider
    }
    this.release = () => {
        providers.clear()
    }

})

const inMemoryStorage = stampit()
    .compose(revisable)
    .init(function() {
        var envelopes = []
        this.store = (env) => {
            if(!env.revision) {
                env.revision = this.revision(this.nextRevision())
            }
            envelopes.push(env)
            return this
        }
        this.events = function*(from, to) {
            from = (from || 0)
            to = (to || Number.MAX_VALUE)
            if(from > to) {
                throw new Error('`from` must be less than or equal `to`')
            }
            if(!envelopes.length) {
                return []
            }
            for(let env of envelopes) {
                if(env.revision > to) {
                    return //we are done streaming
                }
                if(env.revision >= from) {
                    for(let ev of env.events) {
                        yield ev
                    }
                }
            }
        }
    })

const writeableUnitOfWork = stampit()
    .refs({
        storage: undefined
        , identityMap: undefined
    })
    .methods({
        envelope: function enveloper( events) {
            return {
                events: events
            }
        }
    })
    .init(function(){
        let pending = []

        this.append = (e) => {
            pending.push.apply(pending,e)
            return e
        }
        this.commit = () => {
            //move reference to array in case event arrives while flushing
            let commitable = pending.splice(0,pending.length)
            return Promise.resolve(commitable)
                .bind(this)
                .then(this.envelope)
                .bind(this.storage)
                .tap(this.storage.store)
                .return(this)
        }
        this.register = function() {
            //no op
        }
    })

const readableUnitOfWork = stampit()
    .refs({
        storage: undefined
        , identityMap: undefined
    })
    .init(function(){
        this.append = (e) => {
            //no op
            return e
        }
        this.commit = () => {
            return Promise.resolve(this)
        }
        this.register = this.identityMap.register

        const iterate = (cur, iterator) => {
            if(cur.done) {
                return Promise.resolve(this)
            }
            let event = cur.value
            let target = this.identityMap.get(event.id)
            return target.applyEvent(event)
                .bind(this)
                .then(function(){
                    return iterate(iterator.next(), iterator)
                }, function(err) {
                    iterator.throw(err)
                    return err
                })
        }
        this.restore = (root, from, to) => {
            if(!root) {
                throw new Error('`root` is required')
            }
            this.register(root.id(),root)
            let events = this.storage.events(from, to)
            return iterate(events.next(),events)
        }
    })

const unitOfWork = stampit()
    .refs({
        storage: undefined
        , identityMap: undefined
    })
    .methods({
        envelope: function enveloper( events) {
            return {
                events: events
            }
        }
    })
    .init(function(){
        let current
        let writeable = writeableUnitOfWork({
            envelope      : this.envelope
            , identityMap : this.identityMap
            , storage     : this.storage
        })
        let readable = readableUnitOfWork({
            identityMap : this.identityMap
            , storage   : this.storage
        })

        this.append = (e) => {
            return current.append(e)
        }
        this.commit = () => {
            return current.commit()
                .bind(this.identityMap)
                .tap(this.identityMap.release)
                .return(this)
        }
        this.register = (id, provider) => {
            return current.register(id, provider)
        }
        this.restore = (root, from, to) => {
            current = readable
            return current.restore(root, from, to)
                .bind(this)
                .then(function(){
                    this.identityMap.release()
                    current = writeable
                    return this
                })
        }

        //by default we are in writeable state
        current = writeable
    })

const eventable = stampit()
    .init(function(){
        let uow = this.leo.unitOfWork()
        //decorate event(s) with critical properties
        let decorate = (arr) => {
            return arr.map((e) => {
                e.id = this.id()
                e.revision = this.nextRevision()
                return e
            })
        }

        let validateEvents = function (arr) {
            for(let e of arr) {
                if(!e || !e.event) {
                    throw new Error('`event` is required')
                }
            }
            return arr
        }
        this.raise = function (e) {
            if(!Array.isArray(e)) {
                e = [e]
            }
            validateEvents(e)
            decorate(e)

            return Promise.resolve(e)
            .bind(uow)
            .tap(uow.append)
            .bind(this)
            .then(this.applyEvent)
        }
        this.applyEvent = function(e) {
            this.revision(e.revision)
            if(Array.isArray(e)) {
                return Promise.resolve(e)
                    .bind(this)
                    .map(this.applyEvent)
                    .return(this)
            }
            return Promise.resolve(this)
                .bind(this)
                .tap(function(){
                    let fn = this['$' + e.event]
                    if(!fn) {
                        return this
                    }
                    return fn.call(this, e)
                })
                .return(this)

        }
        //register this instance on the unit of work
        uow.register(this.id(), this)
    })

export default stampit()
    .compose(identifiable)
    .init(function() {
        this.storage = (this.storage || inMemoryStorage())
        this.identityMap = (this.identityMap || hashIdentityMap())
        //default uow impl
        let uow = unitOfWork({
            storage: this.storage
            , identityMap: this.identityMap
        })
        /**
         * Expose an `stamp` that may be use for composition
         * with another stamp
         * @method eventable
         * @return {stamp} factory that may be composed to attach
         * `eventable` behaviors onto another stamp
         * */
        this.eventable = () => {
            return stampit()
                .props({leo: this})
                .compose(identifiable)
                .compose(revisable)
                .compose(eventable)
        }

        /**
         * convenience method to commit pending events to storage
         * @return {leopold}
         * */
        this.commit = () => {
            return this.unitOfWork().commit()
        }
        /**
         * convenience method to unitOfWork inside `eventable` impl
         * @return {unitOfWork}
         * */
        this.unitOfWork = () => {
            return uow
        }
        /**
         * mount an envelope having events into storage
         * useful for testing, or perhaps seeding an app from a backend
         * */
        this.mount = (envelope) => {
            return Promise.resolve(envelope)
                .bind(this.storage)
                .then(this.storage.store)
        }
        /**
         * restore to revision `to` from revision `from`
         * using `root` at the entrypoint. `to` and `from` are inclusive.
         * @param {eventable} root any `eventable` object
         * @param {Number} from lower bound revision to include
         * @param {Number} to upper bound revision to include
         * @return {Promise} resolving this leo instance
         */
        this.restore = (root, from, to) => {
            return this.unitOfWork().restore(root, from, to)
        }
    })

<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

var EventEmitter = require('events').EventEmitter;
var rtc = require('rtc');
var debug = rtc.logger('rtc-quickconnect');
var signaller = require('rtc-signaller');
var defaults = require('cog/defaults');
var extend = require('cog/extend');
var reTrailingSlash = /\/$/;
var CHANNEL_HEARTBEAT = '__heartbeat';
var HEARTBEAT = new Uint8Array([0x10]);

/**
  # rtc-quickconnect

  This is a high level helper module designed to help you get up
  an running with WebRTC really, really quickly.  By using this module you
  are trading off some flexibility, so if you need a more flexible
  configuration you should drill down into lower level components of the
  [rtc.io](http://www.rtc.io) suite.  In particular you should check out
  [rtc](https://github.com/rtc-io/rtc).

  ## Example Usage

  In the simplest case you simply call quickconnect with a single string
  argument which tells quickconnect which server to use for signaling:

  <<< examples/simple.js

  ## Example Usage (using data channels)

  When working with WebRTC data channels, you can call the `createDataChannel`
  function helper that is attached to the object returned from the
  `quickconnect` call.  The `createDataChannel` function signature matches
  the signature of the `RTCPeerConnection` `createDataChannel` function.

  At the minimum it requires a label for the channel, but you can also pass
  through a dictionary of options that can be used to fine tune the
  data channel behaviour.  For more information on these options, I'd
  recommend having a quick look at the WebRTC spec:

  http://dev.w3.org/2011/webrtc/editor/webrtc.html#dictionary-rtcdatachannelinit-members

  If in doubt, I'd recommend not passing through options.

  <<< examples/datachannel.js

  __NOTE:__ Data channel interoperability has been tested between Chrome 32
  and Firefox 26, which both make use of SCTP data channels.

  __NOTE:__ The current stable version of Chrome is 31, so interoperability
  with Firefox right now will be hard to achieve.

  ## Example Usage (using captured media)

  Another example is displayed below, and this example demonstrates how
  to use `rtc-quickconnect` to create a simple video conferencing application:

  <<< examples/conference.js

  ## Regarding Signalling and a Signalling Server

  Signaling is an important part of setting up a WebRTC connection and for
  our examples we use our own test instance of the
  [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard). For your
  testing and development you are more than welcome to use this also, but
  just be aware that we use this for our testing so it may go up and down
  a little.  If you need something more stable, why not consider deploying
  an instance of the switchboard yourself - it's pretty easy :)

  ## Handling Peer Disconnection

  __NOTE:__ This functionality is experimental and still in testing, it is
  recommended that you continue to use the `peer:leave` events at this stage.

  Since version `0.11` the following events are also emitted by quickconnect
  objects:

  - `peer:disconnect`
  - `%label%:close` where `%label%` is the label of the channel
     you provided in a `createDataChannel` call.

  Basically the `peer:disconnect` can be used as a more accurate version
  of the `peer:leave` message.  While the `peer:leave` event triggers when
  the background signaller disconnects, the `peer:disconnect` event is
  trigger when the actual WebRTC peer connection is closed.

  At present (due to limited browser support for handling peer close events
  and the like) this is implemented by creating a heartbeat data channel
  which sends messages on a regular basis between the peers.  When these
  messages are stopped being received the connection is considered closed.

  ## Reference

  ```
  quickconnect(signalhost, opts?) => rtc-sigaller instance (+ helpers)
  ```

  ### Valid Quick Connect Options

  The options provided to the `rtc-quickconnect` module function influence the
  behaviour of some of the underlying components used from the rtc.io suite.

  Listed below are some of the commonly used options:

  - `ns` (default: '')

    An optional namespace for your signalling room.  While quickconnect
    will generate a unique hash for the room, this can be made to be more
    unique by providing a namespace.  Using a namespace means two demos
    that have generated the same hash but use a different namespace will be
    in different rooms.

  - `room` (default: null) _added 0.6_

    Rather than use the internal hash generation
    (plus optional namespace) for room name generation, simply use this room
    name instead.  __NOTE:__ Use of the `room` option takes precendence over
    `ns`.

  - `debug` (default: false)

  Write rtc.io suite debug output to the browser console.

  #### Options for Peer Connection Creation

  Options that are passed onto the
  [rtc.createConnection](https://github.com/rtc-io/rtc#createconnectionopts-constraints)
  function:

  - `iceServers`

  This provides a list of ice servers that can be used to help negotiate a
  connection between peers.

  #### Options for P2P negotiation

  Under the hood, quickconnect uses the
  [rtc/couple](https://github.com/rtc-io/rtc#rtccouple) logic, and the options
  passed to quickconnect are also passed onto this function.

**/
module.exports = function(signalhost, opts) {
  var hash = typeof location != 'undefined' && location.hash.slice(1);
  var signaller = require('rtc-signaller')(signalhost, opts);

  // init configurable vars
  var ns = (opts || {}).ns || '';
  var room = (opts || {}).room;
  var debugging = (opts || {}).debug;
  var disableHeartbeat = (opts || {}).disableHeartbeat;
  var heartbeatInterval = (opts || {}).heartbeatInterval || 1000;
  var heartbeatTimeout = (opts || {}).heartbeatTimeout || heartbeatInterval * 3;
  var profile = {};
  var announced = false;

  // collect the local streams
  var localStreams = [];

  // create the peers registry
  var peers = {};

  // create the known data channels registry
  var channels = {};

  function gotPeerChannel(channel, pc, data) {
    // create the channelOpen function
    var emitChannelOpen = signaller.emit.bind(
      signaller,
      channel.label + ':open',
      channel,
      data.id,
      data,
      pc
    );

    debug('channel ' + channel.label + ' discovered for peer: ' + data.id, channel);
    if (channel.readyState === 'open') {
      return emitChannelOpen();
    }

    channel.onopen = emitChannelOpen;
  }

  function handlePeerAnnounce(data) {
    var pc;
    var monitor;

    // if the room is not a match, abort
    if (data.room !== room) {
      return;
    }

    // create a peer connection
    pc = peers[data.id] = rtc.createConnection(opts, (opts || {}).constraints);

    // add the local streams
    localStreams.forEach(function(stream) {
      pc.addStream(stream);
    });

    // add the data channels
    // do this differently based on whether the connection is a
    // master or a slave connection
    if (signaller.isMaster(data.id)) {
      debug('is master, creating data channels: ', Object.keys(channels));

      // unless the heartbeat is disabled then create a heartbeat datachannel
      if (! disableHeartbeat) {
        initHeartbeat(
          pc.createDataChannel(CHANNEL_HEARTBEAT, {
            ordered: false
          }),
          pc,
          data
        );
      }

      // create the channels
      Object.keys(channels).forEach(function(label) {
        gotPeerChannel(pc.createDataChannel(label, channels[label]), pc, data);
      });
    }
    else {
      pc.ondatachannel = function(evt) {
        var channel = evt && evt.channel;

        // if we have no channel, abort
        if (! channel) {
          return;
        }

        // if the channel is the heartbeat, then init the heartbeat
        if (channel.label === CHANNEL_HEARTBEAT) {
          initHeartbeat(channel, pc, data);
        }
        // otherwise, if this is a known channel, initialise it
        else if (channels[channel.label] !== undefined) {
          gotPeerChannel(channel, pc, data);
        }
      };
    }

    // couple the connections
    monitor = rtc.couple(pc, data.id, signaller, opts);

    // emit the peer event as per <= rtc-quickconnect@0.7
    signaller.emit('peer', pc, data.id, data, monitor);

    // once active, trigger the peer connect event
    monitor.once('connected', function() {
      signaller.emit('peer:connect', pc, data.id, data);
    });

    // if we are the master connnection, create the offer
    // NOTE: this only really for the sake of politeness, as rtc couple
    // implementation handles the slave attempting to create an offer
    if (signaller.isMaster(data.id)) {
      monitor.createOffer();
    }
  }

  function initHeartbeat(channel, pc, data) {
    var hbTimeoutTimer;
    var hbTimer;

    function timeoutConnection() {
      // console.log(Date.now() + ', connection with ' + data.id + ' timed out');

      // trigger a peer disconnect event
      signaller.emit('peer:disconnect', data.id);

      // trigger close events for each of the channels
      Object.keys(channels).forEach(function(channel) {
        signaller.emit(channel + ':close');
      });

      // clear the peer reference
      peers[data.id] = undefined;

      // stop trying to send heartbeat messages
      clearInterval(hbTimer);
    }

    // console.log('created heartbeat channel for peer: ' + data.id);

    // start monitoring using the heartbeat channel to keep tabs on our
    // peers availability
    channel.onmessage = function(evt) {
      // console.log(Date.now() + ', ' + data.id + ': ' + evt.data);

      // console.log('received hearbeat message: ' + evt.data)
      clearTimeout(hbTimeoutTimer);
      hbTimeoutTimer = setTimeout(timeoutConnection, heartbeatTimeout);

      // emit the heartbeat for the appropriate connection
      signaller.emit('hb:' + data.id);
    };

    hbTimer  = setInterval(function() {
      // if the channel is not yet, open then abort
      if (channel.readyState !== 'open') {
        // TODO: clear the interval if we have previously been sending
        // messages
        return;
      }

      channel.send(HEARTBEAT);
    }, heartbeatInterval);
  }

  // if the room is not defined, then generate the room name
  if (! room) {
    // if the hash is not assigned, then create a random hash value
    if (! hash) {
      hash = location.hash = '' + (Math.pow(2, 53) * Math.random());
    }

    room = ns + '#' + hash;
  }

  if (debugging) {
    rtc.logger.enable.apply(rtc.logger, Array.isArray(debug) ? debugging : ['*']);
  }

  signaller.on('peer:announce', handlePeerAnnounce);

  // announce ourselves to our new friend
  setTimeout(function() {
    var data = extend({}, profile, { room: room });

    // announce and emit the local announce event
    signaller.announce(data);
    signaller.emit('local:announce', data);
    announced = true;
  }, 0);

  /**
    ### Quickconnect Broadcast and Data Channel Helper Functions

    The following are functions that are patched into the `rtc-signaller`
    instance that make working with and creating functional WebRTC applications
    a lot simpler.

  **/

  /**
    #### broadcast(stream)

    Add the stream to the set of local streams that we will broadcast
    to other peers.

  **/
  signaller.broadcast = function(stream) {
    localStreams.push(stream);
    return signaller;
  };

  /**
    #### close()

    The `close` function provides a convenient way of closing all associated
    peer connections.
  **/
  signaller.close = function() {
    Object.keys(peers).forEach(function(id) {
      if (peers[id]) {
        peers[id].close();
      }
    });

    // reset the peer references
    peers = {};
  };

  /**
    #### createDataChannel(label, config)

    Request that a data channel with the specified `label` is created on
    the peer connection.  When the data channel is open and available, an
    event will be triggered using the label of the data channel.

    For example, if a new data channel was requested using the following
    call:

    ```js
    var qc = quickconnect('http://rtc.io/switchboard').createDataChannel('test');
    ```

    Then when the data channel is ready for use, a `test:open` event would
    be emitted by `qc`.

  **/
  signaller.createDataChannel = function(label, opts) {
    // save the data channel opts in the local channels dictionary
    channels[label] = opts || null;
    return signaller;
  };

  /**
    #### profile(data)

    Update the profile data with the attached information, so when
    the signaller announces it includes this data in addition to any
    room and id information.

  **/
  signaller.profile = function(data) {
    extend(profile, data);

    // if we have already announced, then reannounce our profile to provide
    // others a `peer:update` event
    if (announced) {
      signaller.announce(profile);
    }

    return signaller;
  };

  // pass the signaller on
  return signaller;
};<commit_msg>Added `getChannel` function to retrieve an active datachannel by label<commit_after>/* jshint node: true */
'use strict';

var EventEmitter = require('events').EventEmitter;
var rtc = require('rtc');
var debug = rtc.logger('rtc-quickconnect');
var signaller = require('rtc-signaller');
var defaults = require('cog/defaults');
var extend = require('cog/extend');
var reTrailingSlash = /\/$/;
var CHANNEL_HEARTBEAT = '__heartbeat';
var HEARTBEAT = new Uint8Array([0x10]);

/**
  # rtc-quickconnect

  This is a high level helper module designed to help you get up
  an running with WebRTC really, really quickly.  By using this module you
  are trading off some flexibility, so if you need a more flexible
  configuration you should drill down into lower level components of the
  [rtc.io](http://www.rtc.io) suite.  In particular you should check out
  [rtc](https://github.com/rtc-io/rtc).

  ## Example Usage

  In the simplest case you simply call quickconnect with a single string
  argument which tells quickconnect which server to use for signaling:

  <<< examples/simple.js

  ## Example Usage (using data channels)

  When working with WebRTC data channels, you can call the `createDataChannel`
  function helper that is attached to the object returned from the
  `quickconnect` call.  The `createDataChannel` function signature matches
  the signature of the `RTCPeerConnection` `createDataChannel` function.

  At the minimum it requires a label for the channel, but you can also pass
  through a dictionary of options that can be used to fine tune the
  data channel behaviour.  For more information on these options, I'd
  recommend having a quick look at the WebRTC spec:

  http://dev.w3.org/2011/webrtc/editor/webrtc.html#dictionary-rtcdatachannelinit-members

  If in doubt, I'd recommend not passing through options.

  <<< examples/datachannel.js

  __NOTE:__ Data channel interoperability has been tested between Chrome 32
  and Firefox 26, which both make use of SCTP data channels.

  __NOTE:__ The current stable version of Chrome is 31, so interoperability
  with Firefox right now will be hard to achieve.

  ## Example Usage (using captured media)

  Another example is displayed below, and this example demonstrates how
  to use `rtc-quickconnect` to create a simple video conferencing application:

  <<< examples/conference.js

  ## Regarding Signalling and a Signalling Server

  Signaling is an important part of setting up a WebRTC connection and for
  our examples we use our own test instance of the
  [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard). For your
  testing and development you are more than welcome to use this also, but
  just be aware that we use this for our testing so it may go up and down
  a little.  If you need something more stable, why not consider deploying
  an instance of the switchboard yourself - it's pretty easy :)

  ## Handling Peer Disconnection

  __NOTE:__ This functionality is experimental and still in testing, it is
  recommended that you continue to use the `peer:leave` events at this stage.

  Since version `0.11` the following events are also emitted by quickconnect
  objects:

  - `peer:disconnect`
  - `%label%:close` where `%label%` is the label of the channel
     you provided in a `createDataChannel` call.

  Basically the `peer:disconnect` can be used as a more accurate version
  of the `peer:leave` message.  While the `peer:leave` event triggers when
  the background signaller disconnects, the `peer:disconnect` event is
  trigger when the actual WebRTC peer connection is closed.

  At present (due to limited browser support for handling peer close events
  and the like) this is implemented by creating a heartbeat data channel
  which sends messages on a regular basis between the peers.  When these
  messages are stopped being received the connection is considered closed.

  ## Reference

  ```
  quickconnect(signalhost, opts?) => rtc-sigaller instance (+ helpers)
  ```

  ### Valid Quick Connect Options

  The options provided to the `rtc-quickconnect` module function influence the
  behaviour of some of the underlying components used from the rtc.io suite.

  Listed below are some of the commonly used options:

  - `ns` (default: '')

    An optional namespace for your signalling room.  While quickconnect
    will generate a unique hash for the room, this can be made to be more
    unique by providing a namespace.  Using a namespace means two demos
    that have generated the same hash but use a different namespace will be
    in different rooms.

  - `room` (default: null) _added 0.6_

    Rather than use the internal hash generation
    (plus optional namespace) for room name generation, simply use this room
    name instead.  __NOTE:__ Use of the `room` option takes precendence over
    `ns`.

  - `debug` (default: false)

  Write rtc.io suite debug output to the browser console.

  #### Options for Peer Connection Creation

  Options that are passed onto the
  [rtc.createConnection](https://github.com/rtc-io/rtc#createconnectionopts-constraints)
  function:

  - `iceServers`

  This provides a list of ice servers that can be used to help negotiate a
  connection between peers.

  #### Options for P2P negotiation

  Under the hood, quickconnect uses the
  [rtc/couple](https://github.com/rtc-io/rtc#rtccouple) logic, and the options
  passed to quickconnect are also passed onto this function.

**/
module.exports = function(signalhost, opts) {
  var hash = typeof location != 'undefined' && location.hash.slice(1);
  var signaller = require('rtc-signaller')(signalhost, opts);

  // init configurable vars
  var ns = (opts || {}).ns || '';
  var room = (opts || {}).room;
  var debugging = (opts || {}).debug;
  var disableHeartbeat = (opts || {}).disableHeartbeat;
  var heartbeatInterval = (opts || {}).heartbeatInterval || 1000;
  var heartbeatTimeout = (opts || {}).heartbeatTimeout || heartbeatInterval * 3;
  var profile = {};
  var announced = false;

  // collect the local streams
  var localStreams = [];

  // create the peers registry
  var peers = {};

  // create the known data channels registry
  var channels = {};
  var activeChannels = {};

  function gotPeerChannel(channel, pc, data) {

    function channelReady() {
      // mark the channel as active
      activeChannels[channel.label] = channel;

      // trigger the %channel.label%:open event
      signaller.emit(
        channel.label + ':open',
        channel,
        data.id,
        data,
        pc
      );
    }

    debug('channel ' + channel.label + ' discovered for peer: ' + data.id, channel);
    if (channel.readyState === 'open') {
      return channelReady();
    }

    channel.onopen = channelReady;
  }

  function handlePeerAnnounce(data) {
    var pc;
    var monitor;

    // if the room is not a match, abort
    if (data.room !== room) {
      return;
    }

    // create a peer connection
    pc = peers[data.id] = rtc.createConnection(opts, (opts || {}).constraints);

    // add the local streams
    localStreams.forEach(function(stream) {
      pc.addStream(stream);
    });

    // add the data channels
    // do this differently based on whether the connection is a
    // master or a slave connection
    if (signaller.isMaster(data.id)) {
      debug('is master, creating data channels: ', Object.keys(channels));

      // unless the heartbeat is disabled then create a heartbeat datachannel
      if (! disableHeartbeat) {
        initHeartbeat(
          pc.createDataChannel(CHANNEL_HEARTBEAT, {
            ordered: false
          }),
          pc,
          data
        );
      }

      // create the channels
      Object.keys(channels).forEach(function(label) {
        gotPeerChannel(pc.createDataChannel(label, channels[label]), pc, data);
      });
    }
    else {
      pc.ondatachannel = function(evt) {
        var channel = evt && evt.channel;

        // if we have no channel, abort
        if (! channel) {
          return;
        }

        // if the channel is the heartbeat, then init the heartbeat
        if (channel.label === CHANNEL_HEARTBEAT) {
          initHeartbeat(channel, pc, data);
        }
        // otherwise, if this is a known channel, initialise it
        else if (channels[channel.label] !== undefined) {
          gotPeerChannel(channel, pc, data);
        }
      };
    }

    // couple the connections
    monitor = rtc.couple(pc, data.id, signaller, opts);

    // emit the peer event as per <= rtc-quickconnect@0.7
    signaller.emit('peer', pc, data.id, data, monitor);

    // once active, trigger the peer connect event
    monitor.once('connected', function() {
      signaller.emit('peer:connect', pc, data.id, data);
    });

    // if we are the master connnection, create the offer
    // NOTE: this only really for the sake of politeness, as rtc couple
    // implementation handles the slave attempting to create an offer
    if (signaller.isMaster(data.id)) {
      monitor.createOffer();
    }
  }

  function initHeartbeat(channel, pc, data) {
    var hbTimeoutTimer;
    var hbTimer;

    function timeoutConnection() {
      // console.log(Date.now() + ', connection with ' + data.id + ' timed out');

      // trigger a peer disconnect event
      signaller.emit('peer:disconnect', data.id);

      // trigger close events for each of the channels
      Object.keys(channels).forEach(function(channel) {
        signaller.emit(channel + ':close');
      });

      // clear the peer reference
      peers[data.id] = undefined;

      // stop trying to send heartbeat messages
      clearInterval(hbTimer);
    }

    // console.log('created heartbeat channel for peer: ' + data.id);

    // start monitoring using the heartbeat channel to keep tabs on our
    // peers availability
    channel.onmessage = function(evt) {
      // console.log(Date.now() + ', ' + data.id + ': ' + evt.data);

      // console.log('received hearbeat message: ' + evt.data)
      clearTimeout(hbTimeoutTimer);
      hbTimeoutTimer = setTimeout(timeoutConnection, heartbeatTimeout);

      // emit the heartbeat for the appropriate connection
      signaller.emit('hb:' + data.id);
    };

    hbTimer  = setInterval(function() {
      // if the channel is not yet, open then abort
      if (channel.readyState !== 'open') {
        // TODO: clear the interval if we have previously been sending
        // messages
        return;
      }

      channel.send(HEARTBEAT);
    }, heartbeatInterval);
  }

  // if the room is not defined, then generate the room name
  if (! room) {
    // if the hash is not assigned, then create a random hash value
    if (! hash) {
      hash = location.hash = '' + (Math.pow(2, 53) * Math.random());
    }

    room = ns + '#' + hash;
  }

  if (debugging) {
    rtc.logger.enable.apply(rtc.logger, Array.isArray(debug) ? debugging : ['*']);
  }

  signaller.on('peer:announce', handlePeerAnnounce);

  // announce ourselves to our new friend
  setTimeout(function() {
    var data = extend({}, profile, { room: room });

    // announce and emit the local announce event
    signaller.announce(data);
    signaller.emit('local:announce', data);
    announced = true;
  }, 0);

  /**
    ### Quickconnect Broadcast and Data Channel Helper Functions

    The following are functions that are patched into the `rtc-signaller`
    instance that make working with and creating functional WebRTC applications
    a lot simpler.

  **/

  /**
    #### broadcast(stream)

    Add the stream to the set of local streams that we will broadcast
    to other peers.

  **/
  signaller.broadcast = function(stream) {
    localStreams.push(stream);
    return signaller;
  };

  /**
    #### getChannel(name)

    This is an accessor method to get an **active** channel by it's label. If
    the channel is not yet active, then this function will return `undefined`.
    In this case you should use the `%label%:open` event handler to wait for
    the channel.

  **/
  signaller.getChannel = function(name) {
    return activeChannels[name];
  };

  /**
    #### close()

    The `close` function provides a convenient way of closing all associated
    peer connections.
  **/
  signaller.close = function() {
    Object.keys(peers).forEach(function(id) {
      if (peers[id]) {
        peers[id].close();
      }
    });

    // reset the peer references
    peers = {};
  };

  /**
    #### createDataChannel(label, config)

    Request that a data channel with the specified `label` is created on
    the peer connection.  When the data channel is open and available, an
    event will be triggered using the label of the data channel.

    For example, if a new data channel was requested using the following
    call:

    ```js
    var qc = quickconnect('http://rtc.io/switchboard').createDataChannel('test');
    ```

    Then when the data channel is ready for use, a `test:open` event would
    be emitted by `qc`.

  **/
  signaller.createDataChannel = function(label, opts) {
    // save the data channel opts in the local channels dictionary
    channels[label] = opts || null;
    return signaller;
  };

  /**
    #### profile(data)

    Update the profile data with the attached information, so when
    the signaller announces it includes this data in addition to any
    room and id information.

  **/
  signaller.profile = function(data) {
    extend(profile, data);

    // if we have already announced, then reannounce our profile to provide
    // others a `peer:update` event
    if (announced) {
      signaller.announce(profile);
    }

    return signaller;
  };

  // pass the signaller on
  return signaller;
};<|endoftext|>


<commit_before>#!/usr/bin/env node

var mqtt = require('mqtt');
var log = require('logfmt');

var config = {
  port:         process.env.PORT    || 5000,
  mqtt_host:    process.env.MQTT    || "127.0.0.1",
  mqtt_login:   process.env.LOGIN   || "",
  verbose:      process.env.VERBOSE || false,
};

var io  = require('socket.io').listen(config.port);
io.sessionid = 0;

var host = config.mqtt_login ? config.mqtt_login + "@" + config.mqtt_host : config.mqtt_host;
log.log({type: "info", msg: "App server started", port: config.port, host: host});

io.set('origins', '*');
io.on('connection', function(socket){
  this.sessionid = this.sessionid || 0;
  this.sessionid = this.sessionid + 1;
  socket.sessionid = this.sessionid
  log.log({type: "info", msg: "User connected", session: this.sessionid});

  // Connect to the MQTT broker
  var broker = mqtt.connect('mqtt://' + host);
  broker.on('error',function(error){
    log.log({type: "error", msg: error.message});
  });

  //This is what you get when the connection to the broker fails
  broker.on('offline',function(error){
    log.log({type: "error", msg: "MQTT offline", host: config.mqtt_host});
  });

  if(config.verbose){
    broker.on('connect',function(error){
      log.log({type: "info", msg: "MQTT connected", host: config.mqtt_host});
    });

    broker.on('close',function(error){
      log.log({type: "info", msg: "MQTT closed", host: config.mqtt_host});
    });
  }

  broker.subscribe('appserver/session/' + socket.sessionid + "/#");
  broker.subscribe('appserver/session/all/#');
  //Temp fix
  //socket.mqtt.subscribe('#');
  broker.publish('appserver/session/' + socket.sessionid, "Connected")

  //Subscribe
  socket.on('subscribe', function(data) {
    broker.subscribe(data.topic);
  });

  //Forward all mqtt messages to socket.io
  broker.on('message', function(topic, message) {
    socket.emit('mqtt',{'topic':String(topic),'payload':String(message)});
  });

  //Publish
  socket.on('publish', function(data) {
    broker.publish('appserver/session/' + this.sessionid + "/" + data.topic,data.payload);
  });

  //Disconnection
  socket.on('disconnect', function(){
    log.log({type: "info", msg: "User disconnected", session: this.sessionid});
    broker.publish('appserver/session/' + socket.sessionid,"Disconnected");
    broker.end();
  });
});
<commit_msg>msg on exit<commit_after>#!/usr/bin/env node

var mqtt = require('mqtt');
var log = require('logfmt');

var config = {
  port:         process.env.PORT    || 5000,
  mqtt_host:    process.env.MQTT    || "127.0.0.1",
  mqtt_login:   process.env.LOGIN   || "",
  verbose:      process.env.VERBOSE || false,
};

var io  = require('socket.io').listen(config.port);
io.sessionid = 0;

var host = config.mqtt_login ? config.mqtt_login + "@" + config.mqtt_host : config.mqtt_host;
log.log({type: "info", msg: "App server started", port: config.port, host: host});

io.set('origins', '*');
io.on('connection', function(socket){
  this.sessionid = this.sessionid || 0;
  this.sessionid = this.sessionid + 1;
  socket.sessionid = this.sessionid
  log.log({type: "info", msg: "User connected", session: this.sessionid});

  // Connect to the MQTT broker
  var broker = mqtt.connect('mqtt://' + host);
  broker.on('error',function(error){
    log.log({type: "error", msg: error.message});
  });

  //This is what you get when the connection to the broker fails
  broker.on('offline',function(error){
    log.log({type: "error", msg: "MQTT offline", host: config.mqtt_host});
  });

  if(config.verbose){
    broker.on('connect',function(error){
      log.log({type: "info", msg: "MQTT connected", host: config.mqtt_host});
    });

    broker.on('close',function(error){
      log.log({type: "info", msg: "MQTT closed", host: config.mqtt_host});
    });
  }

  broker.subscribe('appserver/session/' + socket.sessionid + "/#");
  broker.subscribe('appserver/session/all/#');
  //Temp fix
  //socket.mqtt.subscribe('#');
  broker.publish('appserver/session/' + socket.sessionid, "Connected")

  //Subscribe
  socket.on('subscribe', function(data) {
    broker.subscribe(data.topic);
  });

  //Forward all mqtt messages to socket.io
  broker.on('message', function(topic, message) {
    socket.emit('mqtt',{'topic':String(topic),'payload':String(message)});
  });

  //Publish
  socket.on('publish', function(data) {
    broker.publish('appserver/session/' + this.sessionid + "/" + data.topic,data.payload);
  });

  //Disconnection
  socket.on('disconnect', function(){
    log.log({type: "info", msg: "User disconnected", session: this.sessionid});
    broker.publish('appserver/session/' + socket.sessionid,"Disconnected");
    broker.end();
  });
});

process.on('SIGTERM', function () {
  log.log({type: 'info', msg: "SIGTERM: Shutting down"});
  process.exit(0);
});
<|endoftext|>