feedigest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ab1e04866f9a432849b17e607d1c90366e248a7d42ea3d44aec0e6749dd1405e
4
+ data.tar.gz: f0948b0ddd6910cee3bd237e0b2a0a74abcf6167af8e7e1f92a2ec3a978867d1
5
+ SHA512:
6
+ metadata.gz: 57b418b0d2d9ddb580b3ec746012ffc2b00fe5125b3c2a7b9553614ff1db5d79a7b4440df4e13849f7913a736135ecc94af3529f402030d379cadaa7f1353d33
7
+ data.tar.gz: 9ca032d6a782bd4b9a3589a000651df5f660383b79f0f1e51d123150f6f71b9f799e9434a7428d3d70f4cb96a6c0ace5f53843a1ddfc57bea8c28b17a8e5e4e7
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2018 Angelos Orfanakos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'feedigest'
4
+
5
+ puts 'Please provide a list of feed URLs (one per line) and press Ctrl-D'
6
+
7
+ feed_urls = $stdin.readlines.map(&:strip)
8
+
9
+ puts Feedigest.new(feed_urls)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'feedigest'
4
+
5
+ if ENV['FEEDIGEST_EMAIL_RECIPIENT'].nil?
6
+ puts 'Please set $FEEDIGEST_EMAIL_RECIPIENT and try again'
7
+ exit 1
8
+ end
9
+
10
+ puts 'Please provide a list of feed URLs (one per line) and press Ctrl-D'
11
+
12
+ feed_urls = $stdin.readlines.map(&:strip)
13
+
14
+ Feedigest.new(feed_urls).send_email
data/lib/feedigest.rb ADDED
@@ -0,0 +1,192 @@
1
+ require 'dotenv/load'
2
+ require 'feedjira'
3
+ require 'mail'
4
+ require 'nokogiri'
5
+ require 'reverse_markdown'
6
+
7
+ class Feedigest
8
+ ENTRY_WINDOW = ENV.fetch('FEEDIGEST_ENTRY_WINDOW', 60 * 60 * 24) # Seconds
9
+ EMAIL_SENDER = ENV.fetch(
10
+ 'FEEDIGEST_EMAIL_SENDER',
11
+ "feedigest@#{`hostname`.strip}"
12
+ )
13
+ EMAIL_RECIPIENT = ENV.fetch('FEEDIGEST_EMAIL_RECIPIENT')
14
+ DELIVERY_METHOD = ENV.fetch('FEEDIGEST_DELIVERY_METHOD', 'sendmail').to_sym
15
+
16
+ Feed = Struct.new(:url, :title, :entries, :error)
17
+
18
+ attr_reader :feed_urls
19
+
20
+ def initialize(feed_urls)
21
+ @feed_urls = feed_urls
22
+ end
23
+
24
+ def send_email
25
+ build_email.deliver if send_email?
26
+ end
27
+
28
+ def to_s
29
+ build_email.to_s
30
+ end
31
+
32
+ private
33
+
34
+ def send_email?
35
+ feeds.any?
36
+ end
37
+
38
+ def build_email
39
+ return @build_email if @build_email
40
+
41
+ mail = Mail.new
42
+ mail.from = EMAIL_SENDER
43
+ mail.to = EMAIL_RECIPIENT
44
+ mail.subject = email_subject
45
+ mail.text_part = build_email_text_part
46
+ mail.html_part = build_email_html_part
47
+
48
+ setup_delivery_method(mail)
49
+
50
+ @build_email = mail
51
+ end
52
+
53
+ def setup_delivery_method(mail)
54
+ mail.delivery_method(DELIVERY_METHOD, delivery_method_options)
55
+ end
56
+
57
+ def delivery_method_options
58
+ case DELIVERY_METHOD
59
+ when :smtp
60
+ {
61
+ address: ENV.fetch('FEEDIGEST_SMTP_HOST'),
62
+ port: ENV.fetch('FEEDIGEST_SMTP_PORT', '587').to_i,
63
+ user_name: ENV.fetch('FEEDIGEST_SMTP_USERNAME'),
64
+ password: ENV.fetch('FEEDIGEST_SMTP_PASSWORD'),
65
+ authentication: ENV.fetch('FEEDIGEST_SMTP_AUTH', 'plain'),
66
+ enable_starttls: ENV.fetch('FEEDIGEST_SMTP_STARTTLS', 'true') == 'true'
67
+ }
68
+ else
69
+ {}
70
+ end
71
+ end
72
+
73
+ def email_subject
74
+ sprintf(
75
+ 'Digest for %s in %s',
76
+ pluralize(entries_count, 'entry', 'entries'),
77
+ pluralize(feeds_without_error.size, 'feed')
78
+ )
79
+ end
80
+
81
+ def build_email_text_part
82
+ Mail::Part.new.tap do |p|
83
+ p.content_type 'text/plain; charset=utf-8'
84
+ p.body email_body_text
85
+ end
86
+ end
87
+
88
+ def build_email_html_part
89
+ Mail::Part.new.tap do |p|
90
+ p.content_type 'text/html; charset=utf-8'
91
+ p.body email_body_html
92
+ end
93
+ end
94
+
95
+ def email_body_html
96
+ @email_body_html ||=
97
+ Nokogiri::HTML::Builder.new(encoding: 'utf-8') { |builder|
98
+ builder.div do
99
+ feeds.each do |feed|
100
+ feed_html(builder, feed)
101
+ end
102
+ end
103
+ }.to_html
104
+ end
105
+
106
+ def email_body_text
107
+ ReverseMarkdown.convert(email_body_html)
108
+ end
109
+
110
+ def feed_html(builder, feed)
111
+ builder.div do
112
+ if feed.error
113
+ builder.h2 feed.url
114
+ builder.p "Error: #{feed.error}"
115
+ else
116
+ builder.h2 feed.title
117
+ entries_html(builder, feed.entries)
118
+ end
119
+ end
120
+ end
121
+
122
+ def entries_html(builder, entries)
123
+ entries.group_by { |e| e.published.to_date }.each do |date, date_entries|
124
+ builder.h3 date
125
+
126
+ date_entries.each do |entry|
127
+ entry_html(builder, entry)
128
+ end
129
+ end
130
+ end
131
+
132
+ def entry_html(builder, entry)
133
+ builder.p do
134
+ builder.a(entry.title, href: entry.url)
135
+ end
136
+ end
137
+
138
+ def feeds
139
+ @feeds ||= all_feeds.select { |f| process_feed?(f) }
140
+ end
141
+
142
+ def feeds_without_error
143
+ feeds.select { |feed| feed[:error].nil? }
144
+ end
145
+
146
+ def process_feed?(feed)
147
+ feed[:error] || feed[:entries].any?
148
+ end
149
+
150
+ def all_feeds
151
+ feed_urls.map { |url| feed_from_url(url) }
152
+ end
153
+
154
+ def feed_from_url(url)
155
+ feed, error = fetch_and_parse_feed(url)
156
+ Feed.new(
157
+ url,
158
+ feed&.title,
159
+ error ? [] : feed_entries(feed),
160
+ error
161
+ )
162
+ end
163
+
164
+ def fetch_and_parse_feed(url)
165
+ feed = Feedjira::Feed.fetch_and_parse(url)
166
+ [feed, nil]
167
+ rescue StandardError => e
168
+ [nil, e.message]
169
+ end
170
+
171
+ def feed_entries(feed)
172
+ feed.entries.
173
+ select { |e| process_entry?(e) }.
174
+ sort_by { |e| -e.published.to_i }
175
+ end
176
+
177
+ def process_entry?(entry)
178
+ !entry.published.nil? && entry.published >= window_start
179
+ end
180
+
181
+ def window_start
182
+ @window_start ||= Time.now - ENTRY_WINDOW
183
+ end
184
+
185
+ def entries_count
186
+ feeds.reduce(0) { |s, f| s + f.entries.size }
187
+ end
188
+
189
+ def pluralize(count, singular, plural = singular + 's')
190
+ [count, count == 1 ? singular : plural].join(' ')
191
+ end
192
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: feedigest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Angelos Orfanakos
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-06-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dotenv
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: feedjira
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mail
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.7'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nokogiri
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.8'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: reverse_markdown
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.1'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.1'
83
+ description:
84
+ email: me@agorf.gr
85
+ executables:
86
+ - feedigest-send
87
+ - feedigest-generate
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - LICENSE.txt
92
+ - bin/feedigest-generate
93
+ - bin/feedigest-send
94
+ - lib/feedigest.rb
95
+ homepage: https://github.com/agorf/feedigest
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 2.3.3
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.7.3
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: RSS/Atom feed updates as a digest email
119
+ test_files: []