Skip to content

Instantly share code, notes, and snippets.

@lboulard
Last active December 7, 2023 19:19
Show Gist options
  • Save lboulard/062e862a2facc6fe67371878610488fc to your computer and use it in GitHub Desktop.
Save lboulard/062e862a2facc6fe67371878610488fc to your computer and use it in GitHub Desktop.
[Pushover] Pushover notification in ruby program #ruby #notification
*~
*.bak
*.orig
*.sw[a-z]
\#*\#
vendor

Push notification to https://pushover.net

Installation

To puch notification, pushover requires a configuration file with User and Application keys. See configuration files section for setup.

install -m 755 pushover.rb /usr/local/bin/pushover

Configuration files

Use -c CONFIG-FILE to have a specific configuration setup. Otherwise, default configuration files are read consecutively.

Unix

System file /etc/pushover/config.yaml is read first. Then user file $HOME/.config/pushover/config.yaml is read unless environment variable XDG_CONFIG_HOME is defined. In this case, $XDG_CONFIG_HOME/pushover/config.yaml is read instead. At each read, definitions from current configuration file shadows previous definitions.

Windows

No system files exists. First, %APPDATA%\pushover\config.yaml is read. Then, %APPLOCALDATA%\pushover\config.yaml is read. At each read, definitions from current configuration file shadows previous definitions.

Required keys

Store Pushover User Key and Application Key into config.yaml

pushover:
  user_key: 0123456789abcdef0123456789abcd
  application_key: 012345678901234567890123456789

Optional keys

Default Pushover URL is https://api.pushover.net/1/messages.json.

  • pushover/title to replace default title set as current host name
  • pushover/url changes POST URL for HTTP request
  • pushover/insecure_ssl to true to ignore invalid HTTPS certificate
pushover:
  title: my default title
  url: https://localhost:8090/messages.json
  insecure_ssl: true
# frozen_string_literal: true
source 'https://rubygems.org'
group :development do
gem 'rubocop', require: false
gem 'colorize', require: false
gem 'win32console' if Gem.win_platform?
end
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
colorize (0.8.1)
parallel (1.21.0)
parser (3.1.0.0)
ast (~> 2.4.1)
rainbow (3.1.1)
regexp_parser (2.2.1)
rexml (3.2.5)
rubocop (1.25.1)
parallel (~> 1.10)
parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.15.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.15.1)
parser (>= 3.0.1.1)
ruby-progressbar (1.11.0)
unicode-display_width (2.1.0)
win32console (1.3.2)
PLATFORMS
x64-mingw32
DEPENDENCIES
colorize
rubocop
win32console
BUNDLED WITH
2.1.4
#!/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) }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment