nelumba 0.0.13
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.travis.yml +9 -0
- data/Gemfile +20 -0
- data/README.md +242 -0
- data/Rakefile +7 -0
- data/assets/lotus_logo_purple.png +0 -0
- data/assets/lotus_logo_purple.svg +262 -0
- data/lib/nelumba.rb +47 -0
- data/lib/nelumba/activity.rb +250 -0
- data/lib/nelumba/application.rb +11 -0
- data/lib/nelumba/article.rb +11 -0
- data/lib/nelumba/atom/account.rb +50 -0
- data/lib/nelumba/atom/address.rb +56 -0
- data/lib/nelumba/atom/author.rb +176 -0
- data/lib/nelumba/atom/category.rb +41 -0
- data/lib/nelumba/atom/comment.rb +96 -0
- data/lib/nelumba/atom/entry.rb +216 -0
- data/lib/nelumba/atom/feed.rb +198 -0
- data/lib/nelumba/atom/generator.rb +40 -0
- data/lib/nelumba/atom/link.rb +79 -0
- data/lib/nelumba/atom/name.rb +57 -0
- data/lib/nelumba/atom/organization.rb +62 -0
- data/lib/nelumba/atom/person.rb +179 -0
- data/lib/nelumba/atom/portable_contacts.rb +117 -0
- data/lib/nelumba/atom/source.rb +179 -0
- data/lib/nelumba/atom/thread.rb +60 -0
- data/lib/nelumba/audio.rb +39 -0
- data/lib/nelumba/badge.rb +11 -0
- data/lib/nelumba/binary.rb +52 -0
- data/lib/nelumba/bookmark.rb +30 -0
- data/lib/nelumba/category.rb +49 -0
- data/lib/nelumba/collection.rb +34 -0
- data/lib/nelumba/comment.rb +47 -0
- data/lib/nelumba/crypto.rb +144 -0
- data/lib/nelumba/device.rb +11 -0
- data/lib/nelumba/discover.rb +362 -0
- data/lib/nelumba/event.rb +57 -0
- data/lib/nelumba/feed.rb +173 -0
- data/lib/nelumba/file.rb +43 -0
- data/lib/nelumba/generator.rb +53 -0
- data/lib/nelumba/group.rb +11 -0
- data/lib/nelumba/identity.rb +63 -0
- data/lib/nelumba/image.rb +30 -0
- data/lib/nelumba/link.rb +56 -0
- data/lib/nelumba/note.rb +34 -0
- data/lib/nelumba/notification.rb +229 -0
- data/lib/nelumba/object.rb +251 -0
- data/lib/nelumba/person.rb +306 -0
- data/lib/nelumba/place.rb +34 -0
- data/lib/nelumba/product.rb +30 -0
- data/lib/nelumba/publisher.rb +44 -0
- data/lib/nelumba/question.rb +30 -0
- data/lib/nelumba/review.rb +30 -0
- data/lib/nelumba/service.rb +11 -0
- data/lib/nelumba/subscription.rb +117 -0
- data/lib/nelumba/version.rb +3 -0
- data/lib/nelumba/video.rb +43 -0
- data/nelumba.gemspec +28 -0
- data/spec/activity_spec.rb +116 -0
- data/spec/application_spec.rb +136 -0
- data/spec/article_spec.rb +136 -0
- data/spec/atom/comment_spec.rb +455 -0
- data/spec/atom/feed_spec.rb +684 -0
- data/spec/audio_spec.rb +164 -0
- data/spec/badge_spec.rb +136 -0
- data/spec/binary_spec.rb +218 -0
- data/spec/bookmark.rb +150 -0
- data/spec/collection_spec.rb +152 -0
- data/spec/comment_spec.rb +128 -0
- data/spec/crypto_spec.rb +126 -0
- data/spec/device_spec.rb +136 -0
- data/spec/event_spec.rb +239 -0
- data/spec/feed_spec.rb +252 -0
- data/spec/file_spec.rb +190 -0
- data/spec/group_spec.rb +136 -0
- data/spec/helper.rb +10 -0
- data/spec/identity_spec.rb +67 -0
- data/spec/image_spec.rb +150 -0
- data/spec/link_spec.rb +30 -0
- data/spec/note_spec.rb +163 -0
- data/spec/notification_spec.rb +89 -0
- data/spec/person_spec.rb +244 -0
- data/spec/place_spec.rb +162 -0
- data/spec/product_spec.rb +150 -0
- data/spec/question_spec.rb +156 -0
- data/spec/review_spec.rb +149 -0
- data/spec/service_spec.rb +136 -0
- data/spec/video_spec.rb +164 -0
- data/test/example_feed.atom +393 -0
- data/test/example_feed_empty_author.atom +336 -0
- data/test/example_feed_false_connected.atom +359 -0
- data/test/example_feed_link_without_href.atom +134 -0
- data/test/example_page.html +4 -0
- data/test/mime_type_bug_feed.atom +874 -0
- metadata +288 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
module Nelumba
|
2
|
+
class Subscription
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'base64'
|
6
|
+
require 'hmac-sha1'
|
7
|
+
|
8
|
+
# The url that should be used to handle subscription handshakes.
|
9
|
+
attr_reader :callback_url
|
10
|
+
|
11
|
+
# The url of the feed one wishes to subscribe to.
|
12
|
+
attr_reader :topic_url
|
13
|
+
|
14
|
+
# The hub this subscription is made with.
|
15
|
+
attr_reader :hub
|
16
|
+
|
17
|
+
# Creates a representation of a subscription.
|
18
|
+
#
|
19
|
+
# options:
|
20
|
+
# :callback_url => The url that should be used to handle subscription
|
21
|
+
# handshakes.
|
22
|
+
# :topic_url => The url of the feed one wishes to subscribe to.
|
23
|
+
# :secret => A secret that will be passed to the callback to better
|
24
|
+
# verify that communication is not replayed. Default:
|
25
|
+
# A secure random hex.
|
26
|
+
# :hubs => A list of hubs to negotiate the subscription with.
|
27
|
+
# Default: attempts to discover the hubs when it
|
28
|
+
# subscribes for the first time.
|
29
|
+
# :hub => The hub we have a subscription with already.
|
30
|
+
def initialize(options = {})
|
31
|
+
@tokens = []
|
32
|
+
|
33
|
+
secret = options[:secret] || SecureRandom.hex(32)
|
34
|
+
@secret = secret.to_s
|
35
|
+
|
36
|
+
@callback_url = options[:callback_url]
|
37
|
+
@topic_url = options[:topic_url]
|
38
|
+
@tokens << options[:token] if options[:token]
|
39
|
+
|
40
|
+
@hubs = options[:hubs] || []
|
41
|
+
|
42
|
+
@hub = options[:hub]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Actively searches for hubs by talking to publisher directly
|
46
|
+
def discover_hubs_for_topic
|
47
|
+
@hubs = Nelumba.feed_from_url(self.topic_url).hubs
|
48
|
+
end
|
49
|
+
|
50
|
+
# Subscribe to the topic through the given hub.
|
51
|
+
def subscribe
|
52
|
+
return unless self.hub.nil?
|
53
|
+
|
54
|
+
# Discover hubs if none exist
|
55
|
+
@hubs = discover_hubs_for_topic(self.topic_url) if self.hubs.empty?
|
56
|
+
@hub = self.hubs.first
|
57
|
+
change_subscription(:subscribe, token)
|
58
|
+
|
59
|
+
# TODO: Check response, if failed, try a different hub
|
60
|
+
end
|
61
|
+
|
62
|
+
# Unsubscribe to the topic.
|
63
|
+
def unsubscribe
|
64
|
+
return if self.hub.nil?
|
65
|
+
|
66
|
+
change_subscription(:unsubscribe)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Change our subscription to this topic at a hub.
|
70
|
+
# mode: Either :subscribe or :unsubscribe
|
71
|
+
# hub_url: The url of the hub to negotiate with
|
72
|
+
# token: A token to verify the response from the hub.
|
73
|
+
def change_subscription(mode)
|
74
|
+
token ||= SecureRandom.hex(32)
|
75
|
+
@tokens << token.to_s
|
76
|
+
|
77
|
+
# TODO: Set up HTTPS foo
|
78
|
+
res = Net::HTTP.post_form(URI.parse(self.hub),
|
79
|
+
{
|
80
|
+
'hub.mode' => mode.to_s,
|
81
|
+
'hub.callback' => @callback_url,
|
82
|
+
'hub.verify' => 'async',
|
83
|
+
'hub.verify_token' => token,
|
84
|
+
'hub.lease_seconds' => '',
|
85
|
+
'hub.secret' => @secret,
|
86
|
+
'hub.topic' => @topic_url
|
87
|
+
})
|
88
|
+
end
|
89
|
+
|
90
|
+
# Verify that a subscription response is valid.
|
91
|
+
def verify_subscription(token)
|
92
|
+
# Is there a token?
|
93
|
+
result = @tokens.include?(token)
|
94
|
+
|
95
|
+
# Ensure we cannot reuse the token
|
96
|
+
@tokens.delete(token)
|
97
|
+
|
98
|
+
result
|
99
|
+
end
|
100
|
+
|
101
|
+
# Determines if the given body matches the signature.
|
102
|
+
def verify_content(body, signature)
|
103
|
+
hmac = HMAC::SHA1.hexdigest(@secret, body)
|
104
|
+
check = "sha1=" + hmac
|
105
|
+
check == signature
|
106
|
+
end
|
107
|
+
|
108
|
+
# Gives the content of a challenge response given the challenge
|
109
|
+
# body.
|
110
|
+
def challenge_response(challenge_code)
|
111
|
+
{
|
112
|
+
:body => challenge_code,
|
113
|
+
:status => 200
|
114
|
+
}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Nelumba
|
2
|
+
class Video
|
3
|
+
include Nelumba::Object
|
4
|
+
|
5
|
+
# A fragment of HTML markup that, when embedded within another HTML page,
|
6
|
+
# provides an interactive user-interface for viewing or listening to the
|
7
|
+
# video stream.
|
8
|
+
attr_reader :embed_code
|
9
|
+
|
10
|
+
# A MediaLink to the video content itself.
|
11
|
+
attr_reader :stream
|
12
|
+
|
13
|
+
# Creates a new Video activity object.
|
14
|
+
def initialize(options = {}, &blk)
|
15
|
+
init(options, &blk)
|
16
|
+
end
|
17
|
+
|
18
|
+
def init(options = {}, &blk)
|
19
|
+
super(options, &blk)
|
20
|
+
|
21
|
+
@embed_code = options[:embed_code]
|
22
|
+
@stream = options[:stream]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a hash of all relevant fields.
|
26
|
+
def to_hash
|
27
|
+
{
|
28
|
+
:embed_code => @embed_code,
|
29
|
+
:stream => @stream
|
30
|
+
}.merge(super)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a hash of all relevant fields with JSON activity streams
|
34
|
+
# conventions.
|
35
|
+
def to_json_hash
|
36
|
+
{
|
37
|
+
:objectType => "video",
|
38
|
+
:embedCode => @embed_code,
|
39
|
+
:stream => @stream
|
40
|
+
}.merge(super)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/nelumba.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "nelumba/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "nelumba"
|
7
|
+
s.version = Nelumba::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Hackers of the Severed Hand']
|
10
|
+
s.email = ['hotsh@xomb.org']
|
11
|
+
s.homepage = "http://github.com/hotsh/nelumba"
|
12
|
+
s.summary = %q{Generalized federated system backend for social networks with ActivityStreams/OStatus/pump.io.}
|
13
|
+
s.description = %q{This gem allows easier implementation and utilization of distributed, federated social networks.}
|
14
|
+
|
15
|
+
s.add_dependency "ratom"
|
16
|
+
s.add_dependency "ruby-hmac"
|
17
|
+
s.add_dependency "rsa"
|
18
|
+
s.add_dependency "time-lord"
|
19
|
+
s.add_dependency "nokogiri"
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec", "~> 2.10.0"
|
22
|
+
s.add_development_dependency "rake", "~> 0.9.2"
|
23
|
+
|
24
|
+
s.files = `git ls-files`.split("\n")
|
25
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
26
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
27
|
+
s.require_paths = ["lib"]
|
28
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require_relative '../lib/nelumba/activity.rb'
|
3
|
+
|
4
|
+
describe Nelumba::Activity do
|
5
|
+
describe "#initialize" do
|
6
|
+
it "should store an object" do
|
7
|
+
Nelumba::Activity.new(:object => "object").object.must_equal "object"
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should store an type" do
|
11
|
+
Nelumba::Activity.new(:type => :audio).type.must_equal :audio
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should store a verb" do
|
15
|
+
Nelumba::Activity.new(:verb => :follow).verb.must_equal :follow
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should store a target" do
|
19
|
+
Nelumba::Activity.new(:target => "target").target.must_equal "target"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should store an actor" do
|
23
|
+
actor = mock('author')
|
24
|
+
Nelumba::Activity.new(:actor => actor).actor.must_equal actor
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should store the published date" do
|
28
|
+
time = mock('date')
|
29
|
+
Nelumba::Activity.new(:published => time).published.must_equal time
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should store the updated date" do
|
33
|
+
time = mock('date')
|
34
|
+
Nelumba::Activity.new(:updated => time).updated.must_equal time
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should store a source feed" do
|
38
|
+
feed = mock('feed')
|
39
|
+
Nelumba::Activity.new(:source => feed).source.must_equal feed
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should store a url" do
|
43
|
+
Nelumba::Activity.new(:url => "url").url.must_equal "url"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should store an id" do
|
47
|
+
Nelumba::Activity.new(:uid => "id").uid.must_equal "id"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should store an array of threads" do
|
51
|
+
thread = mock('entry')
|
52
|
+
Nelumba::Activity.new(:in_reply_to => [thread]).in_reply_to.must_equal [thread]
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should store an array of threads when only given one entry" do
|
56
|
+
thread = mock('entry')
|
57
|
+
Nelumba::Activity.new(:in_reply_to => thread).in_reply_to.must_equal [thread]
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should store an empty array of threads by default" do
|
61
|
+
Nelumba::Activity.new.in_reply_to.must_equal []
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should store an array of replies" do
|
65
|
+
thread = mock('entry')
|
66
|
+
Nelumba::Activity.new(:replies => [thread]).replies.must_equal [thread]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should store an empty array of replies by default" do
|
70
|
+
Nelumba::Activity.new.replies.must_equal []
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should store an array of shares" do
|
74
|
+
thread = mock('entry')
|
75
|
+
Nelumba::Activity.new(:shares => [thread]).shares.must_equal [thread]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should store an empty array of shares by default" do
|
79
|
+
Nelumba::Activity.new.shares.must_equal []
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should store an array of mentions" do
|
83
|
+
thread = mock('entry')
|
84
|
+
Nelumba::Activity.new(:mentions => [thread]).mentions.must_equal [thread]
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should store an empty array of mentions by default" do
|
88
|
+
Nelumba::Activity.new.mentions.must_equal []
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should store an array of likes" do
|
92
|
+
thread = mock('entry')
|
93
|
+
Nelumba::Activity.new(:likes => [thread]).likes.must_equal [thread]
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should store an empty array of likes by default" do
|
97
|
+
Nelumba::Activity.new.likes.must_equal []
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should store an empty hash of interactions by default" do
|
101
|
+
Nelumba::Activity.new.interactions.must_equal({})
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "#interaction_count" do
|
106
|
+
it "should pull values out of interactions" do
|
107
|
+
Nelumba::Activity.new(:interactions => {:share => {:count => 3}})
|
108
|
+
.interaction_count(:share).must_equal 3
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should defautl a value of 0 when verb isn't found" do
|
112
|
+
Nelumba::Activity.new(:interactions => {:share => {:count => 3}})
|
113
|
+
.interaction_count(:like).must_equal 0
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require_relative '../lib/nelumba/application.rb'
|
3
|
+
|
4
|
+
describe Nelumba::Application do
|
5
|
+
describe "#initialize" do
|
6
|
+
it "should store an author" do
|
7
|
+
author = mock('author')
|
8
|
+
Nelumba::Application.new(:author => author).author.must_equal author
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should store content" do
|
12
|
+
Nelumba::Application.new(:content => "txt").content.must_equal "txt"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should store the published date" do
|
16
|
+
time = mock('date')
|
17
|
+
Nelumba::Application.new(:published => time).published.must_equal time
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should store the updated date" do
|
21
|
+
time = mock('date')
|
22
|
+
Nelumba::Application.new(:updated => time).updated.must_equal time
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should store a display name" do
|
26
|
+
Nelumba::Application.new(:display_name => "url")
|
27
|
+
.display_name.must_equal "url"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should store a summary" do
|
31
|
+
Nelumba::Application.new(:summary => "url").summary.must_equal "url"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should store a url" do
|
35
|
+
Nelumba::Application.new(:url => "url").url.must_equal "url"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should store an id" do
|
39
|
+
Nelumba::Application.new(:uid => "id").uid.must_equal "id"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#to_hash" do
|
44
|
+
it "should contain the content" do
|
45
|
+
Nelumba::Application.new(:content => "Hello")
|
46
|
+
.to_hash[:content].must_equal "Hello"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should contain the author" do
|
50
|
+
author = mock('Nelumba::Person')
|
51
|
+
Nelumba::Application.new(:author => author).to_hash[:author].must_equal author
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should contain the uid" do
|
55
|
+
Nelumba::Application.new(:uid => "Hello").to_hash[:uid].must_equal "Hello"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should contain the url" do
|
59
|
+
Nelumba::Application.new(:url => "Hello").to_hash[:url].must_equal "Hello"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should contain the summary" do
|
63
|
+
Nelumba::Application.new(:summary=> "Hello")
|
64
|
+
.to_hash[:summary].must_equal "Hello"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should contain the display name" do
|
68
|
+
Nelumba::Application.new(:display_name => "Hello")
|
69
|
+
.to_hash[:display_name].must_equal "Hello"
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should contain the published date" do
|
73
|
+
date = mock('Time')
|
74
|
+
Nelumba::Application.new(:published => date).to_hash[:published].must_equal date
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should contain the updated date" do
|
78
|
+
date = mock('Time')
|
79
|
+
Nelumba::Application.new(:updated => date).to_hash[:updated].must_equal date
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#to_json" do
|
84
|
+
before do
|
85
|
+
author = Nelumba::Person.new :display_name => "wilkie"
|
86
|
+
@note = Nelumba::Application.new :content => "Hello",
|
87
|
+
:author => author,
|
88
|
+
:uid => "id",
|
89
|
+
:url => "url",
|
90
|
+
:title => "title",
|
91
|
+
:summary => "foo",
|
92
|
+
:display_name => "meh",
|
93
|
+
:published => Time.now,
|
94
|
+
:updated => Time.now
|
95
|
+
|
96
|
+
@json = @note.to_json
|
97
|
+
@data = JSON.parse(@json)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should contain the embedded json for the author" do
|
101
|
+
@data["author"].must_equal JSON.parse(@note.author.to_json)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should contain a 'application' objectType" do
|
105
|
+
@data["objectType"].must_equal "application"
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should contain the id" do
|
109
|
+
@data["id"].must_equal @note.uid
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should contain the content" do
|
113
|
+
@data["content"].must_equal @note.content
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should contain the url" do
|
117
|
+
@data["url"].must_equal @note.url
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should contain the summary" do
|
121
|
+
@data["summary"].must_equal @note.summary
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should contain the display name" do
|
125
|
+
@data["displayName"].must_equal @note.display_name
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should contain the published date as rfc3339" do
|
129
|
+
@data["published"].must_equal @note.published.to_date.rfc3339 + 'Z'
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should contain the updated date as rfc3339" do
|
133
|
+
@data["updated"].must_equal @note.updated.to_date.rfc3339 + 'Z'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|