|
#!/usr/bin/env ruby |
|
# frozen_string_literal: true |
|
|
|
require 'net/https' |
|
require 'optparse' |
|
require 'socket' |
|
require 'yaml' |
|
|
|
begin |
|
require 'colorize' |
|
require 'Win32/Console/ANSI' if RUBY_PLATFORM =~ /win32|mingw32/ |
|
rescue LoadError |
|
# Dummy colorize method for String |
|
class String |
|
def colorize(*_args) |
|
self |
|
end |
|
end |
|
end |
|
|
|
Options = Struct.new(:configfile, :title, :url, :url_title, :timestamp, :messages, :dump) |
|
options = Options.new |
|
options.messages = [] |
|
|
|
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5') |
|
def merge_configs(*configs) |
|
{}.merge(*configs) do |_key, old, new| |
|
merge_configs(old, new) |
|
end |
|
end |
|
else |
|
def merge_configs(*configs) |
|
def merge_r(a, b) |
|
a.merge(b) do |_key, old, new| |
|
if old.is_a?(Hash) |
|
merge_r(old, new) |
|
else |
|
new |
|
end |
|
end |
|
end |
|
merged = {} |
|
configs.each do |config| |
|
merged = merge_r(merged, config) |
|
end |
|
merged |
|
end |
|
end |
|
|
|
def xdg_config_file |
|
xdg_config_home = ENV['XDG_CONFIG_HOME'] || File.join(Dir.home, '.config') |
|
File.join(xdg_config_home, 'pushover', 'config.yaml') |
|
rescue ArgumentError |
|
'' |
|
end |
|
|
|
def config_files(configfile) |
|
list = [] |
|
if configfile |
|
unless File.exist?(configfile) |
|
warn '** ERROR cannot read configuration file'.colorize(:yellow) |
|
warn "#{configfile}: file not found".colorize(:red) |
|
exit 1 |
|
end |
|
list.push(configfile) |
|
elsif RUBY_PLATFORM =~ /win32|mingw32/ |
|
[ENV['APPDATA'], ENV['LOCALAPPDATA']].each do |path| |
|
list.push(File.join(path, 'pushover', 'config.yaml')) if path |
|
end |
|
else |
|
list.push('/etc/pushover/config.yaml') |
|
end |
|
unless configfile |
|
user_config_file = xdg_config_file |
|
list.push(user_config_file) unless user_config_file.empty? |
|
end |
|
list.map { |path| File.absolute_path(path) } |
|
end |
|
|
|
def can_read_file(file) |
|
File.exist?(file) && File.readable?(file) && File.file?(file) |
|
end |
|
|
|
def read_config(files) |
|
default_config = { |
|
'pushover' => { |
|
'title' => Socket.gethostname, |
|
'api' => 'https://api.pushover.net/1/messages.json', |
|
'insecure_ssl' => false |
|
} |
|
} |
|
configs = files.map do |filepath| |
|
if can_read_file(filepath) |
|
YAML.load_file(filepath) |
|
elsif File.exist?(filepath) |
|
warn "Warning cannot access #{filepath}".colorize(:yellow) |
|
end |
|
end.compact |
|
merge_configs(default_config, *configs) |
|
end |
|
|
|
opt_parser = OptionParser.new do |opt| |
|
opt.on('-c', '--config CONFIG_FILE', 'Use this configuration file') do |o| |
|
options.configfile = o |
|
end |
|
opt.on('-t', '--title TITLE', 'Change default title') { |o| options.title = o } |
|
opt.on('-u', '--url URL', 'Add URL to notification') { |o| options.url = o } |
|
opt.on('-e', '--urltitle TEXT', 'Use TEXT to display link') { |o| options.url_title = o } |
|
opt.on('--timestamp TIMESTAMP', OptionParser::DecimalInteger, |
|
'UTC timestamp in UNIX epoch') do |o| |
|
raise 'TIMESTAMP shall be positive' if o.negative? |
|
|
|
options.timestamp = o |
|
end |
|
opt.on_tail('-V', '--version', 'Dump information') { |_o| options.dump = true } |
|
opt.on_tail('-h', '--help', 'Prints this help') do |
|
puts opt |
|
exit |
|
end |
|
end |
|
opt_parser.parse! |
|
options.messages = ARGV |
|
|
|
begin |
|
conf_files = config_files(options.configfile) |
|
config = read_config(conf_files) |
|
rescue Psych::SyntaxError => e |
|
warn '** ERROR failed to read configuration files'.colorize(:red) |
|
warn e.message.to_s.colorize(:magenta) |
|
exit 1 if options.messages.empty? |
|
end |
|
|
|
if options.dump |
|
puts 'Configuration files:' |
|
conf_files.each do |filepath| |
|
state = if can_read_file(filepath) |
|
'' |
|
elsif File.exist?(filepath) |
|
'not readable' |
|
else |
|
'not found' |
|
end |
|
if state.empty? |
|
puts "- #{filepath.colorize(:green)}" |
|
else |
|
puts "- #{filepath.colorize(:green)} [#{state.colorize(:cyan)}]" |
|
end |
|
end |
|
puts YAML.dump(config) |
|
exit 0 |
|
end |
|
|
|
if options.messages.empty? |
|
warn '** ERROR missing message'.colorize(:red) |
|
warn opt_parser.help |
|
exit 1 |
|
end |
|
|
|
po = config['pushover'] |
|
form = { |
|
token: po['application_key'], |
|
user: po['user_key'], |
|
title: options.title || po['title'], |
|
message: options.messages.join("\n") |
|
} |
|
form[:timestamp] = options.timestamp if options.timestamp |
|
form[:url] = options.url if options.url |
|
form[:url_title] = options.url_title if options.url_title |
|
|
|
url = URI.parse(po['api']) |
|
req = Net::HTTP::Post.new(url.path) |
|
req.set_form_data(form) |
|
res = Net::HTTP.new(url.host, url.port) |
|
res.use_ssl = (url.scheme == 'https') |
|
res.verify_mode = if po['insecure_ssl'] |
|
OpenSSL::SSL::VERIFY_NONE |
|
else |
|
OpenSSL::SSL::VERIFY_PEER |
|
end |
|
res.start { |http| http.request(req) } |