<commit_before>/* jslint node: true, esnext: true */

"use strict";

const blessed = require('blessed'),
  Server = require('ssh2').Server;

const RE_SPECIAL = /[\x00-\x1F\x7F]+|(?:\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K])/g;
const MAX_MSG_LEN = 128;
const MAX_NAME_LEN = 10;

function noop(v) {}

module.exports = function (service, config) {
  const manager = service.owner;

  const server = new Server(config, client => {
    let stream, name;

    client.on('authentication', ctx => {
      name = ctx.username;
      ctx.accept();
    }).on('ready', () => {
      let rows, cols, term;
      client.once('session', (accept, reject) => {
        accept().once('pty', (accept, reject, info) => {
          rows = info.rows;
          cols = info.cols;
          term = info.term;
          if (accept) {
            accept();
          }
        }).on('window-change', (accept, reject, info) => {
          rows = info.rows;
          cols = info.cols;
          if (stream) {
            stream.rows = rows;
            stream.columns = cols;
            stream.emit('resize');
          }
          if (accept) {
            accept();
          }
        }).once('shell', (accept, reject) => {
          stream = accept();

          stream.name = name;
          stream.rows = rows || 24;
          stream.columns = cols || 80;
          stream.isTTY = true;
          stream.setRawMode = noop;
          stream.on('error', noop);

          const screen = new blessed.screen({
            autoPadding: true,
            smartCSR: true,
            program: new blessed.program({
              input: stream,
              output: stream
            }),
            terminal: term || 'ansi'
          });

          screen.title = 'kronos admin';
          // Disable local echo
          //screen.program.attr('invisible', true);

          const output = stream.output = new blessed.log({
            screen: screen,
            top: 0,
            left: 0,
            width: '100%',
            bottom: 2,
            scrollOnInput: true
          });

          screen.append(output);

          screen.append(new blessed.box({
            screen: screen,
            height: 1,
            bottom: 1,
            left: 0,
            width: '100%',
            type: 'line',
            ch: '='
          }));

          const input = new blessed.textbox({
            screen: screen,
            bottom: 0,
            height: 1,
            width: '100%',
            inputOnFocus: true
          });
          screen.append(input);

          input.focus();

          screen.render();
          // XXX This fake resize event is needed for some terminals in order to
          // have everything display correctly
          screen.program.emit('resize');

          const commands = {
            exit() {
                stream.exit();
              },
              help() {
                output.log(`valid commands are: ${Object.keys(commands)}`);
              },
              services() {
                output.log(`services:`);
                Object.keys(manager.services).forEach(n => {
                  output.log(`  ${n}: ${manager.services[n].state}`);
                });
              },
              flows() {
                output.log(`flows:`);
                Object.keys(manager.flows).forEach(n => {
                  output.log(`  ${n}: ${manager.flows[n].state}`);
                });
              },
              interceptors() {
                output.log(`interceptors:`);
                Object.keys(manager.interceptors).forEach(n => {
                  output.log(`  ${n}:`);
                });
              },
              steps() {
                output.log(`steps:`);
                Object.keys(manager.steps).forEach(n => {
                  output.log(`  ${n}:`);
                });
              },
              stop(args) {
                output.log(`stopping service ${args[1]}`);
                const service = manager.services[args[1]];
                service.stop().then(s => {
                  output.log(`${s.name} stopped`);
                });
              },
              loglevel(args) {
                const service = manager.services[args[1]];
                service.logLevel = args[2];
                output.log(`logLevel of ${service.name} set to ${service.logLevel}`);
              },start(args) {
                output.log(`starting service ${args[1]}`);
                const service = manager.services[args[1]];
                service.start().then(s => {
                  output.log(`${s.name} started`);
                });
              },
              restart(args) {
                output.log(`restarting service ${args[1]}`);
                const service = manager.services[args[1]];
                service.restart().then(s => {
                  output.log(`${s.name} restarted`);
                });
              }
          };

          // Read a line of input from the user
          input.on('submit', line => {
            input.clearValue();
            screen.render();
            if (!input.focused)
              input.focus();
            line = line.replace(RE_SPECIAL, '').trim();
            if (line.length > MAX_MSG_LEN)
              line = line.substring(0, MAX_MSG_LEN);
            if (line.length > 0) {
              const tokens = line.split(/\s+/);
              const command = commands[tokens[0]];
              if (command) {
                try {
                  command(tokens);
                } catch (e) {
                  output.log(e);
                  if (tokens[0] === 'exit') {
                    throw e;
                  }
                }
              } else {
                output.log(`unknown command : ${line}`);
                commands.help();
              }
            }
          });
        });
      });
    }).on('end', () => {}).on('error', err => {});
  }).listen(config.port || 0, function () {
    service.info({
      message: 'ssh server listening on port',
      port: this.address().port
    });
  });

  return server;
};
<commit_msg>feat(command): show more flow/service attributes<commit_after>/* jslint node: true, esnext: true */

"use strict";

const blessed = require('blessed'),
  Server = require('ssh2').Server;

const RE_SPECIAL = /[\x00-\x1F\x7F]+|(?:\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K])/g;
const MAX_MSG_LEN = 128;
const MAX_NAME_LEN = 10;

function noop(v) {}

module.exports = function (service, config) {
  const manager = service.owner;

  const server = new Server(config, client => {
    let stream, name;

    client.on('authentication', ctx => {
      name = ctx.username;
      ctx.accept();
    }).on('ready', () => {
      let rows, cols, term;
      client.once('session', (accept, reject) => {
        accept().once('pty', (accept, reject, info) => {
          rows = info.rows;
          cols = info.cols;
          term = info.term;
          if (accept) {
            accept();
          }
        }).on('window-change', (accept, reject, info) => {
          rows = info.rows;
          cols = info.cols;
          if (stream) {
            stream.rows = rows;
            stream.columns = cols;
            stream.emit('resize');
          }
          if (accept) {
            accept();
          }
        }).once('shell', (accept, reject) => {
          stream = accept();

          stream.name = name;
          stream.rows = rows || 24;
          stream.columns = cols || 80;
          stream.isTTY = true;
          stream.setRawMode = noop;
          stream.on('error', noop);

          const screen = new blessed.screen({
            autoPadding: true,
            smartCSR: true,
            program: new blessed.program({
              input: stream,
              output: stream
            }),
            terminal: term || 'ansi'
          });

          screen.title = 'kronos admin';
          // Disable local echo
          //screen.program.attr('invisible', true);

          const output = stream.output = new blessed.log({
            screen: screen,
            top: 0,
            left: 0,
            width: '100%',
            bottom: 2,
            scrollOnInput: true
          });

          screen.append(output);

          screen.append(new blessed.box({
            screen: screen,
            height: 1,
            bottom: 1,
            left: 0,
            width: '100%',
            type: 'line',
            ch: '='
          }));

          const input = new blessed.textbox({
            screen: screen,
            bottom: 0,
            height: 1,
            width: '100%',
            inputOnFocus: true
          });
          screen.append(input);

          input.focus();

          screen.render();
          // XXX This fake resize event is needed for some terminals in order to
          // have everything display correctly
          screen.program.emit('resize');

          const commands = {
            exit() {
                stream.exit();
              },
              help() {
                output.log(`valid commands are: ${Object.keys(commands)}`);
              },
              services() {
                output.log(`services:`);
                Object.keys(manager.services).forEach(n => {
                  const service = manager.services[n];
                  output.log(`  ${n}:`);
                  output.log(`    state: ${service.state}`);
                  output.log(`    logLevel: ${service.logLevel}`);
                });
              },
              flows() {
                output.log(`flows:`);
                Object.keys(manager.flows).forEach(n => {
                  const flow = manager.flows[n];
                  output.log(`  ${n}:`);
                  output.log(`    state: ${flow.state}`);
                  output.log(`    logLevel: ${flow.logLevel}`);
                });
              },
              interceptors() {
                output.log(`interceptors:`);
                Object.keys(manager.interceptors).forEach(n => {
                  output.log(`  ${n}:`);
                });
              },
              steps() {
                output.log(`steps:`);
                Object.keys(manager.steps).forEach(n => {
                  output.log(`  ${n}:`);
                });
              },
              stop(args) {
                output.log(`stopping service ${args[1]}`);
                const service = manager.services[args[1]];
                service.stop().then(s => {
                  output.log(`${s.name} stopped`);
                });
              },
              loglevel(args) {
                const service = manager.services[args[1]];
                service.logLevel = args[2];
                output.log(`logLevel of ${service.name} set to ${service.logLevel}`);
              }, start(args) {
                output.log(`starting service ${args[1]}`);
                const service = manager.services[args[1]];
                service.start().then(s => {
                  output.log(`${s.name} started`);
                });
              },
              restart(args) {
                output.log(`restarting service ${args[1]}`);
                const service = manager.services[args[1]];
                service.restart().then(s => {
                  output.log(`${s.name} restarted`);
                });
              }
          };

          // Read a line of input from the user
          input.on('submit', line => {
            input.clearValue();
            screen.render();
            if (!input.focused)
              input.focus();
            line = line.replace(RE_SPECIAL, '').trim();
            if (line.length > MAX_MSG_LEN)
              line = line.substring(0, MAX_MSG_LEN);
            if (line.length > 0) {
              const tokens = line.split(/\s+/);
              const command = commands[tokens[0]];
              if (command) {
                try {
                  command(tokens);
                } catch (e) {
                  output.log(e);
                  if (tokens[0] === 'exit') {
                    throw e;
                  }
                }
              } else {
                output.log(`unknown command : ${line}`);
                commands.help();
              }
            }
          });
        });
      });
    }).on('end', () => {}).on('error', err => {});
  }).listen(config.port || 0, function () {
    service.info({
      message: 'ssh server listening on port',
      port: this.address().port
    });
  });

  return server;
};
<|endoftext|>


<commit_before>var DOMtoString=function(dom){
	var start = '<';
	var tag = dom.tag;
	var attr = '';
	if (typeof dom.attr!=='undefined'){
		for(var i in dom.attr){
			attr+=' ';
			attr+=dom.attr[i].key;
			attr+='="'+dom.attr[i].value+'"';
		}
	}
	var end = '/>';
	if (typeof dom.childs!='undefined'){
		var chld = '';
		for(var i in dom.childs){
			chld+=DOMtoString(dom.childs[i]);
		}
		end = '>'+chld+'</'+tag+'>';
	}else if (typeof dom.text!='undefined'){
		end = '><![CDATA['+dom.text+']]></'+tag+'>';
	}
	return [start,tag,attr,end].join('');
};

/**
* This class create the SVG stage.
* Options can be:
* * fillColor: color
* * strokeColor: color
* * strokeWidth: number (defaults to 1)
* * unit: ['mm','cm','px','in'] (defaults to mm)
* * width: number or a function
* * height: number or a function
*/
var SVG = function(options){
	this.dom = [];
	this.fillColor='black';
	this.strokeColor='black';
	this.strokeWidth=1;
	this.unit='mm';
	this.width=100;
	this.height=100;

	for(var optionName in options){
		this[optionName]=options[optionName];
	}
}

/**
* returns the svg-string
*/
SVG.prototype.toString=function(fullFile){
	var dom = {
		tag: 'svg',
		attr:[
			{key: 'width',value: ((typeof this.width==='function')?this.width():this.width)+this.unit},
			{key: 'height',value: ((typeof this.height==='function')?this.height():this.height)+this.unit}
		],
		childs: this.dom
	};

	if (fullFile===true){
		return '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
			+"\n\r"
			+'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
			+"\n\r"
			+DOMtoString(dom);

	}else{
		return DOMtoString(dom);
	}
}

