signaly-notify 0.0.3
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 +7 -0
- data/bin/signaly-notify.rb +345 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 13a9873131db788193be40e2c480a033c8a065e0
|
4
|
+
data.tar.gz: ba7fef24bbc5bce843e84aeaa323ef86a6f608e5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 33176d90d86058ca014e1109fe2d8566c426e3350751161eaed98bf66a47af72b0b101c86eacfe9d1328284556e0a1426b6175c1b923a857642fb0c1121f0b25
|
7
|
+
data.tar.gz: 7c864244d36b727f8fe02f846d8642db84c4deeda7f342aa34facf4dbd8d9713fc4a65115da3b91b606467b7a358677d3560380f281fa409aa4bb9711a121068
|
@@ -0,0 +1,345 @@
|
|
1
|
+
#!/bin/env ruby
|
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 'libnotify' # visual notification
|
7
|
+
require 'optparse'
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
SNConfig = Struct.new(:sleep_seconds,
|
11
|
+
:remind_after,
|
12
|
+
:notification_showtime,
|
13
|
+
:debug_output,
|
14
|
+
:login,
|
15
|
+
:password)
|
16
|
+
|
17
|
+
config_layers = []
|
18
|
+
|
19
|
+
config = SNConfig.new
|
20
|
+
defaults = SNConfig.new
|
21
|
+
|
22
|
+
config_layers << defaults
|
23
|
+
config_layers << config
|
24
|
+
|
25
|
+
# merges the config structs so that the last one
|
26
|
+
# in the argument list has the highest priority and value nil is considered
|
27
|
+
# empty
|
28
|
+
def merge_structs(*structs)
|
29
|
+
merged = structs.first.dup
|
30
|
+
|
31
|
+
merged.each_pair do |key, value|
|
32
|
+
k = key.to_s # a sym normally; but we also want to merge a normal Hash received from Yaml
|
33
|
+
structs.each do |s|
|
34
|
+
if s[k] != nil then
|
35
|
+
merged[k] = s[k]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# set config defaults:
|
42
|
+
# how many seconds between two checks of the site
|
43
|
+
defaults.sleep_seconds = 60
|
44
|
+
# if there is some pending content and I don't look at it,
|
45
|
+
# remind me after X seconds
|
46
|
+
defaults.remind_after = 60*5
|
47
|
+
# for how long time the notification shows up
|
48
|
+
defaults.notification_showtime = 10
|
49
|
+
defaults.debug_output = false
|
50
|
+
defaults.password = nil
|
51
|
+
|
52
|
+
|
53
|
+
# finds the first integer in the string and returns it
|
54
|
+
# or returns 0
|
55
|
+
def find_num(str)
|
56
|
+
m = str.match /\d+/
|
57
|
+
|
58
|
+
unless m
|
59
|
+
return 0
|
60
|
+
end
|
61
|
+
|
62
|
+
return m[0].to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
class SignalyStatus < Struct.new(:pm, :notifications, :invitations)
|
66
|
+
|
67
|
+
def initialize(pm=0, notifications=0, invitations=0)
|
68
|
+
super(pm, notifications, invitations)
|
69
|
+
end
|
70
|
+
|
71
|
+
def is_there_anything?
|
72
|
+
self.each_pair {|k,v| return true if v > 0 }
|
73
|
+
return false
|
74
|
+
end
|
75
|
+
|
76
|
+
# utility function to handle the statuses:
|
77
|
+
|
78
|
+
def changed?(old_status, item)
|
79
|
+
(old_status == nil && self[item] > 0) ||
|
80
|
+
(old_status != nil && self[item] != old_status[item])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class SignalyChecker
|
85
|
+
# interaction with signaly.cz
|
86
|
+
|
87
|
+
def initialize(username, password, dbg_print=false)
|
88
|
+
@username = username
|
89
|
+
@password = password
|
90
|
+
|
91
|
+
@agent = Mechanize.new
|
92
|
+
|
93
|
+
@dbg_print_pages = dbg_print # print raw html of all request results?
|
94
|
+
|
95
|
+
login
|
96
|
+
end
|
97
|
+
|
98
|
+
USERMENU_XPATH = ".//div[contains(@class, 'section-usermenu')]"
|
99
|
+
|
100
|
+
# takes user name and password; returns a page (logged-in) or throws
|
101
|
+
# exception
|
102
|
+
def login
|
103
|
+
page = @agent.get('https://www.signaly.cz/')
|
104
|
+
debug_page_print "front page", page
|
105
|
+
|
106
|
+
login_form = page.form_with(:id => 'frm-loginForm')
|
107
|
+
unless login_form
|
108
|
+
raise "Login form not found on the index page!"
|
109
|
+
end
|
110
|
+
login_form['name'] = @username
|
111
|
+
login_form['password'] = @password
|
112
|
+
|
113
|
+
page = @agent.submit(login_form)
|
114
|
+
debug_page_print "first logged in", page
|
115
|
+
|
116
|
+
errors = page.search(".//div[@class='alert alert-error']")
|
117
|
+
if errors.size > 0 then
|
118
|
+
msg = ''
|
119
|
+
errors.each {|e| msg += e.text.strip+"\n" }
|
120
|
+
raise "Login to signaly.cz failed: "+msg
|
121
|
+
end
|
122
|
+
|
123
|
+
usermenu = page.search(USERMENU_XPATH)
|
124
|
+
if usermenu.empty? then
|
125
|
+
raise "User-menu not found. Login failed or signaly.cz UI changed again."
|
126
|
+
end
|
127
|
+
|
128
|
+
return page
|
129
|
+
end
|
130
|
+
|
131
|
+
def user_status
|
132
|
+
status = SignalyStatus.new
|
133
|
+
page = @agent.get('https://www.signaly.cz/')
|
134
|
+
debug_page_print "user main page", page
|
135
|
+
|
136
|
+
menu = page.search(USERMENU_XPATH)
|
137
|
+
|
138
|
+
pm = menu.search(".//a[@href='/vzkazy']")
|
139
|
+
status[:pm] = find_num(pm.text)
|
140
|
+
|
141
|
+
notif = menu.search(".//a[@href='/ohlasky']")
|
142
|
+
status[:notifications] = find_num(notif.text)
|
143
|
+
|
144
|
+
inv = menu.search(".//a[@href='/vyzvy']")
|
145
|
+
if inv then
|
146
|
+
status[:invitations] = find_num(inv.text)
|
147
|
+
end
|
148
|
+
|
149
|
+
return status
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def debug_page_print(title, page)
|
155
|
+
return if ! @dbg_print_pages
|
156
|
+
|
157
|
+
STDERR.puts
|
158
|
+
STDERR.puts(("# "+title).colorize(:yellow))
|
159
|
+
STDERR.puts
|
160
|
+
STDERR.puts page.search(".//div[@class='navbar navbar-fixed-top section-header']")
|
161
|
+
STDERR.puts
|
162
|
+
STDERR.puts("-" * 60)
|
163
|
+
STDERR.puts
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class SignalyStatusOutputter
|
168
|
+
def initialize(config)
|
169
|
+
@config = config
|
170
|
+
end
|
171
|
+
|
172
|
+
def output(new_status, old_status)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class ConsoleOutputter < SignalyStatusOutputter
|
177
|
+
def output(new_status, old_status)
|
178
|
+
print_line new_status, old_status
|
179
|
+
set_console_title new_status
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def print_line(new_status, old_status)
|
185
|
+
t = Time.now
|
186
|
+
|
187
|
+
puts # start on a new line
|
188
|
+
print t.strftime("%H:%M:%S")
|
189
|
+
|
190
|
+
ms = new_status[:pm].to_s
|
191
|
+
if new_status.changed?(old_status, :pm) then
|
192
|
+
ms = ms.colorize(:red)
|
193
|
+
end
|
194
|
+
print " messages: "+ms
|
195
|
+
|
196
|
+
ns = new_status[:notifications].to_s
|
197
|
+
if new_status.changed?(old_status, :notifications) then
|
198
|
+
ns = ns.colorize(:red)
|
199
|
+
end
|
200
|
+
print " notifications: "+ns
|
201
|
+
|
202
|
+
is = new_status[:invitations].to_s
|
203
|
+
if new_status.changed?(old_status, :invitations) then
|
204
|
+
is = is.colorize(:red)
|
205
|
+
end
|
206
|
+
puts " invitations: "+is
|
207
|
+
end
|
208
|
+
|
209
|
+
# doesn't work....
|
210
|
+
def set_console_title(status)
|
211
|
+
t = "signaly-notify: #{status[:pm]}/#{status[:notifications]}"
|
212
|
+
`echo -ne "\\033]0;#{t}\\007"`
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
class LibNotifyOutputter < SignalyStatusOutputter
|
217
|
+
def output(new_status, old_status)
|
218
|
+
send_notification new_status
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def send_notification(status)
|
224
|
+
ms = status[:pm].to_s
|
225
|
+
ns = status[:notifications].to_s
|
226
|
+
is = status[:invitations].to_s
|
227
|
+
text = "pm: #{ms}\nnotifications: #{ns}\ninvitations: #{is}"
|
228
|
+
|
229
|
+
Libnotify.show(:body => text, :summary => "signaly.cz", :timeout => @config.notification_showtime)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
############################################# main
|
235
|
+
|
236
|
+
# find default config
|
237
|
+
config_file = nil
|
238
|
+
default_config_path = "#{ENV['HOME']}/.config/signaly-notify/config.yaml"
|
239
|
+
if default_config_path then
|
240
|
+
config_file = default_config_path
|
241
|
+
end
|
242
|
+
|
243
|
+
# process options
|
244
|
+
optparse = OptionParser.new do |opts|
|
245
|
+
opts.on "-u", "--user NAME", "user name used to log in" do |n|
|
246
|
+
config.login = n
|
247
|
+
end
|
248
|
+
|
249
|
+
opts.on "-p", "--password WORD", "user's password" do |p|
|
250
|
+
config.password = p
|
251
|
+
end
|
252
|
+
|
253
|
+
opts.separator "If you don't provide any of the options above, "\
|
254
|
+
"the program will ask you to type the name and/or password on its start. "\
|
255
|
+
"(And especially "\
|
256
|
+
"for the password it's probably a bit safer to type it this way than "\
|
257
|
+
"to type it on the commandline.)\n\n"
|
258
|
+
|
259
|
+
opts.on "-s", "--sleep SECS", Integer, "how many seconds to sleep between two checks (default is #{config.sleep_seconds})" do |s|
|
260
|
+
config.sleep_seconds = s
|
261
|
+
end
|
262
|
+
|
263
|
+
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|
|
264
|
+
config.remind_after = s
|
265
|
+
end
|
266
|
+
|
267
|
+
opts.on "-d", "--debug", "print debugging information to STDERR" do
|
268
|
+
config.debug_output = true
|
269
|
+
end
|
270
|
+
|
271
|
+
opts.on "-h", "--help", "print this help" do
|
272
|
+
puts opts
|
273
|
+
exit 0
|
274
|
+
end
|
275
|
+
|
276
|
+
opts.on "-c", "--config FILE", "configuration file" do |f|
|
277
|
+
config_file = f
|
278
|
+
end
|
279
|
+
end
|
280
|
+
optparse.parse!
|
281
|
+
|
282
|
+
if config_file then
|
283
|
+
config_layers << YAML.load(File.open(config_file))
|
284
|
+
end
|
285
|
+
|
286
|
+
config = merge_structs(*config_layers)
|
287
|
+
|
288
|
+
unless ARGV.empty?
|
289
|
+
puts "Warning: unused commandline arguments: "+ARGV.join(', ')
|
290
|
+
end
|
291
|
+
|
292
|
+
# ask the user for missing essential information
|
293
|
+
cliio = HighLine.new
|
294
|
+
|
295
|
+
if !config.login then
|
296
|
+
config.login = cliio.ask("login: ")
|
297
|
+
end
|
298
|
+
|
299
|
+
if !config.password then
|
300
|
+
config.password = cliio.ask("password: ") {|q| q.echo = '*' }
|
301
|
+
end
|
302
|
+
|
303
|
+
checker = SignalyChecker.new config.login, config.password, config.debug_output
|
304
|
+
|
305
|
+
old_status = status = nil
|
306
|
+
last_reminder = 0
|
307
|
+
|
308
|
+
co = ConsoleOutputter.new config
|
309
|
+
lno = LibNotifyOutputter.new config
|
310
|
+
|
311
|
+
loop do
|
312
|
+
old_status = status
|
313
|
+
|
314
|
+
begin
|
315
|
+
status = checker.user_status
|
316
|
+
rescue Exception => e
|
317
|
+
Libnotify.show(:body => e.message,
|
318
|
+
:summary => "signaly.cz: ERROR",
|
319
|
+
:timeout => 20)
|
320
|
+
sleep 21
|
321
|
+
raise
|
322
|
+
end
|
323
|
+
|
324
|
+
# print each update to the console:
|
325
|
+
co.output status, old_status
|
326
|
+
|
327
|
+
# send a notification only if there is something interesting:
|
328
|
+
|
329
|
+
if old_status == nil ||
|
330
|
+
(status[:pm] > old_status[:pm] ||
|
331
|
+
status[:notifications] > old_status[:notifications]) then
|
332
|
+
# something new
|
333
|
+
lno.output status, old_status
|
334
|
+
last_reminder = Time.now.to_i
|
335
|
+
|
336
|
+
elsif config.remind_after != 0 &&
|
337
|
+
Time.now.to_i >= last_reminder + config.remind_after &&
|
338
|
+
status.is_there_anything? then
|
339
|
+
# nothing new, but pending content should be reminded
|
340
|
+
lno.output status, old_status
|
341
|
+
last_reminder = Time.now.to_i
|
342
|
+
end
|
343
|
+
|
344
|
+
sleep config.sleep_seconds
|
345
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: signaly-notify
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jakub Pavlík
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mechanize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: colorize
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: highline
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: libnotify
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: "signaly-notify.rb is a simple script \nlogging in with your user data
|
70
|
+
to the social network https://signaly.cz \nand notifying you - by the means of printing
|
71
|
+
to the console \nas well as sending a visual notification using libnotify - \nwhen
|
72
|
+
something new happens.\nCurrently it only detects pending private messages and notifications."
|
73
|
+
email: jkb.pavlik@gmail.com
|
74
|
+
executables:
|
75
|
+
- signaly-notify.rb
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- bin/signaly-notify.rb
|
80
|
+
homepage: http://github.com/igneus/signaly-notify
|
81
|
+
licenses:
|
82
|
+
- LGPL-3.0
|
83
|
+
- MIT
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.0.14
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: notification script for signaly.cz (Czech christian social network)
|
105
|
+
test_files: []
|