feedbook 0.9.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.
Files changed (51) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +129 -0
  8. data/Rakefile +7 -0
  9. data/bin/feedbook +73 -0
  10. data/feedbook.gemspec +35 -0
  11. data/lib/feedbook.rb +6 -0
  12. data/lib/feedbook/comparers/posts_comparer.rb +15 -0
  13. data/lib/feedbook/configuration.rb +47 -0
  14. data/lib/feedbook/errors.rb +10 -0
  15. data/lib/feedbook/errors/invalid_feed_url_error.rb +6 -0
  16. data/lib/feedbook/errors/invalid_interval_format_error.rb +6 -0
  17. data/lib/feedbook/errors/invalid_variables_format_error.rb +6 -0
  18. data/lib/feedbook/errors/no_configuration_file_error.rb +6 -0
  19. data/lib/feedbook/errors/notifier_configuration_error.rb +12 -0
  20. data/lib/feedbook/errors/notifier_notify_error.rb +12 -0
  21. data/lib/feedbook/errors/parse_feed_error.rb +11 -0
  22. data/lib/feedbook/errors/template_syntax_error.rb +6 -0
  23. data/lib/feedbook/errors/unsupported_notifier_error.rb +12 -0
  24. data/lib/feedbook/factories/notifiers_factory.rb +28 -0
  25. data/lib/feedbook/feed.rb +85 -0
  26. data/lib/feedbook/helpers/time_interval_parser.rb +38 -0
  27. data/lib/feedbook/listener.rb +122 -0
  28. data/lib/feedbook/notification.rb +62 -0
  29. data/lib/feedbook/notifiers.rb +5 -0
  30. data/lib/feedbook/notifiers/facebook_notifier.rb +44 -0
  31. data/lib/feedbook/notifiers/irc_notifier.rb +42 -0
  32. data/lib/feedbook/notifiers/mail_notifier.rb +52 -0
  33. data/lib/feedbook/notifiers/null_notifier.rb +25 -0
  34. data/lib/feedbook/notifiers/twitter_notifier.rb +49 -0
  35. data/lib/feedbook/post.rb +33 -0
  36. data/lib/feedbook/version.rb +3 -0
  37. data/spec/spec_helper.rb +30 -0
  38. data/spec/unit/lib/comparers/posts_comparer_spec.rb +13 -0
  39. data/spec/unit/lib/configuration_spec.rb +43 -0
  40. data/spec/unit/lib/factories/notifiers_factory_spec.rb +28 -0
  41. data/spec/unit/lib/feed_spec.rb +42 -0
  42. data/spec/unit/lib/helpers/time_interval_parser_spec.rb +26 -0
  43. data/spec/unit/lib/listener_spec.rb +29 -0
  44. data/spec/unit/lib/notification_spec.rb +44 -0
  45. data/spec/unit/lib/notifiers/facebook_notifier_spec.rb +34 -0
  46. data/spec/unit/lib/notifiers/irc_notifier_spec.rb +41 -0
  47. data/spec/unit/lib/notifiers/mail_notifier_spec.rb +39 -0
  48. data/spec/unit/lib/notifiers/null_notifier_spec.rb +19 -0
  49. data/spec/unit/lib/notifiers/twitter_notifier_spec.rb +36 -0
  50. data/spec/unit/lib/post_spec.rb +51 -0
  51. metadata +304 -0