SVG.prototype.rect=function(x,y,width,height,fillColor,strokeColor,strokeWidth){
	if (typeof x==='undefined'){ x=0; }
	if (typeof y==='undefined'){ y=0; }
	if (typeof width==='undefined'){ width=0; }
	if (typeof height==='undefined'){ height=0; }

	if (typeof fillColor==='undefined'){ fillColor=this.fillColor; }
	if (typeof strokeColor==='undefined'){ strokeColor=this.strokeColor; }
	if (typeof strokeWidth==='undefined'){ strokeWidth=this.strokeWidth; }
	this.dom.push({
		tag: 'rect',
		attr: [
			{key:'x',value: (x)+this.unit},
			{key:'y',value: (y)+this.unit},
			{key:'width',value: (width)+this.unit},
			{key:'height',value: (height)+this.unit},
			{key:'fill',value: fillColor},
			{key:'stroke',value: strokeColor},
			{key:'stroke-width',value: strokeWidth}
		]
	});
}

exports.SVG = SVG;
<commit_msg>Add viewBox attribute to svg tag.<commit_after>var DOMtoString=function(dom){
	var start = '<';
	var tag = dom.tag;
	var attr = '';
	if (typeof dom.attr!=='undefined'){
		for(var i in dom.attr){
			attr+=' ';
			attr+=dom.attr[i].key;
			attr+='="'+dom.attr[i].value+'"';
		}
	}
	var end = '/>';
	if (typeof dom.childs!='undefined'){
		var chld = '';
		for(var i in dom.childs){
			chld+=DOMtoString(dom.childs[i]);
		}
		end = '>'+chld+'</'+tag+'>';
	}else if (typeof dom.text!='undefined'){
		end = '><![CDATA['+dom.text+']]></'+tag+'>';
	}
	return [start,tag,attr,end].join('');
};

/**
* This class create the SVG stage.
* Options can be:
* * fillColor: color
* * strokeColor: color
* * strokeWidth: number (defaults to 1)
* * unit: ['mm','cm','px','in'] (defaults to mm)
* * width: number or a function
* * height: number or a function
*/
var SVG = function(options){
	this.dom = [];
	this.fillColor='black';
	this.strokeColor='black';
	this.strokeWidth=1;
	this.unit='mm';
	this.width=100;
	this.height=100;

	for(var optionName in options){
		this[optionName]=options[optionName];
	}
}

/**
* returns the svg-string
*/
SVG.prototype.toString=function(fullFile){
	width = (typeof this.width==='function')?this.width():this.width
	height = (typeof this.height==='function')?this.height():this.height
	var dom = {
		tag: 'svg',
		attr:[
			{key: 'width',value: width + this.unit},
			{key: 'height',value: height + this.unit},
			{key: 'viewBox',value: '0 0 ' + width + ' ' + height}
		],
		childs: this.dom
	};

	if (fullFile===true){
		return '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
			+"\n\r"
			+'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
			+"\n\r"
			+DOMtoString(dom);

	}else{
		return DOMtoString(dom);
	}
}

SVG.prototype.rect=function(x,y,width,height,fillColor,strokeColor,strokeWidth){
	if (typeof x==='undefined'){ x=0; }
	if (typeof y==='undefined'){ y=0; }
	if (typeof width==='undefined'){ width=0; }
	if (typeof height==='undefined'){ height=0; }

	if (typeof fillColor==='undefined'){ fillColor=this.fillColor; }
	if (typeof strokeColor==='undefined'){ strokeColor=this.strokeColor; }
	if (typeof strokeWidth==='undefined'){ strokeWidth=this.strokeWidth; }
	this.dom.push({
		tag: 'rect',
		attr: [
			{key:'x',value: (x)+this.unit},
			{key:'y',value: (y)+this.unit},
			{key:'width',value: (width)+this.unit},
			{key:'height',value: (height)+this.unit},
			{key:'fill',value: fillColor},
			{key:'stroke',value: strokeColor},
			{key:'stroke-width',value: strokeWidth}
		]
	});
}

exports.SVG = SVG;
<|endoftext|>


<commit_before>var debug = require('debug')('zmq-zap:zap'),
	net = require('net');

var ZAP = function(options) {
	this._options = options || {};
	this._mechanisms = {};
}

ZAP.prototype._captureReturnPath = function(data) {
	var returnPath = [];

	var frame = data.shift();
	while (frame.length != 0) {
		returnPath.push(frame);
		frame = data.shift();
	}
	returnPath.push(frame);

	return returnPath;
}

ZAP.prototype._parseRequestData = function(data) {
	var req;
	if (data && (data.length >= 6)) {
		req = {
			version: new Buffer(data.shift()).toString('utf8'),
			requestId: new Buffer(data.shift()).toString('utf8'),
			domain: new Buffer(data.shift()).toString('utf8'),
			address: new Buffer(data.shift()).toString('utf8'),
			identity: new Buffer(data.shift()).toString('utf8'),
			mechanism: new Buffer(data.shift()).toString('utf8'),
			credentials: data.slice(0)
		};
	}
	return req;
}

ZAP.prototype._validateRequest = function(req, mech) {
	if (req.version != "1.0") return false;
	if (!req.requestId) return false;
	if (!net.isIP(req.address)) return false;

	if (mech && (typeof mech.validate === 'function')) return mech.validate(req);
	else return true;
}

ZAP.prototype._generateResponse = function(req, returnPath, statusCode, message, userId, metadata) {
	if (!message && statusCode) {
		switch (statusCode) {
			case 200:
				message = "OK";
				break;
			case 300:
				message = "ERR";
				break;
			case 400:
				message = "AUTHFAIL";
				break;
			case 500:
				message = "INTERR";
				break;
		}
	}

	if (req && req.version && req.requestId && returnPath && statusCode) {
		debug('Authentication response: %s', req ? JSON.stringify({
			version: req.version,
			requestId: req.requestId,
			statusCode: statusCode,
			message: message,
			userId: userId,
			metadata: metadata
		}, true, 2) : 'undefined');

		return (returnPath || []).splice(0).concat([
			new Buffer(req.version, 'utf8'),
			new Buffer(req.requestId, 'utf8'),
			new Buffer(statusCode.toString(), 'utf8'),
			(message ? new Buffer(message, 'utf8') : new Buffer(0)),
			(((statusCode == 200) && userId) ? new Buffer(userId, 'utf8') : new Buffer(0)),
			(((statusCode == 200) && (metadata)) ? metadata : new Buffer(0))
		]);
	} else {
		return;
	}
}

ZAP.prototype.use = function(mechanism) {
	if (!mechanism) throw new Error('Invalid mechanism');
	if ((typeof mechanism.name !== 'string') || !mechanism.name.length) throw new Error("Invalid mechanism name");
	if (typeof mechanism.authenticate !== 'function') throw new Error('Invalid mechanism authenticate handler');

	this._mechanisms[mechanism.name] = mechanism;
}

ZAP.prototype.authenticate = function(data, callback) {
	if (!data) {
		callback(new Error("Invalid Message"));
		return;
	}

	var data = Array.prototype.slice.call(data); // This forces data to be an array (for when 'arguments' is passed in directly)

	var returnPath = this._captureReturnPath(data),
		req = this._parseRequestData(data),
		mech = this._mechanisms[req.mechanism];

	if (req && this._validateRequest(req, mech)) {
		debug('Authentication request: %s', JSON.stringify(req, true, 2));

		if (mech) {
			mech.authenticate(req, function(err, user, message, metadata) {
				var statusCode, userId, meta;

				if (err) {
					statusCode = 500;
					if (!message) message = err.message;
				} else if (user) {
					statusCode = 200;
					userId = (user !== true) ? user : undefined;
					meta = metadata;
				} else {
					statusCode = 400;
				}

				var res = this._generateResponse(req, returnPath, statusCode, message, userId, meta);
				callback(err, res);
			}.bind(this));
		} else {
			var res = this._generateResponse(req, returnPath, 500, "NO AUTHENTICATOR");
			callback(new Error("No Authenticator"), res);
		}
	} else {
		var res = this._generateResponse(req, returnPath, 300, "INVALID MESSAGE");
		callback(new Error("Invalid Message"), res);
	}
}

module.exports = ZAP;
<commit_msg>Don't crash on empty message<commit_after>var debug = require('debug')('zmq-zap:zap'),
	net = require('net');

var ZAP = function(options) {
	this._options = options || {};
	this._mechanisms = {};
}

ZAP.prototype._captureReturnPath = function(data) {
	var returnPath = [];

	var frame = data.shift();
	if (frame) {
		while (frame && (frame.length != 0)) {
			returnPath.push(frame);
			frame = data.shift();
		}
		returnPath.push(frame);
	}

	return returnPath;
}

ZAP.prototype._parseRequestData = function(data) {
	var req;
	if (data && (data.length >= 6)) {
		req = {
			version: new Buffer(data.shift()).toString('utf8'),
			requestId: new Buffer(data.shift()).toString('utf8'),
			domain: new Buffer(data.shift()).toString('utf8'),
			address: new Buffer(data.shift()).toString('utf8'),
			identity: new Buffer(data.shift()).toString('utf8'),
			mechanism: new Buffer(data.shift()).toString('utf8'),
			credentials: data.slice(0)
		};
	}
	return req;
}

ZAP.prototype._validateRequest = function(req, mech) {
	if (req.version != "1.0") return false;
	if (!req.requestId) return false;
	if (!net.isIP(req.address)) return false;

	if (mech && (typeof mech.validate === 'function')) return mech.validate(req);
	else return true;
}

ZAP.prototype._generateResponse = function(req, returnPath, statusCode, message, userId, metadata) {
	if (!message && statusCode) {
		switch (statusCode) {
			case 200:
				message = "OK";
				break;
			case 300:
				message = "ERR";
				break;
			case 400:
				message = "AUTHFAIL";
				break;
			case 500:
				message = "INTERR";
				break;
		}
	}

	if (req && req.version && req.requestId && returnPath && statusCode) {
		debug('Authentication response: %s', req ? JSON.stringify({
			version: req.version,
			requestId: req.requestId,
			statusCode: statusCode,
			message: message,
			userId: userId,
			metadata: metadata
		}, true, 2) : 'undefined');

		return (returnPath || []).splice(0).concat([
			new Buffer(req.version, 'utf8'),
			new Buffer(req.requestId, 'utf8'),
			new Buffer(statusCode.toString(), 'utf8'),
			(message ? new Buffer(message, 'utf8') : new Buffer(0)),
			(((statusCode == 200) && userId) ? new Buffer(userId, 'utf8') : new Buffer(0)),
			(((statusCode == 200) && (metadata)) ? metadata : new Buffer(0))
		]);
	} else {
		return;
	}
}

ZAP.prototype.use = function(mechanism) {
	if (!mechanism) throw new Error('Invalid mechanism');
	if ((typeof mechanism.name !== 'string') || !mechanism.name.length) throw new Error("Invalid mechanism name");
	if (typeof mechanism.authenticate !== 'function') throw new Error('Invalid mechanism authenticate handler');

	this._mechanisms[mechanism.name] = mechanism;
}

ZAP.prototype.authenticate = function(data, callback) {
	if (!data) {
		callback(new Error("Invalid Message"));
		return;
	}

	var data = Array.prototype.slice.call(data); // This forces data to be an array (for when 'arguments' is passed in directly)

	var returnPath = this._captureReturnPath(data),
		req = this._parseRequestData(data),
		mech;

	if (req) mech = this._mechanisms[req.mechanism];

	if (req && this._validateRequest(req, mech)) {
		debug('Authentication request: %s', JSON.stringify(req, true, 2));

		if (mech) {
			mech.authenticate(req, function(err, user, message, metadata) {
				var statusCode, userId, meta;

				if (err) {
					statusCode = 500;
					if (!message) message = err.message;
				} else if (user) {
					statusCode = 200;
					userId = (user !== true) ? user : undefined;
					meta = metadata;
				} else {
					statusCode = 400;
				}

				var res = this._generateResponse(req, returnPath, statusCode, message, userId, meta);
				callback(err, res);
			}.bind(this));
		} else {
			var res = this._generateResponse(req, returnPath, 500, "NO AUTHENTICATOR");
			callback(new Error("No Authenticator"), res);
		}
	} else {
		var res = this._generateResponse(req, returnPath, 300, "INVALID MESSAGE");
		callback(new Error("Invalid Message"), res);
	}
}

