signaly-notify 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d504ba72d7f41e9ae2d5fecf6ed2d6ac7a821525
4
- data.tar.gz: d669974e5f5e67cc144d2ad216897284a50bc5c2
3
+ metadata.gz: 0ccb5948a481aa78a08ebbed8ff8cda4c33e9515
4
+ data.tar.gz: e2901bff258d213961d7982a755aa5b55fee7fc4
5
5
  SHA512:
6
- metadata.gz: f8b1aed68273983cc7c0b862829fc3c4d2960c6c71118271f398310425e97a404113fa4b18fb55415680adc16e85b0b2d8ba0f172581c7d92afc71877815db55
7
- data.tar.gz: 024beb8fc12147e2a095839dbcd2aa68631f621674927d1456c33e83bfa90d2366f8b7b40d5fdfb41c27bffdcaa1aead313af0e152b6b8dc491940a1b66861d7
6
+ metadata.gz: 758f07968904bba363def2bef562ac2a450db0c34f7074a29722c6155b2ca1fdf56d743a5dab50ea0475708a7aa227a3641de0d586eb35ab9e1cb14a05a2c8f3
7
+ data.tar.gz: 78aa078ec0218bcde21dac1ea229766ba07c7da19870a740a05a0098d130705b79e66744452ea6d5cfd66e12ecbc34223e6b3364079e96bd801b884f4ed5e7f2
@@ -1,439 +1,4 @@
1
1
  #!/bin/env ruby
2
2
 