@@ -0,0 +1,42 @@
1
+ require 'singleton'
2
+ require 'irc-notify'
3
+
4
+ module Feedbook
5
+ module Notifiers
6
+ class IRCNotifier
7
+ include Singleton
8
+
9
+ # Sends notification to IRC
10
+ # @param message [String] message to be send to IRC
11
+ #
12
+ # @return [NilClass] nil
13
+ def notify(message)
14
+ client.register(nick)
15
+ client.notify(message)
16
+ client.quit
17
+
18
+ puts "New message has been notified on IRC: #{message}"
19
+ end
20
+
21
+ # Load configuration for IRCNotifier
22
+ # @param configuration = {} [Hash] Configuration hash (required: address, domain, username, password, to, from, subject)
23
+ #
24
+ # @return [NilClass] nil
25
+ def load_configuration(configuration = {})
26
+ irc_url = configuration.fetch('url')
27
+ irc_port = configuration.fetch('port')
28
+ ssl_enabled = configuration.fetch('ssl_enabled')
29
+
30
+ @channel = configuration.fetch('channel')
31
+ @nick = configuration.fetch('nick')
32
+
33
+ @client = IrcNotify::Client.build(irc_url, irc_port, ssl: ssl_enabled)
34
+
35
+ puts 'Configuration loaded for IRCNotifier'
36
+ end
37
+
38
+ private
39
+ attr_reader :client, :nick, :channel
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ require 'singleton'
2
+ require 'mail'
3
+
4
+ module Feedbook
5
+ module Notifiers
6
+ class MailNotifier
7
+ include Singleton
8
+
9
+ # Sends notification to Mail
10
+ # @param message [String] message to be send via email
11
+ #
12
+ # @return [NilClass] nil
13
+ def notify(message)
14
+ Mail.deliver do
15
+ to to
16
+ from from
17
+ subject subject
18
+ body message
19
+ end
20
+
21
+ puts "New message has been notified on Mail: #{message}"
22
+ end
23
+
24
+ # Load configuration for MailNotifier
25
+ # @param configuration = {} [Hash] Configuration hash (required: address, domain, username, password, to, from, subject)
26
+ #
27
+ # @return [NilClass] nil
28
+ def load_configuration(configuration = {})
29
+ options = { address: configuration.fetch('address'),
30
+ port: configuration.fetch('port', 5870),
31
+ domain: configuration.fetch('domain'),
32
+ user_name: configuration.fetch('username'),
33
+ password: configuration.fetch('password'),
34
+ authentication: configuration.fetch('authentication', 'plain'),
35
+ enable_starttls_auto: configuration.fetch('enable_starttls_auto', true) }
36
+
37
+ @to = configuration.fetch('to')
38
+ @from = configuration.fetch('from')
39
+ @subject = configuration.fetch('subject')
40
+
41
+ Mail.defaults do
42
+ delivery_method :smtp, options
43
+ end
44
+
45
+ puts 'Configuration loaded for MailNotifier'
46
+ end
47
+
48
+ private
49
+ attr_reader :to, :from, :subject
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,25 @@
1
+ require 'singleton'
2
+
3
+ module Feedbook
4
+ module Notifiers
5
+ class NullNotifier
6
+ include Singleton
7
+
8
+ # Sends notification to Nil
9
+ # @param message [String] message to be displayed in console
10
+ #
11
+ # @return [NilClass] nil
12
+ def notify(message)
13
+ puts "New message has been notified: #{message}"
14
+ end
15
+
16
+ # Load configuration for NullNotifier
17
+ # @param configuration = {} [Hash] Configuration hash
18
+ #
19
+ # @return [NilClass] nil
20
+ def load_configuration(configuration = {})
21
+ puts 'Configuration loaded for NullNotifier'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ require 'singleton'
2
+ require 'twitter'
3
+ require 'feedbook/errors/notifier_configuration_error'
4
+ require 'feedbook/errors/notifier_notify_error'
5
+
6
+ module Feedbook
7
+ module Notifiers
8
+ class TwitterNotifier
9
+ include Singleton
10
+
11
+ # Sends notification to Twitter
12
+ # @param message [String] message to be send to Twitter
13
+ #
14
+ # @return [NilClass] nil
15
+ # @raise [Feedbook::Errors::NotifierNotifyError] if notify method fails
16
+ def notify(message)
17
+ if client.nil?
18
+ puts "Message has not been notified on Twitter: #{message} because of invalid client configuration"
19
+ else
20
+ client.update(message)
21
+ puts "New message has been notified on Twitter: #{message}"
22
+ end
23
+ rescue Twitter::Error => e
24
+ raise Errors::NotifierNotifyError.new(:twitter, e.message)
25
+ end
26
+
27
+ # Load configuration for TwitterNotifier
28
+ # @param configuration = {} [Hash] Configuration hash (required: consumer_key, consumer_secret, access_token, access_token_secret)
29
+ #
30
+ # @return [NilClass] nil
31
+ # @raise [Feedbook::Errors::NotifierConfigurationError] if notifier configuration fails
32
+ def load_configuration(configuration = {})
33
+ @client = Twitter::REST::Client.new do |config|
34
+ config.consumer_key = configuration.fetch('consumer_key')
35
+ config.consumer_secret = configuration.fetch('consumer_secret')
36
+ config.access_token = configuration.fetch('access_token')
37
+ config.access_token_secret = configuration.fetch('access_token_secret')
38
+ end
39
+
40
+ puts 'Configuration loaded for TwitterNotifier'
41
+ rescue Twitter::Error => e
42
+ raise Errors::NotifierConfigurationError.new(:twitter, e.message)
43
+ end
44
+
45
+ private
46
+ attr_reader :client
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,33 @@
1
+ module Feedbook
2
+ class Post
3
+
4
+ attr_reader :author, :published, :url, :title, :feed_title
5
+
6
+ # Initializes new Post from opts Hash
7
+ # @param opts = {} [Hash] Hash with params required for Post creation
8
+ #
9
+ # @return [NilClass] nil
10
+ def initialize(opts = {})
11
+ @author = opts.fetch(:author)
12
+ @published = opts.fetch(:published)
13
+ @url = opts.fetch(:url)
14
+ @title = opts.fetch(:title)
15
+ @feed_title = opts.fetch(:feed_title)
16
+ end
17
+
18
+ # Returns hash with values from posts.
19
+ #
20
+ # @return [Hash] Hash with post variables
21
+ def to_hash
22
+ {
23
+ 'author' => author,
24
+ 'published' => published,
25
+ 'url' => url,
26
+ 'title' => title,
27
+ 'feed_title' => feed_title
28
+ }
29
+ end
30
+ alias :to_h :to_hash
31
+
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module Feedbook
2
+ VERSION = '0.9.0'
3
+ end
@@ -0,0 +1,30 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'coveralls'
5
+ Coveralls.wear!
6
+
7
+ require 'feedbook'
8
+
9
+ RSpec.configure do |config|
10
+ config.filter_run :focus
11
+ config.run_all_when_everything_filtered = true
12
+
13
+ if config.files_to_run.one?
14
+ config.default_formatter = 'doc'
15
+ end
16
+
17
+ config.profile_examples = 10
18
+ config.order = :random
19
+
20
+ Kernel.srand config.seed
21
+
22
+ config.expect_with :rspec do |expectations|
23
+ expectations.syntax = :expect
24
+ end
25
+
26
+ config.mock_with :rspec do |mocks|
27
+ mocks.syntax = :expect
28
+ mocks.verify_partial_doubles = true
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Feedbook::Comparers::PostsComparer do
4
+
5
+ describe '#get_new_posts' do
6
+ let(:old_posts) { [OpenStruct.new(name: 'test1', url: 'test1.blog.lo'), OpenStruct.new(name: 'test1', url: 'test1.blog.lo'), OpenStruct.new(name: 'test3', url: 'test3.blog.lo'), OpenStruct.new(name: 'test1', url: 'test2.blog.lo')] }
7
+ let(:new_posts) { old_posts + [OpenStruct.new(name: 'test1', url: 'test4.blog.lo'), OpenStruct.new(name: 'test1', url: 'test7.blog.lo'), OpenStruct.new(name: 'test6', url: 'test5.blog.lo'), OpenStruct.new(name: 'test7', url: 'test2.blog.lo')] }
8
+
9
+ it 'should return only posts with urls that did not exists in old posts' do
10
+ expect(Feedbook::Comparers::PostsComparer.get_new_posts(old_posts, new_posts)).to eq([OpenStruct.new(name: 'test1', url: 'test4.blog.lo'), OpenStruct.new(name: 'test1', url: 'test7.blog.lo'), OpenStruct.new(name: 'test6', url: 'test5.blog.lo')])
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Feedbook::Configuration do
4
+
5
+ let(:hash) do
6
+ {
7
+ twitter: {},
8
+ facebook: {},
9
+ irc: {},
10
+ interval: '5m'
11
+ }
12
+ end
13
+
14
+ before :each do
15
+ allow(Feedbook::Helpers::TimeIntervalParser).to receive(:parse).with('5m').and_return(300)
16
+ allow(Feedbook::Helpers::TimeIntervalParser).to receive(:parse).with('').and_raise(Feedbook::Errors::InvalidIntervalFormatError.new)
17
+ end
18
+
19
+ subject { Feedbook::Configuration.new(hash) }
20
+
21
+ describe '#initialize' do
22
+
23
+ it 'parses hash and creates Configuration instance' do
24
+ expect(subject.interval).to eq(300)
25
+ end
26
+
27
+ it 'should raise Errors::InvalidIntervalFormatError if interval parameter is missing' do
28
+ expect { Feedbook::Configuration.new({}) }.to raise_error(Feedbook::Errors::InvalidIntervalFormatError)
29
+ end
30
+ end
31
+
32
+ describe '#load_notifiers' do
33
+
34
+ it 'should return hash with Post parameters' do
35
+ expect(Feedbook::Notifiers::TwitterNotifier).to receive_message_chain(:instance, load_configuration: {}) { true }
36
+ expect(Feedbook::Notifiers::FacebookNotifier).to receive_message_chain(:instance, load_configuration: {}) { true }
37
+ expect(Feedbook::Notifiers::IRCNotifier).to receive_message_chain(:instance, load_configuration: {}) { true }
38
+
39
+ subject.load_notifiers
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Feedbook::Factories::NotifiersFactory do
4
+
5
+ describe '#create' do
6
+
7
+ context 'FacebookNotifier' do
8
+ it { expect(Feedbook::Factories::NotifiersFactory.create('facebook')).to be(Feedbook::Notifiers::FacebookNotifier.instance) }
9
+ it { expect(Feedbook::Factories::NotifiersFactory.create(:facebook)).to be(Feedbook::Notifiers::FacebookNotifier.instance) }
10
+ end
11
+
12
+ context 'IRCNotifier' do
13
+ it { expect(Feedbook::Factories::NotifiersFactory.create('irc')).to be(Feedbook::Notifiers::IRCNotifier.instance) }
14
+ it { expect(Feedbook::Factories::NotifiersFactory.create(:irc)).to be(Feedbook::Notifiers::IRCNotifier.instance) }
15
+ end
16
+
17
+ context 'NullNotifier' do
18
+ it { expect(Feedbook::Factories::NotifiersFactory.create('null')).to be(Feedbook::Notifiers::NullNotifier.instance) }
19
+ it { expect(Feedbook::Factories::NotifiersFactory.create(:null)).to be(Feedbook::Notifiers::NullNotifier.instance) }
20
+ end
21
+
22
+ context 'TwitterNotifier' do
23
+ it { expect(Feedbook::Factories::NotifiersFactory.create('twitter')).to be(Feedbook::Notifiers::TwitterNotifier.instance) }
24
+ it { expect(Feedbook::Factories::NotifiersFactory.create(:twitter)).to be(Feedbook::Notifiers::TwitterNotifier.instance) }
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Feedbook::Feed do
4
+
5
+ let(:hash) do
6
+ {
7
+ urls: 'http://blog.test.lo/blog http://blog.test.lo/blog2',
8
+ notifications: [
9
+ { 'type' => 'twitter', 'template' => '{{ test_variable }} POST' },
10
+ { 'type' => 'facebook', 'template' => '{{ test_variable }} POST 2' }
11
+ ],
12
+ variables: { 'test_variable' => 'test_value' }
13
+ }
14
+ end
15
+
16
+ subject { Feedbook::Feed.new(hash) }
17
+
18
+ describe '#initialize' do
19
+
20
+ it 'parses hash and creates Feed instance' do
21
+ expect(subject.urls).to eq(['http://blog.test.lo/blog', 'http://blog.test.lo/blog2'])
22
+ expect(subject.variables).to eq({ 'test_variable' => 'test_value' })
23
+ end
24
+
25
+ it 'should raise Errors::InvalidIntervalFormatError if interval parameter is missing' do
26
+ expect(Feedbook::Feed.new({}).urls).to eq([])
27
+ expect(Feedbook::Feed.new({}).notifications).to eq([])
28
+ expect(Feedbook::Feed.new({}).variables).to eq({})
29
+ end
30
+ end
31
+
32
+ describe '#fetch' do
33
+
34
+ it 'should send fetch_and_parse to Feedjira::Feed' do
35
+ allow(Feedjira::Feed).to receive(:fetch_and_parse).with('http://blog.test.lo/blog').and_return([])
36
+ allow(Feedjira::Feed).to receive(:fetch_and_parse).with('http://blog.test.lo/blog2').and_return([])
37
+
38
+ expect(subject.fetch).to eq([])
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Feedbook::Helpers::TimeIntervalParser do
4
+
5
+ describe '#parse' do
6
+
7
+ context 'with proper input parameter' do
8
+ it { expect(Feedbook::Helpers::TimeIntervalParser.parse('1s')).to eq(1) }
9
+ it { expect(Feedbook::Helpers::TimeIntervalParser.parse('40s')).to eq(40) }
10
+ it { expect(Feedbook::Helpers::TimeIntervalParser.parse('1m')).to eq(60) }
11
+ it { expect(Feedbook::Helpers::TimeIntervalParser.parse('30m')).to eq(1800) }
12
+ it { expect(Feedbook::Helpers::TimeIntervalParser.parse('1h')).to eq(3600) }
13
+ it { expect(Feedbook::Helpers::TimeIntervalParser.parse('24h')).to eq(86400) }
14
+ it { expect(Feedbook::Helpers::TimeIntervalParser.parse('1d')).to eq(86400) }
15
+ it { expect(Feedbook::Helpers::TimeIntervalParser.parse('10d')).to eq(864000) }
16
+ end
17
+
18
+ context 'with invalid input parameter' do
19
+ it { expect { Feedbook::Helpers::TimeIntervalParser.parse(nil) }.to raise_error(Feedbook::Errors::InvalidIntervalFormatError) }
20
+ it { expect { Feedbook::Helpers::TimeIntervalParser.parse('') }.to raise_error(Feedbook::Errors::InvalidIntervalFormatError) }
21
+ it { expect { Feedbook::Helpers::TimeIntervalParser.parse('xx') }.to raise_error(Feedbook::Errors::InvalidIntervalFormatError) }
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Feedbook::Listener do
4
+
5
+ subject { Feedbook::Listener }
6
+
7
+ describe '#start' do
8
+
9
+ let(:configuration) { double }
10
+ let(:feeds) { [double, double, double] }
11
+
12
+ before :each do
13
+ allow(configuration).to receive(:interval).and_return(300)
14
+ allow(subject).to receive(:load_configuration).with('feedbook.yml').and_return([feeds, configuration])
15
+ end
16
+
17
+ it 'parses hash and creates Feed instance' do
18
+ allow(configuration).to receive(:load_notifiers)
19
+ feeds.each do |feed|
20
+ expect(feed).to receive(:fetch)
21
+ expect(feed).to receive(:valid?)
22
+ end
23
+
24
+ expect(Object).to receive(:every).with(300).and_return(300)
25
+
26
+ subject.start('feedbook.yml')
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Feedbook::Notification do
4
+
5
+ let(:hash) do
6
+ {
7
+ type: 'twitter',
8
+ template: 'Post {{ test_variable }} on {{ url }}',
9
+ variables: { 'test_variable' => 'test_value' }
10
+ }
11
+ end
12
+
13
+ subject { Feedbook::Notification.new(hash) }
14
+
15
+ describe '#initialize' do
16
+
17
+ it 'parses hash and creates Notification instance' do
18
+ expect(subject.type).to eq('twitter')
19
+ expect(subject.variables).to eq({ 'test_variable' => 'test_value' })
20
+ end
21
+
22
+ it 'should set default values' do
23
+ expect(Feedbook::Notification.new({}).type).to eq('')
24
+ expect(Feedbook::Notification.new({}).variables).to eq({})
25
+ end
26
+ end
27
+
28
+ describe '#notify' do
29
+
30
+ it 'create and send notification message to notifier' do
31
+ expect(Liquid::Template).to receive_message_chain(parse: 'Post {{ test_variable }} on {{ url }}', render: { 'test_variable' => 'test_value', 'url' => 'test_url' })
32
+ expect(subject).to receive_message_chain(:notifier, notify: OpenStruct.new(to_hash: { 'url' => 'test_url' }))
33
+
34
+ subject.notify(OpenStruct.new(to_hash: { 'url' => 'test_url' }))
35
+ end
36
+
37
+ it 'raises TemplateSyntaxError if template was not correct' do
38
+ allow(Liquid::Template).to receive(:parse).with('Post {{ test_variable }} on {{ url }}').and_raise(SyntaxError)
39
+
40
+ expect { subject.notify(OpenStruct.new(to_hash: { 'url' => 'test_url' })) }.to raise_error(Feedbook::Errors::TemplateSyntaxError)
41
+ end
42
+ end
43
+
44
+ end