rss_notifier 0.1.5 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ab295187f2099b9dd59cac181cbc86299d93fa43
4
- data.tar.gz: 8293bf6ac91fd26a831ba495bc7515710cf4d3cc
3
+ metadata.gz: 19880610ce9f65b63fb64bb674fea707c709f720
4
+ data.tar.gz: 70e5677949a56fb3baf1fabf7b1245101d883716
5
5
  SHA512:
6
- metadata.gz: 6f5cf66c18989cf7b32aa290f662094faf23fc14b51b20ae4f3802bfef59cbaff7154b296bb6585ed2d15cfced9be8c966ed8b56f97af044fd518ed72b12353c
7
- data.tar.gz: 7cd8955e87cd8e51f867f110da13d39f1811c916057341593cd1b3fd5f733f8fe14dd3979b515024229d0c16e9d02551b36ce5f0b9580c00bc0933b0bdd971cb
6
+ metadata.gz: fcd67b4add33589b3695441d5e5c225680e57848b266f53100f9d9fb47842ac4ae7f9df69978686c204a4d78a500aeb2aca01c176136bd16a44204fe949309cc
7
+ data.tar.gz: 32419a690ee70c8cc94be2b5877bf4250e4f806a9ee04b2b4b762de847a798decb74e399922a2248b11596af84d236d790949e6a8dbff94263384ba70d1341cf
data/README.md CHANGED
@@ -3,6 +3,17 @@
3
3
  It checks given rss urls every given period and notifies you using [Pushbullet](https://www.pushbullet.com).
4
4
 
5
5
 
6
+ ## Getting notifications
7
+
8
+ ### Email
9
+
10
+ Create an account on https://sendgrid.com and generate API Key.
11
+
12
+ ### Pushbullet
13
+
14
+ Create an account on https://www.pushbullet.com and generate Access Token.
15
+
16
+
6
17
  ## Usage
7
18
 
8
19
  Install the gem
@@ -42,11 +42,11 @@
42
42
  </style>
43
43
  </head>
44
44
  <body>
45
- <h1><a href="<%= item.link %>"><%= item.title %></a></h1>
45
+ <h1><a href="<%= item.link %>"><%= item.feed.name %> | <%= item.title %></a></h1>
46
46
  <hr>
47
47
  <div class="row">
48
48
  <div class="mini-info">
49
- <b>RSS URL:</b> <span style="word-wrap: break-word;"><%= item.rss_url %></span> <br><br>
49
+ <b>RSS URL:</b> <span style="word-wrap: break-word;"><%= item.feed.url %></span> <br><br>
50
50
  <b>Date:</b> <%= item.date.strftime('%Y-%m-%d %H:%M:%S') %> <br>
51
51
  </div>
52
52
  <div class="clearfix"></div>
@@ -1,5 +1,6 @@
1
1
  require 'http'
2
2
  require 'erb'
3
+ require 'sendgrid-ruby'
3
4
 
4
5
  module RssNotifier
5
6
  module Adapter
@@ -9,30 +10,63 @@ module RssNotifier
9
10
 
10
11
  HTML_TEMPLATE = File.expand_path('../email.html.erb', __FILE__)
11
12
 
12
- # curl -s --user 'api:YOUR_API_KEY' \
13
- # https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages \
14
- # -F from='Excited User <mailgun@YOUR_DOMAIN_NAME>' \
15
- # -F to=YOU@YOUR_DOMAIN_NAME \
16
- # -F to=bar@example.com \
17
- # -F subject='Hello' \
18
- # -F text='Testing some Mailgun awesomness!'
19
-
20
- def initialize(email)
13
+ # @param email [String]
14
+ # @param config [RssNotifier::Config]
15
+ def initialize(email, config)
16
+ @config = config
21
17
  @email = email
22
18
  @template = ERB.new(File.read(HTML_TEMPLATE))
19
+
20
+ @config[:adapters][:email][:from] or
21
+ raise 'config[:adapters][:email][:from] is not configured.'
22
+
23
+ @config[:adapters][:email][:sendgrid_api_key] or
24
+ raise 'config[:adapters][:email][:sendgrid_api_key] is not configured.'
23
25
  end
24
26
 
25
27
  def notify(item)
26
28
  b = binding
27
29
  b.local_variable_set(:item, item)
28
30
  html = @template.result(b)
29
- puts "-------"
31
+
32
+ RssNotifier.logger.debug("Adapter::Email #{email}, #{html}")
33
+
34
+ mail = SendGrid::Mail.new do |m|
35
+ m.to = email
36
+ m.from = @config[:adapters][:email][:from]
37
+ m.subject = "#{item.feed.name} | #{item.title}"
38
+ m.text = html
39
+ end
40
+
41
+ client = RssNotifier::Adapter::Email.client(@config[:adapters][:email][:sendgrid_api_key])
42
+
43
+ begin
44
+ res = client.send(mail)
45
+ if res.code == 200
46
+ RssNotifier.logger.warn("Could not notify #{self}. Code=#{res.code}")
47
+ p res.body
48
+ else
49
+ RssNotifier.logger.debug("#{self} notified")
50
+ return false
51
+ end
52
+ rescue => e
53
+ puts e.to_s
54
+ puts e.backtrace
55
+ return false
56
+ end
57
+
58
+ true
30
59
  end
31
60
 
32
61
  def to_s
33
62
  "<Adapter::Email email=#{email}>"
34
63
  end
35
64
 
65
+ def self.client(api_key)
66
+ @clients ||= {}
67
+ @clients[api_key] ||= SendGrid::Client.new(api_key: api_key)
68
+ end
69
+
36
70
  end
37
71
  end
38
72
  end
@@ -16,7 +16,7 @@ module RssNotifier
16
16
  def notify(item)
17
17
  body = JSON.dump({
18
18
  'type' => 'link',
19
- 'title' => "#{item.rss_title}",
19
+ 'title' => "#{item.feed.name}",
20
20
  'body' => item.title,
21
21
  'url' => item.link
22
22
  })
@@ -1,7 +1,7 @@
1
1
  require 'rss_notifier/adapter'
2
2
  require 'rss_notifier/config'
3
3
  require 'rss_notifier/db'
4
- require 'rss_notifier/item'
4
+ require 'rss_notifier/models'
5
5
 
6
6
  require 'pathname'
7
7
  require 'rss'
@@ -50,7 +50,7 @@ module RssNotifier
50
50
  notify.each do |d|
51
51
  next unless d[:enabled]
52
52
  if d[:adapter] == 'email'
53
- @notify << Adapter::Email.new(d[:email])
53
+ @notify << Adapter::Email.new(d[:email], @config)
54
54
  elsif d[:adapter] == 'pushbullet'
55
55
  @notify << Adapter::Pushbullet.new(d[:name], d[:access_token])
56
56
  else
@@ -67,40 +67,47 @@ module RssNotifier
67
67
  end
68
68
  end
69
69
 
70
+ # @return [Array] of tuples (url, name)
71
+ def rss_urls_from_config
72
+ @rss_urls_from_config ||= @config.rss_urls.map do |o|
73
+ [ o[:url].to_s.strip, o[:name].to_s.strip ]
74
+ end.to_h
75
+ end
76
+
70
77
  def check_rss!
71
- @config.rss_urls.each do |url_object|
72
- title, url = url_object[:title], url_object[:url]
78
+ changed_items = []
79
+
80
+ rss_urls_from_config.each do |url, name|
81
+ RssNotifier.logger.info "Checking #{name} | #{url}"
73
82
 
74
- RssNotifier.logger.info "Checking #{title} | #{url}"
83
+ feed = @db.get_feed(url: url, name: name)
75
84
 
76
- raw = HTTP.get(url).to_s
77
- feed = nil
85
+ rss_raw = nil
86
+ response = nil
78
87
  begin
79
- raw = raw.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
80
- feed = RSS::Parser.parse(raw)
88
+ response = HTTP.get(url)
89
+ # require 'pry'; binding.pry
90
+ raw_encoded = response.to_s.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
91
+ rss_raw = RSS::Parser.parse(raw_encoded)
81
92
  rescue => e
82
93
  RssNotifier.logger.warn "Cannot parse RSS: #{e}"
83
94
  puts e.backtrace
84
95
  next
85
96
  end
86
- items = []
87
- feed.items.each do |item|
88
- items << Item.new({
89
- rss_url: url,
90
- rss_title: title,
91
- link: item.link,
92
- title: item.title,
93
- description: item.description,
94
- date: item.date
97
+ rss_raw.items.each do |raw_item|
98
+ item = feed.find_or_create_item(link: raw_item.link)
99
+ item.update({
100
+ title: raw_item.title,
101
+ description: raw_item.description,
102
+ date: raw_item.date
95
103
  })
104
+ changed_items << item if item.changed?
96
105
  end
97
- @db.update(items)
106
+
107
+ feed.save
98
108
  end
99
- changed_items = @db.changed_items
100
- is_changed = @db.changed?
101
- @db.save
102
109
 
103
- unless is_changed
110
+ if changed_items.empty?
104
111
  RssNotifier.logger.info 'No changes'
105
112
  return
106
113
  end
@@ -111,6 +118,8 @@ module RssNotifier
111
118
  changed_items.each do |item|
112
119
  notify!(item)
113
120
  end
121
+ else
122
+ RssNotifier.logger.info "#{changed_items.size} items changed"
114
123
  end
115
124
  end
116
125
 
@@ -7,16 +7,16 @@ module RssNotifier
7
7
  DEFAULTS = {
8
8
  adapters: {
9
9
  email: {
10
- mailgun_api: 'something',
11
- from: 'Excited User <mailgun@YOUR_DOMAIN_NAME>'
10
+ sendgrid_api_key: '<Get it from sendgrid.com>',
11
+ from: 'RSS Notifier <rss-notifier@joma.pw>',
12
12
  }
13
13
  },
14
14
  notify: [
15
15
  { email: 'me@example.com', adapter: 'email', enabled: false },
16
- { name: 'mini me', access_token: '<get from pushbullet.com/#settings>', adapter: 'pushbullet', enabled: false },
16
+ { name: 'mini me', access_token: '<Get it from pushbullet.com>', adapter: 'pushbullet', enabled: false },
17
17
  ],
18
18
  rss_urls: [
19
- { title: 'Some News', url: 'http://some-site.com/rss' }
19
+ { name: 'Some News', url: 'http://some-site.com/rss' }
20
20
  ],
21
21
  period_in_minutes: 20
22
22
  }
@@ -1,15 +1,18 @@
1
1
  require 'yaml/store'
2
2
 
3
+ require 'rss_notifier/models'
4
+
5
+ require 'pry'
6
+
3
7
  module RssNotifier
4
8
  class Db
5
9
 
6
10
  attr_reader :store
7
- attr_accessor :items
11
+ attr_accessor :feeds
8
12
 
9
13
 
10
14
  def initialize(store)
11
- # { item_url => Item, ... }
12
- @items = {}
15
+ @feeds = {}
13
16
  @store = store
14
17
  end
15
18
 
@@ -56,13 +59,42 @@ module RssNotifier
56
59
  true
57
60
  end
58
61
 
59
- def self.load(filename)
60
- store = YAML::Store.new(filename)
61
- db = RssNotifier::Db.new(store)
62
+ # @return [RssNotifier::Models::Feed] creates new Feed if not found
63
+ def get_feed(url:, name:)
64
+ @feeds[url] ||= RssNotifier::Models::Feed.new(url: url.to_s.strip, name: name)
65
+ end
66
+
67
+ # @return [RssNotifier::Models::Feed]
68
+ def find_feed(url:)
69
+ @feeds[url.to_s.strip]
70
+ end
71
+
72
+ def load
73
+ @feeds = {}
62
74
  store.transaction do
63
- db.items = store['items'] || {}
75
+ feeds_raw = store['feeds'] || {}
76
+ feeds_raw.each do |url, feed_raw|
77
+ feed = RssNotifier::Models::Feed.new(feed_raw)
78
+ @feeds[feed.url] = feed
79
+ end
64
80
  end
65
- db
81
+ self
82
+ end
83
+
84
+ # @return [RssNotifier::Db]
85
+ def self.default
86
+ @default or raise "Not initialized"
87
+ end
88
+
89
+ def self.default=(default)
90
+ @default = default
91
+ end
92
+
93
+ def self.load(filename)
94
+ store = YAML::Store.new(filename)
95
+ RssNotifier::Db.default = RssNotifier::Db.new(store)
96
+ RssNotifier::Db.default.load
97
+ RssNotifier::Db.default
66
98
  end
67
99
 
68
100
  end
@@ -0,0 +1,9 @@
1
+ require 'rss_notifier/models/item'
2
+ require 'rss_notifier/models/feed'
3
+
4
+ Virtus.finalize
5
+
6
+ module RssNotifier
7
+ module Models
8
+ end
9
+ end
@@ -0,0 +1,65 @@
1
+ require 'virtus'
2
+ require 'inflecto'
3
+ require 'json'
4
+
5
+ # {
6
+ # rss_url => {
7
+ # 'Last-Modified' => 'blah',
8
+ # 'items' => { item_url => Item, ... },
9
+ # }
10
+ # }
11
+ module RssNotifier
12
+ module Models
13
+ class Feed
14
+ include Virtus.model(:finalize => false)
15
+
16
+ attribute :url, String
17
+ attribute :name, String
18
+ attribute :items, Array['RssNotifier::Models::Item']
19
+
20
+ # updated_at in DB
21
+ attribute :updated_at, Time
22
+
23
+
24
+ # @return [RssNotifier::Models::Item]
25
+ def find_or_create_item(link:)
26
+ item = items.detect { |i| i.link.to_s.strip == link.to_s.strip }
27
+ unless item
28
+ item = RssNotifier::Models::Item.new(feed_url: self.url, link: link)
29
+ self.items << item
30
+ end
31
+ item
32
+ end
33
+
34
+ def store
35
+ @store ||= RssNotifier::Db.default.store
36
+ end
37
+
38
+ def changed?
39
+ !updated_at || items.any?(&:changed?)
40
+ end
41
+
42
+ def save
43
+ items.each do |item|
44
+ item.updated_at = Time.now
45
+ end
46
+ self.updated_at = Time.now
47
+
48
+ store.transaction do
49
+ store["feeds"] ||= {}
50
+ store["feeds"][url] = self.to_db_object
51
+ end
52
+ end
53
+
54
+ def to_db_object
55
+ {
56
+ 'url' => url,
57
+ 'name' => name,
58
+ 'updated_at' => updated_at,
59
+ 'items' => items.map(&:to_db_object),
60
+ }
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,61 @@
1
+ require 'virtus'
2
+
3
+ module RssNotifier
4
+ module Models
5
+ class Item
6
+ include Virtus.model
7
+
8
+ attribute :link, String
9
+ attribute :title, String
10
+ attribute :description, String
11
+ attribute :date, Time
12
+
13
+ # updated_at in DB
14
+ attribute :updated_at, Time
15
+
16
+ attribute :feed_url, String
17
+
18
+
19
+ def feed
20
+ @feed ||= RssNotifier::Db.default.find_feed(url: feed_url)
21
+ end
22
+
23
+ def update(attributes)
24
+ __title = self.title
25
+ self.attributes = attributes
26
+ @__changed = __title != self.title
27
+ end
28
+
29
+ def changed?
30
+ new_record? || @__changed
31
+ end
32
+
33
+ def new_record?
34
+ !self.updated_at
35
+ end
36
+
37
+ def to_h
38
+ {
39
+ feed_url: feed.url,
40
+ feed_name: feed.name,
41
+ link: link,
42
+ title: title,
43
+ description: description,
44
+ date: date,
45
+ }
46
+ end
47
+
48
+ def to_db_object
49
+ {
50
+ 'link' => link,
51
+ 'title' => title,
52
+ 'description' => description,
53
+ 'date' => date,
54
+ 'updated_at' => updated_at,
55
+ 'feed_url' => feed_url,
56
+ }
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -1,3 +1,3 @@
1
1
  module RssNotifier
2
- VERSION = "0.1.5"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -21,6 +21,9 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_dependency 'http', '~> 1.0'
23
23
  spec.add_dependency 'thor', '~> 0.19'
24
+ spec.add_dependency 'virtus', '~> 1.0'
25
+ spec.add_dependency 'inflecto', '~> 0.0'
26
+ spec.add_dependency 'sendgrid-ruby', '~> 1.1'
24
27
 
25
28
  spec.add_development_dependency "bundler", "~> 1.11"
26
29
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rss_notifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zhomart Mukhamejanov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-23 00:00:00.000000000 Z
11
+ date: 2016-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http
@@ -38,6 +38,48 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.19'
41
+ - !ruby/object:Gem::Dependency
42
+ name: virtus
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: inflecto
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.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.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sendgrid-ruby
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'
41
83
  - !ruby/object:Gem::Dependency
42
84
  name: bundler
43
85
  requirement: !ruby/object:Gem::Requirement
@@ -119,7 +161,9 @@ files:
119
161
  - lib/rss_notifier/cli.rb
120
162
  - lib/rss_notifier/config.rb
121
163
  - lib/rss_notifier/db.rb
122
- - lib/rss_notifier/item.rb
164
+ - lib/rss_notifier/models.rb
165
+ - lib/rss_notifier/models/feed.rb
166
+ - lib/rss_notifier/models/item.rb
123
167
  - lib/rss_notifier/version.rb
124
168
  - rss_notifier.gemspec
125
169
  homepage: https://github.com/Zhomart/rss_notifier
@@ -1,60 +0,0 @@
1
- module RssNotifier
2
- class Item
3
- DONT_SAVE = [ '@is_changed' ]
4
-
5
- attr_accessor :rss_url
6
- attr_accessor :rss_title
7
- attr_accessor :link
8
- attr_accessor :title
9
- attr_accessor :description
10
- attr_accessor :date
11
- attr_accessor :saved_at
12
-
13
- attr_accessor :is_changed
14
-
15
-
16
- def initialize(params = nil)
17
- params and params.each do |k, v|
18
- self.send("#{k}=", v)
19
- end
20
- self.is_changed = false
21
- end
22
-
23
- def update(params)
24
- old_title = self.title
25
- params.each do |k, v|
26
- send("#{k}=", v)
27
- end
28
- self.is_changed = old_title != self.title
29
- end
30
-
31
- def changed?
32
- self.is_changed
33
- end
34
-
35
- def new_record?
36
- !self.saved_at
37
- end
38
-
39
- def to_h
40
- {
41
- rss_url: rss_url,
42
- rss_title: rss_title,
43
- link: link,
44
- title: title,
45
- description: description,
46
- date: date,
47
- }
48
- end
49
-
50
- def encode_with(coder)
51
- vars = instance_variables.map { |x| x.to_s } - DONT_SAVE
52
-
53
- vars.each do |var|
54
- var_val = eval(var)
55
- coder[var.gsub('@', '')] = var_val
56
- end
57
- end
58
-
59
- end
60
- end