nelumba 0.0.13
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.
- 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
|