superfeedr-ruby 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 julien
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,69 @@
1
+ = superfeedr-ruby
2
+
3
+ Yes, the gem is called superfeedr-ruby, but the library is superfeedr only ;)
4
+
5
+ {Superfeedr.com}[http://superfeedr.com] is a Real-time Cloud Feed Parsing service
6
+
7
+ * *Realtime* : By combining different technologies we can notify you of new entries in less than 15 minutes (or it's free)
8
+ * *No* *more* *polling* : Stop wasting your time and resources fetching 'old data', and stop being late when there is new data.
9
+ * *Simplicity* : give us urls, and we'll do the rest. Check our docs and tutorials.
10
+ * *Standardization* : No more nightmares with gazillions of formats, we will send you strict ATOM, whatever the original feed format is.
11
+ * *Multi*-*channel* : receive the notifications by XMPP, on a local JID or your own external JID, or using WebHooks.
12
+ * *Cost* *saving* : we will match the cost of your existing system.
13
+
14
+ == FEATURES:
15
+
16
+ * Subscribe to a feed
17
+
18
+ Superfeedr.subscribe("http://github.com/superfeedr.atom") do |result|
19
+ puts "Yay, subscribed to the github Atom feed for Superfeedr" if result
20
+ end
21
+
22
+ * Unsubscribe from a feed
23
+
24
+ Superfeedr.unsubscribe("http://github.com/superfeedr.atom") do |result|
25
+ puts "Sad, you unsubscribed from the github Atom feed for Superfeedr" if result
26
+ end
27
+
28
+ * List subscriptions by page
29
+
30
+ Superfeedr.subscriptions(5) do |page, feeds|
31
+ puts "On page #{page}" :
32
+ puts feeds.inspect
33
+ end
34
+
35
+ * Receive notifications
36
+
37
+ Superfeedr.on_notification do |notification|
38
+ puts "The feed #{notification.feed_url} has been fetched (#{notification.http_status}: #{notification.message_status}) and will be fetched again in #{(notification.next_fetch - Time.now)/60} minutes."
39
+ notification.entries.each do |e|
40
+ puts " - #{e.title} (#{e.link}) was published (#{e.published}) with #{e.unique_id} as unique id : \n #{e.summary} (#{e.chunk}/#{e.chunks})"
41
+ end
42
+ end
43
+
44
+ == Install
45
+
46
+ Install the gem from Github :
47
+
48
+ gem sources -a http://gems.github.com
49
+ sudo gem install superfeedr-superfeedr-ruby
50
+
51
+ Source :
52
+
53
+ git clone git@github.com:superfeedr/superfeedr-ruby.git
54
+
55
+
56
+ == Example
57
+
58
+ Please see this {Gist}[http://gist.github.com/110247] while I am trying to find a way to integrate it into this document ;)
59
+
60
+
61
+ == REQUIREMENTS:
62
+
63
+ {Babylon}[http://github.com/julien51/skates/tree/master] : please note that there are 2 gems named Baylon. The one you want is the one about XMPP, you can get it with :
64
+
65
+ sudo gem install skates
66
+
67
+ == Copyright
68
+
69
+ Copyright (c) 2009 julien. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,80 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ config = YAML.load(File.read('VERSION.yml'))
8
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
9
+ gem.name = "superfeedr-ruby"
10
+ gem.version = "#{version}"
11
+ gem.summary = %Q{Ruby Client for the Superfeedr}
12
+ gem.email = "julien.genestoux@gmail.com"
13
+ gem.homepage = "http://github.com/julien51/superfeedr-ruby"
14
+ gem.authors = ["julien Genestoux"]
15
+ gem.rubyforge_project = "superfeedr-ruby"
16
+ gem.add_dependency('skates')
17
+ gem.add_dependency('nokogiri')
18
+ gem.has_rdoc = true
19
+ gem.homepage = 'http://github.com/julien51/superfeedr-ruby/'
20
+ gem.files = FileList["[A-Z]*", "{bin,generators,lib,test,spec}/**/*"]
21
+ end
22
+ rescue LoadError
23
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
24
+ end
25
+
26
+ require 'spec/rake/spectask'
27
+ Spec::Rake::SpecTask.new(:spec) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.spec_files = FileList['spec/**/*_spec.rb']
30
+ end
31
+
32
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
33
+ spec.libs << 'lib' << 'spec'
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ end
37
+
38
+
39
+ task :default => :spec
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ if File.exist?('VERSION.yml')
44
+ config = YAML.load(File.read('VERSION.yml'))
45
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
46
+ else
47
+ version = ""
48
+ end
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "superfeedr-ruby #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
55
+
56
+ begin
57
+ require 'rake/contrib/sshpublisher'
58
+ namespace :rubyforge do
59
+
60
+ desc "Release gem and RDoc documentation to RubyForge"
61
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
62
+
63
+ namespace :release do
64
+ desc "Publish RDoc to RubyForge."
65
+ task :docs => [:rdoc] do
66
+ config = YAML.load(
67
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
68
+ )
69
+
70
+ host = "#{config['username']}@rubyforge.org"
71
+ remote_dir = "/var/www/gforge-projects/superfeedr-ruby/"
72
+ local_dir = 'rdoc'
73
+
74
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
75
+ end
76
+ end
77
+ end
78
+ rescue LoadError
79
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
80
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 4
4
+ :patch: 0
data/lib/config.yaml ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ :superfeedr_jid: firehoser.superfeedr.com
@@ -0,0 +1,36 @@
1
+ class IqQueryStanza
2
+
3
+ def initialize(params = {})
4
+ @doc = Nokogiri::XML::Document.new
5
+ @iq = Nokogiri::XML::Node.new("iq", @doc)
6
+ @iq["type"] = params[:type].to_s
7
+ @iq["to"] = Superfeedr.conf[:superfeedr_jid]
8
+ @iq["id"] = "#{random_iq_id}"
9
+ @iq["from"] = params[:from] if params[:from]
10
+ end
11
+
12
+ def type
13
+ @iq["type"]
14
+ end
15
+
16
+ def to
17
+ @iq["to"]
18
+ end
19
+
20
+ def from
21
+ @iq["from"]
22
+ end
23
+
24
+ def id
25
+ @iq["id"]
26
+ end
27
+
28
+ def random_iq_id
29
+ rand(1000)
30
+ end
31
+
32
+ def to_s
33
+ @iq.to_s
34
+ end
35
+
36
+ end
@@ -0,0 +1,75 @@
1
+ ##
2
+ # Repesents the items published by the firehoser (the feed entries).
3
+ # They have accessors for the following fields :
4
+ # - title
5
+ # - summary
6
+ # - link
7
+ # - published
8
+ # - unique_id
9
+ # - chunks (long entries might be notified in several chunks)
10
+ # - chunk (current chunk out of chunks)
11
+ #
12
+ require "cgi"
13
+ class Item
14
+ include SAXMachine
15
+ element :item, :as => :chunk, :value => :chunk
16
+ element :item, :as => :chunks, :value => :chunks
17
+ element :title
18
+ element :summary
19
+ element :link, :as => :link, :value => :href
20
+ element :id, :as => :unique_id
21
+ element :published
22
+
23
+ def link
24
+ CGI.unescape(@link).gsub("\n", "")
25
+ end
26
+
27
+ def published
28
+ Time.parse(@published)
29
+ end
30
+
31
+ def chunks
32
+ @chunks.to_i
33
+ end
34
+
35
+ def chunk
36
+ @chunk.to_i
37
+ end
38
+
39
+ end
40
+
41
+
42
+ ##
43
+ # Notification : sent every time a feed has been fetched. It has the following methods:
44
+ # - message_status : a simple message that gives information about the last fetch
45
+ # - http_status : status of the http response
46
+ # - feed_url : url of the feed
47
+ # - next_fetch : Time when the feed will be fetched again (this is purely informative and it might change)
48
+ # - items : array of new items detected (might be empty)
49
+ class NotificationStanza
50
+ include SAXMachine
51
+
52
+ def initialize(xml)
53
+ parse(xml.to_s)
54
+ end
55
+
56
+ def next_fetch
57
+ Time.parse(@next_fetch)
58
+ end
59
+
60
+ def http_status
61
+ @http_status.to_i
62
+ end
63
+
64
+ def feed_url
65
+ CGI.unescape(@feed_url).gsub("\n", "")
66
+ end
67
+
68
+ element :http, :as => :message_status
69
+ element :http, :as => :http_status, :value => :code
70
+ element :status, :value => :feed, :as => :feed_url
71
+ element :next_fetch
72
+ elements :item, :as => :entries, :class => Item
73
+
74
+ end
75
+
@@ -0,0 +1,26 @@
1
+ class SubscribeQueryStanza < IqQueryStanza
2
+
3
+ def initialize(params)
4
+ raise NoFeedToSubscribe if params[:nodes].nil? or params[:nodes].empty?
5
+ raise TooManyFeeds if params[:nodes].size > 30
6
+ super(params.merge({:type => :set}))
7
+ @pubsub = Nokogiri::XML::Node.new("pubsub", @doc)
8
+ @pubsub["xmlns"] = "http://jabber.org/protocol/pubsub"
9
+ params[:nodes].each do |node|
10
+ add_node(node)
11
+ end
12
+ @iq.add_child(@pubsub)
13
+ end
14
+
15
+ def add_node(node)
16
+ subscribe = Nokogiri::XML::Node.new("subscribe", @doc)
17
+ subscribe["node"] = node
18
+ subscribe["jid"] = from.split("/").first
19
+ @pubsub.add_child(subscribe)
20
+ end
21
+
22
+ def nodes
23
+ @pubsub.children.map {|c| c["node"]}
24
+ end
25
+
26
+ end
@@ -0,0 +1,18 @@
1
+ class SubscriptionsQueryStanza < IqQueryStanza
2
+
3
+ def initialize(params)
4
+ super(params.merge({:type => :get}))
5
+ pubsub = Nokogiri::XML::Node.new("pubsub", @doc)
6
+ pubsub["xmlns"] = "http://jabber.org/protocol/pubsub"
7
+ @iq.add_child(pubsub)
8
+ subscriptions = Nokogiri::XML::Node.new("subscriptions", @doc)
9
+ subscriptions["page"] = params[:page].to_s
10
+ subscriptions["jid"] = from.split("/").first
11
+ pubsub.add_child(subscriptions)
12
+ end
13
+
14
+ def page
15
+ @iq.search("subscriptions").first["page"]
16
+ end
17
+
18
+ end
@@ -0,0 +1,27 @@
1
+ class UnsubscribeQueryStanza < IqQueryStanza
2
+
3
+ def initialize(params)
4
+ raise NoFeedToSubscribe if params[:nodes].nil? or params[:nodes].empty?
5
+ raise TooManyFeeds if params[:nodes].size > 30
6
+ super(params.merge({:type => :set}))
7
+
8
+ @pubsub = Nokogiri::XML::Node.new("pubsub", @doc)
9
+ params[:nodes].each do |node|
10
+ add_node(node)
11
+ end
12
+ @pubsub["xmlns"] = "http://jabber.org/protocol/pubsub"
13
+ @iq.add_child(@pubsub)
14
+ end
15
+
16
+ def add_node(node)
17
+ unsubscribe = Nokogiri::XML::Node.new("unsubscribe", @doc)
18
+ unsubscribe["node"] = node.to_s
19
+ unsubscribe["jid"] = from.split("/").first
20
+ @pubsub.add_child(unsubscribe)
21
+ end
22
+
23
+ def nodes
24
+ @pubsub.children.map {|c| c["node"]}
25
+ end
26
+
27
+ end
data/lib/superfeedr.rb ADDED
@@ -0,0 +1,210 @@
1
+ require "skates"
2
+ require "stanzas/iq_query_stanza.rb"
3
+ require "stanzas/notification_stanza.rb"
4
+ require "stanzas/subscribe_query_stanza.rb"
5
+ require "stanzas/unsubscribe_query_stanza.rb"
6
+ require "stanzas/subscriptions_query_stanza.rb"
7
+
8
+ ##
9
+ # By default, the log level is at error. You can change that at anytime in your app
10
+ Babylon.logger.level = Log4r::ERROR
11
+
12
+
13
+ ##
14
+ # Based on the API documented there : http://superfeedr.com/documentation
15
+ module Superfeedr
16
+
17
+ class NotConnected < StandardError; end
18
+
19
+ @@connection = nil
20
+ @@callbacks = {}
21
+ @@connection_callback = nil
22
+ @@notification_callback = nil
23
+
24
+ ##
25
+ # Connects your client to the Superfeedr.com XMPP server. You need to pass the following arguments :
26
+ # "jid" : login@superfeedr.com
27
+ # "password" : your superfeedr.com password
28
+ # ["host" : host for your jid or component : only useful if you use an external jid ]
29
+ # ["port" : port for your jid or component : only useful if you use an external jid ]
30
+ # ["app_type" : (client | component) only useful if you use an external jid ]
31
+ # The optional block will be called upon connection.
32
+ def self.connect(jid, password, host = nil, port = nil, app_type = "client", &block)
33
+
34
+ params = {
35
+ "jid" => jid,
36
+ "password" => password,
37
+ "host" => host,
38
+ "port" => port
39
+ }
40
+ @@connection_callback = block
41
+
42
+ run = Proc.new {
43
+ if app_type == "client"
44
+ Babylon::ClientConnection.connect(params, self)
45
+ else
46
+ Babylon::ComponentConnection.connect(params, self)
47
+ end
48
+ }
49
+
50
+ if EventMachine.reactor_running?
51
+ run.call
52
+ else
53
+ EventMachine.run {
54
+ run.call
55
+ }
56
+ end
57
+ end
58
+
59
+ ##
60
+ # Subscribes to the multiple feeds, 30 by 30. Calls the block after each set of 30 feeds.
61
+ def self.subscribe(*feeds, &block)
62
+ return if feeds.flatten! == []
63
+ subset = feeds.slice!(0..29)
64
+ Superfeedr.add_feeds(subset) do |result|
65
+ subscribe(feeds, &block)
66
+ block.call(subset)
67
+ end
68
+ end
69
+
70
+ ##
71
+ # Ubsubscribe to multiple feeds, one by one. Calls the block after each set of 30 feeds.
72
+ def self.unsubscribe(*feeds, &block)
73
+ return if feeds.flatten! == []
74
+ subset = feeds.slice!(0..29)
75
+ Superfeedr.remove_feeds(subset) do |result|
76
+ unsubscribe(feeds, &block)
77
+ block.call(subset)
78
+ end
79
+ end
80
+
81
+ ##
82
+ # List all subscriptions, by sending them by blocks (page), starting at page specified in argument
83
+ def self.subscriptions(start_page = 1, &block)
84
+ Superfeedr.subscriptions_by_page(start_page) do |page, result|
85
+ if !result.empty?
86
+ subscriptions(start_page + 1, &block)
87
+ end
88
+ block.call(page, result)
89
+ end
90
+ end
91
+
92
+ ##
93
+ # Adds the url to the list of feeds you're monitoring. The block passed in argument will be called upon success.
94
+ # The block will take one boolen argument : true means everything went right... false means something failed!
95
+ # (Please set Babylon's log to Log4r::INFO for more info)
96
+ def self.add_feeds(feeds_url, &block)
97
+ raise NotConnected unless connection
98
+ stanza = SubscribeQueryStanza.new({:nodes => feeds_url, :from => connection.jid})
99
+ @@callbacks[stanza.id] = Hash.new
100
+ @@callbacks[stanza.id][:method] = method(:on_subscribe)
101
+ @@callbacks[stanza.id][:param] = block
102
+ send(stanza)
103
+ end
104
+
105
+ ##
106
+ # Unsubscribe from a feed. The block passed in argument will be called upon success.
107
+ # The block will take one boolen argument : true means everything went right... false means something failed!
108
+ # (Please set Babylon's log to Log4r::INFO for more info)
109
+ def self.remove_feeds(feeds_url, &block)
110
+ raise NotConnected unless connection
111
+ stanza = UnsubscribeQueryStanza.new({:nodes => feeds_url, :from => connection.jid})
112
+ @@callbacks[stanza.id] = Hash.new
113
+ @@callbacks[stanza.id][:method] = method(:on_unsubscribe)
114
+ @@callbacks[stanza.id][:param] = block
115
+ send(stanza)
116
+ end
117
+
118
+ ##
119
+ # Lists the subscriptions by page. The block passed in argument will be called with 2 arguments : the page,
120
+ # and an array of the feed's url in the page you requested.
121
+ # (Currently the Superfeedr API only supports 30 feeds per page.)
122
+ def self.subscriptions_by_page(page = 1, &block)
123
+ raise NotConnected unless connection
124
+ stanza = SubscriptionsQueryStanza.new({:page => page, :from => connection.jid})
125
+ @@callbacks[stanza.id] = Hash.new
126
+ @@callbacks[stanza.id][:method] = method(:on_subscriptions)
127
+ @@callbacks[stanza.id][:param] = block
128
+ send(stanza)
129
+ end
130
+
131
+ ##
132
+ # Specifies the block that will be called upon notification.
133
+ # Your block should take a NotificationStanza instance argument.
134
+ def self.on_notification(&block)
135
+ @@notification_callback = block
136
+ end
137
+
138
+ ##
139
+ # Called with a response to a subscriptions listing
140
+ def self.on_subscriptions(stanza, &block)
141
+ page = stanza.xpath('//subscriptions').first["page"].to_i
142
+ feeds = stanza.xpath('//subscription').map { |s| CGI.unescapeHTML(s["node"]) }
143
+ block.call(page, feeds)
144
+ end
145
+
146
+ ##
147
+ # Called with a response to a subscribe
148
+ def self.on_subscribe(stanza, &block)
149
+ block.call(stanza["type"] == "result")
150
+ end
151
+
152
+ ##
153
+ # Called with a response to an unsubscribe.
154
+ def self.on_unsubscribe(stanza, &block)
155
+ block.call(stanza["type"] == "result")
156
+ end
157
+
158
+ ##
159
+ # ::nodoc::
160
+ def self.callbacks
161
+ @@callbacks
162
+ end
163
+
164
+ ##
165
+ # ::nodoc::
166
+ def self.connection
167
+ @@connection
168
+ end
169
+
170
+ ##
171
+ # ::nodoc::
172
+ def self.send(xml)
173
+ connection.send_xml(xml)
174
+ end
175
+
176
+ ##
177
+ # ::nodoc::
178
+ def self.on_connected(connection)
179
+ @@connection = connection
180
+ @@connection_callback.call
181
+ end
182
+
183
+ ##
184
+ # ::nodoc::
185
+ def self.on_disconnected()
186
+ @@connection = false
187
+ end
188
+
189
+ ##
190
+ # This shall not be called by your application. It is called upon stanza recetion. If it is a reply to a stanza we sent earlier, then, we just call it's associated callback. If it is a notification stanza, then, we call the notification callback (that you should have given when calling Superfeedr.connect) with a NotificationStanza instance.
191
+ def self.on_stanza(stanza)
192
+ if stanza["id"] && @@callbacks[stanza["id"]]
193
+ @@callbacks[stanza["id"]][:method].call(stanza, &@@callbacks[stanza["id"]][:param])
194
+ @@callbacks.delete(stanza["id"])
195
+ else
196
+ if stanza.name == "message" and stanza.at("event")
197
+ @@notification_callback.call(NotificationStanza.new(stanza)) if @@notification_callback
198
+ # Here we need to call the main notification callback!
199
+ end
200
+ end
201
+ end
202
+
203
+ ##
204
+ # Config loaded from config.yaml
205
+ def self.conf
206
+ @@conf ||= YAML::load(File.read(File.dirname(__FILE__) + '/config.yaml'))
207
+ end
208
+
209
+
210
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format nested
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,11 @@
1
+ require 'spec'
2
+ require "rubygems"
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'superfeedr'
7
+ require "skates"
8
+
9
+ Spec::Runner.configure do |config|
10
+
11
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ shared_examples_for "Iq Query Stanzas" do
4
+
5
+ it "should have the right type" do
6
+ IqQueryStanza.new(@params).type.should == @params[:type]
7
+ end
8
+
9
+ it "should have the right to" do
10
+ IqQueryStanza.new(@params).to.should == "firehoser.superfeedr.com"
11
+ end
12
+
13
+ it "should have a random id" do
14
+ IqQueryStanza.new(@params).id.should match /[0..9]*/
15
+ end
16
+
17
+ it "should have the right from" do
18
+ IqQueryStanza.new(@params).from.should == @params[:from]
19
+ end
20
+
21
+ end
22
+
23
+ describe IqQueryStanza do
24
+ before(:each) do
25
+ @params = { :type => "set", :from => "me@server.com/resource"}
26
+ end
27
+
28
+ it_should_behave_like "Iq Query Stanzas"
29
+
30
+ end
@@ -0,0 +1,91 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ describe NotificationStanza do
3
+
4
+ before(:each) do
5
+ xml = <<-EOXML
6
+ <message from='firehoser.superfeedr.com' to='you@superfeedr.com'>
7
+ <event xmlns='http://jabber.org/protocol/pubsub#event'>
8
+ <status feed="http://domain.tld/path/to/feed.xml" xmlns:superfeedr='http://superfeedr.com/xmpp-pubsub-ext'>
9
+ <http code="200">9718 bytes fetched in 1.462708s : 2 new entries.</http>
10
+ <next_fetch>2009-05-10T11:19:38-07:00</next_fetch>
11
+ </status>
12
+ <items node='http://domain.tld/path/to/feed.xml'>
13
+ <item chunk="1" chunks="2" >
14
+ <entry xmlns='http://www.w3.org/2005/Atom'>
15
+ <title>Soliloquy</title>
16
+ <summary>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</summary>
17
+ <link rel='alternate' type='text/html' href='http://superfeedr.com/entries/12345789'/>
18
+ <id>tag:domain.tld,2009:Soliloquy-32397</id>
19
+ <published>2010-04-05T11:04:21Z</published>
20
+ </entry>
21
+ </item>
22
+ <item chunk="2" chunks="2" >
23
+ <entry xmlns='http://www.w3.org/2005/Atom'>
24
+ <title>Finibus Bonorum et Malorum</title>
25
+ <summary>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</summary>
26
+ <link rel='alternate' type='text/html' href='http://superfeedr.com/entries/12345788'/>
27
+ <id>tag:domain.tld,2009:Finibus-32398</id>
28
+ <published>2010-04-06T08:54:02Z</published>
29
+ </entry>
30
+ </item>
31
+ </items>
32
+ </event>
33
+ </message>
34
+ EOXML
35
+ @stanza = NotificationStanza.new(xml)
36
+ end
37
+
38
+ it "should have the right feed_url" do
39
+ @stanza.feed_url.should == "http://domain.tld/path/to/feed.xml"
40
+ end
41
+
42
+ it "should have the right message_status"
43
+
44
+ it "should have the right http_status" do
45
+ @stanza.http_status.should == 200
46
+ end
47
+
48
+ it "should have the have the right next_fetch" do
49
+ @stanza.next_fetch.should == Time.parse("2009-05-10T11:19:38-07:00")
50
+ end
51
+
52
+ it "should have the right number of items" do
53
+ @stanza.entries.count.should == 2
54
+ end
55
+
56
+ describe "items" do
57
+ before(:each) do
58
+ @item = @stanza.entries.first
59
+ end
60
+
61
+ it "should have the right chunk" do
62
+ @item.chunk.should == 1
63
+ end
64
+
65
+ it "should have the right chunks" do
66
+ @item.chunks.should == 2
67
+ end
68
+
69
+ it "should have the right title" do
70
+ @item.title.should == "Soliloquy"
71
+ end
72
+
73
+ it "should have the right summary" do
74
+ @item.summary.should == "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
75
+ end
76
+
77
+ it "should have the right link" do
78
+ @item.link.should == "http://superfeedr.com/entries/12345789"
79
+ end
80
+
81
+ it "should have the right unique_id" do
82
+ @item.unique_id.should == "tag:domain.tld,2009:Soliloquy-32397"
83
+ end
84
+
85
+ it "should have the right published" do
86
+ @item.published.should == Time.parse("2010-04-05T11:04:21Z")
87
+ end
88
+
89
+ end
90
+
91
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require File.dirname(__FILE__) + '/iq_query_stanza_spec'
3
+ describe SubscribeQueryStanza do
4
+
5
+ it_should_behave_like "Iq Query Stanzas"
6
+
7
+ before(:each) do
8
+ @params = { :type => "set", :from => "me@server.com/resource", :nodes => ["http//domain.tld/feed.xml"], :type => "set", :from => "me@server.com/resource"}
9
+ end
10
+
11
+ it "should have the right node value" do
12
+ SubscribeQueryStanza.new(@params).nodes.should == @params[:nodes]
13
+ end
14
+
15
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require File.dirname(__FILE__) + '/iq_query_stanza_spec'
3
+ describe SubscriptionsQueryStanza do
4
+
5
+ it_should_behave_like "Iq Query Stanzas"
6
+
7
+ before(:each) do
8
+ @params = { :type => "set", :from => "me@server.com/resource", :page => 3, :type => "set", :from => "me@server.com/resource"}
9
+ end
10
+
11
+ it "should have the right page value" do
12
+ SubscriptionsQueryStanza.new(@params).page.should == @params[:page].to_s
13
+ end
14
+
15
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require File.dirname(__FILE__) + '/iq_query_stanza_spec'
3
+ describe UnsubscribeQueryStanza do
4
+
5
+ it_should_behave_like "Iq Query Stanzas"
6
+
7
+ before(:each) do
8
+ @params = { :type => "set", :from => "me@server.com/resource", :nodes => ["http//domain.tld/feed.xml", "http//domain.tld/feed2.xml"], :type => "set", :from => "me@server.com/resource"}
9
+ end
10
+
11
+ it "should have the right node value" do
12
+ UnsubscribeQueryStanza.new(@params).nodes.should == @params[:nodes]
13
+ end
14
+
15
+ end
@@ -0,0 +1,237 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Superfeedr do
4
+
5
+ before(:each) do
6
+ @mock_connection = mock(Babylon::XmppConnection, {:send_xml => true, :jid => "client@server.tld/resource"})
7
+ end
8
+
9
+ describe "connect" do
10
+ end
11
+
12
+ describe "on_stanza" do
13
+ end
14
+
15
+
16
+ describe "subscribe" do
17
+ it "should call add_feeds with feeds supplied" do
18
+ feeds = ["a"] * 50
19
+ Superfeedr.should_receive(:add_feeds).with(["a"] * 30).and_yield(true)
20
+ Superfeedr.should_receive(:add_feeds).with(["a"] * 20).and_yield(true)
21
+ Superfeedr.subscribe(feeds) do |result|
22
+ result.should be_true
23
+ end
24
+ end
25
+ end
26
+
27
+ describe "unsubscribe" do
28
+ it "should call remove_feeds with feeds supplied" do
29
+ feeds = ["a"] * 40
30
+ Superfeedr.should_receive(:remove_feeds).with(["a"] * 30).and_yield(true)
31
+ Superfeedr.should_receive(:remove_feeds).with(["a"] * 10).and_yield(true)
32
+ Superfeedr.unsubscribe(feeds) do |result|
33
+ result.should be_true
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "subscriptions" do
39
+ it "should call subscriptions_by_page for each page as long as they're not empty" do
40
+ def method_called_upon_page
41
+ end
42
+ self.should_receive(:method_called_upon_page).exactly(4).times
43
+ 3.times do |t|
44
+ Superfeedr.should_receive(:subscriptions_by_page).with(t+1).and_yield( t+1 , ["a", "b", "c"])
45
+ end
46
+ Superfeedr.should_receive(:subscriptions_by_page).with(4).and_yield(4, [])
47
+ Superfeedr.subscriptions do |page, result|
48
+ method_called_upon_page
49
+ end
50
+ end
51
+ end
52
+
53
+
54
+ describe "add_feeds" do
55
+ before(:each) do
56
+ Superfeedr.stub!(:connection).and_return(@mock_connection)
57
+ Superfeedr.stub!(:send).and_return(true)
58
+ @block = Proc.new {
59
+
60
+ }
61
+ @node = "http://domain.com/feed.xml"
62
+ @mock_stanza = mock(SubscribeQueryStanza, {:id => "123"})
63
+ SubscribeQueryStanza.stub!(:new).and_return(@mock_stanza)
64
+ end
65
+
66
+ it "should raise an error if not connected" do
67
+ Superfeedr.should_receive(:connection).and_return(nil)
68
+ lambda {
69
+ Superfeedr.add_feeds(@node, &@block)
70
+ }.should raise_error(Superfeedr::NotConnected)
71
+ end
72
+
73
+ it "should create a new SubscribeQueryStanza with the right url" do
74
+ SubscribeQueryStanza.should_receive(:new).with({:nodes => @node, :from => @mock_connection.jid}).and_return(@mock_stanza)
75
+ Superfeedr.add_feeds(@node, &@block)
76
+ end
77
+
78
+ it "should add a Proc that just calls the block in params to the @@callbacks" do
79
+ Superfeedr.add_feeds(@node, &@block)
80
+ Superfeedr.callbacks[@mock_stanza.id][:method].should == Superfeedr.method(:on_subscribe)
81
+ Superfeedr.callbacks[@mock_stanza.id][:param].should == @block
82
+ end
83
+
84
+ it "should send the stanza" do
85
+ Superfeedr.should_receive(:send).with(@mock_stanza).and_return(true)
86
+ Superfeedr.add_feeds(@node, &@block)
87
+ end
88
+ end
89
+
90
+ describe "remove_feeds" do
91
+ before(:each) do
92
+ Superfeedr.stub!(:connection).and_return(@mock_connection)
93
+ Superfeedr.stub!(:send).and_return(true)
94
+ @block = Proc.new {
95
+
96
+ }
97
+ @nodes = ["http://domain.com/feed.xml"]
98
+ @mock_stanza = mock(UnsubscribeQueryStanza, {:id => "123"})
99
+ UnsubscribeQueryStanza.stub!(:new).and_return(@mock_stanza)
100
+ end
101
+
102
+ it "should raise an error if not connected" do
103
+ Superfeedr.should_receive(:connection).and_return(nil)
104
+ lambda {
105
+ Superfeedr.remove_feeds(@nodes, &@block)
106
+ }.should raise_error(Superfeedr::NotConnected)
107
+ end
108
+
109
+ it "should create a new SubscribeQueryStanza with the right url" do
110
+ UnsubscribeQueryStanza.should_receive(:new).with({:nodes => @nodes, :from => @mock_connection.jid}).and_return(@mock_stanza)
111
+ Superfeedr.remove_feeds(@nodes, &@block)
112
+ end
113
+
114
+ it "should add a Proc that just calls the block in params to the @@callbacks" do
115
+ Superfeedr.remove_feeds(@nodes, &@block)
116
+ Superfeedr.callbacks[@mock_stanza.id][:method].should == Superfeedr.method(:on_unsubscribe)
117
+ Superfeedr.callbacks[@mock_stanza.id][:param].should == @block
118
+ end
119
+
120
+ it "should send the stanza" do
121
+ Superfeedr.should_receive(:send).with(@mock_stanza).and_return(true)
122
+ Superfeedr.remove_feeds(@nodes, &@block)
123
+ end
124
+ end
125
+
126
+ describe "subscriptions_by_page" do
127
+ before(:each) do
128
+ Superfeedr.stub!(:connection).and_return(@mock_connection)
129
+ Superfeedr.stub!(:send).and_return(true)
130
+ @block = Proc.new {
131
+
132
+ }
133
+ @page = 3
134
+ @mock_stanza = mock(SubscriptionsQueryStanza, {:id => "123"})
135
+ SubscriptionsQueryStanza.stub!(:new).and_return(@mock_stanza)
136
+ end
137
+
138
+ it "should raise an error if not connected" do
139
+ Superfeedr.should_receive(:connection).and_return(nil)
140
+ lambda {
141
+ Superfeedr.subscriptions_by_page(@page, &@block)
142
+ }.should raise_error(Superfeedr::NotConnected)
143
+ end
144
+
145
+ it "should create a new SubscribeQueryStanza with the right url" do
146
+ SubscriptionsQueryStanza.should_receive(:new).with({:page => @page, :from => @mock_connection.jid}).and_return(@mock_stanza)
147
+ Superfeedr.subscriptions_by_page(@page, &@block)
148
+ end
149
+
150
+ it "should add a Proc that just calls the block in params to the @@callbacks" do
151
+ Superfeedr.subscriptions_by_page(@page, &@block)
152
+ Superfeedr.callbacks[@mock_stanza.id][:method].should == Superfeedr.method(:on_subscriptions)
153
+ Superfeedr.callbacks[@mock_stanza.id][:param].should == @block
154
+
155
+ end
156
+
157
+ it "should send the stanza" do
158
+ Superfeedr.should_receive(:send).with(@mock_stanza).and_return(true)
159
+ Superfeedr.subscriptions_by_page(@page, &@block)
160
+ end
161
+ end
162
+
163
+ describe "on_subscribe" do
164
+ it "should call the block with true if the stanza type is 'result'" do
165
+ xml = <<-EOXML
166
+ <iq type="result" to="you@superfeedr.com/home" from="firehoser.superfeedr.com" id="sub1">
167
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
168
+ <subscription jid="you@superfeedr.com" subscription="subscribed" node="http://domain.tld/path/to/feed.xml"/>
169
+ </pubsub>
170
+ </iq>
171
+ EOXML
172
+ stanza = Nokogiri::XML(xml)
173
+ Superfeedr.on_subscribe(stanza.root) do |res|
174
+ res.should be_true
175
+ end
176
+ end
177
+
178
+ it "should call the block with false if the stanza type is not 'result'" do
179
+ xml = <<-EOXML
180
+ <iq type="error" to="you@superfeedr.com/home" from="firehoser.superfeedr.com" id="sub1">
181
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
182
+ <subscription jid="you@superfeedr.com" subscription="subscribed" node="http://domain.tld/path/to/feed.xml"/>
183
+ </pubsub>
184
+ </iq>
185
+ EOXML
186
+ stanza = Nokogiri::XML(xml)
187
+ Superfeedr.on_subscribe(stanza.root) do |res|
188
+ res.should be_false
189
+ end
190
+ end
191
+
192
+ end
193
+
194
+ describe "on_unsubscribe" do
195
+ it "should call the block with true if the stanza type is 'result'" do
196
+ xml = <<-EOXML
197
+ <iq type='result' from='firehoser.superfeedr.com' to='you@superfeedr.com/home' id='unsub1' />
198
+ EOXML
199
+ stanza = Nokogiri::XML(xml)
200
+ Superfeedr.on_unsubscribe(stanza.root) do |res|
201
+ res.should be_true
202
+ end
203
+ end
204
+
205
+ it "should call the block with false if the stanza type is not 'result'" do
206
+ xml = <<-EOXML
207
+ <iq type='error' from='firehoser.superfeedr.com' to='you@superfeedr.com/home' id='unsub1' />
208
+ EOXML
209
+ stanza = Nokogiri::XML(xml)
210
+ Superfeedr.on_unsubscribe(stanza.root) do |res|
211
+ res.should be_false
212
+ end
213
+ end
214
+ end
215
+
216
+ describe "on_subscriptions" do
217
+ it "should call the block with the page number and the list of feeds as an array" do
218
+ xml = <<-EOXML
219
+ <iq type="result" to="you@superfeedr.com/home" id="subman1" from="firehoser.superfeedr.com">
220
+ <pubsub>
221
+ <subscriptions page="3">
222
+ <subscription node="http://domain.tld/path/to/a/feed/atom.xml&amp;toto=tutu" subscription="subscribed" jid="you@superfeedr.com" />
223
+ <subscription node="http://domain2.tld/path/to/feed.rss" subscription="subscribed" jid="you@superfeedr.com" />
224
+ </subscriptions>
225
+ </pubsub>
226
+ </iq>
227
+ EOXML
228
+ stanza = Nokogiri::XML(xml)
229
+ Superfeedr.on_subscriptions(stanza.root) do |page, subscriptions|
230
+ page.should == 3
231
+ subscriptions.should == ["http://domain.tld/path/to/a/feed/atom.xml&toto=tutu", "http://domain2.tld/path/to/feed.rss"]
232
+ end
233
+ end
234
+
235
+ end
236
+
237
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: superfeedr-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - julien Genestoux
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-10 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: skates
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: nokogiri
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description:
36
+ email: julien.genestoux@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - LICENSE
46
+ - README.rdoc
47
+ - Rakefile
48
+ - VERSION.yml
49
+ - lib/config.yaml
50
+ - lib/stanzas/iq_query_stanza.rb
51
+ - lib/stanzas/notification_stanza.rb
52
+ - lib/stanzas/subscribe_query_stanza.rb
53
+ - lib/stanzas/subscriptions_query_stanza.rb
54
+ - lib/stanzas/unsubscribe_query_stanza.rb
55
+ - lib/superfeedr.rb
56
+ - spec/spec.opts
57
+ - spec/spec_helper.rb
58
+ - spec/stanzas/iq_query_stanza_spec.rb
59
+ - spec/stanzas/notifications_stanza_spec.rb
60
+ - spec/stanzas/subscribe_stanza_spec.rb
61
+ - spec/stanzas/subscriptions_stanza_spec.rb
62
+ - spec/stanzas/unsubscribe_stanza_spec.rb
63
+ - spec/superfeedr_ruby_spec.rb
64
+ has_rdoc: true
65
+ homepage: http://github.com/julien51/superfeedr-ruby/
66
+ licenses: []
67
+
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --charset=UTF-8
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ requirements: []
86
+
87
+ rubyforge_project: superfeedr-ruby
88
+ rubygems_version: 1.3.5
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Ruby Client for the Superfeedr
92
+ test_files:
93
+ - spec/spec_helper.rb
94
+ - spec/stanzas/iq_query_stanza_spec.rb
95
+ - spec/stanzas/notifications_stanza_spec.rb
96
+ - spec/stanzas/subscribe_stanza_spec.rb
97
+ - spec/stanzas/subscriptions_stanza_spec.rb
98
+ - spec/stanzas/unsubscribe_stanza_spec.rb
99
+ - spec/superfeedr_ruby_spec.rb