wcc 0.0.3 → 0.0.4
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.
- 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
|