module.exports = ZAP;
<|endoftext|>


<commit_before>(function () {
    "use strict";

    var Frame = function (elem) {
        if (typeof elem === 'string') {
            elem = document.getElementById(elem);
        }

        var height = elem.scrollHeight;
        var width = elem.scrollWidth;

        var viewAngle = 45;
        var aspect = width / (1.0 * height);
        var near = 0.1;
        var far = 10000;

        this.scene = new THREE.Scene();

        this.camera = new THREE.PerspectiveCamera(viewAngle, aspect, near, far);
        this.camera.position.z = 10;
        this.scene.add(this.camera);

        this.renderer = new THREE.WebGLRenderer();
        this.renderer.setSize(width, height);

        elem.appendChild(this.renderer.domElement);
    };
    window.Frame = Frame;

    Frame.prototype.addNode = function (node) {
        this.scene.add(node.mesh);
    };

    Frame.prototype.render = function () {
        this.renderer.render(this.scene, this.camera);
    };

    var Node = function (x, y, z) {
        var geometry = new THREE.SphereGeometry(1, 4, 4);
        var material = new THREE.MeshBasicMaterial({color: 0x00ff00, wireframe: true});
        this.mesh = new THREE.Mesh(geometry, material);
        this.mesh.position = {x: x, y: y, z: z};
    };
    window.Node = Node;

    Node.prototype.addTo = function (frame) {
        frame.addNode(this);
    };
}());
<commit_msg>Prefix everything by arbitrary letter G<commit_after>window.G = (function () {
    "use strict";

    var Frame = function (elem) {
        if (typeof elem === 'string') {
            elem = document.getElementById(elem);
        }

        var height = elem.scrollHeight;
        var width = elem.scrollWidth;

        var viewAngle = 45;
        var aspect = width / (1.0 * height);
        var near = 0.1;
        var far = 10000;

        this.scene = new THREE.Scene();

        this.camera = new THREE.PerspectiveCamera(viewAngle, aspect, near, far);
        this.camera.position.z = 10;
        this.scene.add(this.camera);

        this.renderer = new THREE.WebGLRenderer();
        this.renderer.setSize(width, height);

        elem.appendChild(this.renderer.domElement);
    };

    Frame.prototype.addNode = function (node) {
        this.scene.add(node.mesh);
    };

    Frame.prototype.render = function () {
        this.renderer.render(this.scene, this.camera);
    };

    var Node = function (x, y, z) {
        var geometry = new THREE.SphereGeometry(1, 4, 4);
        var material = new THREE.MeshBasicMaterial({color: 0x00ff00, wireframe: true});
        this.mesh = new THREE.Mesh(geometry, material);
        this.mesh.position = {x: x, y: y, z: z};
    };

    Node.prototype.addTo = function (frame) {
        frame.addNode(this);
    };

    return {
        "Frame": Frame,
        "Node": Node,
    };
}());
<|endoftext|>


<commit_before>
// dependencies
var fs = require('fs');
var config = require('./config');

// obtains persisted library object, if exists
var library = fs.existsSync(config.libraryFile)
	? JSON.parse(fs.readFileSync(config.libraryFile))
	: {};

/**
 * Persists library object
 * @param  {object} library Library object
 */
function save(library){
	fs.writeFile(config.libraryFile, JSON.stringify(library));
	}

/**
 * Adds song to library
 * @param {string} tempFile Temporary filepath
 * @param {string} tempName Original filename
 */
exports.add = function addSong(tempFile, tempName){
	var name = unescape(tempName);
	var time = new Date().getTime();
	var ext = name.split('.').pop();
	var newPath = config.libraryDir+'/'+time+'.'+ext;
	fs.rename(tempFile, newPath, function RenamingNewSongFile(err){
		if(err)
			{
			fs.unlink(tempFile);
			console.log('x '+name);
			}
		else
			{
			library[time] = {
				"name":name,
				"time":time
				};
			save(library);
			console.log('+ '+name);
			}
		});
	};<commit_msg>Better code comments<commit_after>
// dependencies
var fs = require('fs');
var config = require('./config');

// obtains persisted library object, if exists
var library = fs.existsSync(config.libraryFile)
	? JSON.parse(fs.readFileSync(config.libraryFile))
	: {};

/**
 * Persists library object
 * @param  {object} library Library object
 */
function save(library){
	fs.writeFile(config.libraryFile, JSON.stringify(library));
	}

/**
 * Adds song to library
 * @param {string} tempFile Temporary filepath
 * @param {string} tempName Original filename
 */
exports.add = function addSong(tempFile, tempName){

	// prepare item info
	var name = unescape(tempName);
	var time = new Date().getTime();
	var ext = name.split('.').pop();
	var newPath = config.libraryDir+'/'+time+'.'+ext;

	// move temp file to library dir
	fs.rename(tempFile, newPath, function RenamingNewSongFile(err){

		// if an error happened while moving, remove temporary file
		if(err)
			{
			fs.unlink(tempFile);
			console.log('x '+name);
			}

		// if moved, update and persists library json
		else
			{
			library[time] = {
				"name":name,
				"time":time
				};
			save(library);
			console.log('+ '+name);
			}
		});
	};<|endoftext|>


<commit_before>(function(module) {
	"use strict";

	var user = module.parent.require('./user'),
		db = module.parent.require('../src/database'),
		passport = module.parent.require('passport'),
  		passportFacebook = require('passport-facebook').Strategy,
  		fs = module.parent.require('fs'),
  		path = module.parent.require('path'),
  		nconf = module.parent.require('nconf');

	var constants = Object.freeze({
		'name': "Facebook",
		'admin': {
			'route': '/facebook',
			'icon': 'fa-facebook-square'
		}
	});

	var Facebook = {};

	Facebook.getStrategy = function(strategies) {
		if (meta.config['social:facebook:app_id'] && meta.config['social:facebook:secret']) {
			passport.use(new passportFacebook({
				clientID: meta.config['social:facebook:app_id'],
				clientSecret: meta.config['social:facebook:secret'],
				callbackURL: nconf.get('url') + '/auth/facebook/callback'
			}, function(accessToken, refreshToken, profile, done) {
				Facebook.login(profile.id, profile.displayName, profile.emails[0].value, function(err, user) {
					if (err) {
						return done(err);
					}
					done(null, user);
				});
			}));

			strategies.push({
				name: 'facebook',
				url: '/auth/facebook',
				callbackURL: '/auth/facebook/callback',
				icon: 'facebook',
				scope: 'email'
			});
		}

		return strategies;
	};

	Facebook.login = function(fbid, name, email, callback) {
		Facebook.getUidByFbid(fbid, function(err, uid) {
			if(err) {
				return callback(err);
			}

			if (uid !== null) {
				// Existing User
				callback(null, {
					uid: uid
				});
			} else {
				// New User
				var success = function(uid) {
					// Save facebook-specific information to the user
					user.setUserField(uid, 'fbid', fbid);
					db.setObjectField('fbid:uid', fbid, uid);
					callback(null, {
						uid: uid
					});
				};

				user.getUidByEmail(email, function(err, uid) {
					if(err) {
						return callback(err);
					}

					if (!uid) {
						user.create({username: name, email: email}, function(err, uid) {
							if(err) {
								return callback(err);
							}

							success(uid);
						});
					} else {
						success(uid); // Existing account -- merge
					}
				});
			}
		});
	}

	Facebook.getUidByFbid = function(fbid, callback) {
		db.getObjectField('fbid:uid', fbid, function(err, uid) {
			if (err) {
				return callback(err);
			}
			callback(null, uid);
		});
	};

	Facebook.addMenuItem = function(custom_header) {
		custom_header.authentication.push({
			"route": constants.admin.route,
			"icon": constants.admin.icon,
			"name": constants.name
		});

		return custom_header;
	}

	Facebook.addAdminRoute = function(custom_routes, callback) {
		fs.readFile(path.resolve(__dirname, './static/admin.tpl'), function (err, template) {
			custom_routes.routes.push({
				"route": constants.admin.route,
				"method": "get",
				"options": function(req, res, callback) {
					callback({
						req: req,
						res: res,
						route: constants.admin.route,
						name: constants.name,
						content: template
					});
				}
			});

			callback(null, custom_routes);
		});
	};

	module.exports = Facebook;
}(module));<commit_msg>fixed missing meta require<commit_after>(function(module) {
	"use strict";

	var user = module.parent.require('./user'),
		meta = module.parent.require('./meta'),
		db = module.parent.require('../src/database'),
		passport = module.parent.require('passport'),
  		passportFacebook = require('passport-facebook').Strategy,
  		fs = module.parent.require('fs'),
  		path = module.parent.require('path'),
  		nconf = module.parent.require('nconf');

	var constants = Object.freeze({
		'name': "Facebook",
		'admin': {
			'route': '/facebook',
			'icon': 'fa-facebook-square'
		}
	});

	var Facebook = {};

	Facebook.getStrategy = function(strategies) {
		if (meta.config['social:facebook:app_id'] && meta.config['social:facebook:secret']) {
			passport.use(new passportFacebook({
				clientID: meta.config['social:facebook:app_id'],
				clientSecret: meta.config['social:facebook:secret'],
				callbackURL: nconf.get('url') + '/auth/facebook/callback'
			}, function(accessToken, refreshToken, profile, done) {
				Facebook.login(profile.id, profile.displayName, profile.emails[0].value, function(err, user) {
					if (err) {
						return done(err);
					}
					done(null, user);
				});
			}));

			strategies.push({
				name: 'facebook',
				url: '/auth/facebook',
				callbackURL: '/auth/facebook/callback',
				icon: 'facebook',
				scope: 'email'
			});
		}

		return strategies;
	};

	Facebook.login = function(fbid, name, email, callback) {
		Facebook.getUidByFbid(fbid, function(err, uid) {
			if(err) {
				return callback(err);
			}

			if (uid !== null) {
				// Existing User
				callback(null, {
					uid: uid
				});
			} else {
				// New User
				var success = function(uid) {
					// Save facebook-specific information to the user
					user.setUserField(uid, 'fbid', fbid);
					db.setObjectField('fbid:uid', fbid, uid);
					callback(null, {
						uid: uid
					});
				};

				user.getUidByEmail(email, function(err, uid) {
					if(err) {
						return callback(err);
					}

					if (!uid) {
						user.create({username: name, email: email}, function(err, uid) {
							if(err) {
								return callback(err);
							}

							success(uid);
						});
					} else {
						success(uid); // Existing account -- merge
					}
				});
			}
		});
	}

	Facebook.getUidByFbid = function(fbid, callback) {
		db.getObjectField('fbid:uid', fbid, function(err, uid) {
			if (err) {
				return callback(err);
			}
			callback(null, uid);
		});
	};

	Facebook.addMenuItem = function(custom_header) {
		custom_header.authentication.push({
			"route": constants.admin.route,
			"icon": constants.admin.icon,
			"name": constants.name
		});

		return custom_header;
	}

	Facebook.addAdminRoute = function(custom_routes, callback) {
		fs.readFile(path.resolve(__dirname, './static/admin.tpl'), function (err, template) {
			custom_routes.routes.push({
				"route": constants.admin.route,
				"method": "get",
				"options": function(req, res, callback) {
					callback({
						req: req,
						res: res,
						route: constants.admin.route,
						name: constants.name,
						content: template
					});
				}
			});

			callback(null, custom_routes);
		});
	};

	module.exports = Facebook;
}(module));<|endoftext|>


<commit_before>"use strict";

var controllers = require('./lib/controllers');
var meta = module.parent.require('./meta');
var request = module.parent.require('request');
var async = module.parent.require('async');
var nconf = module.parent.require('nconf');
var winston = module.parent.require('winston');
var validator = module.parent.require('validator');
var meta = require.main.require('./src/meta');

