341 lines
7.9 KiB
JavaScript
341 lines
7.9 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
// Growl - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||
|
|
||
|
/**
|
||
|
* Module dependencies.
|
||
|
*/
|
||
|
|
||
|
const spawn = require('child_process').spawn;
|
||
|
const fs = require('fs');
|
||
|
const path = require('path');
|
||
|
const os = require('os');
|
||
|
|
||
|
const exists = fs.existsSync || path.existsSync;
|
||
|
let cmd;
|
||
|
|
||
|
function which(name) {
|
||
|
const paths = process.env.PATH.split(':');
|
||
|
let loc;
|
||
|
|
||
|
for (let i = 0, len = paths.length; i < len; i += 1) {
|
||
|
loc = path.join(paths[i], name);
|
||
|
if (exists(loc)) return loc;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setupCmd() {
|
||
|
switch (os.type()) {
|
||
|
case 'Darwin':
|
||
|
if (which('terminal-notifier')) {
|
||
|
cmd = {
|
||
|
type: 'Darwin-NotificationCenter',
|
||
|
pkg: 'terminal-notifier',
|
||
|
msg: '-message',
|
||
|
title: '-title',
|
||
|
subtitle: '-subtitle',
|
||
|
icon: '-appIcon',
|
||
|
sound: '-sound',
|
||
|
url: '-open',
|
||
|
priority: {
|
||
|
cmd: '-execute',
|
||
|
range: [],
|
||
|
},
|
||
|
};
|
||
|
} else {
|
||
|
cmd = {
|
||
|
type: 'Darwin-Growl',
|
||
|
pkg: 'growlnotify',
|
||
|
msg: '-m',
|
||
|
sticky: '--sticky',
|
||
|
url: '--url',
|
||
|
priority: {
|
||
|
cmd: '--priority',
|
||
|
range: [
|
||
|
-2,
|
||
|
-1,
|
||
|
0,
|
||
|
1,
|
||
|
2,
|
||
|
'Very Low',
|
||
|
'Moderate',
|
||
|
'Normal',
|
||
|
'High',
|
||
|
'Emergency',
|
||
|
],
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
break;
|
||
|
case 'Linux':
|
||
|
if (which('growl')) {
|
||
|
cmd = {
|
||
|
type: 'Linux-Growl',
|
||
|
pkg: 'growl',
|
||
|
msg: '-m',
|
||
|
title: '-title',
|
||
|
subtitle: '-subtitle',
|
||
|
host: {
|
||
|
cmd: '-H',
|
||
|
hostname: '192.168.33.1',
|
||
|
},
|
||
|
};
|
||
|
} else {
|
||
|
cmd = {
|
||
|
type: 'Linux',
|
||
|
pkg: 'notify-send',
|
||
|
msg: '',
|
||
|
sticky: '-t',
|
||
|
icon: '-i',
|
||
|
priority: {
|
||
|
cmd: '-u',
|
||
|
range: [
|
||
|
'low',
|
||
|
'normal',
|
||
|
'critical',
|
||
|
],
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
break;
|
||
|
case 'Windows_NT':
|
||
|
cmd = {
|
||
|
type: 'Windows',
|
||
|
pkg: 'growlnotify',
|
||
|
msg: '',
|
||
|
sticky: '/s:true',
|
||
|
title: '/t:',
|
||
|
icon: '/i:',
|
||
|
url: '/cu:',
|
||
|
priority: {
|
||
|
cmd: '/p:',
|
||
|
range: [
|
||
|
-2,
|
||
|
-1,
|
||
|
0,
|
||
|
1,
|
||
|
2,
|
||
|
],
|
||
|
},
|
||
|
};
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Send growl notification _msg_ with _options_.
|
||
|
*
|
||
|
* Options:
|
||
|
*
|
||
|
* - title Notification title
|
||
|
* - sticky Make the notification stick (defaults to false)
|
||
|
* - priority Specify an int or named key (default is 0)
|
||
|
* - name Application name (defaults to growlnotify)
|
||
|
* - sound Sound efect ( in OSx defined in preferences -> sound -> effects)
|
||
|
* works only in OSX > 10.8x
|
||
|
* - image
|
||
|
* - path to an icon sets --iconpath
|
||
|
* - path to an image sets --image
|
||
|
* - capitalized word sets --appIcon
|
||
|
* - filename uses extname as --icon
|
||
|
* - otherwise treated as --icon
|
||
|
*
|
||
|
* Examples:
|
||
|
*
|
||
|
* growl('New email')
|
||
|
* growl('5 new emails', { title: 'Thunderbird' })
|
||
|
* growl('5 new emails', { title: 'Thunderbird', sound: 'Purr' })
|
||
|
* growl('Email sent', function(){
|
||
|
* // ... notification sent
|
||
|
* })
|
||
|
*
|
||
|
* @param {string} msg
|
||
|
* @param {object} opts
|
||
|
* @param {function} callback
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
function growl(msg, opts, callback) {
|
||
|
let image;
|
||
|
const options = opts || {};
|
||
|
const fn = callback || function noop() {};
|
||
|
|
||
|
setupCmd();
|
||
|
|
||
|
if (options.exec) {
|
||
|
cmd = {
|
||
|
type: 'Custom',
|
||
|
pkg: options.exec,
|
||
|
range: [],
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// noop
|
||
|
if (!cmd) {
|
||
|
fn(new Error('growl not supported on this platform'));
|
||
|
return;
|
||
|
}
|
||
|
const args = [cmd.pkg];
|
||
|
|
||
|
// image
|
||
|
if (image || options.image) {
|
||
|
image = options.image;
|
||
|
switch (cmd.type) {
|
||
|
case 'Darwin-Growl': {
|
||
|
let flag;
|
||
|
const ext = path.extname(image).substr(1);
|
||
|
flag = ext === 'icns' && 'iconpath';
|
||
|
flag = flag || (/^[A-Z]/.test(image) && 'appIcon');
|
||
|
flag = flag || (/^png|gif|jpe?g$/.test(ext) && 'image');
|
||
|
flag = flag || (ext && (image = ext) && 'icon');
|
||
|
flag = flag || 'icon';
|
||
|
args.push(`--${flag}`, image);
|
||
|
break;
|
||
|
}
|
||
|
case 'Darwin-NotificationCenter':
|
||
|
args.push(cmd.icon, image);
|
||
|
break;
|
||
|
case 'Linux':
|
||
|
args.push(cmd.icon, image);
|
||
|
// libnotify defaults to sticky, set a hint for transient notifications
|
||
|
if (!options.sticky) args.push('--hint=int:transient:1');
|
||
|
break;
|
||
|
case 'Windows':
|
||
|
args.push(cmd.icon + image);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// sticky
|
||
|
if (options.sticky) args.push(cmd.sticky);
|
||
|
if (options.sticky && cmd.type === 'Linux') args.push('0');
|
||
|
|
||
|
// priority
|
||
|
if (options.priority) {
|
||
|
const priority = `${options.priority}`;
|
||
|
const checkindexOf = cmd.priority.range.indexOf(priority);
|
||
|
if (checkindexOf > -1) {
|
||
|
args.push(cmd.priority, options.priority);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// sound
|
||
|
if (options.sound && cmd.type === 'Darwin-NotificationCenter') {
|
||
|
args.push(cmd.sound, options.sound);
|
||
|
}
|
||
|
|
||
|
// name
|
||
|
if (options.name && cmd.type === 'Darwin-Growl') {
|
||
|
args.push('--name', options.name);
|
||
|
}
|
||
|
|
||
|
switch (cmd.type) {
|
||
|
case 'Darwin-Growl':
|
||
|
args.push(cmd.msg);
|
||
|
args.push(msg.replace(/\\n/g, '\n'));
|
||
|
if (options.title) args.push(options.title);
|
||
|
if (options.url) {
|
||
|
args.push(cmd.url);
|
||
|
args.push(options.url);
|
||
|
}
|
||
|
break;
|
||
|
case 'Darwin-NotificationCenter': {
|
||
|
args.push(cmd.msg);
|
||
|
const stringifiedMsg = msg;
|
||
|
const escapedMsg = stringifiedMsg.replace(/\\n/g, '\n');
|
||
|
args.push(escapedMsg);
|
||
|
if (options.title) {
|
||
|
args.push(cmd.title);
|
||
|
args.push(options.title);
|
||
|
}
|
||
|
if (options.subtitle) {
|
||
|
args.push(cmd.subtitle);
|
||
|
args.push(options.subtitle);
|
||
|
}
|
||
|
if (options.url) {
|
||
|
args.push(cmd.url);
|
||
|
args.push(options.url);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case 'Linux-Growl':
|
||
|
args.push(cmd.msg);
|
||
|
args.push(msg.replace(/\\n/g, '\n'));
|
||
|
if (options.title) args.push(options.title);
|
||
|
if (cmd.host) {
|
||
|
args.push(cmd.host.cmd, cmd.host.hostname);
|
||
|
}
|
||
|
break;
|
||
|
case 'Linux':
|
||
|
if (options.title) args.push(options.title);
|
||
|
args.push(msg.replace(/\\n/g, '\n'));
|
||
|
break;
|
||
|
case 'Windows':
|
||
|
args.push(msg.replace(/\\n/g, '\n'));
|
||
|
if (options.title) args.push(cmd.title + options.title);
|
||
|
if (options.url) args.push(cmd.url + options.url);
|
||
|
break;
|
||
|
case 'Custom': {
|
||
|
const customCmd = args[0];
|
||
|
const message = options.title
|
||
|
? `${options.title}: ${msg}`
|
||
|
: msg;
|
||
|
let command = customCmd.replace(/(^|[^%])%s/g, `$1${message}`);
|
||
|
const splitCmd = command.split(' ');
|
||
|
if (splitCmd.length > 1) {
|
||
|
command = splitCmd.shift();
|
||
|
Array.prototype.push.apply(args, splitCmd);
|
||
|
}
|
||
|
if (customCmd.indexOf('%s') < 0) {
|
||
|
args.push(message);
|
||
|
}
|
||
|
args[0] = command;
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
const cmdToExec = args.shift();
|
||
|
|
||
|
const child = spawn(cmdToExec, args);
|
||
|
let stdout = '';
|
||
|
let stderr = '';
|
||
|
let error;
|
||
|
|
||
|
const now = new Date();
|
||
|
const timestamp = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}.${now.getMilliseconds()}`
|
||
|
|
||
|
stderr += `[${timestamp}][node-growl] : Executed command '${cmdToExec}' with arguments '${args}'\n[stderr] : `;
|
||
|
|
||
|
child.on('error', (err) => {
|
||
|
console.error('An error occured.', err);
|
||
|
error = err;
|
||
|
});
|
||
|
|
||
|
child.stdout.on('data', (data) => {
|
||
|
stdout += data;
|
||
|
});
|
||
|
|
||
|
child.stderr.on('data', (data) => {
|
||
|
stderr += data;
|
||
|
});
|
||
|
|
||
|
child.on('close', () => {
|
||
|
if (typeof fn === 'function') {
|
||
|
fn(error, stdout, stderr);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Expose `growl`.
|
||
|
*/
|
||
|
|
||
|
module.exports = growl;
|