feed2email 0.5.0 → 0.6.0
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 +4 -4
- data/CHANGELOG.md +8 -0
- data/LICENSE.txt +1 -1
- data/README.md +48 -20
- data/TODO.md +13 -0
- data/bin/feed2email +1 -0
- data/bin/feed2email-migrate-history +29 -0
- data/lib/feed2email/config.rb +92 -17
- data/lib/feed2email/core_ext.rb +12 -0
- data/lib/feed2email/entry.rb +13 -8
- data/lib/feed2email/feed.rb +183 -84
- data/lib/feed2email/feed_data_file.rb +65 -0
- data/lib/feed2email/feed_history.rb +28 -0
- data/lib/feed2email/feed_meta.rb +28 -0
- data/lib/feed2email/feeds.rb +77 -0
- data/lib/feed2email/logger.rb +23 -22
- data/lib/feed2email/mail.rb +24 -47
- data/lib/feed2email/version.rb +1 -1
- data/lib/feed2email.rb +15 -14
- metadata +127 -27
- data/.gitignore +0 -4
- data/Gemfile +0 -3
- data/Gemfile.lock +0 -39
- data/Rakefile +0 -1
- data/feed2email.gemspec +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9a92661dec1838e02af880c1e7c0e4c2a37dced
|
4
|
+
data.tar.gz: 879f563ae943c253b987a1c5beeec95b41bb95c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad5c3c4fe63c1e5e860a3ea7bfaa5af4b345806c0569853e4a365f34f53cd0c3672fac725e3e2067cca5f5b9a1348f0a1651e5113a2488ee4d8ed80ad82d14be
|
7
|
+
data.tar.gz: f90bb39169407674065bedd943645ec94aee5f2ab825070e0563cc51fd5053665efdc15e054bb51146f466715b06387db668b127bb3faf6e232d48377ecdbe29
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
### 0.6.0
|
2
|
+
|
3
|
+
* Render text/plain body as Markdown
|
4
|
+
* Cache feed fetching with Last-Modified and ETag HTTP headers
|
5
|
+
* Update feed URI on permanent redirect
|
6
|
+
* Make sender a required config option
|
7
|
+
* Maintain a separate history file per feed
|
8
|
+
|
1
9
|
### 0.5.0
|
2
10
|
|
3
11
|
* Sanitize SMTP user in from address
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License
|
2
2
|
|
3
|
-
Copyright (c) 2013 Aggelos Orfanakos
|
3
|
+
Copyright (c) 2013, 2014, 2015 Aggelos Orfanakos
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
6
|
this software and associated documentation files (the "Software"), to deal in
|
data/README.md
CHANGED
@@ -27,8 +27,8 @@ $ gem install feed2email
|
|
27
27
|
|
28
28
|
Through a [YAML][] file at `~/.feed2email/config.yml`.
|
29
29
|
|
30
|
-
It is possible to send email via SMTP or an [MTA][]. If `config.yml`
|
31
|
-
options for both, feed2email will use SMTP.
|
30
|
+
It is possible to send email via SMTP or an [MTA][] (default). If `config.yml`
|
31
|
+
contains options for both, feed2email will use SMTP.
|
32
32
|
|
33
33
|
[YAML]: http://en.wikipedia.org/wiki/YAML
|
34
34
|
[MTA]: http://en.wikipedia.org/wiki/Message_transfer_agent
|
@@ -41,13 +41,11 @@ pair is separated with a colon: `foo: bar`
|
|
41
41
|
### Generic options
|
42
42
|
|
43
43
|
* `recipient` (required) is the email address to send email to
|
44
|
-
* `sender` (
|
45
|
-
from the feed entry author or, if missing, it is generated from the SMTP user
|
46
|
-
and host or, if missing, it is the same as `recipient`)
|
44
|
+
* `sender` (required) is the email address to send email from (can be any)
|
47
45
|
* `send_delay` (optional) is the number of seconds to wait between each email to
|
48
46
|
avoid SMTP server throttling errors (default is `10`; use `0` to disable)
|
49
47
|
* `log_path` (optional) is the _absolute_ path to the log file (default is
|
50
|
-
`true` which logs to standard output; use `false` to disable)
|
48
|
+
`true` which logs to standard output; use `false` to disable logging)
|
51
49
|
* `log_level` (optional) is the logging verbosity level and can be `fatal`
|
52
50
|
(least verbose), `error`, `warn`, `info` (default) and `debug` (most verbose)
|
53
51
|
* `max_entries` (optional) is the maximum number of entries to process per feed
|
@@ -58,10 +56,11 @@ pair is separated with a colon: `foo: bar`
|
|
58
56
|
For this method you need to have access to an SMTP service. [Mailgun][] has a
|
59
57
|
free plan.
|
60
58
|
|
61
|
-
* `smtp_host` is the SMTP service hostname to connect to
|
62
|
-
* `smtp_port` is the SMTP service port to connect to
|
63
|
-
* `smtp_user` is the username of your email account
|
64
|
-
* `smtp_pass` is the password of your email account (see the warning
|
59
|
+
* `smtp_host` (required) is the SMTP service hostname to connect to
|
60
|
+
* `smtp_port` (required) is the SMTP service port to connect to
|
61
|
+
* `smtp_user` (required) is the username of your email account
|
62
|
+
* `smtp_pass` (required) is the password of your email account (see the warning
|
63
|
+
below)
|
65
64
|
* `smtp_tls` (optional) controls TLS (default is `true`; can also be `false`)
|
66
65
|
* `smtp_auth` (optional) controls the authentication method (default is `login`;
|
67
66
|
can also be `plain` or `cram_md5`)
|
@@ -87,6 +86,8 @@ interface setup and working in your system like [msmtp][] or [Postfix][].
|
|
87
86
|
|
88
87
|
## Use
|
89
88
|
|
89
|
+
### Managing feeds
|
90
|
+
|
90
91
|
Create `~/.feed2email/feeds.yml` and add the address of each feed you want to
|
91
92
|
subscribe to, prefixed with a dash and a space:
|
92
93
|
|
@@ -94,30 +95,57 @@ subscribe to, prefixed with a dash and a space:
|
|
94
95
|
- https://github.com/agorf/feed2email/commits.atom
|
95
96
|
~~~
|
96
97
|
|
97
|
-
To disable a feed
|
98
|
+
To disable a feed, comment it:
|
98
99
|
|
99
100
|
~~~ yaml
|
100
101
|
#- https://github.com/agorf/feed2email/commits.atom
|
101
102
|
~~~
|
102
103
|
|
103
|
-
|
104
|
+
### Running
|
105
|
+
|
106
|
+
Simply:
|
104
107
|
|
105
108
|
~~~ sh
|
106
109
|
$ feed2email
|
107
110
|
~~~
|
108
111
|
|
109
|
-
When
|
110
|
-
|
112
|
+
When feed2email runs for the first time or after adding a new feed:
|
113
|
+
|
114
|
+
* All feed entries are skipped (no email sent)
|
115
|
+
* `~/.feed2email/history-<digest>.yml` is created for each feed containing these
|
116
|
+
(old) entries, where `<digest>` is the MD5 hex digest of the feed URL
|
117
|
+
|
118
|
+
**Warning:** Versions prior to 0.6.0 used a single history file for all feeds.
|
119
|
+
Before using version 0.6.0 for the first time, please make sure you run the
|
120
|
+
provided migration script: `feed2email-migrate-history` If you don't, feed2email
|
121
|
+
will think it's run for the first time and will treat all entries as old (thus
|
122
|
+
no email will be sent and you may miss some entries).
|
123
|
+
|
124
|
+
To receive existing entries from a new feed:
|
111
125
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
126
|
+
1. Add it to `feeds.yml` (see above)
|
127
|
+
1. Run feed2email once so that the feed's history file is generated
|
128
|
+
1. Remove the entries you want to receive from the feed's history (i.e. with
|
129
|
+
your text editor)
|
130
|
+
1. Remove the feed's meta file (`meta-<digest>.yml`) to bust feed fetching
|
131
|
+
caching
|
116
132
|
|
117
|
-
|
118
|
-
delete them from `history.yml`. Next time feed2email runs, they will be
|
133
|
+
Next time feed2email runs, these entries will be treated as new and will be
|
119
134
|
processed (sent as email).
|
120
135
|
|
136
|
+
### Permanent redirections
|
137
|
+
|
138
|
+
Before processing each feed, feed2email issues a [HEAD request][] to check
|
139
|
+
whether it has been permanently moved by looking for a _301 Moved Permanently_
|
140
|
+
HTTP status and its respective _Location_ header. In such case, feed2email
|
141
|
+
updates `feeds.yml` with the new location and all feed entries are skipped (no
|
142
|
+
email sent). If you do want to have some of them sent as email, please refer to
|
143
|
+
the _Running_ section.
|
144
|
+
|
145
|
+
[HEAD request]: http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods
|
146
|
+
|
147
|
+
### Automating
|
148
|
+
|
121
149
|
You can use [cron][] to run feed2email automatically e.g. once every hour.
|
122
150
|
|
123
151
|
[cron]: http://en.wikipedia.org/wiki/Cron
|
data/TODO.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# TODO
|
2
|
+
|
3
|
+
* Specs
|
4
|
+
* Do not mark entry as sent if email was not sent
|
5
|
+
* Show entry metadata in email (e.g. pubdate, author)
|
6
|
+
* Implement a command-line interface to manage feeds.yml
|
7
|
+
* Detect entry URI changes (maybe by comparing body hashes?)
|
8
|
+
* Filters (e.g. skip entries matching a pattern)
|
9
|
+
* Support "dispatch interfaces" where email is one such interface (another could
|
10
|
+
be writing to the filesystem)
|
11
|
+
* Profiles (support many feed lists and recipients)
|
12
|
+
* Send email notifications to user (e.g. when a feed is not available anymore)
|
13
|
+
* Plugin architecture
|
data/bin/feed2email
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'digest/md5'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
CONFIG_DIR = File.expand_path('~/.feed2email')
|
7
|
+
HISTORY_FILE = File.join(CONFIG_DIR, 'history.yml')
|
8
|
+
|
9
|
+
if !File.exist?(HISTORY_FILE)
|
10
|
+
$stderr.puts "Missing history file #{HISTORY_FILE}"
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
|
14
|
+
history_data = YAML.load(open(HISTORY_FILE))
|
15
|
+
|
16
|
+
if !history_data.is_a?(Hash)
|
17
|
+
$stderr.puts "Invalid data type for history file #{HISTORY_FILE}"
|
18
|
+
exit 2
|
19
|
+
end
|
20
|
+
|
21
|
+
history_data.each do |feed_uri, entries|
|
22
|
+
hist_filename = "history-#{Digest::MD5.hexdigest(feed_uri)}.yml"
|
23
|
+
hist_path = File.join(CONFIG_DIR, hist_filename)
|
24
|
+
open(hist_path, 'w') {|f| f.write(entries.to_yaml) }
|
25
|
+
puts "Extracted history of #{feed_uri} to #{hist_filename}"
|
26
|
+
end
|
27
|
+
|
28
|
+
File.rename(HISTORY_FILE, "#{HISTORY_FILE}.bak")
|
29
|
+
puts "Renamed #{HISTORY_FILE} to #{HISTORY_FILE}.bak"
|
data/lib/feed2email/config.rb
CHANGED
@@ -1,31 +1,106 @@
|
|
1
|
-
|
2
|
-
CONFIG_DIR = File.expand_path('~/.feed2email')
|
1
|
+
require 'yaml'
|
3
2
|
|
3
|
+
module Feed2Email
|
4
4
|
class Config
|
5
|
-
|
5
|
+
class MissingConfigError < StandardError; end
|
6
|
+
class InvalidConfigPermissionsError < StandardError; end
|
7
|
+
class InvalidConfigSyntaxError < StandardError; end
|
8
|
+
class InvalidConfigDataTypeError < StandardError; end
|
9
|
+
class MissingConfigOptionError < StandardError; end
|
10
|
+
|
11
|
+
def initialize(path)
|
12
|
+
@path = File.expand_path(path)
|
13
|
+
check
|
14
|
+
end
|
6
15
|
|
7
|
-
|
16
|
+
def [](option)
|
17
|
+
merged_config[option] # delegate
|
18
|
+
end
|
8
19
|
|
9
|
-
|
20
|
+
private
|
10
21
|
|
11
|
-
def
|
12
|
-
|
22
|
+
def path
|
23
|
+
@path
|
24
|
+
end
|
13
25
|
|
14
|
-
|
26
|
+
def data
|
27
|
+
@data
|
28
|
+
end
|
15
29
|
|
16
|
-
|
17
|
-
|
18
|
-
|
30
|
+
def check
|
31
|
+
check_existence
|
32
|
+
check_permissions
|
33
|
+
check_syntax
|
34
|
+
check_data_type
|
35
|
+
check_recipient_existence
|
36
|
+
check_sender_existence
|
37
|
+
end
|
38
|
+
|
39
|
+
def check_existence
|
40
|
+
if !File.exist?(path)
|
41
|
+
raise MissingConfigError, "Missing config file #{path}"
|
19
42
|
end
|
43
|
+
end
|
20
44
|
|
21
|
-
|
22
|
-
|
23
|
-
|
45
|
+
def check_permissions
|
46
|
+
if '%o' % (File.stat(path).mode & 0777) != '600'
|
47
|
+
raise InvalidConfigPermissionsError,
|
48
|
+
"Invalid permissions for config file #{path}"
|
24
49
|
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_syntax
|
53
|
+
begin
|
54
|
+
load_yaml
|
55
|
+
rescue Psych::SyntaxError
|
56
|
+
raise InvalidConfigSyntaxError,
|
57
|
+
"Invalid YAML syntax for config file #{path}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def check_data_type
|
62
|
+
if !data.is_a?(Hash)
|
63
|
+
raise InvalidConfigDataTypeError,
|
64
|
+
"Invalid data type (not a Hash) for config file #{path}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def check_recipient_existence
|
69
|
+
check_option_existence('recipient')
|
70
|
+
end
|
71
|
+
|
72
|
+
def check_sender_existence
|
73
|
+
check_option_existence('sender')
|
74
|
+
end
|
75
|
+
|
76
|
+
def load_yaml
|
77
|
+
@data = YAML.load(read_file)
|
78
|
+
end
|
79
|
+
|
80
|
+
def read_file
|
81
|
+
File.read(path)
|
82
|
+
end
|
83
|
+
|
84
|
+
def merged_config
|
85
|
+
@merged_config ||= defaults.merge(data)
|
86
|
+
end
|
87
|
+
|
88
|
+
def defaults
|
89
|
+
{
|
90
|
+
'log_level' => 'info',
|
91
|
+
'log_path' => true,
|
92
|
+
'max_entries' => 20,
|
93
|
+
'send_delay' => 10,
|
94
|
+
'sendmail_path' => '/usr/sbin/sendmail',
|
95
|
+
'smtp_auth' => 'login',
|
96
|
+
'smtp_tls' => true,
|
97
|
+
}
|
98
|
+
end
|
25
99
|
|
26
|
-
|
27
|
-
|
28
|
-
|
100
|
+
def check_option_existence(option)
|
101
|
+
if data[option].nil?
|
102
|
+
raise MissingConfigOptionError,
|
103
|
+
"Option #{option} missing from config file #{path}"
|
29
104
|
end
|
30
105
|
end
|
31
106
|
end
|
data/lib/feed2email/core_ext.rb
CHANGED
@@ -1,11 +1,23 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'reverse_markdown'
|
3
|
+
require 'sanitize'
|
4
|
+
|
1
5
|
class String
|
2
6
|
def escape_html
|
3
7
|
CGI.escapeHTML(self)
|
4
8
|
end
|
5
9
|
|
10
|
+
def pluralize(count, plural = self + 's')
|
11
|
+
"#{count} #{count == 1 ? self : plural}"
|
12
|
+
end
|
13
|
+
|
6
14
|
def strip_html
|
7
15
|
CGI.unescapeHTML(Sanitize.clean(self))
|
8
16
|
end
|
17
|
+
|
18
|
+
def to_markdown
|
19
|
+
ReverseMarkdown.convert(self, unknown_tags: :drop)
|
20
|
+
end
|
9
21
|
end
|
10
22
|
|
11
23
|
class Time
|
data/lib/feed2email/entry.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'feed2email/mail'
|
2
|
+
|
1
3
|
module Feed2Email
|
2
4
|
class Entry
|
3
5
|
def initialize(data, feed_uri, feed_title)
|
@@ -14,22 +16,25 @@ module Feed2Email
|
|
14
16
|
@data.content || @data.summary
|
15
17
|
end
|
16
18
|
|
17
|
-
def
|
19
|
+
def send_mail
|
18
20
|
Mail.new(self, @feed_title).send
|
19
21
|
end
|
20
22
|
|
21
23
|
def title
|
22
|
-
@data.title
|
24
|
+
@data.title.strip
|
23
25
|
end
|
24
26
|
|
25
27
|
def uri
|
26
|
-
@uri
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
return @uri if @uri
|
29
|
+
|
30
|
+
@uri = @data.url
|
31
|
+
|
32
|
+
# Make relative entry URL absolute by prepending feed URL
|
33
|
+
if @uri && @uri.start_with?('/')
|
34
|
+
@uri = @feed_uri[%r{https?://[^/]+}] + @uri
|
32
35
|
end
|
36
|
+
|
37
|
+
@uri
|
33
38
|
end
|
34
39
|
end
|
35
40
|
end
|