wcc 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +16 -16
- data/doc/Filters.md +37 -0
- data/lib/wcc/filter.rb +40 -0
- data/lib/wcc/mail.rb +60 -0
- data/lib/wcc/site.rb +75 -0
- data/lib/wcc.rb +59 -207
- metadata +8 -4
data/README.md
CHANGED
@@ -8,11 +8,21 @@ from old content to new content so minor changes produce only few lines of text
|
|
8
8
|
Note: wcc relies on native `diff` command to produce the unified diff shown in mails -
|
9
9
|
plans are to remove this dependency by using [something like this](https://github.com/samg/diffy) later...
|
10
10
|
|
11
|
+
Setup
|
12
|
+
-----
|
13
|
+
|
14
|
+
You need Ruby (preferably version 1.8.7) and Rubygems installed
|
15
|
+
(consider using [rvm](http://beginrescueend.com/)). Install wcc:
|
16
|
+
|
17
|
+
gem install wcc
|
18
|
+
|
19
|
+
(If you *don't* use [rvm](http://beginrescueend.com/) you should add a 'sudo'.)
|
20
|
+
|
11
21
|
Usage
|
12
22
|
-----
|
13
23
|
|
14
|
-
|
15
|
-
|
24
|
+
The installed 'wcc' gem provides a ´wcc´ binary on the command line.
|
25
|
+
It can invoked by hand or automatically via *cron* on a server environment.
|
16
26
|
|
17
27
|
For using wcc you need to specify some options:
|
18
28
|
|
@@ -20,7 +30,7 @@ For using wcc you need to specify some options:
|
|
20
30
|
* or in a configuration file in [YAML](https://secure.wikimedia.org/wikipedia/en/wiki/YAML) format
|
21
31
|
|
22
32
|
The location of the configuration file (usually called 'conf.yml' or something like this)
|
23
|
-
can itself be given on command line as last argument. Each option has
|
33
|
+
can itself be given on command line as last argument. Each option has a hard-coded default
|
24
34
|
(e.g. the configuration file name is assumed to be './conf.yml'). Command line options
|
25
35
|
overwrite configuration file entries.
|
26
36
|
|
@@ -32,16 +42,6 @@ An example crontab entry that runs wcc every 10 minutes might look like this:
|
|
32
42
|
|
33
43
|
*/10 * * * * root cd /path/to/dir/with/conf;./wcc
|
34
44
|
|
35
|
-
By default wcc only outputs ERROR messages to avoid your cron daemon spammin' around.
|
36
|
-
It is recommended to place 'conf.yml' (and optionally the 'filter.d') within
|
37
|
-
directory and use `cd` in cron entry.
|
38
|
-
|
39
|
-
Setup
|
40
|
-
-----
|
41
|
-
|
42
|
-
You need Ruby (preferably version 1.8.7) and Rubygems installed
|
43
|
-
(consider using [rvm](http://beginrescueend.com/)). Install wcc:
|
44
|
-
|
45
|
-
gem install wcc
|
46
|
-
|
47
|
-
(If you *don't* use [rvm](http://beginrescueend.com/) you should add a 'sudo'.)
|
45
|
+
By default wcc only outputs ERROR and FATAL messages to avoid your cron daemon spammin' around.
|
46
|
+
It is recommended to place 'conf.yml' (and optionally the 'filter.d' and 'template.d') within
|
47
|
+
a separate directory and use `cd` in cron entry.
|
data/doc/Filters.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Filter-API
|
2
|
+
==========
|
3
|
+
|
4
|
+
Filters formally are Ruby Proc blocks (mostly contained in small ruby
|
5
|
+
scripts) referenced by an ID that decide upon a given website diff whether
|
6
|
+
to notify the user (by email) or not.
|
7
|
+
|
8
|
+
These Proc blocks get registered using `Filter.add` with a given ID (a
|
9
|
+
string, not a :symbol to prevent some problems).
|
10
|
+
|
11
|
+
wcc provides an autoloading mechanism that loads all ruby files contained in
|
12
|
+
a specific directory - the *filter.d* - via `require`. The filter file
|
13
|
+
should contain some bootstrap code that may rely only on the *Filter* class
|
14
|
+
out of wcc:
|
15
|
+
|
16
|
+
Filter.add 'my_custom_id' do |data,arguments|
|
17
|
+
# do your filter magic here
|
18
|
+
# and return a boolean
|
19
|
+
end
|
20
|
+
|
21
|
+
The format of the `data` may change over time, currently it's a string
|
22
|
+
containing all lines separated by newline of the unified diff (raw from unix
|
23
|
+
diff). The arguments are a - possibly empty - hash directly passed in from
|
24
|
+
the configuration file to parameterize the filter (e.g. ignoring all diffs
|
25
|
+
shorter than let's say 10 lines).
|
26
|
+
|
27
|
+
The output of this filter should be boolean true or false, indicating that
|
28
|
+
an email should be sent or not.
|
29
|
+
|
30
|
+
In the configuration file a filter is referenced like this:
|
31
|
+
|
32
|
+
sites:
|
33
|
+
- url: ...
|
34
|
+
:
|
35
|
+
filters:
|
36
|
+
- test
|
37
|
+
- paramtest: { minLines: 10 }
|
data/lib/wcc/filter.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
module WCC
|
3
|
+
class FilterRef
|
4
|
+
attr_reader :id, :arguments
|
5
|
+
|
6
|
+
def initialize(id, arguments = {})
|
7
|
+
@id = id
|
8
|
+
@arguments = arguments
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s; @id end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Filter
|
15
|
+
@@filters = {}
|
16
|
+
|
17
|
+
def self.add(id, &block)
|
18
|
+
WCC.logger.info "Adding filter '#{id}'"
|
19
|
+
@@filters[id] = block
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.accept(data, filters)
|
23
|
+
return true if filters.nil?
|
24
|
+
|
25
|
+
WCC.logger.info "Testing with filters: #{filters.join(', ')}"
|
26
|
+
filters.each do |fref|
|
27
|
+
block = @@filters[fref.id]
|
28
|
+
if block.nil?
|
29
|
+
WCC.logger.error "Requested filter '#{fref.id}' not found, skipping it."
|
30
|
+
next
|
31
|
+
end
|
32
|
+
if not block.call(data, fref.arguments)
|
33
|
+
WCC.logger.info "Filter '#{fref.id}' failed!"
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/wcc/mail.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
module WCC
|
3
|
+
# An email address container with internal conversion
|
4
|
+
# routines.
|
5
|
+
class MailAddress
|
6
|
+
def initialize(email)
|
7
|
+
email = email.to_s if email.is_a?(MailAddress)
|
8
|
+
@email = email.strip
|
9
|
+
end
|
10
|
+
|
11
|
+
# Extract the 'name' out of an mail address:
|
12
|
+
# "Me <me@example.org>" -> "Me"
|
13
|
+
# "me2@example.org" -> "me2"
|
14
|
+
#
|
15
|
+
# @return [String] name
|
16
|
+
def name
|
17
|
+
if @email =~ /^[\w\s]+<.+@[^@]+>$/
|
18
|
+
@email.gsub(/<.+?>/, '').strip
|
19
|
+
else
|
20
|
+
@email.split("@")[0...-1].join("@")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return the real mail address:
|
25
|
+
# "Me <me@example.org>" -> "me@example.org"
|
26
|
+
# "me2@example.org" -> "me2@example.org"
|
27
|
+
#
|
28
|
+
# @return [String] mail address
|
29
|
+
def address
|
30
|
+
if @email =~ /^[\w\s]+<.+@[^@]+>$/
|
31
|
+
@email.match(/<([^>]+@[^@>]+)>/)[1]
|
32
|
+
else
|
33
|
+
@email
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s; @email end
|
38
|
+
end
|
39
|
+
|
40
|
+
# SmtpMailer is a specific implementation of an mail deliverer that
|
41
|
+
# does plain SMTP to host:port using [Net::SMTP].
|
42
|
+
class SmtpMailer
|
43
|
+
def initialize(host, port)
|
44
|
+
@host = host
|
45
|
+
@port = port
|
46
|
+
end
|
47
|
+
|
48
|
+
def send(data, template, from, tos = [])
|
49
|
+
Net::SMTP.start(@host, @port) do |smtp|
|
50
|
+
tos.each do |to|
|
51
|
+
# eval ERB
|
52
|
+
msg = template.result(binding)
|
53
|
+
smtp.send_message(msg, from.address, to.address)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue
|
57
|
+
WCC.logger.fatal "Cannot send mails via SMTP to #{@host}:#{@port} : #{$!.to_s}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/wcc/site.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
module WCC
|
3
|
+
class Site
|
4
|
+
attr_reader :uri, :emails, :filters, :auth, :cookie, :id
|
5
|
+
|
6
|
+
def initialize(url, strip_html, emails, filters, auth, cookie)
|
7
|
+
@uri = URI.parse(url)
|
8
|
+
@strip_html = strip_html
|
9
|
+
@emails = emails.is_a?(Array) ? emails : [emails]
|
10
|
+
@filters = filters.is_a?(Array) ? filters : [filters]
|
11
|
+
@auth = auth
|
12
|
+
@cookie = cookie
|
13
|
+
@id = Digest::MD5.hexdigest(url.to_s)[0...8]
|
14
|
+
# invalid hashes are ""
|
15
|
+
load_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
def strip_html?; @strip_html end
|
19
|
+
|
20
|
+
def new?
|
21
|
+
hash.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_hash
|
25
|
+
file = Conf.file(@id + '.md5')
|
26
|
+
if File.exists?(file)
|
27
|
+
WCC.logger.debug "Load hash from file '#{file}'"
|
28
|
+
File.open(file, 'r') { |f| @hash = f.gets; break }
|
29
|
+
else
|
30
|
+
WCC.logger.info "Site #{uri.host} was never checked before."
|
31
|
+
@hash = ""
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def load_content
|
36
|
+
file = Conf.file(@id + '.site')
|
37
|
+
if File.exists?(file)
|
38
|
+
File.open(file, 'r') { |f| @content = f.read }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def hash; @hash end
|
43
|
+
|
44
|
+
def hash=(hash)
|
45
|
+
@hash = hash
|
46
|
+
File.open(Conf.file(@id + '.md5'), 'w') { |f| f.write(@hash) } unless Conf.simulate?
|
47
|
+
end
|
48
|
+
|
49
|
+
def content; load_content if @content.nil?; @content end
|
50
|
+
|
51
|
+
def content=(content)
|
52
|
+
@content = content
|
53
|
+
File.open(Conf.file(@id + '.site'), 'w') { |f| f.write(@content) } unless Conf.simulate?
|
54
|
+
end
|
55
|
+
|
56
|
+
def fetch
|
57
|
+
http = Net::HTTP.new(@uri.host, @uri.port)
|
58
|
+
if @uri.is_a?(URI::HTTPS)
|
59
|
+
http.use_ssl = true
|
60
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
61
|
+
end
|
62
|
+
http.start do |http|
|
63
|
+
req = Net::HTTP::Get.new(@uri.request_uri)
|
64
|
+
if @auth['type'] == 'basic'
|
65
|
+
WCC.logger.debug "Doing basic auth"
|
66
|
+
req.basic_auth(@auth['username'], @auth['password'])
|
67
|
+
end
|
68
|
+
if not @cookie.nil?
|
69
|
+
req.add_field("Cookie", @cookie)
|
70
|
+
end
|
71
|
+
http.request(req)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/wcc.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
|
2
2
|
require 'base64'
|
3
3
|
require 'digest/md5'
|
4
|
+
require 'erb'
|
4
5
|
require 'iconv'
|
5
6
|
require 'logger'
|
6
7
|
require 'net/http'
|
7
8
|
require 'net/https'
|
8
9
|
require 'net/smtp'
|
9
10
|
require 'optparse'
|
11
|
+
require 'ostruct'
|
10
12
|
require 'pathname'
|
11
13
|
require 'singleton'
|
12
14
|
require 'tempfile'
|
@@ -16,8 +18,13 @@ require 'yaml'
|
|
16
18
|
# ruby gem dependencies
|
17
19
|
require 'htmlentities'
|
18
20
|
|
21
|
+
# wcc
|
22
|
+
require 'wcc/filter'
|
23
|
+
require 'wcc/mail'
|
24
|
+
require 'wcc/site'
|
25
|
+
|
19
26
|
class String
|
20
|
-
# Remove all HTML
|
27
|
+
# Remove all HTML tags with at least one character name and
|
21
28
|
# decode all HTML entities into utf-8 characters.
|
22
29
|
#
|
23
30
|
# @return [String] stripped string
|
@@ -31,6 +38,7 @@ module WCC
|
|
31
38
|
DIFF_TIME_FMT = '%Y-%m-%d %H:%M:%S %Z'
|
32
39
|
|
33
40
|
# logging via WCC.logger.blub
|
41
|
+
|
34
42
|
def self.logger
|
35
43
|
@logger
|
36
44
|
end
|
@@ -57,10 +65,12 @@ module WCC
|
|
57
65
|
:simulate => false,
|
58
66
|
:clean => false,
|
59
67
|
:nomails => false,
|
60
|
-
|
68
|
+
# when you want to use ./tmp it must be writeable
|
69
|
+
:cache_dir => '/var/tmp/wcc',
|
61
70
|
:tag => 'wcc',
|
62
71
|
:syslog => false,
|
63
|
-
:
|
72
|
+
:filter_dir => './filter.d',
|
73
|
+
:template_dir => './template.d',
|
64
74
|
:mailer => 'smtp',
|
65
75
|
:smtp_host => 'localhost',
|
66
76
|
:smtp_port => 25
|
@@ -75,14 +85,15 @@ module WCC
|
|
75
85
|
opts.banner += "\nOptions:\n"
|
76
86
|
opts.on('-v', '--verbose', 'Output more information') do self[:verbose] = true end
|
77
87
|
opts.on('-d', '--debug', 'Enable debug mode') do self[:debug] = true end
|
78
|
-
opts.on('-
|
88
|
+
opts.on('--cache-dir DIR', 'Save hash and diff files to DIR') do |dir| self[:cache_dir] = dir end
|
79
89
|
opts.on('-s', '--simulate', 'Check for update but do not save hash or diff files') do self[:simulate] = true end
|
80
|
-
opts.on('
|
90
|
+
opts.on('--clean', 'Remove all saved hash and diff files') do self[:clean] = true end
|
81
91
|
opts.on('-t', '--tag TAG', 'Set TAG used in output') do |t| self[:tag] = t end
|
82
92
|
opts.on('-n', '--no-mails', 'Do not send any emails') do self[:nomails] = true end
|
83
93
|
opts.on('-f', '--from MAIL', 'Set From: mail address') do |m| self[:from_mail] = m end
|
84
94
|
opts.on('--host HOST', 'Set SMTP host') do |h| self[:host] = h end
|
85
95
|
opts.on('--port PORT', 'Set SMTP port') do |p| self[:port] = p end
|
96
|
+
#opts.on('--init', '--initialize')
|
86
97
|
opts.on('--show-config', 'Show config after loading config file (debug purposes)') do self[:show_config] = true end
|
87
98
|
opts.on('-h', '-?', '--help', 'Display this screen') do
|
88
99
|
puts opts
|
@@ -115,10 +126,11 @@ module WCC
|
|
115
126
|
yaml = YAML.load_file(self[:conf])
|
116
127
|
if yaml.is_a?(Hash) and (yaml = yaml['conf']).is_a?(Hash)
|
117
128
|
@options[:from_mail] ||= yaml['from_addr']
|
118
|
-
@options[:
|
129
|
+
@options[:cache_dir] ||= yaml['cache_dir']
|
119
130
|
@options[:tag] ||= yaml['tag']
|
120
131
|
@options[:syslog] ||= yaml['use_syslog']
|
121
|
-
@options[:
|
132
|
+
@options[:filter_dir] ||= yaml['filterd']
|
133
|
+
@options[:template_dir] ||= yaml['templated']
|
122
134
|
|
123
135
|
if yaml['email'].is_a?(Hash)
|
124
136
|
if yaml['email']['smtp'].is_a?(Hash)
|
@@ -142,18 +154,23 @@ module WCC
|
|
142
154
|
exit 0
|
143
155
|
end
|
144
156
|
|
145
|
-
# create dir for hash files
|
146
|
-
Dir.mkdir(self[:
|
157
|
+
# create cache dir for hash and diff files
|
158
|
+
Dir.mkdir(self[:cache_dir]) unless File.directory?(self[:cache_dir])
|
147
159
|
|
148
160
|
if(self[:clean])
|
149
161
|
WCC.logger.warn "Cleanup hash and diff files"
|
150
|
-
Dir.foreach(self[:
|
162
|
+
Dir.foreach(self[:cache_dir]) do |f|
|
151
163
|
File.delete(self.file(f)) if f =~ /^.*\.(md5|site)$/
|
152
164
|
end
|
153
165
|
end
|
154
166
|
|
155
167
|
# read filter.d
|
156
|
-
Dir[File.join(self[:
|
168
|
+
Dir[File.join(self[:filter_dir], '*.rb')].each { |file| require file }
|
169
|
+
|
170
|
+
# attach --no-mails filter
|
171
|
+
WCC::Filter.add '--no-mails' do |data|
|
172
|
+
!self[:nomails]
|
173
|
+
end
|
157
174
|
end
|
158
175
|
|
159
176
|
def self.sites
|
@@ -163,19 +180,25 @@ module WCC
|
|
163
180
|
|
164
181
|
WCC.logger.debug "Load sites from '#{Conf[:conf]}'"
|
165
182
|
|
166
|
-
# may be false if file is empty
|
183
|
+
# may be *false* if file is empty
|
167
184
|
yaml = YAML.load_file(Conf[:conf])
|
168
185
|
|
186
|
+
if not yaml
|
187
|
+
WCC.logger.info "No sites loaded"
|
188
|
+
return @sites
|
189
|
+
end
|
190
|
+
|
169
191
|
yaml['sites'].to_a.each do |yaml_site|
|
170
|
-
|
192
|
+
# query --no-mails filter for every site
|
193
|
+
frefs = [FilterRef.new('--no-mails')]
|
171
194
|
(yaml_site['filters'] || []).each do |entry|
|
172
195
|
if entry.is_a?(Hash)
|
173
196
|
# hash containing only one key (filter id),
|
174
197
|
# the value is the argument hash
|
175
198
|
id = entry.keys[0]
|
176
|
-
|
199
|
+
frefs << FilterRef.new(id, entry[id])
|
177
200
|
else entry.is_a?(String)
|
178
|
-
|
201
|
+
frefs << FilterRef.new(entry)
|
179
202
|
end
|
180
203
|
end
|
181
204
|
|
@@ -187,10 +210,10 @@ module WCC
|
|
187
210
|
yaml_site['url'],
|
188
211
|
yaml_site['strip_html'] || true,
|
189
212
|
yaml_site['emails'].map { |m| MailAddress.new(m) } || [],
|
190
|
-
|
213
|
+
frefs,
|
191
214
|
yaml_site['auth'] || {},
|
192
215
|
cookie)
|
193
|
-
end
|
216
|
+
end
|
194
217
|
|
195
218
|
WCC.logger.debug @sites.length.to_s + (@sites.length == 1 ? ' site' : ' sites') + " loaded\n" +
|
196
219
|
@sites.map { |s| " #{s.uri.host.to_s}\n url: #{s.uri.to_s}\n id: #{s.id}" }.join("\n")
|
@@ -199,197 +222,21 @@ module WCC
|
|
199
222
|
end
|
200
223
|
|
201
224
|
def self.mailer
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
225
|
+
return @mailer unless @mailer.nil?
|
226
|
+
|
227
|
+
# smtp mailer
|
228
|
+
if Conf[:mailer] == 'smtp'
|
229
|
+
@mailer = SmtpMailer.new(Conf[:smtp_host], Conf[:smtp_port])
|
207
230
|
end
|
231
|
+
|
208
232
|
@mailer
|
209
233
|
end
|
210
234
|
|
211
|
-
def self.file(path = nil) File.join(self[:
|
235
|
+
def self.file(path = nil) File.join(self[:cache_dir], path) end
|
212
236
|
def self.simulate?; self[:simulate] end
|
213
|
-
def self.send_mails?; !self[:nomails] end
|
214
237
|
def self.[](key); Conf.instance[key] end
|
215
238
|
end
|
216
239
|
|
217
|
-
class FilterRef
|
218
|
-
attr_reader :id, :arguments
|
219
|
-
|
220
|
-
def initialize(id, arguments)
|
221
|
-
@id = id
|
222
|
-
@arguments = arguments
|
223
|
-
end
|
224
|
-
|
225
|
-
def to_s; @id end
|
226
|
-
end
|
227
|
-
|
228
|
-
class Site
|
229
|
-
attr_reader :uri, :emails, :filters, :auth, :cookie, :id
|
230
|
-
|
231
|
-
def initialize(url, strip_html, emails, filters, auth, cookie)
|
232
|
-
@uri = URI.parse(url)
|
233
|
-
@strip_html = strip_html
|
234
|
-
@emails = emails.is_a?(Array) ? emails : [emails]
|
235
|
-
@filters = filters.is_a?(Array) ? filters : [filters]
|
236
|
-
@auth = auth
|
237
|
-
@cookie = cookie
|
238
|
-
@id = Digest::MD5.hexdigest(url.to_s)[0...8]
|
239
|
-
# invalid hashes are ""
|
240
|
-
load_hash
|
241
|
-
end
|
242
|
-
|
243
|
-
def strip_html?; @strip_html end
|
244
|
-
|
245
|
-
def new?
|
246
|
-
hash.empty?
|
247
|
-
end
|
248
|
-
|
249
|
-
def load_hash
|
250
|
-
file = Conf.file(@id + '.md5')
|
251
|
-
if File.exists?(file)
|
252
|
-
WCC.logger.debug "Load hash from file '#{file}'"
|
253
|
-
File.open(file, 'r') { |f| @hash = f.gets; break }
|
254
|
-
else
|
255
|
-
WCC.logger.info "Site #{uri.host} was never checked before."
|
256
|
-
@hash = ""
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
def load_content
|
261
|
-
file = Conf.file(@id + '.site')
|
262
|
-
if File.exists?(file)
|
263
|
-
File.open(file, 'r') { |f| @content = f.read }
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
def hash; @hash end
|
268
|
-
|
269
|
-
def hash=(hash)
|
270
|
-
@hash = hash
|
271
|
-
File.open(Conf.file(@id + '.md5'), 'w') { |f| f.write(@hash) } unless Conf.simulate?
|
272
|
-
end
|
273
|
-
|
274
|
-
def content; load_content if @content.nil?; @content end
|
275
|
-
|
276
|
-
def content=(content)
|
277
|
-
@content = content
|
278
|
-
File.open(Conf.file(@id + '.site'), 'w') { |f| f.write(@content) } unless Conf.simulate?
|
279
|
-
end
|
280
|
-
|
281
|
-
def fetch
|
282
|
-
http = Net::HTTP.new(@uri.host, @uri.port)
|
283
|
-
if @uri.is_a?(URI::HTTPS)
|
284
|
-
http.use_ssl = true
|
285
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
286
|
-
end
|
287
|
-
http.start do |http|
|
288
|
-
req = Net::HTTP::Get.new(@uri.request_uri)
|
289
|
-
if @auth['type'] == 'basic'
|
290
|
-
WCC.logger.debug "Doing basic auth"
|
291
|
-
req.basic_auth(@auth['username'], @auth['password'])
|
292
|
-
end
|
293
|
-
if not @cookie.nil?
|
294
|
-
req.add_field("Cookie", @cookie)
|
295
|
-
end
|
296
|
-
http.request(req)
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
class MailAddress
|
302
|
-
def initialize(email)
|
303
|
-
email = email.to_s if email.is_a?(MailAddress)
|
304
|
-
@email = email.strip
|
305
|
-
end
|
306
|
-
|
307
|
-
def name
|
308
|
-
if @email =~ /^[\w\s]+<.+@[^@]+>$/
|
309
|
-
@email.gsub(/<.+?>/, '').strip
|
310
|
-
else
|
311
|
-
@email.split("@")[0...-1].join("@")
|
312
|
-
end
|
313
|
-
end
|
314
|
-
|
315
|
-
def address
|
316
|
-
if @email =~ /^[\w\s]+<.+@[^@]+>$/
|
317
|
-
@email.match(/<([^>]+@[^@>]+)>/)[1]
|
318
|
-
else
|
319
|
-
@email
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
def to_s; @email end
|
324
|
-
end
|
325
|
-
|
326
|
-
class Mail
|
327
|
-
attr_reader :title, :message
|
328
|
-
|
329
|
-
def initialize(title, message, options = {})
|
330
|
-
@title = title
|
331
|
-
@message = message
|
332
|
-
@options = {:from => MailAddress.new(Conf[:from_mail])}
|
333
|
-
@options[:from] = MailAddress.new(options[:from]) unless options[:from].nil?
|
334
|
-
end
|
335
|
-
|
336
|
-
def send(tos = [])
|
337
|
-
Conf.mailer.send(self, @options[:from], tos)
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
class SmtpMailer
|
342
|
-
def initialize(host, port)
|
343
|
-
@host = host
|
344
|
-
@port = port
|
345
|
-
end
|
346
|
-
|
347
|
-
def send(mail, from, to = [])
|
348
|
-
Net::SMTP.start(@host, @port) do |smtp|
|
349
|
-
to.each do |toaddr|
|
350
|
-
msg = "From: #{from.name} <#{from.address}>\n"
|
351
|
-
msg += "To: #{toaddr}\n"
|
352
|
-
msg += "Subject: #{mail.title.gsub(/\s+/, ' ')}\n"
|
353
|
-
msg += "Content-Type: text/plain; charset=\"utf-8\"\n"
|
354
|
-
msg += "Content-Transfer-Encoding: base64\n"
|
355
|
-
msg += "\n"
|
356
|
-
msg += Base64.encode64(mail.message)
|
357
|
-
|
358
|
-
smtp.send_message(msg, from.address, toaddr.address)
|
359
|
-
end
|
360
|
-
end
|
361
|
-
rescue
|
362
|
-
WCC.logger.fatal "Cannot send mails at #{@host}:#{@port} : #{$!.to_s}"
|
363
|
-
end
|
364
|
-
end
|
365
|
-
|
366
|
-
class Filter
|
367
|
-
@@filters = {}
|
368
|
-
|
369
|
-
def self.add(id, &block)
|
370
|
-
WCC.logger.info "Adding filter '#{id}'"
|
371
|
-
@@filters[id] = block
|
372
|
-
end
|
373
|
-
|
374
|
-
def self.accept(data, filters)
|
375
|
-
return true if filters.nil?
|
376
|
-
|
377
|
-
WCC.logger.info "Testing with filters: #{filters.join(', ')}"
|
378
|
-
filters.each do |fref|
|
379
|
-
block = @@filters[fref.id]
|
380
|
-
if block.nil?
|
381
|
-
WCC.logger.error "Requested filter '#{fref.id}' not found, skipping it."
|
382
|
-
next
|
383
|
-
end
|
384
|
-
if not block.call(data, fref.arguments)
|
385
|
-
WCC.logger.info "Filter #{fref.id} failed!"
|
386
|
-
return false
|
387
|
-
end
|
388
|
-
end
|
389
|
-
true
|
390
|
-
end
|
391
|
-
end
|
392
|
-
|
393
240
|
class LogFormatter
|
394
241
|
def initialize(use_color = true)
|
395
242
|
@color = use_color
|
@@ -450,7 +297,7 @@ module WCC
|
|
450
297
|
return false
|
451
298
|
end
|
452
299
|
|
453
|
-
# strip html
|
300
|
+
# strip html
|
454
301
|
new_content = new_content.strip_html if site.strip_html?
|
455
302
|
new_hash = Digest::MD5.hexdigest(new_content)
|
456
303
|
|
@@ -459,7 +306,6 @@ module WCC
|
|
459
306
|
|
460
307
|
# do not try diff or anything if site was never checked before
|
461
308
|
if site.new?
|
462
|
-
# update content
|
463
309
|
site.hash, site.content = new_hash, new_content
|
464
310
|
|
465
311
|
# set custom diff message
|
@@ -474,19 +320,20 @@ module WCC
|
|
474
320
|
old_label = "OLD (%s)" % File.mtime(Conf.file(site.id + ".md5")).strftime(DIFF_TIME_FMT)
|
475
321
|
new_label = "NEW (%s)" % Time.now.strftime(DIFF_TIME_FMT)
|
476
322
|
|
477
|
-
# do update
|
478
323
|
site.hash, site.content = new_hash, new_content
|
479
324
|
|
480
325
|
# diff between OLD and NEW
|
481
326
|
diff = %x[diff -U 1 --label "#{old_label}" --label "#{new_label}" #{old_site_file.path} #{Conf.file(site.id + '.site')}]
|
482
327
|
end
|
483
328
|
|
329
|
+
# HACK: there *was* an update but no notification is required
|
484
330
|
return false if not Filter.accept(diff, site.filters)
|
485
331
|
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
332
|
+
data = OpenStruct.new
|
333
|
+
data.title = "[#{Conf[:tag]}] #{site.uri.host} changed"
|
334
|
+
data.message = "Change at #{site.uri.to_s} - diff follows:\n\n#{diff}"
|
335
|
+
|
336
|
+
Conf.mailer.send(data, @@mail_plain, MailAddress.new(Conf[:from_mail]), site.emails)
|
490
337
|
|
491
338
|
system("logger -t '#{Conf[:tag]}' 'Change at #{site.uri.to_s} (tag #{site.id}) detected'") if Conf[:syslog]
|
492
339
|
|
@@ -495,8 +342,13 @@ module WCC
|
|
495
342
|
|
496
343
|
# main
|
497
344
|
def self.run!
|
345
|
+
# first use of Conf initializes it
|
498
346
|
WCC.logger = Logger.new(STDOUT)
|
499
347
|
|
348
|
+
mp_path = File.join(Conf[:template_dir], 'mail-plain.erb')
|
349
|
+
mp = File.open(mp_path, 'r') { |f| f.read }
|
350
|
+
@@mail_plain = ERB.new(mp)
|
351
|
+
|
500
352
|
Conf.sites.each do |site|
|
501
353
|
if checkForUpdate(site)
|
502
354
|
WCC.logger.warn "#{site.uri.host.to_s} has an update!"
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wcc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Christian Nicolai
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-10-
|
18
|
+
date: 2011-10-02 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: htmlentities
|
@@ -41,6 +41,10 @@ extra_rdoc_files: []
|
|
41
41
|
|
42
42
|
files:
|
43
43
|
- bin/wcc
|
44
|
+
- doc/Filters.md
|
45
|
+
- lib/wcc/filter.rb
|
46
|
+
- lib/wcc/mail.rb
|
47
|
+
- lib/wcc/site.rb
|
44
48
|
- lib/wcc.rb
|
45
49
|
- README.md
|
46
50
|
homepage: https://github.com/cmur2/wcc
|