var postCache = module.parent.require('./posts/cache');
var LRU = require('lru-cache');
var url = require('url');
var moment = require('moment');
var crypto = require('crypto');

var iframely = {
	config: undefined,
	apiBase: 'http://iframe.ly/api/iframely?origin=nodebb&align=left',
	cache: LRU({
		maxAge: 1000*60*60*24	// one day
	}),
	htmlRegex: /(.*?)<\/a>/g
};
var app;

iframely.init = function(params, callback) {
	var router = params.router,
		hostMiddleware = params.middleware;

	app = params.app;

	router.get('/admin/plugins/iframely', hostMiddleware.admin.buildHeader, controllers.renderAdminPage);
	router.get('/api/admin/plugins/iframely', controllers.renderAdminPage);

	meta.settings.get('iframely', function(err, config) {

		config.blacklist = (config.blacklist && config.blacklist.split(',')) || [];

		iframely.config = config;
	});

	callback();
};

iframely.updateConfig = function(data) {
	if (data.plugin === 'iframely') {
		winston.verbose('[plugin/iframely] Config updated');
		postCache.reset();
		data.settings.blacklist = data.settings.blacklist.split(',');
		iframely.config = data.settings;
	}
};

iframely.addAdminNavigation = function(header, callback) {
	header.plugins.push({
		route: '/plugins/iframely',
		icon: 'fa-link',
		name: 'Iframely'
	});

	callback(null, header);
};

iframely.replace = function(raw, options, callback) {

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

	if (raw && typeof raw !== 'string' && raw.hasOwnProperty('postData') && raw.postData.hasOwnProperty('content')) {

		/**
		 *	If a post object is received (`filter:post.parse`),
		 *	instead of a plain string, call self.
		 */

		iframely.replace(raw.postData.content, {
			votes: parseInt(raw.postData.votes),
			isPost: true
		}, function(err, html) {
			raw.postData.content = html;
			return callback(err, raw);
		});

	} else {

		// Skip parsing post with negative votes.
		if (options && options.isPost) {
			var votes = (options && typeof options.votes === 'number') ? options.votes : 0;
			if (votes < getIntValue(iframely.config.doNoteParseIfVotesLessThen, -10)) {
				return callback(null, raw);
			}
		}

		var urls = [],
			urlsDict = {},
			match;

		// Isolate matches
		while(match = iframely.htmlRegex.exec(raw)) {
			// Only match if it is a naked link (no anchor text)

			var target = url.parse(match[1]);

			if ((
				(match[1] === match[2]) ||
				(match[1] === encodeURI(match[2])) ||
				(target.host + target.path === match[2])

				) && !hostInBlacklist(target.host)) {

				var uri = match[1];

				// Eliminate duplicates and internal links
				if (!(uri in urlsDict) && uri.indexOf(nconf.get('url')) !== 0) {
					urls.push({
						match: match[0],
						url: uri
					});
				}
			}
		}

		async.waterfall([

			// Query urls from Iframely, in batches of 10
			async.apply(async.mapLimit, urls, 10, iframely.query),

			function(embeds, next) {

				async.reduce(embeds.filter(Boolean), raw, function(html, data, next) {

					var embed = data.embed;
					var match = data.match;
					var url = data.url;
					var embedHtml = embed.html;

					var generateCard = false;

					if (!embedHtml) {
						var image = getImage(embed);
						if (image) {
							// Generate own card with thumbnail.
							generateCard = true;
							embedHtml = '';
						} else {
							// No embed code. Show link with title only.
							app.render('partials/iframely-link-title', {
								title: embed.meta.title || url
								embed: embed,
								url: url
							}, function (err, parsed) {

								if (err) {
									winston.error('[plugin/iframely] Could not parse embed: ' + err.message);
									return next(null, html);
								}

								next(null, html.replace(match, parsed));
							});
							return;
						}
					}

					// Format meta info.
					var meta = [];

					if (generateCard) {
						if (embed.meta.author) {
							meta.push(embed.meta.author);
						}

						var date = getDate(embed.meta.date);
						if (date) {
							meta.push(date);
						}

						var currency = embed.meta.currency_code || embed.meta.currency;
						var price = embed.meta.price ? (embed.meta.price + (currency ? (' ' + currency) : '')) : null;
						if (price) {
							meta.push(price);
						}

						var duration = getDuration(embed.meta.duration);
						if (duration) {
							meta.push(duration);
						}

						var views = getViews(embed.meta.views);
						if (views) {
							meta.push(views);
						}

						if (embed.meta.category) {
							meta.push(embed.meta.category);
						}
					}

					// END Format meta info.

					var context = {
						show_title: false,
						domain: getDomain(embed),
						title: validator.escape(shortenText(embed.meta.title, 200)),
						description: validator.escape(shortenText(embed.meta.description, 300)),
						favicon: wrapImage(embed.links.icon && embed.links.icon[0].href) || false,
						embed: embed,
						url: url,
						metaString: meta.length ? meta.join('  /  ') : false,
						embedHtml: wrapHtmlImages(embedHtml)
					};

					if (context.title && embed.rel.indexOf('player') > -1 && embed.rel.indexOf('gifv') === -1) {
						context.show_title = true;
					}

					if (embed.rel.indexOf('file') > -1 && embed.rel.indexOf('reader') > -1) {
						context.title = embed.meta.canonical;
						context.show_title = true;
					}

					function renderWidgetWrapper(err, embed_widget) {

						if (err) {
							winston.error('[plugin/iframely] Could not parse embed: ' + err.message);
							return next(null, html);
						}

						context.widget_html = embed_widget;

						app.render('partials/iframely-widget-wrapper', context, function (err, parsed) {
							if (err) {
								winston.error('[plugin/iframely] Could not parse embed! ' + err.message);
								return next(null, html);
							}

							next(null, html.replace(match, parsed));
						});
					}

					if (generateCard) {
						app.render('partials/iframely-widget-card', context, renderWidgetWrapper);
					} else {
						renderWidgetWrapper(null, context.embedHtml);
					}

				}, next);
			}

		], function(error, html) {

			if (error) {
				winston.error('[plugin/iframely] Could not parse embed! ' + err.message);
			}

			callback(null, html);
		});
	}
};

iframely.query = function(data, callback) {
	if (iframely.cache.has(data.url)) {
		winston.verbose('[plugin/iframely] Retrieving \'' + data.url + '\' from cache...');
		setImmediate(function() {
			try {
				callback(null, {
					url: data.url,
					match: data.match,
					embed: iframely.cache.get(data.url)
				});
			} catch(ex) {
				winston.error('[plugin/iframely] Could not parse embed! ' + ex);
				callback();
			}
		});
	} else {
		winston.verbose('[plugin/iframely] Querying \'' + data.url + '\' via Iframely...')

		if (iframely.config.endpoint) {

			var custom_endpoint = /^https?:\/\//i.test(iframely.config.endpoint);

			var iframelyAPI = custom_endpoint ? iframely.config.endpoint : iframely['apiBase'] + '&api_key=' + iframely.config.endpoint;
			iframelyAPI += (iframelyAPI.indexOf('?') > -1 ? '&' : '?') + 'url=' + encodeURIComponent(data.url);

			if (custom_endpoint) {
				iframelyAPI += '&group=true';
			}

			request({
				url: iframelyAPI,
				json: true
			}, function(err, res, body) {
				if (err) {
					winston.error('[plugin/iframely] Encountered error querying Iframely API: ' + err.message);
					return callback();
				} else {
					if (res.statusCode === 200 && body) {
						iframely.cache.set(data.url, body);
						try {
							callback(null, {
								url: data.url,
								match: data.match,
								embed: body
							});
						} catch(ex) {
							winston.error('[plugin/iframely] Could not parse embed! ' + ex);
							callback();
						}
					} else {
						callback();
					}
				}
			});
		} else {
			winston.error('[plugin/iframely] No API key or endpoint configured, skipping Iframely');
			callback();
		}
	}
};

function hostInBlacklist(host) {
	return iframely.config.blacklist && iframely.config.blacklist.indexOf(host) > -1;
}

function wrapHtmlImages(html) {

	if (html && iframely.config.camoProxyKey && iframely.config.camoProxyHost) {
		return html.replace(/]+src=["'][^'"]+["']/gi, function(item) {
			var m = item.match(/(]+src=["'])([^'"]+)(["'])/i);
			var url = wrapImage(m[2]);
			return m[1] + url + m[3];
		});

	} else {
		return html;
	}
}

function wrapImage(url) {

	if (url && iframely.config.camoProxyKey && iframely.config.camoProxyHost && url.indexOf(iframely.config.camoProxyHost) === -1) {

		var hexDigest, hexEncodedPath;

		hexDigest = crypto.createHmac('sha1', iframely.config.camoProxyKey).update(url).digest('hex');
		hexEncodedPath = (new Buffer(url)).toString('hex');

		return [
			iframely.config.camoProxyHost.replace(/\/$/, ''),	// Remove tail '/'
			hexDigest,
			hexEncodedPath
		].join('/');

	} else {
		return url;
	}
}

function getIntValue(value, defaultValue) {
	value = parseInt(value);
	if (typeof value === 'number' && !isNaN(value)) {
		return value;
	} else {
		return defaultValue;
	}
}

function shortenText(value, maxlength) {

	if (!value) {
		return '';
	}

	maxlength = maxlength || 130;

	value = '' + value;

	if (value.length <= maxlength) {
		return value;
	} else {

		value = value.substr(0, maxlength);

		var m = value.match(/(.*)[\. ,\/-]/);

		if (m) {
			value = m[1]
			return m[1] + '...';
		}

		return value + '...';
	}
}

function getDuration(duration) {
	if (duration) {
		var seconds = duration % 60;
		var minutes = Math.floor((duration - seconds) / 60);
		var hours = Math.floor(minutes / 60);
		minutes = minutes % 60;

		if (seconds < 10) {
			seconds = '0' + seconds;
		}

		if (minutes < 10) {
			minutes = '0' + minutes;
		}

		return (hours ? (hours + ':') : '') + minutes + ':' + seconds;
	}
}

function numberWithCommas(x) {
	var parts = x.toString().split(".");
	parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, "'");
	return parts.join(".");
}

function getViews(views) {
	if (views) {
		if (views > 1000000) {
			return numberWithCommas((views / 1000000).toFixed(1)) + 'Mln';
		} else if (views > 1000) {
			return numberWithCommas((views / 1000).toFixed(1)) + 'K';
		} else {
			return numberWithCommas(views);
		}
	}
}

function getDomain(embed) {
	var domain = embed.meta.site;
	if (!domain) {
		var url = embed.meta.canonical;
		var m = url.match(/(?:https?:\/\/)?(?:www\.)?([^\/]+)/i);
		if (m) {
			domain = m[1];
		} else {
			domain = url;
		}
	}
	return domain;
}

function getDate(date) {

	var onDate = '';
	if (date) {
		date = new Date(date);
		if (date && !isNaN(date.getTime())) {

			var language = meta.config.defaultLang || 'en_GB';

			onDate = moment(date).locale(language).format('MMM D');

			if (date.getFullYear() !== new Date().getFullYear()) {
				onDate = onDate + ', ' + date.getFullYear();
			}
		}
	}

	return onDate;
}

function getImage(embed) {
	var image = (embed.links.thumbnail && embed.links.thumbnail[0]) || (embed.links.image && embed.links.image[0]);
	return image && image.href;
}

module.exports = iframely;
<commit_msg>missed comma<commit_after>"use strict";

var controllers = require('./lib/controllers');
var meta = module.parent.require('./meta');
var request = module.parent.require('request');
var async = module.parent.require('async');
var nconf = module.parent.require('nconf');
var winston = module.parent.require('winston');
var validator = module.parent.require('validator');
var meta = require.main.require('./src/meta');

