signaly-notify 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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