signaly-notify 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|