var postCache = module.parent.require('./posts/cache');
var LRU = require('lru-cache');
var url = require('url');
var moment = require('moment');
var crypto = require('crypto');

var iframely = {
	config: undefined,
	apiBase: 'http://iframe.ly/api/iframely?origin=nodebb&align=left',
	cache: LRU({
		maxAge: 1000*60*60*24	// one day
	}),
	htmlRegex: /(.*?)<\/a>/g
};
var app;

iframely.init = function(params, callback) {
	var router = params.router,
		hostMiddleware = params.middleware;

	app = params.app;

	router.get('/admin/plugins/iframely', hostMiddleware.admin.buildHeader, controllers.renderAdminPage);
	router.get('/api/admin/plugins/iframely', controllers.renderAdminPage);

	meta.settings.get('iframely', function(err, config) {

		config.blacklist = (config.blacklist && config.blacklist.split(',')) || [];

		iframely.config = config;
	});

	callback();
};

iframely.updateConfig = function(data) {
	if (data.plugin === 'iframely') {
		winston.verbose('[plugin/iframely] Config updated');
		postCache.reset();
		data.settings.blacklist = data.settings.blacklist.split(',');
		iframely.config = data.settings;
	}
};

iframely.addAdminNavigation = function(header, callback) {
	header.plugins.push({
		route: '/plugins/iframely',
		icon: 'fa-link',
		name: 'Iframely'
	});

	callback(null, header);
};

iframely.replace = function(raw, options, callback) {

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

	if (raw && typeof raw !== 'string' && raw.hasOwnProperty('postData') && raw.postData.hasOwnProperty('content')) {

		/**
		 *	If a post object is received (`filter:post.parse`),
		 *	instead of a plain string, call self.
		 */

		iframely.replace(raw.postData.content, {
			votes: parseInt(raw.postData.votes),
			isPost: true
		}, function(err, html) {
			raw.postData.content = html;
			return callback(err, raw);
		});

	} else {

		// Skip parsing post with negative votes.
		if (options && options.isPost) {
			var votes = (options && typeof options.votes === 'number') ? options.votes : 0;
			if (votes < getIntValue(iframely.config.doNoteParseIfVotesLessThen, -10)) {
				return callback(null, raw);
			}
		}

		var urls = [],
			urlsDict = {},
			match;

		// Isolate matches
		while(match = iframely.htmlRegex.exec(raw)) {
			// Only match if it is a naked link (no anchor text)

			var target = url.parse(match[1]);

			if ((
				(match[1] === match[2]) ||
				(match[1] === encodeURI(match[2])) ||
				(target.host + target.path === match[2])

				) && !hostInBlacklist(target.host)) {

				var uri = match[1];

				// Eliminate duplicates and internal links
				if (!(uri in urlsDict) && uri.indexOf(nconf.get('url')) !== 0) {
					urls.push({
						match: match[0],
						url: uri
					});
				}
			}
		}

		async.waterfall([

			// Query urls from Iframely, in batches of 10
			async.apply(async.mapLimit, urls, 10, iframely.query),

			function(embeds, next) {

				async.reduce(embeds.filter(Boolean), raw, function(html, data, next) {

					var embed = data.embed;
					var match = data.match;
					var url = data.url;
					var embedHtml = embed.html;

					var generateCard = false;

					if (!embedHtml) {
						var image = getImage(embed);
						if (image) {
							// Generate own card with thumbnail.
							generateCard = true;
							embedHtml = '';
						} else {
							// No embed code. Show link with title only.
							app.render('partials/iframely-link-title', {
								title: embed.meta.title || url,
								embed: embed,
								url: url
							}, function (err, parsed) {

								if (err) {
									winston.error('[plugin/iframely] Could not parse embed: ' + err.message);
									return next(null, html);
								}

								next(null, html.replace(match, parsed));
							});
							return;
						}
					}

					// Format meta info.
					var meta = [];

					if (generateCard) {
						if (embed.meta.author) {
							meta.push(embed.meta.author);
						}

						var date = getDate(embed.meta.date);
						if (date) {
							meta.push(date);
						}

						var currency = embed.meta.currency_code || embed.meta.currency;
						var price = embed.meta.price ? (embed.meta.price + (currency ? (' ' + currency) : '')) : null;
						if (price) {
							meta.push(price);
						}

						var duration = getDuration(embed.meta.duration);
						if (duration) {
							meta.push(duration);
						}

						var views = getViews(embed.meta.views);
						if (views) {
							meta.push(views);
						}

						if (embed.meta.category) {
							meta.push(embed.meta.category);
						}
					}

					// END Format meta info.

					var context = {
						show_title: false,
						domain: getDomain(embed),
						title: validator.escape(shortenText(embed.meta.title, 200)),
						description: validator.escape(shortenText(embed.meta.description, 300)),
						favicon: wrapImage(embed.links.icon && embed.links.icon[0].href) || false,
						embed: embed,
						url: url,
						metaString: meta.length ? meta.join('  /  ') : false,
						embedHtml: wrapHtmlImages(embedHtml)
					};

					if (context.title && embed.rel.indexOf('player') > -1 && embed.rel.indexOf('gifv') === -1) {
						context.show_title = true;
					}

					if (embed.rel.indexOf('file') > -1 && embed.rel.indexOf('reader') > -1) {
						context.title = embed.meta.canonical;
						context.show_title = true;
					}

					function renderWidgetWrapper(err, embed_widget) {

						if (err) {
							winston.error('[plugin/iframely] Could not parse embed: ' + err.message);
							return next(null, html);
						}

						context.widget_html = embed_widget;

						app.render('partials/iframely-widget-wrapper', context, function (err, parsed) {
							if (err) {
								winston.error('[plugin/iframely] Could not parse embed! ' + err.message);
								return next(null, html);
							}

							next(null, html.replace(match, parsed));
						});
					}

					if (generateCard) {
						app.render('partials/iframely-widget-card', context, renderWidgetWrapper);
					} else {
						renderWidgetWrapper(null, context.embedHtml);
					}

				}, next);
			}

		], function(error, html) {

			if (error) {
				winston.error('[plugin/iframely] Could not parse embed! ' + err.message);
			}

			callback(null, html);
		});
	}
};

iframely.query = function(data, callback) {
	if (iframely.cache.has(data.url)) {
		winston.verbose('[plugin/iframely] Retrieving \'' + data.url + '\' from cache...');
		setImmediate(function() {
			try {
				callback(null, {
					url: data.url,
					match: data.match,
					embed: iframely.cache.get(data.url)
				});
			} catch(ex) {
				winston.error('[plugin/iframely] Could not parse embed! ' + ex);
				callback();
			}
		});
	} else {
		winston.verbose('[plugin/iframely] Querying \'' + data.url + '\' via Iframely...')

		if (iframely.config.endpoint) {

			var custom_endpoint = /^https?:\/\//i.test(iframely.config.endpoint);

			var iframelyAPI = custom_endpoint ? iframely.config.endpoint : iframely['apiBase'] + '&api_key=' + iframely.config.endpoint;
			iframelyAPI += (iframelyAPI.indexOf('?') > -1 ? '&' : '?') + 'url=' + encodeURIComponent(data.url);

			if (custom_endpoint) {
				iframelyAPI += '&group=true';
			}

			request({
				url: iframelyAPI,
				json: true
			}, function(err, res, body) {
				if (err) {
					winston.error('[plugin/iframely] Encountered error querying Iframely API: ' + err.message);
					return callback();
				} else {
					if (res.statusCode === 200 && body) {
						iframely.cache.set(data.url, body);
						try {
							callback(null, {
								url: data.url,
								match: data.match,
								embed: body
							});
						} catch(ex) {
							winston.error('[plugin/iframely] Could not parse embed! ' + ex);
							callback();
						}
					} else {
						callback();
					}
				}
			});
		} else {
			winston.error('[plugin/iframely] No API key or endpoint configured, skipping Iframely');
			callback();
		}
	}
};

function hostInBlacklist(host) {
	return iframely.config.blacklist && iframely.config.blacklist.indexOf(host) > -1;
}

function wrapHtmlImages(html) {

	if (html && iframely.config.camoProxyKey && iframely.config.camoProxyHost) {
		return html.replace(/]+src=["'][^'"]+["']/gi, function(item) {
			var m = item.match(/(]+src=["'])([^'"]+)(["'])/i);
			var url = wrapImage(m[2]);
			return m[1] + url + m[3];
		});

	} else {
		return html;
	}
}

function wrapImage(url) {

	if (url && iframely.config.camoProxyKey && iframely.config.camoProxyHost && url.indexOf(iframely.config.camoProxyHost) === -1) {

		var hexDigest, hexEncodedPath;

		hexDigest = crypto.createHmac('sha1', iframely.config.camoProxyKey).update(url).digest('hex');
		hexEncodedPath = (new Buffer(url)).toString('hex');

		return [
			iframely.config.camoProxyHost.replace(/\/$/, ''),	// Remove tail '/'
			hexDigest,
			hexEncodedPath
		].join('/');

	} else {
		return url;
	}
}

function getIntValue(value, defaultValue) {
	value = parseInt(value);
	if (typeof value === 'number' && !isNaN(value)) {
		return value;
	} else {
		return defaultValue;
	}
}

function shortenText(value, maxlength) {

	if (!value) {
		return '';
	}

	maxlength = maxlength || 130;

	value = '' + value;

	if (value.length <= maxlength) {
		return value;
	} else {

		value = value.substr(0, maxlength);

		var m = value.match(/(.*)[\. ,\/-]/);

		if (m) {
			value = m[1]
			return m[1] + '...';
		}

		return value + '...';
	}
}

function getDuration(duration) {
	if (duration) {
		var seconds = duration % 60;
		var minutes = Math.floor((duration - seconds) / 60);
		var hours = Math.floor(minutes / 60);
		minutes = minutes % 60;

		if (seconds < 10) {
			seconds = '0' + seconds;
		}

		if (minutes < 10) {
			minutes = '0' + minutes;
		}

		return (hours ? (hours + ':') : '') + minutes + ':' + seconds;
	}
}

function numberWithCommas(x) {
	var parts = x.toString().split(".");
	parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, "'");
	return parts.join(".");
}

function getViews(views) {
	if (views) {
		if (views > 1000000) {
			return numberWithCommas((views / 1000000).toFixed(1)) + 'Mln';
		} else if (views > 1000) {
			return numberWithCommas((views / 1000).toFixed(1)) + 'K';
		} else {
			return numberWithCommas(views);
		}
	}
}

function getDomain(embed) {
	var domain = embed.meta.site;
	if (!domain) {
		var url = embed.meta.canonical;
		var m = url.match(/(?:https?:\/\/)?(?:www\.)?([^\/]+)/i);
		if (m) {
			domain = m[1];
		} else {
			domain = url;
		}
	}
	return domain;
}

function getDate(date) {

	var onDate = '';
	if (date) {
		date = new Date(date);
		if (date && !isNaN(date.getTime())) {

			var language = meta.config.defaultLang || 'en_GB';

			onDate = moment(date).locale(language).format('MMM D');

			if (date.getFullYear() !== new Date().getFullYear()) {
				onDate = onDate + ', ' + date.getFullYear();
			}
		}
	}

	return onDate;
}

function getImage(embed) {
	var image = (embed.links.thumbnail && embed.links.thumbnail[0]) || (embed.links.image && embed.links.image[0]);
	return image && image.href;
}

module.exports = iframely;
<|endoftext|>


<commit_before>'use strict';

var	async = require('async');
var winston = module.parent.require('winston');
var XRegExp = require('xregexp');
var validator = require('validator');
var nconf = module.parent.require('nconf');

var Topics = module.parent.require('./topics');
var User = module.parent.require('./user');
var Groups = module.parent.require('./groups');
var Notifications = module.parent.require('./notifications');
var Privileges = module.parent.require('./privileges');
var Meta = module.parent.require('./meta');
var Utils = module.parent.require('../public/src/utils');
var batch = module.parent.require('./batch');