3
- require 'mechanize' # interaction with websites
4
- require 'colorize' # colorful console output
5
- require 'highline' # automating some tasks of the cli user interaction
6
- require 'optparse'
7
- require 'yaml'
8
-
9
- # optional gems for visual notifications
10
- begin
11
- require 'libnotify'
12
- rescue LoadError
13
- end
14
-
15
- begin
16
- require 'ruby-growl'
17
- rescue LoadError
18
- end
19
-
20
- SNConfig = Struct.new(:sleep_seconds, # between checks
21
- :remind_after, # the first notification
22
- :notification_showtime,
23
- :debug_output,
24
- :login,
25
- :password,
26
- :url, # of the checked page
27
- :skip_login,
28
- :config_file,
29
- :console_only,
30
- :notify)
31
-
32
- defaults = SNConfig.new
33
- # how many seconds between two checks of the site
34
- defaults.sleep_seconds = 60
35
- # if there is some pending content and I don't look at it,
36
- # remind me after X seconds
37
- defaults.remind_after = 60*5
38
- # for how long time the notification shows up
39
- defaults.notification_showtime = 10
40
- defaults.debug_output = false
41
- defaults.password = nil
42
- # use first available visual notification engine
43
- if defined? Libnotify
44
- defaults.notify = :libnotify
45
- elsif defined? Growl
46
- defaults.notify = :growl
47
- end
48
-
49
- # how many PMs, notifications, invitations a user has at a time
50
- class SignalyStatus < Struct.new(:pm, :notifications, :invitations)
51
-
52
- def initialize(pm=0, notifications=0, invitations=0)
53
- super(pm, notifications, invitations)
54
- end
55
-
56
- def is_there_anything?
57
- self.each_pair {|k,v| return true if v > 0 }
58
- return false
59
- end
60
-
61
- # does self have anything new compared with other?
62
- def >(other)
63
- if other.nil?
64
- return true
65
- end
66
-
67
- [:pm, :notifications, :invitations].each do |prop|
68
- if send(prop) > other.send(prop) then
69
- return true
70
- end
71
- end
72
-
73
- return false
74
- end
75
-
76
- # is there any change between old_status and self in property prop?
77
- def changed?(old_status, prop)
78
- (old_status == nil && self[prop] > 0) ||
79
- (old_status != nil && self[prop] != old_status[prop])
80
- end
81
- end
82
-
83
- # interaction with signaly.cz
84
- class SignalyChecker
85
-
86
- def initialize(config)
87
- @username = config.login
88
- @password = config.password
89
-
90
- @agent = Mechanize.new
91
-
92
- @dbg_print_pages = config.debug_output || false # print raw html of all request results?
93
-
94
- @checked_page = config.url || 'https://www.signaly.cz/'
95
- end
96
-
97
- USERMENU_XPATH = ".//div[contains(@class, 'section-usermenu')]"
98
-
99
- # takes user name and password; returns a page (logged-in) or throws
100
- # exception
101
- def login
102
- page = @agent.get(@checked_page)
103
- debug_page_print "front page", page
104
-
105
- login_form = page.form_with(:id => 'frm-loginForm')
106
- unless login_form
107
- raise "Login form not found on the index page!"
108
- end
109
- login_form['name'] = @username
110
- login_form['password'] = @password
111
-
112
- page = @agent.submit(login_form)
113
- debug_page_print "first logged in", page
114
-
115
- errors = page.search(".//div[@class='alert alert-error']")
116
- if errors.size > 0 then
117
- msg = ''
118
- errors.each {|e| msg += e.text.strip+"\n" }
119
- raise "Login to signaly.cz failed: "+msg
120
- end
121
-
122
- usermenu = page.search(USERMENU_XPATH)
123
- if usermenu.empty? then
124
- raise "User-menu not found. Login failed or signaly.cz UI changed again."
125
- end
126
-
127
- return page
128
- end
129
-
130
- def user_status
131
- status = SignalyStatus.new
132
- page = @agent.get(@checked_page)
133
- debug_page_print "user main page", page
134
-
135
- menu = page.search(USERMENU_XPATH)
136
-
137
- pm = menu.search(".//a[@href='/vzkazy']")
138
- status[:pm] = find_num(pm.text)
139
-
140
- notif = menu.search(".//a[@href='/ohlasky']")
141
- status[:notifications] = find_num(notif.text)
142
-
143
- inv = menu.search(".//a[@href='/vyzvy']")
144
- if inv then
145
- status[:invitations] = find_num(inv.text)
146
- end
147
-
148
- return status
149
- end
150
-
151
- private
152
-
153
- def debug_page_print(title, page)
154
- return if ! @dbg_print_pages
155
-
156
- STDERR.puts
157
- STDERR.puts ("# "+title).colorize(:yellow)
158
- STDERR.puts
159
- STDERR.puts page.search(".//div[@class='navbar navbar-fixed-top section-header']")
160
- STDERR.puts
161
- STDERR.puts "-" * 60
162
- STDERR.puts
163
- end
164
-
165
- # finds the first integer in the string and returns it
166
- def find_num(str, default=0)
167
- m = str.match /\d+/
168
- return m ? m[0].to_i : default
169
- end
170
- end
171
-
172
- # dispatches events to observers - subscribed notifiers
173
- class Notifier
174
- def initialize
175
- @outputters = {}
176
- end
177
-
178
- def add_outputter(outputter, *events)
179
- events.each do |event|
180
- @outputters[event] ||= []
181
- @outputters[event] << outputter
182
- end
183
- self
184
- end
185
-
186
- def emit(event, *args)
187
- if @outputters[event]
188
- @outputters[event].each {|o| o.output *args }
189
- end
190
- self
191
- end
192
- end
193
-
194
- class SignalyStatusOutputter
195
- def initialize(config)
196
- @config = config
197
- end
198
-
199
- def output(new_status, old_status)
200
- end
201
- end
202
-
203
- class ConsoleOutputter < SignalyStatusOutputter
204
- def output(new_status, old_status)
205
- print_line new_status, old_status
206
- set_console_title new_status
207
- end
208
-
209
- private
210
-
211
- def print_line(new_status, old_status)
212
- t = Time.now
213
-
214
- puts # start on a new line
215
- print t.strftime("%H:%M:%S")
216
-
217
- [:pm, :notifications, :invitations].each do |what|
218
- num = new_status[what].to_s
219
- if new_status.changed?(old_status, what) then
220
- num = num.colorize(:red)
221
- end
222
- print " #{what}: #{num}"
223
- end
224
- puts
225
- end
226
-
227
- # doesn't work....
228
- def set_console_title(status)
229
- t = "signaly-notify: #{status[:pm]}/#{status[:notifications]}"
230
- `echo -ne "\\033]0;#{t}\\007"`
231
- end
232
- end
233
-
234
- class LibNotifyOutputter < SignalyStatusOutputter
235
- def output(new_status, old_status)
236
- text = [:pm, :notifications, :invitations].collect do |what|
237
- "#{what}: #{new_status[what]}"
238
- end.join "\n"
239
-
240
- Libnotify.show(:body => text, :summary => "signaly.cz", :timeout => @config.notification_showtime)
241
- end
242
- end
243
-
244
- class GrowlOutputter < SignalyStatusOutputter
245
- def output(new_status, old_status)
246
- text = [:pm, :notifications, :invitations].collect do |what|
247
- "#{what}: #{new_status[what]}"
248
- end.join "\n"
249
-
250
- notif = Growl.new 'localhost', 'ruby-growl', 'GNTP'
251
- notif.notify('signaly.cz', 'signaly.cz', text)
252
- end
253
- end
254
-
255
- class SignalyNotifyApp
256
-
257
- DEFAULT_CONFIG_PATH = ".config/signaly-notify/config.yaml"
258
-
259
- attr_accessor :default_config
260
-
261
- def call(argv)
262
- options = process_options(argv)
263
- config = merge_structs(
264
- @default_config,
265
- config_file(options.config_file),
266
- options
267
- )
268
- ask_config config
269
-
270
- notifier = Notifier.new
271
- prepare_outputters config, notifier
272
-
273
- check_loop config, notifier
274
- end
275
-
276
- private
277
-
278
- # load configuration from config file
279
- def config_file(path=nil)
280
- path ||= File.join ENV['HOME'], DEFAULT_CONFIG_PATH
281
-
282
- if File.exist? path then
283
- cfg = YAML.load(File.open(path))
284
- # symbolize keys
285
- cfg = cfg.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
286
- return cfg
287
- end
288
-
289
- return nil
290
- end
291
-
292
- def process_options(argv)
293
- config = SNConfig.new
294
- optparse = OptionParser.new do |opts|
295
- opts.on "-u", "--user NAME", "user name used to log in" do |n|
296
- config.login = n
297
- end
298
-
299
- opts.on "-p", "--password WORD", "user's password" do |p|
300
- config.password = p
301
- end
302
-
303
- opts.separator "If you don't provide any of the options above, "\
304
- "the program will ask you to type the name and/or password on its start. "\
305
- "(And especially "\
306
- "for the password it's probably a bit safer to type it this way than "\
307
- "to type it on the commandline.)\n\n"
308
-
309
- opts.on "-s", "--sleep SECS", Integer, "how many seconds to sleep between two checks (default is #{config.sleep_seconds})" do |s|
310
- config.sleep_seconds = s
311
- end
312
-
313
- opts.on "-r", "--remind SECS", Integer, "if I don't bother about the contents I recieved a notification about, remind me after X seconds (default is #{config.remind_after}; set to 0 to disable)" do |s|
314
- config.remind_after = s
315
- end
316
-
317
- opts.on "--notify NOTIFIER", "choose visual notification engine (possible values are 'libnotify' and 'growl')" do |s|
318
- config.notify = s.to_sym
319
- end
320
-
321
- opts.on "--console-only", "don't display any visual notifications" do
322
- config.console_only = true
323
- end
324
-
325
- opts.on "-d", "--debug", "print debugging information to STDERR" do
326
- config.debug_output = true
327
- end
328
-
329
- opts.on "--url URL", "check URL different from the default (for developmeng)" do |s|
330
- config.url = s
331
- end
332
-
333
- opts.on "--skip-login", "don't login (for development)" do
334
- config.skip_login = true
335
- end
336
-
337
- opts.on "-h", "--help", "print this help" do
338
- puts opts
339
- exit 0
340
- end
341
-
342
- opts.on "-c", "--config FILE", "configuration file" do |f|
343
- config.config_file = f
344
- end
345
- end
346
- optparse.parse! argv
347
-
348
-
349
- unless argv.empty?
350
- STDERR.puts "Warning: unused commandline arguments: "+ARGV.join(', ')
351
- end
352
-
353
- return config
354
- end
355
-
356
- # ask the user for missing essential information
357
- def ask_config(config)
358
- cliio = HighLine.new
359
- config.login ||= cliio.ask("login: ")
360
- config.password ||= cliio.ask("password: ") {|q| q.echo = '*' }
361
- end
362
-
363
- # merges the config structs so that the last one
364
- # in the argument list has the highest priority and value nil is considered
365
- # empty
366
- def merge_structs(*structs)
367
- merged = structs.shift.dup
368
-
369
- merged.each_pair do |key, value|
370
- structs.each do |s|
371
- next if s.nil?
372
- if s[key] != nil then
373
- merged[key] = s[key]
374
- end
375
- end
376
- end
377
-
378
- return merged
379
- end
380
-
381
- def prepare_outputters(config, notifier)
382
- notifier.add_outputter ConsoleOutputter.new(config), :checked
383
-
384
- return if config.console_only
385
- return unless config.notify
386
-
387
- case config.notify.to_sym
388
- when :libnotify
389
- notifier.add_outputter LibNotifyOutputter.new(config), :changed, :remind
390
- when :growl
391
- notifier.add_outputter GrowlOutputter.new(config), :changed, :remind
392
- end
393
- end
394
-
395
- def check_loop(config, notifier)
396
- checker = SignalyChecker.new config
397
- checker.login unless config.skip_login
398
-
399
- old_status = status = nil
400
- last_reminder = 0
401
-
402
- loop do
403
- old_status = status
404
-
405
- begin
406
- status = checker.user_status
407
- rescue Exception, SocketError => e
408
- STDERR.puts "#{e.class}: #{e.message}"
409
- sleep config.sleep_seconds
410
- retry
411
- end
412
-
413
- notifier.emit :checked, status, old_status
414
-
415
- if status > old_status then
416
- # something new
417
- notifier.emit :changed, status, old_status
418
- last_reminder = Time.now.to_i
419
-
420
- elsif config.remind_after != 0 &&
421
- Time.now.to_i >= last_reminder + config.remind_after &&
422
- status.is_there_anything? then
423
- # nothing new, but pending content should be reminded
424
- notifier.emit :remind, status, old_status
425
- last_reminder = Time.now.to_i
426
- end
427
-
428
- sleep config.sleep_seconds
429
- end
430
- end
431
- end
432
-
433
-
434
-
435
- if $0 == __FILE__ then
436
- app = SignalyNotifyApp.new
437
- app.default_config = defaults
438
- app.call ARGV
439
- end
3
+ require 'signaly'
4
+ Signaly::NotifyApp.new.call ARGV
@@ -0,0 +1,90 @@
1
+ module Signaly
2
+ # interaction with signaly.cz
3
+ class Client
4
+
5
+ def initialize(config)
6
+ @username = config.login
7
+ @password = config.password
8
+
9
+ @agent = Mechanize.new
10
+
11
+ @dbg_print_pages = config.debug_output || false # print raw html of all request results?
12
+
13
+ @checked_page = config.url || 'https://www.signaly.cz/'
14
+ end
15
+
16
+ USERMENU_XPATH = ".//div[contains(@class, 'section-usermenu')]"
17
+
18
+ # takes user name and password; returns a page (logged-in) or throws
19
+ # exception
20
+ def login
21
+ page = @agent.get(@checked_page)
22
+ debug_page_print "front page", page
23
+
24
+ login_form = page.form_with(:id => 'frm-loginForm')
25
+ unless login_form
26
+ raise "Login form not found on the index page!"
27
+ end
28
+ login_form['name'] = @username
29
+ login_form['password'] = @password
30
+
31
+ page = @agent.submit(login_form)
32
+ debug_page_print "first logged in", page
33
+
34
+ errors = page.search(".//div[@class='alert alert-error']")
35
+ if errors.size > 0 then
36
+ msg = ''
37
+ errors.each {|e| msg += e.text.strip+"\n" }
38
+ raise "Login to signaly.cz failed: "+msg
39
+ end
40
+
41
+ usermenu = page.search(USERMENU_XPATH)
42
+ if usermenu.empty? then
43
+ raise "User-menu not found. Login failed or signaly.cz UI changed again."
44
+ end
45
+
46
+ return page
47
+ end
48
+
49
+ def user_status
50
+ status = Status.new
51
+ page = @agent.get(@checked_page)
52
+ debug_page_print "user main page", page
53
+
54
+ menu = page.search(USERMENU_XPATH)
55
+
56
+ pm = menu.search(".//a[@href='/vzkazy']")
57
+ status[:pm] = find_num(pm.text)
58
+
59
+ notif = menu.search(".//a[@href='/ohlasky']")
60
+ status[:notifications] = find_num(notif.text)
61
+
62
+ inv = menu.search(".//a[@href='/vyzvy']")
63
+ if inv then
64
+ status[:invitations] = find_num(inv.text)
65
+ end
66
+
67
+ return status
68
+ end
69
+
70
+ private
71
+
72
+ def debug_page_print(title, page)
73
+ return if ! @dbg_print_pages
74
+
75
+ STDERR.puts
76
+ STDERR.puts ("# "+title).colorize(:yellow)
77
+ STDERR.puts
78
+ STDERR.puts page.search(".//div[@class='navbar navbar-fixed-top section-header']")
79
+ STDERR.puts
80
+ STDERR.puts "-" * 60
81
+ STDERR.puts
82
+ end
83
+
84
+ # finds the first integer in the string and returns it
85
+ def find_num(str, default=0)
86
+ m = str.match /\d+/
87
+ return m ? m[0].to_i : default
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,23 @@
1
+ module Signaly
2
+ # dispatches events to observers - subscribed notifiers
3
+ class Notifier
4
+ def initialize
5
+ @outputters = {}
6
+ end
7
+
8
+ def add_outputter(outputter, *events)
9
+ events.each do |event|
10
+ @outputters[event] ||= []
11
+ @outputters[event] << outputter
12
+ end
13
+ self
14
+ end
15
+
16
+ def emit(event, *args)
17
+ if @outputters[event]
18
+ @outputters[event].each {|o| o.output *args }
19
+ end
20
+ self
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,209 @@
1
+ module Signaly
2
+ class NotifyApp
3
+
4
+ SNConfig = Struct.new(:sleep_seconds, # between checks
5
+ :remind_after, # the first notification
6
+ :notification_showtime,
7
+ :debug_output,
8
+ :login,
9
+ :password,
10
+ :url, # of the checked page
11
+ :skip_login,
12
+ :config_file,
13
+ :console_only,
14
+ :notify)
15
+
16
+ DEFAULT_CONFIG_PATH = ".config/signaly-notify/config.yaml"
17
+
18
+ def call(argv)
19
+ options = process_options(argv)
20
+ config = merge_structs(
21
+ default_config,
22
+ config_file(options.config_file),
23
+ options
24
+ )
25
+ ask_config config
26
+
27
+ notifier = Notifier.new
28
+ prepare_outputters config, notifier
29
+
30
+ check_loop config, notifier
31
+ end
32
+
33
+ private
34
+
35
+ # load configuration from config file
36
+ def config_file(path=nil)
37
+ path ||= File.join ENV['HOME'], DEFAULT_CONFIG_PATH
38
+
39
+ if File.exist? path then
40
+ cfg = YAML.load(File.open(path))
41
+ # symbolize keys
42
+ cfg = cfg.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
43
+ return cfg
44
+ end
45
+
46
+ return nil
47
+ end
48
+
49
+ def process_options(argv)
50
+ config = SNConfig.new
51
+ optparse = OptionParser.new do |opts|
52
+ opts.on "-u", "--user NAME", "user name used to log in" do |n|
53
+ config.login = n
54
+ end
55
+
56
+ opts.on "-p", "--password WORD", "user's password" do |p|
57
+ config.password = p
58
+ end
59
+
60
+ opts.separator "If you don't provide any of the options above, "\
61
+ "the program will ask you to type the name and/or password on its start. "\
62
+ "(And especially "\
63
+ "for the password it's probably a bit safer to type it this way than "\
64
+ "to type it on the commandline.)\n\n"
65
+
66
+ opts.on "-s", "--sleep SECS", Integer, "how many seconds to sleep between two checks (default is #{config.sleep_seconds})" do |s|
67
+ config.sleep_seconds = s
68
+ end
69
+
70
+ opts.on "-r", "--remind SECS", Integer, "if I don't bother about the contents I recieved a notification about, remind me after X seconds (default is #{config.remind_after}; set to 0 to disable)" do |s|
71
+ config.remind_after = s
72
+ end
73
+
74
+ opts.on "--notify NOTIFIER", "choose visual notification engine (possible values are 'libnotify' and 'growl')" do |s|
75
+ config.notify = s.to_sym
76
+ end
77
+
78
+ opts.on "--console-only", "don't display any visual notifications" do
79
+ config.console_only = true
80
+ end
81
+
82
+ opts.on "-d", "--debug", "print debugging information to STDERR" do
83
+ config.debug_output = true
84
+ end
85
+
86
+ opts.on "--url URL", "check URL different from the default (for developmeng)" do |s|
87
+ config.url = s
88
+ end
89
+
90
+ opts.on "--skip-login", "don't login (for development)" do
91
+ config.skip_login = true
92
+ end
93
+
94
+ opts.on "-h", "--help", "print this help" do
95
+ puts opts
96
+ exit 0
97
+ end
98
+
99
+ opts.on "-c", "--config FILE", "configuration file" do |f|
100
+ config.config_file = f
101
+ end
102
+ end
103
+ optparse.parse! argv
104
+
105
+
106
+ unless argv.empty?
107
+ STDERR.puts "Warning: unused commandline arguments: "+ARGV.join(', ')
108
+ end
109
+
110
+ return config
111
+ end
112
+
113
+ # ask the user for missing essential information
114
+ def ask_config(config)
115
+ cliio = HighLine.new
116
+ config.login ||= cliio.ask("login: ")
117
+ config.password ||= cliio.ask("password: ") {|q| q.echo = '*' }
118
+ end
119
+
120
+ # merges the config structs so that the last one
121
+ # in the argument list has the highest priority and value nil is considered
122
+ # empty
123
+ def merge_structs(*structs)
124
+ merged = structs.shift.dup
125
+
126
+ merged.each_pair do |key, value|
127
+ structs.each do |s|
128
+ next if s.nil?
129
+ if s[key] != nil then
130
+ merged[key] = s[key]
131
+ end
132
+ end
133
+ end
134
+
135
+ return merged
136
+ end
137
+
138
+ def prepare_outputters(config, notifier)
139
+ notifier.add_outputter ConsoleOutputter.new(config), :checked
140
+
141
+ return if config.console_only
142
+ return unless config.notify
143
+
144
+ case config.notify.to_sym
145
+ when :libnotify
146
+ notifier.add_outputter LibNotifyOutputter.new(config), :changed, :remind
147
+ when :growl
148
+ notifier.add_outputter GrowlOutputter.new(config), :changed, :remind
149
+ end
150
+ end
151
+
152
+ def check_loop(config, notifier)
153
+ checker = Client.new config
154
+ checker.login unless config.skip_login
155
+
156
+ old_status = status = nil
157
+ last_reminder = 0
158
+
159
+ loop do
160
+ old_status = status
161
+
162
+ begin
163
+ status = checker.user_status
164
+ rescue Exception, SocketError => e
165
+ STDERR.puts "#{e.class}: #{e.message}"
166
+ sleep config.sleep_seconds
167
+ retry
168
+ end
169
+
170
+ notifier.emit :checked, status, old_status
171
+
172
+ if status > old_status then
173
+ # something new
174
+ notifier.emit :changed, status, old_status
175
+ last_reminder = Time.now.to_i
176
+
177
+ elsif config.remind_after != 0 &&
178
+ Time.now.to_i >= last_reminder + config.remind_after &&
179
+ status.is_there_anything? then
180
+ # nothing new, but pending content should be reminded
181
+ notifier.emit :remind, status, old_status
182
+ last_reminder = Time.now.to_i
183
+ end
184
+
185
+ sleep config.sleep_seconds
186
+ end
187
+ end
188
+
189
+ def default_config
190
+ defaults = SNConfig.new
191
+ # how many seconds between two checks of the site
192
+ defaults.sleep_seconds = 60
193
+ # if there is some pending content and I don't look at it,
194
+ # remind me after X seconds
195
+ defaults.remind_after = 60*5
196
+ # for how long time the notification shows up
197
+ defaults.notification_showtime = 10
198
+ defaults.debug_output = false
199
+ defaults.password = nil
200
+ # use first available visual notification engine
201
+ if defined? Libnotify
202
+ defaults.notify = :libnotify
203
+ elsif defined? Growl
204
+ defaults.notify = :growl
205
+ end
206
+ return defaults
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,35 @@
1
+ module Signaly
2
+ # how many PMs, notifications, invitations a user has at a time
3
+ class Status < Struct.new(:pm, :notifications, :invitations)
4
+
5
+ def initialize(pm=0, notifications=0, invitations=0)
6
+ super(pm, notifications, invitations)
7
+ end
8
+
9
+ def is_there_anything?
10
+ self.each_pair {|k,v| return true if v > 0 }
11
+ return false
12
+ end
13
+
14
+ # does self have anything new compared with other?
15
+ def >(other)
16
+ if other.nil?
17
+ return true
18
+ end
19
+
20
+ [:pm, :notifications, :invitations].each do |prop|
21
+ if send(prop) > other.send(prop) then
22
+ return true
23
+ end
24
+ end
25
+
26
+ return false
27
+ end
28
+
29
+ # is there any change between old_status and self in property prop?
30
+ def changed?(old_status, prop)
31
+ (old_status == nil && self[prop] > 0) ||
32
+ (old_status != nil && self[prop] != old_status[prop])
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,62 @@
1
+ module Signaly
2
+ class StatusOutputter
3
+ def initialize(config)
4
+ @config = config
5
+ end
6
+
7
+ def output(new_status, old_status)
8
+ end
9
+ end
10
+
11
+ class ConsoleOutputter < StatusOutputter
12
+ def output(new_status, old_status)
13
+ print_line new_status, old_status
14
+ set_console_title new_status
15
+ end
16
+
17
+ private
18
+
19
+ def print_line(new_status, old_status)
20
+ t = Time.now
21
+
22
+ puts # start on a new line
23
+ print t.strftime("%H:%M:%S")
24
+
25
+ [:pm, :notifications, :invitations].each do |what|
26
+ num = new_status[what].to_s
27
+ if new_status.changed?(old_status, what) then
28
+ num = num.colorize(:red)
29
+ end
30
+ print " #{what}: #{num}"
31
+ end
32
+ puts
33
+ end
34
+
35
+ # doesn't work....
36
+ def set_console_title(status)
37
+ t = "signaly-notify: #{status[:pm]}/#{status[:notifications]}"
38
+ `echo -ne "\\033]0;#{t}\\007"`
39
+ end
40
+ end
41
+
42
+ class LibNotifyOutputter < StatusOutputter
43
+ def output(new_status, old_status)
44
+ text = [:pm, :notifications, :invitations].collect do |what|
45
+ "#{what}: #{new_status[what]}"
46
+ end.join "\n"
47
+
48
+ Libnotify.show(:body => text, :summary => "signaly.cz", :timeout => @config.notification_showtime)
49
+ end
50
+ end
51
+
52
+ class GrowlOutputter < StatusOutputter
53
+ def output(new_status, old_status)
54
+ text = [:pm, :notifications, :invitations].collect do |what|
55
+ "#{what}: #{new_status[what]}"
56
+ end.join "\n"
57
+
58
+ notif = Growl.new 'localhost', 'ruby-growl', 'GNTP'
59
+ notif.notify('signaly.cz', 'signaly.cz', text)
60
+ end
61
+ end
62
+ end
data/lib/signaly.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'mechanize'
2
+ require 'colorize'
3
+ require 'highline'
4
+ require 'optparse'
5
+ require 'yaml'
6
+
7
+ # optional gems for visual notifications
8
+ begin
9
+ require 'libnotify'
10
+ rescue LoadError
11
+ end
12
+
13
+ begin
14
+ require 'ruby-growl'
15
+ rescue LoadError
16
+ end
17
+
18
+
19
+ %w(
20
+ client
21
+ notifier
22
+ notify_app
23
+ status_outputter
24
+ status
25
+ ).each {|l| require_relative "signaly/#{l}" }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: signaly-notify
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakub Pavlík
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-30 00:00:00.000000000 Z
11
+ date: 2015-12-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mechanize
@@ -63,6 +63,12 @@ extensions: []
63
63
  extra_rdoc_files: []
64
64
  files:
65
65
  - bin/signaly-notify.rb
66
+ - lib/signaly.rb
67
+ - lib/signaly/client.rb
68
+ - lib/signaly/notifier.rb
69
+ - lib/signaly/notify_app.rb
70
+ - lib/signaly/status.rb
71
+ - lib/signaly/status_outputter.rb
66
72
  homepage: http://github.com/igneus/signaly-notify
67
73
  licenses:
68
74
  - LGPL-3.0