|
#!/usr/bin/env php |
|
<?php |
|
|
|
// Fallback title for notifications without a title specified |
|
define('UNKNOWN_TITLE', 'Push over notification on ' . gethostname()); |
|
|
|
// PushOver's message limit is 1024 4-byte UTF8 characters, but a |
|
// smaller limit can be enforced to keep notifications readable. |
|
define('MESSAGE_LENGTH_LIMIT', 800); |
|
|
|
function dieHelp() |
|
{ |
|
echo <<<EOD |
|
Usage: notify [options] <message> |
|
|
|
<Message> can be alternatively be piped into stdin. |
|
|
|
Content options: |
|
--title <title> Message title. Defaults to app name |
|
--url <url> URL to show with message |
|
--url-name <name> Title to show for URL |
|
--app <name> Name of application in config to use. Defaults to the |
|
first item in ~/.pushover.json |
|
--config <path> Path to a specific config file |
|
|
|
Fallback options: |
|
--fallback-stdout If the message is too large for a single notification, |
|
the full message will be written to stdout. This is |
|
useful for use with cron, which includes more context |
|
in its job output notifcation emails. |
|
|
|
--fallback-email <email> If the message is too large for a single notification, |
|
a copy of the full message will be sent via email. |
|
|
|
Notification options: |
|
--slient Silent notification |
|
--high-priority Display as high priority |
|
--critical Require user confirmation of the notification |
|
--critical-expire <sec> Time before critical alert stops prompting in seconds |
|
(max 86400, defaults to 1 hour) |
|
--critical-retry <sec> Time in seconds between delivery retries for a critical |
|
alert (minimum 30 seconds, defaults to 2 minutes) |
|
|
|
External configuration: |
|
This script requires a configuration file containing Pushover application and user |
|
tokens. Unless a file is specified with the --config-file option, the script uses |
|
~/.pushover.json. In each named set, the 'token' and 'user' value are required. |
|
API requests are built on top of a config, so defaults can effectively be set for |
|
the request. See the Pushover API doc for accepted key-value pairs. |
|
|
|
{ |
|
"<application-name>": { |
|
"token": "<app-token>", |
|
"user": "<user-key>" |
|
} |
|
} |
|
|
|
EOD; |
|
exit(1); |
|
} |
|
|
|
function parseCommandLine($existingMessage = null) |
|
{ |
|
// Boolean options |
|
$options = array( |
|
'silent', |
|
'high-priority', |
|
'critical', |
|
'fallback-stdout', |
|
); |
|
|
|
// Options that take an argument |
|
$argOptions = array( |
|
'title', |
|
'url', |
|
'url-name', |
|
'confirm-expire', |
|
'config', |
|
'app', |
|
'fallback-email', |
|
); |
|
|
|
global $argv; |
|
$args = array_slice($argv, 1); |
|
$message = $existingMessage; |
|
|
|
$settings = array(); |
|
|
|
// Normalise default values |
|
foreach ($options as $option) { |
|
$settings[$option] = false; |
|
} |
|
|
|
foreach ($argOptions as $option) { |
|
$settings[$option] = null; |
|
} |
|
|
|
// Collect input values from commandline |
|
for ($i = 0, $c = count($args); $i<$c; $i++) { |
|
|
|
$value = $args[$i]; |
|
|
|
// If not using option syntax, assume this argument is the message |
|
if (substr($value, 0, 2) != '--') { |
|
if (!$message) { |
|
$message = $value; |
|
continue; |
|
} else if ($existingMessage) { |
|
echo "A non-option argument cannot be passed in addition to stdin!\n"; |
|
exit(3); |
|
} else { |
|
echo "Multiple non-option arguments passed!\n"; |
|
dieHelp(); |
|
} |
|
} |
|
|
|
$name = substr($value, 2); |
|
foreach ($options as $option) { |
|
if ($name == $option) { |
|
$settings[$option] = true; |
|
$found = true; |
|
continue 2; |
|
} |
|
} |
|
|
|
foreach ($argOptions as $option) { |
|
if ($name == $option) { |
|
if (!isset($args[$i+1])) { |
|
echo "No matching argument for option '$value'\n"; |
|
exit(3); |
|
} else if (substr($args[$i+1], 0, 2) == '--') { |
|
echo "No matching argument for option '$value'\n"; |
|
exit(3); |
|
} else { |
|
$settings[$option] = $args[$i+1]; |
|
} |
|
$i++; |
|
continue 2; |
|
} |
|
} |
|
|
|
echo "Invalid option '$value'. Specify --help for usage information.\n"; |
|
exit(4); |
|
} |
|
|
|
if (!$message) { |
|
echo "A message must be specified!\n"; |
|
exit(2); |
|
} |
|
|
|
$settings['message'] = $message; |
|
|
|
return $settings; |
|
} |
|
|
|
function loadConfig($file) |
|
{ |
|
if (!file_exists($file)) { |
|
echo "Config file $file not found.\n"; |
|
exit(4); |
|
} |
|
|
|
if (!is_readable($file)) { |
|
echo "Config file $file is not readable.\n"; |
|
} |
|
|
|
$config = json_decode(file_get_contents($file), true); |
|
if (!$config) { |
|
echo "$file parsed to nothing. Maybe check the syntax.\n"; |
|
exit(5); |
|
} |
|
|
|
return $config; |
|
} |
|
|
|
// Check if help needed |
|
if (in_array('--help', $argv)) { |
|
dieHelp(); |
|
} |
|
|
|
$stdinData = null; |
|
|
|
// Read message from stdin if we're being used in a pipe |
|
if (!posix_isatty(STDIN)) { |
|
$stdinData = stream_get_contents(STDIN); |
|
} |
|
|
|
$settings = parseCommandLine($stdinData); |
|
$configFile = isset($settings['config']) ? $settings['config'] : getenv('HOME').'/.pushover.json'; |
|
$applications = loadConfig($configFile); |
|
|
|
// Build base request |
|
if (isset($settings['app'])) { |
|
if (isset($applications[$settings['app']])) { |
|
$config = $applications[$settings['app']]; |
|
} else { |
|
echo "No application defined with the name '{$settings['app']}' in $configFile\n"; |
|
exit(4); |
|
} |
|
} else { |
|
// Default to the first item in $applications |
|
$config = reset($applications); |
|
} |
|
|
|
|
|
// Alert level |
|
if ($settings['critical']) { |
|
$config['priority'] = '2'; |
|
|
|
if (isset($settings['critical-expire'])) { |
|
$config['expire'] = intval($settings['critical-expire']); |
|
} else { |
|
// Default to 1 hour |
|
$config['expire'] = 3600; |
|
} |
|
|
|
if (isset($settings['critical-retry'])) { |
|
$config['retry'] = intval($settings['critical-retry']); |
|
} else { |
|
$config['retry'] = 120; |
|
} |
|
|
|
if ($config['retry'] < 30) { |
|
echo "Critical alert retry rate cannot be less than 30 seconds\n"; |
|
exit(1); |
|
} |
|
|
|
if ($config['expire'] > 86400) { |
|
echo "Critical alert expiry delay cannot be more than 86400 seconds\n"; |
|
exit(1); |
|
} |
|
} else if ($settings['high-priority']) { |
|
$config['priority'] = '1'; |
|
} else if ($settings['silent']) { |
|
$config['priority'] = '-1'; |
|
} |
|
|
|
// Content settings |
|
if (isset($settings['title'])) { |
|
$config['title'] = $settings['title']; |
|
} |
|
|
|
if (isset($settings['url'])) { |
|
$config['url'] = $settings['url']; |
|
} |
|
|
|
if (isset($settings['url-name'])) { |
|
$config['url_title'] = $settings['url-name']; |
|
} |
|
|
|
// Truncate message for notification and apply any selected fallback |
|
if (strlen($settings['message']) > MESSAGE_LENGTH_LIMIT) { |
|
// Data for fallback handlers |
|
$fallbackSubject = (isset($config['title']) ? $config['title'] : UNKNOWN_TITLE) . ' (full message)'; |
|
$fallbackMsg = "Notification message truncated. The full message was:\n\n{$settings['message']}"; |
|
|
|
// Message to tack onto the end of the truncated PushOver message. |
|
// Fallback handlers can modify this to inficate the truncated data is not lost. |
|
$truncationMsg = ' ... [truncated]'; |
|
|
|
if ($settings['fallback-stdout']) { |
|
$truncationMsg .= ' [full message dumped to stdout]'; |
|
echo $fallbackMsg; |
|
} |
|
|
|
if (isset($settings['fallback-email'])) { |
|
$toAddr = $settings['fallback-email']; |
|
$truncationMsg .= " [full message emailed to $toAddr]"; |
|
|
|
mail($toAddr, $fallbackSubject, $fallbackMsg); |
|
} |
|
|
|
// Truncate the notification for PushOver |
|
$settings['message'] = substr($settings['message'], 0, 900) . $truncationMsg; |
|
} |
|
|
|
$config['message'] = $settings['message']; |
|
|
|
// Send request |
|
curl_setopt_array($ch = curl_init(), array( |
|
CURLOPT_URL => "https://api.pushover.net/1/messages.json", |
|
CURLOPT_POSTFIELDS => $config, |
|
CURLOPT_RETURNTRANSFER => true |
|
)); |
|
$response = curl_exec($ch); |
|
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE); |
|
curl_close($ch); |
|
|
|
if ($status != 200) { |
|
echo "Pushover returned status $status\n."; |
|
print_r(json_decode($response)); |
|
exit(1); |
|
} |