radamanthus-superfeedr-ruby 0.4.3

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.
@@ -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
@@ -0,0 +1,213 @@
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
+ Skates.logger.level = Log4r::ERROR
11
+
12
+ ##
13
+ # Based on the API documented there : http://superfeedr.com/documentation
14
+ module Superfeedr
15
+
16
+ class NotConnected < StandardError; end
17
+
18
+ @@connection = nil
19
+ @@callbacks = {}
20
+ @@connection_callback = nil
21
+ @@notification_callback = nil
22
+
23
+ ##
24
+ # Connects your client to the Superfeedr.com XMPP server. You need to pass the following arguments :
25
+ # "jid" : login@superfeedr.com
26
+ # "password" : your superfeedr.com password
27
+ # ["host" : host for your jid or component : only useful if you use an external jid ]
28
+ # ["port" : port for your jid or component : only useful if you use an external jid ]
29
+ # ["app_type" : (client | component) only useful if you use an external jid ]
30
+ # The optional block will be called upon connection.
31
+ def self.connect(jid, password, host = nil, port = nil, app_type = "client", &block)
32
+
33
+ params = {
34
+ "jid" => jid,
35
+ "password" => password,
36
+ "host" => host,
37
+ "port" => port
38
+ }
39
+ @@connection_callback = block
40
+
41
+ run = Proc.new {
42
+ if app_type == "client"
43
+ Skates::ClientConnection.connect(params, self)
44
+ else
45
+ Skates::ComponentConnection.connect(params, self)
46
+ end
47
+ }
48
+
49
+ if EventMachine.reactor_running?
50
+ run.call
51
+ else
52
+ EventMachine.run {
53
+ run.call
54
+ }
55
+ end
56
+ end
57
+
58
+ ##
59
+ # Subscribes to the multiple feeds, 30 by 30. Calls the block after each set of 30 feeds.
60
+ def self.subscribe(*feeds, &block)
61
+ return if feeds.flatten! == []
62
+ subset = feeds.slice!(0..29)
63
+ Superfeedr.add_feeds(subset) do |result|
64
+ subscribe(feeds, &block)
65
+ block.call(subset)
66
+ end
67
+ end
68
+
69
+ ##
70
+ # Ubsubscribe to multiple feeds, one by one. Calls the block after each set of 30 feeds.
71
+ def self.unsubscribe(*feeds, &block)
72
+ return if feeds.flatten! == []
73
+ subset = feeds.slice!(0..29)
74
+ Superfeedr.remove_feeds(subset) do |result|
75
+ unsubscribe(feeds, &block)
76
+ block.call(subset)
77
+ end
78
+ end
79
+
80
+ ##
81
+ # List all subscriptions, by sending them by blocks (page), starting at page specified in argument
82
+ def self.subscriptions(start_page = 1, &block)
83
+ Superfeedr.subscriptions_by_page(start_page) do |page, result|
84
+ if !result.empty?
85
+ subscriptions(start_page + 1, &block)
86
+ end
87
+ block.call(page, result)
88
+ end
89
+ end
90
+
91
+ ##
92
+ # Adds the url to the list of feeds you're monitoring. The block passed in argument will be called upon success.
93
+ # The block will take one boolen argument : true means everything went right... false means something failed!
94
+ # (Please set Skates's log to Log4r::INFO for more info)
95
+ def self.add_feeds(feeds_url, &block)
96
+ raise NotConnected unless connection
97
+ stanza = SubscribeQueryStanza.new({:nodes => feeds_url, :from => connection.jid})
98
+ @@callbacks[stanza.id] = Hash.new
99
+ @@callbacks[stanza.id][:method] = method(:on_subscribe)
100
+ @@callbacks[stanza.id][:param] = block
101
+ send(stanza)
102
+ end
103
+
104
+ ##
105
+ # Unsubscribe from a feed. The block passed in argument will be called upon success.
106
+ # The block will take one boolen argument : true means everything went right... false means something failed!
107
+ # (Please set Skates's log to Log4r::INFO for more info)
108
+ def self.remove_feeds(feeds_url, &block)
109
+ raise NotConnected unless connection
110
+ stanza = UnsubscribeQueryStanza.new({:nodes => feeds_url, :from => connection.jid})
111
+ @@callbacks[stanza.id] = Hash.new
112
+ @@callbacks[stanza.id][:method] = method(:on_unsubscribe)
113
+ @@callbacks[stanza.id][:param] = block
114
+ send(stanza)
115
+ end
116
+
117
+ ##
118
+ # Lists the subscriptions by page. The block passed in argument will be called with 2 arguments : the page,
119
+ # and an array of the feed's url in the page you requested.
120
+ # (Currently the Superfeedr API only supports 30 feeds per page.)
121
+ def self.subscriptions_by_page(page = 1, &block)
122
+ raise NotConnected unless connection
123
+ stanza = SubscriptionsQueryStanza.new({:page => page, :from => connection.jid})
124
+ @@callbacks[stanza.id] = Hash.new
125
+ @@callbacks[stanza.id][:method] = method(:on_subscriptions)
126
+ @@callbacks[stanza.id][:param] = block
127
+ send(stanza)
128
+ end
129
+
130
+ ##
131
+ # Specifies the block that will be called upon notification.
132
+ # Your block should take a NotificationStanza instance argument.
133
+ def self.on_notification(&block)
134
+ @@notification_callback = block
135
+ end
136
+
137
+ ##
138
+ # Called with a response to a subscriptions listing
139
+ def self.on_subscriptions(stanza, &block)
140
+ xmlns = {
141
+ 'pubsub' => 'http://jabber.org/protocol/pubsub',
142
+ 'superfeedr' => 'http://superfeedr.com/xmpp-pubsub-ext'
143
+ }
144
+ page = stanza.xpath('//pubsub:subscriptions/@superfeedr:page', xmlns).to_s.to_i
145
+ feeds = stanza.xpath('//pubsub:subscription', xmlns).map { |s| CGI.unescapeHTML(s["node"]) }
146
+ block.call(page, feeds)
147
+ end
148
+
149
+ ##
150
+ # Called with a response to a subscribe
151
+ def self.on_subscribe(stanza, &block)
152
+ block.call(stanza["type"] == "result")
153
+ end
154
+
155
+ ##
156
+ # Called with a response to an unsubscribe.
157
+ def self.on_unsubscribe(stanza, &block)
158
+ block.call(stanza["type"] == "result")
159
+ end
160
+
161
+ ##
162
+ # ::nodoc::
163
+ def self.callbacks
164
+ @@callbacks
165
+ end
166
+
167
+ ##
168
+ # ::nodoc::
169
+ def self.connection
170
+ @@connection
171
+ end
172
+
173
+ ##
174
+ # ::nodoc::
175
+ def self.send(xml)
176
+ connection.send_xml(xml)
177
+ end
178
+
179
+ ##
180
+ # ::nodoc::
181
+ def self.on_connected(connection)
182
+ @@connection = connection
183
+ @@connection_callback.call
184
+ end
185
+
186
+ ##
187
+ # ::nodoc::
188
+ def self.on_disconnected()
189
+ @@connection = false
190
+ end
191
+
192
+ ##
193
+ # 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.
194
+ def self.on_stanza(stanza)
195
+ if stanza["id"] && @@callbacks[stanza["id"]]
196
+ @@callbacks[stanza["id"]][:method].call(stanza, &@@callbacks[stanza["id"]][:param])
197
+ @@callbacks.delete(stanza["id"])
198
+ else
199
+ if stanza.name == "message" and !stanza.xpath("./ps:event", {"ps" => "http://jabber.org/protocol/pubsub#event"}).empty?
200
+ @@notification_callback.call(NotificationStanza.new(stanza)) if @@notification_callback
201
+ # Here we need to call the main notification callback!
202
+ end
203
+ end
204
+ end
205
+
206
+ ##
207
+ # Config loaded from config.yaml
208
+ def self.conf
209
+ @@conf ||= YAML::load(File.read(File.dirname(__FILE__) + '/config.yaml'))
210
+ end
211
+
212
+
213
+ end
@@ -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,184 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ describe NotificationStanza do
3
+
4
+ before(:each) do
5
+ xml = <<-EOXML
6
+ <message xmlns="jabber:client" from="firehoser.superfeedr.com" to="julien@superfeedr.com">
7
+ <event xmlns="http://jabber.org/protocol/pubsub#event">
8
+ <status xmlns="http://superfeedr.com/xmpp-pubsub-ext" feed="http://pubsubhubbub-example-app.appspot.com/feed">
9
+ <http code="200">25002 bytes fetched in 0.73s for 1 new entries.</http>
10
+ <next_fetch>2010-03-25T17:06:30+00:00</next_fetch>
11
+ <title>PubSubHubBub example app</title>
12
+ </status>
13
+ <items node="http://pubsubhubbub-example-app.appspot.com/feed">
14
+ <item xmlns="http://jabber.org/protocol/pubsub" chunks="2" chunk="1">
15
+ <entry xmlns="http://www.w3.org/2005/Atom" xml:lang="" xmlns:xml="http://www.w3.org/XML/1998/namespace">
16
+ <id>tag:pubhubsubbub-example-app,2009:ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAw</id>
17
+ <title>cool</title>
18
+ <content type="text">cool</content>
19
+ <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>
20
+ <published>2010-03-25T16:57:18Z</published>
21
+ <link type="text/html" href="http://pubsubhubbub-example-app.appspot.com/ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAw" title="cool" rel="alternate"/>
22
+ <link href="http://domain.tld/entries/12345/comments.xml" rel="replies" type="application/atom+xml" />
23
+ <point xmlns="http://www.georss.org/georss">47.597553 -122.15925</point>
24
+ <author>
25
+ <name>John Doe</name>
26
+ <email>john@superfeedr.com</email>
27
+ </author>
28
+ <category term="tag" scheme="http://www.sixapart.com/ns/types#tag" />
29
+ <category term="category" scheme="http://www.sixapart.com/ns/types#tag" />
30
+ </entry>
31
+ </item>
32
+ <item xmlns="http://jabber.org/protocol/pubsub" chunks="2" chunk="2">
33
+ <entry xmlns="http://www.w3.org/2005/Atom" xml:lang="" xmlns:xml="http://www.w3.org/XML/1998/namespace">
34
+ <title>great</title>
35
+ <content type="text">great</content>
36
+ <id>tag:pubhubsubbub-example-app,2009:ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAx</id>
37
+ <published>2010-03-25T16:57:19Z</published>
38
+ <link type="text/html" href="http://pubsubhubbub-example-app.appspot.com/ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAx" title="" rel="alternate"/>
39
+ </entry>
40
+ </item>
41
+ </items>
42
+ </event>
43
+ </message>
44
+ EOXML
45
+ @stanza = NotificationStanza.new(Nokogiri::XML(xml).root)
46
+ end
47
+
48
+ it "should have the right feed_url" do
49
+ @stanza.feed_url.should == "http://pubsubhubbub-example-app.appspot.com/feed"
50
+ end
51
+
52
+ it "should have the right message_status" do
53
+ @stanza.message_status.should == "25002 bytes fetched in 0.73s for 1 new entries."
54
+ end
55
+
56
+ it "should have the right http_status" do
57
+ @stanza.http_status.should == 200
58
+ end
59
+
60
+ it "should have the right next_fetch" do
61
+ @stanza.next_fetch.should == Time.parse("2010-03-25T17:06:30+00:00")
62
+ end
63
+
64
+ it "should have the title" do
65
+ @stanza.title.should == 'PubSubHubBub example app'
66
+ end
67
+
68
+ it "should have the right number of items" do
69
+ @stanza.entries.count.should == 2
70
+ end
71
+
72
+ describe "items" do
73
+ before(:each) do
74
+ @item = @stanza.entries.first
75
+ end
76
+
77
+ it "should have the right chunk" do
78
+ @item.chunk.should == 1
79
+ end
80
+
81
+ it "should have the right chunks" do
82
+ @item.chunks.should == 2
83
+ end
84
+
85
+ it "should have the right title" do
86
+ @item.title.should == "cool"
87
+ end
88
+
89
+ it "should have the right summary" do
90
+ @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."
91
+ end
92
+
93
+ it "should have the right unique_id" do
94
+ @item.unique_id.should == "tag:pubhubsubbub-example-app,2009:ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAw"
95
+ end
96
+
97
+ it "should have the right published" do
98
+ @item.published.should == Time.parse("2010-03-25T16:57:18Z")
99
+ end
100
+
101
+ it "should have the right number of links" do
102
+ @item.should have(2).links
103
+ end
104
+
105
+ describe "links" do
106
+ before(:each) do
107
+ @link = @item.links.first
108
+ end
109
+
110
+ it "should have the right title" do
111
+ @link.title.should == "cool"
112
+ end
113
+
114
+ it "should have the right href" do
115
+ @link.href.should == "http://pubsubhubbub-example-app.appspot.com/ahhwdWJzdWJodWJidWItZXhhbXBsZS1hcHByDQsSBUVudHJ5GMGOEAw"
116
+ end
117
+
118
+ it "should have the right rel" do
119
+ @link.rel.should == "alternate"
120
+ end
121
+
122
+ it "should have the right mime type" do
123
+ @link.type.should == "text/html"
124
+ end
125
+
126
+ end
127
+
128
+ it "should have the right number of locations" do
129
+ @item.should have(1).locations
130
+ end
131
+
132
+ describe "locations" do
133
+ before(:each) do
134
+ @location = @item.locations.first
135
+ end
136
+
137
+ it "should have the right lat" do
138
+ @location.lat.should == 47.597553
139
+ end
140
+
141
+ it "should have the right lon" do
142
+ @location.lon.should == -122.15925
143
+ end
144
+
145
+ end
146
+
147
+ it "should have the right number of authors" do
148
+ @item.should have(1).authors
149
+ end
150
+
151
+ describe "authors" do
152
+ before(:each) do
153
+ @author = @item.authors.first
154
+ end
155
+
156
+ it "should have the right name" do
157
+ @author.name.should == "John Doe"
158
+ end
159
+
160
+ it "should have the right uri" do
161
+ @author.uri.should == nil
162
+ end
163
+
164
+ it "should have the right email" do
165
+ @author.email.should == "john@superfeedr.com"
166
+ end
167
+
168
+ end
169
+
170
+ it "should have the right number of categories" do
171
+ @item.should have(2).categories
172
+ end
173
+
174
+ describe "categories" do
175
+ before(:each) do
176
+ @category = @item.categories.first
177
+ end
178
+
179
+ it "should have the right term" do
180
+ @category.term.should == "tag"
181
+ end
182
+ end
183
+ end
184
+ end