var SocketPlugins = module.parent.require('./socket.io/plugins');

var regex = XRegExp('(?:>|\\s)(@[\\p{L}\\d\\-_.]+)', 'g');	// used in post text transform, accounts for HTML
var rawRegex = XRegExp('(?:^|\\s)(@[\\p{L}\\d\-_.]+)', 'g');	// used in notifications, as raw text is passed in this hook
var isLatinMention = /@[\w\d\-_.]+$/;
var removePunctuationSuffix = function(string) {
	return string.replace(/[!?.]*$/, '');
};
var Entities = require('html-entities').XmlEntities;
var entities = new Entities();

var Mentions = {
	_settings: {},
	_defaults: {
		autofillGroups: 'off',
	}
};
SocketPlugins.mentions = {};

Mentions.init = function (data, callback) {
	var hostMiddleware = module.parent.require('./middleware');
	var controllers = require('./controllers');

	data.router.get('/admin/plugins/mentions', hostMiddleware.admin.buildHeader, controllers.renderAdminPage);
	data.router.get('/api/admin/plugins/mentions', controllers.renderAdminPage);

	// Retrieve settings
	Meta.settings.get('mentions', function (err, settings) {
		Object.assign(Mentions._settings, Mentions._defaults, settings);
		callback();
	});
};

Mentions.addAdminNavigation = function (header, callback) {
	header.plugins.push({
		route: '/plugins/mentions',
		name: 'Mentions'
	});

	callback(null, header);
};

Mentions.notify = function(data) {
	var postData = data.post;
	var cleanedContent = Mentions.clean(postData.content, true, true, true);
	var matches = cleanedContent.match(rawRegex);

	if (!matches) {
		return;
	}

	var noMentionGroups = ['registered-users', 'guests'];

	matches = matches.map(function(match) {
		return Utils.slugify(match);
	}).filter(function(match, index, array) {
		return match && array.indexOf(match) === index && noMentionGroups.indexOf(match) === -1;
	});

	if (!matches.length) {
		return;
	}

	async.parallel({
		userRecipients: function(next) {
			async.filter(matches, User.existsBySlug, next);
		},
		groupRecipients: function(next) {
			async.filter(matches, Groups.existsBySlug, next);
		}
	}, function(err, results) {
		if (err) {
			return;
		}

		if (!results.userRecipients.length && !results.groupRecipients.length) {
			return;
		}

		async.parallel({
			topic: function(next) {
				Topics.getTopicFields(postData.tid, ['title', 'cid'], next);
			},
			author: function(next) {
				User.getUserField(postData.uid, 'username', next);
			},
			uids: function(next) {
				async.map(results.userRecipients, function(slug, next) {
					User.getUidByUserslug(slug, next);
				}, next);
			},
			groupData: function(next) {
				getGroupMemberUids(results.groupRecipients, next);
			},
			topicFollowers: function(next) {
				Topics.getFollowers(postData.tid, next);
			}
		}, function(err, results) {
			if (err) {
				return;
			}

			var title = entities.decode(results.topic.title);
			var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ',');

			var uids = results.uids.filter(function(uid, index, array) {
				return array.indexOf(uid) === index && parseInt(uid, 10) !== parseInt(postData.uid, 10) && results.topicFollowers.indexOf(uid.toString()) === -1;
			});

			var groupMemberUids = {};
			results.groupData.groupNames.forEach(function(groupName, index) {
				results.groupData.groupMembers[index] = results.groupData.groupMembers[index].filter(function(uid) {
					if (!uid || groupMemberUids[uid]) {
						return false;
					}
					groupMemberUids[uid] = 1;
					return uids.indexOf(uid) === -1 &&
						parseInt(uid, 10) !== parseInt(postData.uid, 10) &&
						results.topicFollowers.indexOf(uid.toString()) === -1;
				});
			});

			sendNotificationToUids(postData, uids, 'user', '[[notifications:user_mentioned_you_in, ' + results.author + ', ' + titleEscaped + ']]');

			results.groupData.groupNames.forEach(function(groupName, index) {
				var memberUids = results.groupData.groupMembers[index];
				sendNotificationToUids(postData, memberUids, groupName, '[[notifications:user_mentioned_group_in, ' + results.author + ', ' + groupName + ', ' + titleEscaped + ']]');
			});
		});
	});
};

Mentions.addFilters = function (data, callback) {
	data.regularFilters.push({ name: '[[notifications:mentions]]', filter: 'mention' });
	callback(null, data);
};

Mentions.notificationTypes = function (data, callback) {
	data.types.push('notificationType_mention');
	callback(null, data);
};

Mentions.filterUserSaveSettings = function (hookData, callback) {
	hookData.settings.notificationType_mention = hookData.data.notificationType_mention;
	callback(null, hookData);
};

function sendNotificationToUids(postData, uids, nidType, notificationText) {
	if (!uids.length) {
		return;
	}

	var filteredUids = [];
	var notification;
	async.waterfall([
		function (next) {
			createNotification(postData, nidType, notificationText, next);
		},
		function (_notification, next) {
			notification = _notification;
			if (!notification) {
				return next();
			}

			batch.processArray(uids, function (uids, next) {
				async.waterfall([
					function(next) {
						Privileges.topics.filterUids('read', postData.tid, uids, next);
					},
					function(_uids, next) {
						Topics.filterIgnoringUids(postData.tid, _uids, next);
					},
					function(_uids, next) {
						if (!_uids.length) {
							return next();
						}

						filteredUids = filteredUids.concat(_uids);

						next();
					}
				], next);
			}, {
				interval: 1000,
				batch: 500,
			}, next);
		},
	], function (err) {
		if (err) {
			return winston.error(err);
		}
		if (notification) {
			Notifications.push(notification, filteredUids);
		}
	});
}

function createNotification(postData, nidType, notificationText, callback) {
	Notifications.create({
		type: 'mention',
		bodyShort: notificationText,
		bodyLong: postData.content,
		nid: 'tid:' + postData.tid + ':pid:' + postData.pid + ':uid:' + postData.uid + ':' + nidType,
		pid: postData.pid,
		tid: postData.tid,
		from: postData.uid,
		path: '/post/' + postData.pid,
		importance: 6
	}, callback);
}

function getGroupMemberUids(groupRecipients, callback) {
	async.map(groupRecipients, function(slug, next) {
		Groups.getGroupNameByGroupSlug(slug, next);
	}, function(err, groupNames) {
		if (err) {
			return callback(err);
		}
		async.map(groupNames, function(groupName, next) {
			Groups.getMembers(groupName, 0, -1, next);
		}, function(err, groupMembers) {
			if (err) {
				return callback(err);
			}
			callback(null, {groupNames: groupNames, groupMembers: groupMembers});
		});
	});
}

Mentions.parsePost = function(data, callback) {
	if (!data || !data.postData || !data.postData.content) {
		return callback(null, data);
	}

	Mentions.parseRaw(data.postData.content, function(err, content) {
		if (err) {
			return callback(err);
		}

		data.postData.content = content;
		callback(null, data);
	});
};

Mentions.parseRaw = function(content, callback) {
	var splitContent = Mentions.split(content, false, false, true);
	var matches = [];
	splitContent.forEach(function(cleanedContent, i) {
		if ((i & 1) === 0) {
			matches = matches.concat(cleanedContent.match(regex) || []);
		}
	});

	if (!matches.length) {
		return callback(null, content);
	}

	matches = matches.filter(function(cur, idx) {
		// Eliminate duplicates
		return idx === matches.indexOf(cur);
	}).map(function(match) {
		/**
		 *	Javascript-favour of regex does not support lookaround,
		 *	so need to clean up the cruft by discarding everthing
		 *	before the @
		 */
		var atIndex = match.indexOf('@');
		return atIndex !== 0 ? match.slice(atIndex) : match;
	});

	async.each(matches, function(match, next) {
		var slug = Utils.slugify(match.slice(1));

		match = removePunctuationSuffix(match);

		async.parallel({
			groupExists: async.apply(Groups.existsBySlug, slug),
			uid: async.apply(User.getUidByUserslug, slug)
		}, function(err, results) {
			if (err) {
				return next(err);
			}

			if (results.uid || results.groupExists) {
				var regex = isLatinMention.test(match)
					? new RegExp('[>|\\s]' + match + '\\b', 'g')
					: new RegExp('[>|\\s]' + match, 'g');

				splitContent = splitContent.map(function(c, i) {
					if ((i & 1) === 1) {
						return c;
					}
					return c.replace(regex, function(match) {
						// Again, cleaning up lookaround leftover bits
						var atIndex = match.indexOf('@');
						var plain = match.slice(0, atIndex);
						match = match.slice(atIndex);
						var str = results.uid
								? '' + match + ''
								: '' + match + '';

						return plain + str;
					});
				});
			}

			next();
		});
	}, function(err) {
		callback(err, splitContent.join(''));
	});
};

Mentions.clean = function(input, isMarkdown, stripBlockquote, stripCode) {
	var split = Mentions.split(input, isMarkdown, stripBlockquote, stripCode);
	split = split.filter(function(e, i) {
		// only keep non-code/non-blockquote
		return (i & 1) === 0;
	});
	return split.join('');
};

