ostatus 0.0.1

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 ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ostatus.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,9 @@
1
+ OStatus
2
+ =======
3
+
4
+ This gem implements the OStatus protocol data streams and the technologies that are related to it such as ActivityStreams, PortableContacts, and Salmon.
5
+
6
+ What it does
7
+ ------------
8
+
9
+ Right now, it simply parses Atom and gives the ability to parse the XML for each of the objects in the OStatus world.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ # rake spec
4
+ RSpec::Core::RakeTask.new(:spec) do |spec|
5
+ spec.pattern = 'spec/*_spec.rb'
6
+ end
7
+
8
+ # rake doc
9
+ RSpec::Core::RakeTask.new(:doc) do |spec|
10
+ spec.pattern = 'spec/*_spec.rb'
11
+ spec.rspec_opts = ['--format documentation']
12
+ end
13
+
14
+ require 'bundler'
15
+ Bundler::GemHelper.install_tasks
data/lib/ostatus.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'nokogiri'
2
+
3
+ module OStatus
4
+ end
@@ -0,0 +1,63 @@
1
+ module OStatus
2
+
3
+ # This class represents an Activity object for an OStatus entry.
4
+ class Activity
5
+
6
+ # This will create an instance of an Activity class populated
7
+ # with the given data as a Hash or parsable XML given by a
8
+ # Nokogiri::XML::Element that serves as the root node of
9
+ # anything containing the activity tags.
10
+ def initialize(activity_root)
11
+ if activity_root.class == Hash
12
+ @activity_data = activity_root
13
+ @activity = nil
14
+ else
15
+ @activity = activity_root
16
+ @activity_data = nil
17
+ end
18
+ end
19
+
20
+ def pick_first_node(a)
21
+ if a.empty?
22
+ nil
23
+ else
24
+ a[0].content
25
+ end
26
+ end
27
+ private :pick_first_node
28
+
29
+ # Returns the object field or nil if it does not exist.
30
+ def object
31
+ return @activity_data[:object] unless @activity_data == nil
32
+ pick_first_node(@activity.xpath('./activity:object'))
33
+ end
34
+
35
+ # Returns the target field or nil if it does not exist.
36
+ def target
37
+ return @activity_data[:target] unless @activity_data == nil
38
+ pick_first_node(@activity.xpath('./activity:target'))
39
+ end
40
+
41
+ # Returns the verb field or nil if it does not exist.
42
+ def verb
43
+ return @activity_data[:verb] unless @activity_data == nil
44
+ pick_first_node(@activity.xpath('./activity:verb'))
45
+ end
46
+
47
+ # Returns the object-type field or nil if it does not exist.
48
+ def object_type
49
+ return @activity_data[:object_type] unless @activity_data == nil
50
+ pick_first_node(@activity.xpath('./activity:object-type'))
51
+ end
52
+
53
+ # Returns a hash of all relevant fields.
54
+ def info
55
+ {
56
+ :object => self.object,
57
+ :target => self.target,
58
+ :verb => self.verb,
59
+ :object_type => self.object_type
60
+ }
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,62 @@
1
+ require_relative 'activity'
2
+ require_relative 'portable_contacts'
3
+
4
+ module OStatus
5
+
6
+ # Holds information about the author of the Feed.
7
+ class Author
8
+
9
+ # Instantiates an Author object either from a given <author></author> root
10
+ # passed as an instance of a Nokogiri::XML::Element or a Hash containing
11
+ # the properties.
12
+ def initialize(author_node)
13
+ if author_node.class == Hash
14
+ @author_data = author_node
15
+ @author = nil
16
+ else
17
+ @author = author_node
18
+ @author_data = nil
19
+ end
20
+ end
21
+
22
+ # Gives an instance of an OStatus::Activity that parses the fields
23
+ # having an activity prefix.
24
+ def activity
25
+ OStatus::Activity.new(@author)
26
+ end
27
+
28
+ def pick_first_node(a)
29
+ if a.empty?
30
+ nil
31
+ else
32
+ a[0].content
33
+ end
34
+ end
35
+ private :pick_first_node
36
+
37
+ # Returns the name of the author, if it exists.
38
+ def name
39
+ return @author_data[:name] unless @author_data == nil
40
+ pick_first_node(@author.css('name'))
41
+ end
42
+
43
+ # Returns the email of the author, if it exists.
44
+ def email
45
+ return @author_data[:email] unless @author_data == nil
46
+ pick_first_node(@author.css('email'))
47
+ end
48
+
49
+ # Returns the uri of the author, if it exists.
50
+ def uri
51
+ return @author_data[:uri] unless @author_data == nil
52
+ pick_first_node(@author.css('uri'))
53
+ end
54
+
55
+ # Returns an instance of a PortableContacts that further describe the
56
+ # author's contact information, if it exists.
57
+ def portable_contacts
58
+ return @author_data[:portable_contacts] unless @author_data == nil
59
+ PortableContacts.new(@author)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,107 @@
1
+ require_relative 'activity'
2
+
3
+ module OStatus
4
+
5
+ # Holds information about an individual entry in the Feed.
6
+ class Entry
7
+
8
+ # Instantiates an Entry object from either a given <entry></entry> root
9
+ # passed as an instance of a Nokogiri::XML::Element or a Hash
10
+ # containing the properties.
11
+ def initialize(entry_node)
12
+ if entry_node.class == Hash
13
+ @entry_data = entry_node
14
+ @entry = nil
15
+ else
16
+ @entry = entry_node
17
+ @entry_data = nil
18
+ end
19
+ end
20
+
21
+ # Gives an instance of an OStatus::Activity that parses the fields
22
+ # having an activity prefix.
23
+ def activity
24
+ Activity.new(@entry)
25
+ end
26
+
27
+ def pick_first_node(a)
28
+ if a.empty?
29
+ nil
30
+ else
31
+ a[0].content
32
+ end
33
+ end
34
+ private :pick_first_node
35
+
36
+ # Returns the title of the entry.
37
+ def title
38
+ return @entry_data[:title] unless @entry_data == nil
39
+ pick_first_node(@entry.css('title'))
40
+ end
41
+
42
+ # Returns the content of the entry.
43
+ def content
44
+ return @entry_data[:content] unless @entry_data == nil
45
+ pick_first_node(@entry.css('content'))
46
+ end
47
+
48
+ # Returns the content-type of the entry.
49
+ def content_type
50
+ return @entry_data[:content_type] unless @entry_data == nil
51
+ content = @entry.css('content')
52
+ content.empty? ? "" : content[0]['type']
53
+ end
54
+
55
+ # Returns the DateTime that this entry was published.
56
+ def published
57
+ return @entry_data[:published] unless @entry_data == nil
58
+ DateTime.parse(pick_first_node(@entry.css('published')))
59
+ end
60
+
61
+ # Returns the DateTime that this entry was updated.
62
+ def updated
63
+ return @entry_data[:updated] unless @entry_data == nil
64
+ DateTime.parse(pick_first_node(@entry.css('updated')))
65
+ end
66
+
67
+ # Returns the id of the entry.
68
+ def id
69
+ return @entry_data[:id] unless @entry_data == nil
70
+ pick_first_node(@entry.css('id'))
71
+ end
72
+
73
+ def link
74
+ return @entry_data[:link] unless @entry_data == nil
75
+
76
+ result = {}
77
+
78
+ @entry.css('link').each do |node|
79
+ if node[:rel] != nil
80
+ rel = node[:rel].intern
81
+ if result[rel] == nil
82
+ result[rel] = []
83
+ end
84
+
85
+ result[rel] << node
86
+ end
87
+ end
88
+
89
+ result
90
+ end
91
+
92
+ # Returns a Hash of all fields.
93
+ def info
94
+ return @entry_data unless @entry_data == nil
95
+ {
96
+ :activity => self.activity.info,
97
+ :id => pick_first_node(@entry.css('id')),
98
+ :title => self.title,
99
+ :content => self.content,
100
+ :content_type => self.content_type,
101
+ :link => self.link,
102
+ :published => self.published,
103
+ :updated => self.updated
104
+ }
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,156 @@
1
+ require 'nokogiri'
2
+ require 'open-uri'
3
+ require 'tinyatom'
4
+
5
+ require_relative 'entry'
6
+
7
+ module OStatus
8
+
9
+ # This class represents an OStatus Feed object.
10
+ class Feed
11
+ def initialize(url, access_token, author, entries, id, title, links)
12
+ @url = url
13
+ @access_token = access_token
14
+ @author = author
15
+ @entries = entries
16
+ @id = id
17
+ @title = title
18
+ @links = links
19
+
20
+ if id == nil
21
+ @xml = Nokogiri::XML::Document.parse(self.atom)
22
+ end
23
+ end
24
+
25
+ # Creates a new Feed instance given by the atom feed located at 'url'
26
+ # and optionally using the OAuth::AccessToken given.
27
+ def Feed.from_url(url, access_token = nil)
28
+ Feed.new(url, access_token, nil, nil, nil, nil, nil)
29
+ end
30
+
31
+ # Creates a new Feed instance that contains the information given by
32
+ # the various instances of author and entries.
33
+ def Feed.from_data(id, title, url, author, entries, links)
34
+ Feed.new(url, nil, author, entries, id, title, links)
35
+ end
36
+
37
+ # Returns an array of Nokogiri::XML::Element instances for all link tags
38
+ # that have a rel equal to that given by attribute. This can be used
39
+ # generally as a Hash where the keys are intern strings that give an attribute.
40
+ #
41
+ # For example:
42
+ # link(:hub).first[:href] -- Gets the first link tag with rel="hub" and
43
+ # returns the contents of the href attribute.
44
+ #
45
+ def link(attribute)
46
+ return @links[attribute] unless @links == nil
47
+
48
+ # get all links with rel attribute being equal to attribute
49
+ @xml.xpath('/xmlns:feed/xmlns:link').select do |link|
50
+ link[:rel] == attribute.to_s
51
+ end
52
+ end
53
+
54
+ # Returns an array of URLs for each hub link tag.
55
+ def hubs
56
+ link(:hub).map do |link|
57
+ link[:href]
58
+ end
59
+ end
60
+
61
+ # Returns the salmon URL from the link tag.
62
+ def salmon
63
+ link(:salmon).first[:href]
64
+ end
65
+
66
+ # This method will return a String containing the actual content of
67
+ # the atom feed. It will make a network request (through OAuth if
68
+ # an access token was given) to retrieve the document if necessary.
69
+ def atom
70
+ if @id == nil and @access_token == nil
71
+ # simply open the url
72
+ open(@url).read
73
+ elsif @id == nil and @url != nil
74
+ # open the url through OAuth
75
+ @access_token.get(@url).body
76
+ else
77
+ # build the atom file from internal information
78
+ feed = TinyAtom::Feed.new(
79
+ self.id,
80
+ self.title,
81
+ @url,
82
+
83
+ :author_name => self.author.name,
84
+ :author_email => self.author.email,
85
+ :author_uri => self.author.uri,
86
+
87
+ :hubs => self.hubs
88
+ )
89
+
90
+ @entries.each do |entry|
91
+ feed.add_entry(
92
+ entry.id,
93
+ entry.title,
94
+ entry.updated,
95
+
96
+ '',
97
+
98
+ :content => entry.content,
99
+
100
+ :author_name => self.author.name,
101
+ :author_email => self.author.email,
102
+ :author_uri => self.author.uri
103
+ )
104
+ end
105
+
106
+ feed.make(:indent => 2)
107
+ end
108
+ end
109
+
110
+ def pick_first_node(a)
111
+ if a.empty?
112
+ nil
113
+ else
114
+ a[0].content
115
+ end
116
+ end
117
+ private :pick_first_node
118
+
119
+ def id
120
+ return @id if @xml == nil
121
+
122
+ pick_first_node(@xml.xpath('/xmlns:feed/xmlns:id'))
123
+ end
124
+
125
+ def title
126
+ return @title if @xml == nil
127
+
128
+ pick_first_node(@xml.xpath('/xmlns:feed/xmlns:title'))
129
+ end
130
+
131
+ def url
132
+ return @url
133
+ end
134
+
135
+ # Returns an OStatus::Author that will parse the author information
136
+ # within the Feed.
137
+ def author
138
+ return @author if @xml == nil
139
+
140
+ author_xml = @xml.at_css('author')
141
+ OStatus::Author.new(author_xml)
142
+ end
143
+
144
+ # This method gives you an array of OStatus::Entry instances for
145
+ # each entry listed in the feed.
146
+ def entries
147
+ return @entries if @xml == nil
148
+
149
+ entries_xml = @xml.css('entry')
150
+
151
+ entries_xml.map do |entry|
152
+ OStatus::Entry.new(entry)
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,124 @@
1
+ module OStatus
2
+
3
+ # Holds information about the extended contact information
4
+ # in the Feed given in the Portable Contacts specification.
5
+ class PortableContacts
6
+
7
+ # Instantiates a OStatus::PortableContacts object from either
8
+ # a given root that contains all <poco:*> tags as a
9
+ # Nokogiri::XML::Element or a Hash containing the properties.
10
+ def initialize(author_node)
11
+ if author_node.class == Hash
12
+ @poco_data = author_node
13
+ @poco = nil
14
+ else
15
+ @poco = author_node
16
+ @poco_data = nil
17
+ end
18
+ end
19
+
20
+ def pick_first_node(a)
21
+ if a.empty?
22
+ nil
23
+ else
24
+ a[0].content
25
+ end
26
+ end
27
+ private :pick_first_node
28
+
29
+ # Returns the id of the contact, if it exists.
30
+ def id
31
+ return @poco_data[:id] unless @poco_data == nil
32
+ pick_first_node(@poco.xpath('./poco:id'))
33
+ end
34
+
35
+ # Returns the display_name of the contact, if it exists.
36
+ def display_name
37
+ return @poco_data[:display_name] unless @poco_data == nil
38
+ pick_first_node(@poco.xpath('./poco:displayName'))
39
+ end
40
+
41
+ # Returns the name of the contact, if it exists.
42
+ def name
43
+ return @poco_data[:name] unless @poco_data == nil
44
+ pick_first_node(@poco.xpath('./poco:name'))
45
+ end
46
+
47
+ # Returns the nickname of the contact, if it exists.
48
+ def nickname
49
+ return @poco_data[:nickname] unless @poco_data == nil
50
+ pick_first_node(@poco.xpath('./poco:nickname'))
51
+ end
52
+
53
+ # Returns the published of the contact, if it exists.
54
+ def published
55
+ return @poco_data[:published] unless @poco_data == nil
56
+ pub = pick_first_node(@poco.xpath('./poco:published'))
57
+ if pub != nil
58
+ DateTime.parse(pub)
59
+ end
60
+ end
61
+
62
+ # Returns the updated of the contact, if it exists.
63
+ def updated
64
+ return @poco_data[:updated] unless @poco_data == nil
65
+ upd = pick_first_node(@poco.xpath('./poco:updated'))
66
+ if upd != nil
67
+ DateTime.parse(upd)
68
+ end
69
+ end
70
+
71
+ # Returns the birthday of the contact, if it exists.
72
+ def birthday
73
+ return @poco_data[:birthday] unless @poco_data == nil
74
+ bday = pick_first_node(@poco.xpath('./poco:birthday'))
75
+ if bday != nil
76
+ Date.parse(bday)
77
+ end
78
+ end
79
+
80
+ # Returns the anniversary of the contact, if it exists.
81
+ def anniversary
82
+ return @poco_data[:anniversary] unless @poco_data == nil
83
+ anni = pick_first_node(@poco.xpath('./poco:anniversary'))
84
+ if anni != nil
85
+ Date.parse(anni)
86
+ end
87
+ end
88
+
89
+ # Returns the gender of the contact, if it exists.
90
+ def gender
91
+ return @poco_data[:gender] unless @poco_data == nil
92
+ pick_first_node(@poco.xpath('./poco:gender'))
93
+ end
94
+
95
+ # Returns the note of the contact, if it exists.
96
+ def note
97
+ return @poco_data[:note] unless @poco_data == nil
98
+ pick_first_node(@poco.xpath('./poco:note'))
99
+ end
100
+
101
+ # Returns the preferred username of the contact, if it exists.
102
+ def preferred_username
103
+ return @poco_data[:preferred_username] unless @poco_data == nil
104
+ pick_first_node(@poco.xpath('./poco:preferredUsername'))
105
+ end
106
+
107
+ # Returns a boolean that indicates that a bi-directional connection
108
+ # has been established between the user and the contact, if it is
109
+ # able to assert this.
110
+ def connected
111
+ return @poco_data[:connected] unless @poco_data == nil
112
+ str = pick_first_node(@poco.xpath('./poco:connected'))
113
+ return nil if str == nil
114
+
115
+ if str == "true"
116
+ true
117
+ elsif str == "false"
118
+ false
119
+ else
120
+ nil
121
+ end
122
+ end
123
+ end
124
+ end