rss_notifier 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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