Mentions.split = function(input, isMarkdown, splitBlockquote, splitCode) {
	if (!input) {
		return [];
	}

	var matchers = [isMarkdown ? '\\[.*?\\]\\(.*?\\)' : '<a[\\s\\S]*?'];
	if (splitBlockquote) {
		matchers.push(isMarkdown ? '^>.*$' : '^
.*?
');
	}
	if (splitCode) {
		matchers.push(isMarkdown ? '`[^`\n]+`' : '<code[\\s\\S]*?');
	}
	return input.split(new RegExp('(' + matchers.join('|') + ')', 'gm'));
};

/*
	WebSocket methods
*/

SocketPlugins.mentions.listGroups = function(socket, data, callback) {
	if (Mentions._settings.autofillGroups === 'off') {
		return callback(null, []);
	}

	Groups.getGroups('groups:visible:createtime', 0, -1, function(err, groups) {
		if (err) {
			return callback(err);
		}
		groups = groups.filter(Boolean).map(function(groupName) {
			return validator.escape(groupName);
		});
		callback(null, groups);
	});
};

module.exports = Mentions;
<commit_msg>Fix mentions being replaced within HTML attributes<commit_after>'use strict';

var	async = require('async');
var winston = module.parent.require('winston');
var XRegExp = require('xregexp');
var validator = require('validator');
var nconf = module.parent.require('nconf');

var Topics = module.parent.require('./topics');
var User = module.parent.require('./user');
var Groups = module.parent.require('./groups');
var Notifications = module.parent.require('./notifications');
var Privileges = module.parent.require('./privileges');
var Meta = module.parent.require('./meta');
var Utils = module.parent.require('../public/src/utils');
var batch = module.parent.require('./batch');

var SocketPlugins = module.parent.require('./socket.io/plugins');

var regex = XRegExp('(?:>|\\s)(@[\\p{L}\\d\\-_.]+)', 'g');	// used in post text transform, accounts for HTML
var rawRegex = XRegExp('(?:^|\\s)(@[\\p{L}\\d\-_.]+)', 'g');	// used in notifications, as raw text is passed in this hook
var isLatinMention = /@[\w\d\-_.]+$/;
var removePunctuationSuffix = function(string) {
	return string.replace(/[!?.]*$/, '');
};
var Entities = require('html-entities').XmlEntities;
var entities = new Entities();

var Mentions = {
	_settings: {},
	_defaults: {
		autofillGroups: 'off',
	}
};
SocketPlugins.mentions = {};

Mentions.init = function (data, callback) {
	var hostMiddleware = module.parent.require('./middleware');
	var controllers = require('./controllers');

	data.router.get('/admin/plugins/mentions', hostMiddleware.admin.buildHeader, controllers.renderAdminPage);
	data.router.get('/api/admin/plugins/mentions', controllers.renderAdminPage);

	// Retrieve settings
	Meta.settings.get('mentions', function (err, settings) {
		Object.assign(Mentions._settings, Mentions._defaults, settings);
		callback();
	});
};

Mentions.addAdminNavigation = function (header, callback) {
	header.plugins.push({
		route: '/plugins/mentions',
		name: 'Mentions'
	});

	callback(null, header);
};

Mentions.notify = function(data) {
	var postData = data.post;
	var cleanedContent = Mentions.clean(postData.content, true, true, true);
	var matches = cleanedContent.match(rawRegex);

	if (!matches) {
		return;
	}

	var noMentionGroups = ['registered-users', 'guests'];

	matches = matches.map(function(match) {
		return Utils.slugify(match);
	}).filter(function(match, index, array) {
		return match && array.indexOf(match) === index && noMentionGroups.indexOf(match) === -1;
	});

	if (!matches.length) {
		return;
	}

	async.parallel({
		userRecipients: function(next) {
			async.filter(matches, User.existsBySlug, next);
		},
		groupRecipients: function(next) {
			async.filter(matches, Groups.existsBySlug, next);
		}
	}, function(err, results) {
		if (err) {
			return;
		}

		if (!results.userRecipients.length && !results.groupRecipients.length) {
			return;
		}

		async.parallel({
			topic: function(next) {
				Topics.getTopicFields(postData.tid, ['title', 'cid'], next);
			},
			author: function(next) {
				User.getUserField(postData.uid, 'username', next);
			},
			uids: function(next) {
				async.map(results.userRecipients, function(slug, next) {
					User.getUidByUserslug(slug, next);
				}, next);
			},
			groupData: function(next) {
				getGroupMemberUids(results.groupRecipients, next);
			},
			topicFollowers: function(next) {
				Topics.getFollowers(postData.tid, next);
			}
		}, function(err, results) {
			if (err) {
				return;
			}

			var title = entities.decode(results.topic.title);
			var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ',');

			var uids = results.uids.filter(function(uid, index, array) {
				return array.indexOf(uid) === index && parseInt(uid, 10) !== parseInt(postData.uid, 10) && results.topicFollowers.indexOf(uid.toString()) === -1;
			});

			var groupMemberUids = {};
			results.groupData.groupNames.forEach(function(groupName, index) {
				results.groupData.groupMembers[index] = results.groupData.groupMembers[index].filter(function(uid) {
					if (!uid || groupMemberUids[uid]) {
						return false;
					}
					groupMemberUids[uid] = 1;
					return uids.indexOf(uid) === -1 &&
						parseInt(uid, 10) !== parseInt(postData.uid, 10) &&
						results.topicFollowers.indexOf(uid.toString()) === -1;
				});
			});

			sendNotificationToUids(postData, uids, 'user', '[[notifications:user_mentioned_you_in, ' + results.author + ', ' + titleEscaped + ']]');

			results.groupData.groupNames.forEach(function(groupName, index) {
				var memberUids = results.groupData.groupMembers[index];
				sendNotificationToUids(postData, memberUids, groupName, '[[notifications:user_mentioned_group_in, ' + results.author + ', ' + groupName + ', ' + titleEscaped + ']]');
			});
		});
	});
};

Mentions.addFilters = function (data, callback) {
	data.regularFilters.push({ name: '[[notifications:mentions]]', filter: 'mention' });
	callback(null, data);
};

Mentions.notificationTypes = function (data, callback) {
	data.types.push('notificationType_mention');
	callback(null, data);
};

Mentions.filterUserSaveSettings = function (hookData, callback) {
	hookData.settings.notificationType_mention = hookData.data.notificationType_mention;
	callback(null, hookData);
};

function sendNotificationToUids(postData, uids, nidType, notificationText) {
	if (!uids.length) {
		return;
	}

	var filteredUids = [];
	var notification;
	async.waterfall([
		function (next) {
			createNotification(postData, nidType, notificationText, next);
		},
		function (_notification, next) {
			notification = _notification;
			if (!notification) {
				return next();
			}

			batch.processArray(uids, function (uids, next) {
				async.waterfall([
					function(next) {
						Privileges.topics.filterUids('read', postData.tid, uids, next);
					},
					function(_uids, next) {
						Topics.filterIgnoringUids(postData.tid, _uids, next);
					},
					function(_uids, next) {
						if (!_uids.length) {
							return next();
						}

						filteredUids = filteredUids.concat(_uids);

						next();
					}
				], next);
			}, {
				interval: 1000,
				batch: 500,
			}, next);
		},
	], function (err) {
		if (err) {
			return winston.error(err);
		}
		if (notification) {
			Notifications.push(notification, filteredUids);
		}
	});
}

function createNotification(postData, nidType, notificationText, callback) {
	Notifications.create({
		type: 'mention',
		bodyShort: notificationText,
		bodyLong: postData.content,
		nid: 'tid:' + postData.tid + ':pid:' + postData.pid + ':uid:' + postData.uid + ':' + nidType,
		pid: postData.pid,
		tid: postData.tid,
		from: postData.uid,
		path: '/post/' + postData.pid,
		importance: 6
	}, callback);
}

function getGroupMemberUids(groupRecipients, callback) {
	async.map(groupRecipients, function(slug, next) {
		Groups.getGroupNameByGroupSlug(slug, next);
	}, function(err, groupNames) {
		if (err) {
			return callback(err);
		}
		async.map(groupNames, function(groupName, next) {
			Groups.getMembers(groupName, 0, -1, next);
		}, function(err, groupMembers) {
			if (err) {
				return callback(err);
			}
			callback(null, {groupNames: groupNames, groupMembers: groupMembers});
		});
	});
}

Mentions.parsePost = function(data, callback) {
	if (!data || !data.postData || !data.postData.content) {
		return callback(null, data);
	}

	Mentions.parseRaw(data.postData.content, function(err, content) {
		if (err) {
			return callback(err);
		}

		data.postData.content = content;
		callback(null, data);
	});
};

Mentions.parseRaw = function(content, callback) {
	var splitContent = Mentions.split(content, false, false, true);
	var matches = [];
	splitContent.forEach(function(cleanedContent, i) {
		if ((i & 1) === 0) {
			matches = matches.concat(cleanedContent.match(regex) || []);
		}
	});

	if (!matches.length) {
		return callback(null, content);
	}

	matches = matches.filter(function(cur, idx) {
		// Eliminate duplicates
		return idx === matches.indexOf(cur);
	}).map(function(match) {
		/**
		 *	Javascript-favour of regex does not support lookaround,
		 *	so need to clean up the cruft by discarding everthing
		 *	before the @
		 */
		var atIndex = match.indexOf('@');
		return atIndex !== 0 ? match.slice(atIndex) : match;
	});

	async.each(matches, function(match, next) {
		var slug = Utils.slugify(match.slice(1));

		match = removePunctuationSuffix(match);

		async.parallel({
			groupExists: async.apply(Groups.existsBySlug, slug),
			uid: async.apply(User.getUidByUserslug, slug)
		}, function(err, results) {
			if (err) {
				return next(err);
			}

			if (results.uid || results.groupExists) {
				var regex = isLatinMention.test(match)
					? new RegExp('[>|\\s]' + match + '\\b', 'g')
					: new RegExp('[>|\\s]' + match, 'g');

				splitContent = splitContent.map(function(c, i) {
					if ((i & 1) === 1) {
						return c;
					}
					return c.replace(regex, function(match) {
						// Again, cleaning up lookaround leftover bits
						var atIndex = match.indexOf('@');
						var plain = match.slice(0, atIndex);
						match = match.slice(atIndex);
						var str = results.uid
								? '' + match + ''
								: '' + match + '';

						return plain + str;
					});
				});
			}

			next();
		});
	}, function(err) {
		callback(err, splitContent.join(''));
	});
};

Mentions.clean = function(input, isMarkdown, stripBlockquote, stripCode) {
	var split = Mentions.split(input, isMarkdown, stripBlockquote, stripCode);
	split = split.filter(function(e, i) {
		// only keep non-code/non-blockquote
		return (i & 1) === 0;
	});
	return split.join('');
};

Mentions.split = function(input, isMarkdown, splitBlockquote, splitCode) {
	if (!input) {
		return [];
	}

	var matchers = [isMarkdown ? '\\[.*?\\]\\(.*?\\)' : '<a[\\s\\S]*?|<[^>]+>'];
	if (splitBlockquote) {
		matchers.push(isMarkdown ? '^>.*$' : '^
.*?
');
	}
	if (splitCode) {
		matchers.push(isMarkdown ? '`[^`\n]+`' : '<code[\\s\\S]*?');
	}
	return input.split(new RegExp('(' + matchers.join('|') + ')', 'gm'));
};

/*
	WebSocket methods
*/

SocketPlugins.mentions.listGroups = function(socket, data, callback) {
	if (Mentions._settings.autofillGroups === 'off') {
		return callback(null, []);
	}

	Groups.getGroups('groups:visible:createtime', 0, -1, function(err, groups) {
		if (err) {
			return callback(err);
		}
		groups = groups.filter(Boolean).map(function(groupName) {
			return validator.escape(groupName);
		});
		callback(null, groups);
	});
};

module.exports = Mentions;
<|endoftext|>


<commit_before>(function(module) {
	"use strict";

	var Comments = {};

	var db = module.parent.require('../src/database.js'),
		meta = module.parent.require('../src/meta.js'),
		posts = module.parent.require('../src/posts.js'),
		topics = module.parent.require('../src/topics.js'),
		user = module.parent.require('../src/user.js'),
		groups = module.parent.require('../src/groups.js'),
		fs = module.parent.require('fs'),
		path = module.parent.require('path'),
		async = module.parent.require('async'),
		winston = module.parent.require('winston');

	module.exports = Comments;

	Comments.getTopicIDByCommentID = function(commentID, callback) {
		db.getObjectField('blog-comments', commentID, function(err, tid) {
			callback(err, tid);
		});
	};

	Comments.getCommentData = function(req, res, callback) {
		var commentID = req.params.id,
			pagination = req.params.pagination ? req.params.pagination : 0,
			uid = req.user ? req.user.uid : 0;

		Comments.getTopicIDByCommentID(commentID, function(err, tid) {
			var disabled = false;

			async.parallel({
				posts: function(next) {
					if (disabled) {
						next(err, []);
					} else {
						topics.getTopicPosts(tid, 'tid:' + tid + ':posts', 0 + req.params.pagination * 10, 9 + req.params.pagination * 9, uid, true, next);
					}
				},
				postCount: function(next) {
					topics.getTopicField(tid, 'postcount', next);
				},
				user: function(next) {
					user.getUserData(uid, next);
				},
				isAdministrator: function(next) {
					user.isAdministrator(uid, next);
				},
				isPublisher: function(next) {
					groups.isMember(uid, 'publishers', next);
				},
				category: function(next) {
					topics.getCategoryData(tid, next);
				},
				mainPost: function(next) {
					topics.getMainPost(tid, uid, next);
				}
			}, function(err, data) {
				var hostUrls = (meta.config['blog-comments:url'] || '').split(','),
					url;

				hostUrls.forEach(function(hostUrl) {
					hostUrl = hostUrl.trim();
					if (hostUrl[hostUrl.length - 1] === '/') {
						hostUrl = hostUrl.substring(0, hostUrl.length - 1);
					}

					if (hostUrl === req.get('origin')) {
						url = req.get('origin');
					}
				});

				if (!url) {
					winston.warn('[nodebb-plugin-blog-comments] Origin (' + req.get('origin') + ') does not match hostUrls: ' + hostUrls.join(', '));
				}

				res.header("Access-Control-Allow-Origin", url);
				res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
				res.header("Access-Control-Allow-Credentials", "true");

				var posts = data.posts.filter(function(post) {
					return post.deleted === false;
				});

				var top = true;
				var bottom = false;
				var compose_location = meta.config['blog-comments:compose-location'];
				if (compose_location == "bottom"){ bottom = true; top = false;}

				res.json({
					posts: posts,
					postCount: data.postCount,
					user: data.user,
					template: Comments.template,
					token: res.locals.csrf_token,
					isAdmin: !data.isAdministrator ? data.isPublisher : data.isAdministrator,
					isLoggedIn: !!uid,
					tid: tid,
					category: data.category,
					mainPost: data.mainPost ? data.mainPost[0] : null,
					atBottom: bottom,
					atTop: top
				});
			});
		});
	};

	Comments.replyToComment = function(req, res, callback) {
		var content = req.body.content,
			tid = req.body.tid,
			url = req.body.url,
			uid = req.user ? req.user.uid : 0;

		topics.reply({
			tid: tid,
			uid: uid,
			content: content
		}, function(err, postData) {
			if(err) {
				return res.redirect(url + '?error=' + err.message + '#nodebb/comments');
			}

			res.redirect(url + '#nodebb/comments');
		});
	};

	Comments.publishArticle = function(req, res, callback) {
		var markdown = req.body.markdown,
			title = req.body.title,
			url = req.body.url,
			commentID = req.body.id,
			tags = req.body.tags,
			uid = req.user ? req.user.uid : 0;

		var hostUrls = (meta.config['blog-comments:url'] || '').split(','),
			position = 0;

		hostUrls.forEach(function(hostUrl, i) {
			hostUrl = hostUrl.trim();
			if (hostUrl[hostUrl.length - 1] === '/') {
				hostUrl = hostUrl.substring(0, hostUrl.length - 1);
			}

			if (hostUrl === req.get('origin')) {
				position = i;
			}
		});

		var cid = meta.config['blog-comments:cid'] || '';
		cid = parseInt(cid.split(',')[position], 10) || parseInt(cid.split(',')[0], 10) || 1;

		async.parallel({
			isAdministrator: function(next) {
				user.isAdministrator(uid, next);
			},
			isPublisher: function(next) {
				groups.isMember(uid, 'publishers', next);
			}
		}, function(err, userStatus) {
			if (!userStatus.isAdministrator && !userStatus.isPublisher) {
				return res.json({error: "Only Administrators or members of the publishers group can publish articles"});
			}

			topics.post({
				uid: uid,
				title: title,
				content: markdown,
				tags: tags ? JSON.parse(tags) : [],
				req: req,
				cid: cid
			}, function(err, result) {
				if (!err && result && result.postData && result.postData.tid) {
					posts.setPostField(result.postData.pid, 'blog-comments:url', url, function(err) {
						if (err) {
							return res.json({error: "Unable to post topic", result: result});
						}

						db.setObjectField('blog-comments', commentID, result.postData.tid);
						res.redirect((req.header('Referer') || '/') + '#nodebb/comments');
					});
				} else {
					res.json({error: "Unable to post topic", result: result});
				}
			});
		});

	};

	Comments.addLinkbackToArticle = function(post, callback) {
		var hostUrls = (meta.config['blog-comments:url'] || '').split(','),
			position;

		posts.getPostField(post.pid, 'blog-comments:url', function(err, url) {
			if (url) {
				hostUrls.forEach(function(hostUrl, i) {
					if (url.indexOf(hostUrl.trim().replace(/^https?:\/\//, '')) !== -1) {
						position = i;
					}
				});

				var blogName = (meta.config['blog-comments:name'] || '');
				blogName = parseInt(blogName.split(',')[position], 10) || parseInt(blogName.split(',')[0], 10) || 1;

				post.profile.push({
					content: "Posted from " + blogName + ""
				});
			}

			callback(err, post);
		});
	};

	Comments.addAdminLink = function(custom_header, callback) {
		custom_header.plugins.push({
			"route": "/blog-comments",
			"icon": "fa-book",
			"name": "Blog Comments"
		});

		callback(null, custom_header);
	};

	function renderAdmin(req, res, callback) {
		res.render('comments/admin', {});
	}

	Comments.init = function(params, callback) {
		var app = params.router,
			middleware = params.middleware,
			controllers = params.controllers;

		fs.readFile(path.resolve(__dirname, './public/templates/comments/comments.tpl'), function (err, data) {
			Comments.template = data.toString();
		});

		app.get('/comments/get/:id/:pagination?', Comments.getCommentData);
		app.post('/comments/reply', Comments.replyToComment);
		app.post('/comments/publish', Comments.publishArticle);

		app.get('/admin/blog-comments', middleware.admin.buildHeader, renderAdmin);
		app.get('/api/admin/blog-comments', renderAdmin);

		callback();
	};

}(module));
<commit_msg>closes #31<commit_after>(function(module) {
	"use strict";

	var Comments = {};

	var db = module.parent.require('../src/database.js'),
		meta = module.parent.require('../src/meta.js'),
		posts = module.parent.require('../src/posts.js'),
		topics = module.parent.require('../src/topics.js'),
		user = module.parent.require('../src/user.js'),
		groups = module.parent.require('../src/groups.js'),
		fs = module.parent.require('fs'),
		path = module.parent.require('path'),
		async = module.parent.require('async'),
		winston = module.parent.require('winston');

	module.exports = Comments;

	Comments.getTopicIDByCommentID = function(commentID, callback) {
		db.getObjectField('blog-comments', commentID, function(err, tid) {
			callback(err, tid);
		});
	};

	Comments.getCommentData = function(req, res, callback) {
		var commentID = req.params.id,
			pagination = req.params.pagination ? req.params.pagination : 0,
			uid = req.user ? req.user.uid : 0;

		Comments.getTopicIDByCommentID(commentID, function(err, tid) {
			var disabled = false;

			async.parallel({
				posts: function(next) {
					if (disabled) {
						next(err, []);
					} else {
						topics.getTopicPosts(tid, 'tid:' + tid + ':posts', 0 + req.params.pagination * 10, 9 + req.params.pagination * 9, uid, true, next);
					}
				},
				postCount: function(next) {
					topics.getTopicField(tid, 'postcount', next);
				},
				user: function(next) {
					user.getUserData(uid, next);
				},
				isAdministrator: function(next) {
					user.isAdministrator(uid, next);
				},
				isPublisher: function(next) {
					groups.isMember(uid, 'publishers', next);
				},
				category: function(next) {
					topics.getCategoryData(tid, next);
				},
				mainPost: function(next) {
					topics.getMainPost(tid, uid, next);
				}
			}, function(err, data) {
				var hostUrls = (meta.config['blog-comments:url'] || '').split(','),
					url;

				hostUrls.forEach(function(hostUrl) {
					hostUrl = hostUrl.trim();
					if (hostUrl[hostUrl.length - 1] === '/') {
						hostUrl = hostUrl.substring(0, hostUrl.length - 1);
					}

					if (hostUrl === req.get('origin')) {
						url = req.get('origin');
					}
				});

				if (!url) {
					winston.warn('[nodebb-plugin-blog-comments] Origin (' + req.get('origin') + ') does not match hostUrls: ' + hostUrls.join(', '));
				}

				res.header("Access-Control-Allow-Origin", url);
				res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
				res.header("Access-Control-Allow-Credentials", "true");

				var posts = data.posts.filter(function(post) {
					return post.deleted === false;
				});

				var top = true;
				var bottom = false;
				var compose_location = meta.config['blog-comments:compose-location'];
				if (compose_location == "bottom"){ bottom = true; top = false;}

				res.json({
					posts: posts,
					postCount: data.postCount,
					user: data.user,
					template: Comments.template,
					token: req.csrfToken(),
					isAdmin: !data.isAdministrator ? data.isPublisher : data.isAdministrator,
					isLoggedIn: !!uid,
					tid: tid,
					category: data.category,
					mainPost: data.mainPost ? data.mainPost[0] : null,
					atBottom: bottom,
					atTop: top
				});
			});
		});
	};

	Comments.replyToComment = function(req, res, callback) {
		var content = req.body.content,
			tid = req.body.tid,
			url = req.body.url,
			uid = req.user ? req.user.uid : 0;

		topics.reply({
			tid: tid,
			uid: uid,
			content: content
		}, function(err, postData) {
			if(err) {
				return res.redirect(url + '?error=' + err.message + '#nodebb/comments');
			}

			res.redirect(url + '#nodebb/comments');
		});
	};

	Comments.publishArticle = function(req, res, callback) {
		var markdown = req.body.markdown,
			title = req.body.title,
			url = req.body.url,
			commentID = req.body.id,
			tags = req.body.tags,
			uid = req.user ? req.user.uid : 0;

		var hostUrls = (meta.config['blog-comments:url'] || '').split(','),
			position = 0;

		hostUrls.forEach(function(hostUrl, i) {
			hostUrl = hostUrl.trim();
			if (hostUrl[hostUrl.length - 1] === '/') {
				hostUrl = hostUrl.substring(0, hostUrl.length - 1);
			}

			if (hostUrl === req.get('origin')) {
				position = i;
			}
		});

		var cid = meta.config['blog-comments:cid'] || '';
		cid = parseInt(cid.split(',')[position], 10) || parseInt(cid.split(',')[0], 10) || 1;

		async.parallel({
			isAdministrator: function(next) {
				user.isAdministrator(uid, next);
			},
			isPublisher: function(next) {
				groups.isMember(uid, 'publishers', next);
			}
		}, function(err, userStatus) {
			if (!userStatus.isAdministrator && !userStatus.isPublisher) {
				return res.json({error: "Only Administrators or members of the publishers group can publish articles"});
			}

			topics.post({
				uid: uid,
				title: title,
				content: markdown,
				tags: tags ? JSON.parse(tags) : [],
				req: req,
				cid: cid
			}, function(err, result) {
				if (!err && result && result.postData && result.postData.tid) {
					posts.setPostField(result.postData.pid, 'blog-comments:url', url, function(err) {
						if (err) {
							return res.json({error: "Unable to post topic", result: result});
						}

						db.setObjectField('blog-comments', commentID, result.postData.tid);
						res.redirect((req.header('Referer') || '/') + '#nodebb/comments');
					});
				} else {
					res.json({error: "Unable to post topic", result: result});
				}
			});
		});

	};

	Comments.addLinkbackToArticle = function(post, callback) {
		var hostUrls = (meta.config['blog-comments:url'] || '').split(','),
			position;

		posts.getPostField(post.pid, 'blog-comments:url', function(err, url) {
			if (url) {
				hostUrls.forEach(function(hostUrl, i) {
					if (url.indexOf(hostUrl.trim().replace(/^https?:\/\//, '')) !== -1) {
						position = i;
					}
				});

				var blogName = (meta.config['blog-comments:name'] || '');
				blogName = parseInt(blogName.split(',')[position], 10) || parseInt(blogName.split(',')[0], 10) || 1;

				post.profile.push({
					content: "Posted from " + blogName + ""
				});
			}

			callback(err, post);
		});
	};

	Comments.addAdminLink = function(custom_header, callback) {
		custom_header.plugins.push({
			"route": "/blog-comments",
			"icon": "fa-book",
			"name": "Blog Comments"
		});

		callback(null, custom_header);
	};

	function renderAdmin(req, res, callback) {
		res.render('comments/admin', {});
	}

	Comments.init = function(params, callback) {
		var app = params.router,
			middleware = params.middleware,
			controllers = params.controllers;

		fs.readFile(path.resolve(__dirname, './public/templates/comments/comments.tpl'), function (err, data) {
			Comments.template = data.toString();
		});

		app.get('/comments/get/:id/:pagination?', middleware.applyCSRF, Comments.getCommentData);
		app.post('/comments/reply', Comments.replyToComment);
		app.post('/comments/publish', Comments.publishArticle);

		app.get('/admin/blog-comments', middleware.admin.buildHeader, renderAdmin);
		app.get('/api/admin/blog-comments', renderAdmin);

		callback();
	};

}(module));
<|endoftext|>