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
data/lib/nelumba/note.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Nelumba
|
2
|
+
class Note
|
3
|
+
include Nelumba::Object
|
4
|
+
|
5
|
+
# Create a new note.
|
6
|
+
#
|
7
|
+
# options:
|
8
|
+
# :title => The title of the note. Defaults: "Untitled"
|
9
|
+
# :text => The content of the note. Defaults: ""
|
10
|
+
# :html => The content of the note as html.
|
11
|
+
# :author => An Person that wrote the note.
|
12
|
+
# :url => Permanent location for an html representation of the
|
13
|
+
# note.
|
14
|
+
# :published => When the note was originally published.
|
15
|
+
# :updated => When the note was last updated.
|
16
|
+
# :uid => The unique id that identifies this note.
|
17
|
+
def initialize(options = {}, &blk)
|
18
|
+
init(options, &blk)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns a hash of all relevant fields.
|
22
|
+
def to_hash
|
23
|
+
super.to_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns a hash of all relevant fields with JSON activity streams
|
27
|
+
# conventions.
|
28
|
+
def to_json_hash
|
29
|
+
{
|
30
|
+
:objectType => "note",
|
31
|
+
}.merge(super)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
module Nelumba
|
2
|
+
# This represents a notification that can be sent to a server when you wish
|
3
|
+
# to send information to a server that has not yet subscribed to you. Since
|
4
|
+
# this implies a lack of trust, a notification adds a layer so that the
|
5
|
+
# recipiant can verify the message contents.
|
6
|
+
class Notification
|
7
|
+
require 'xml'
|
8
|
+
require 'digest/sha2'
|
9
|
+
|
10
|
+
# The Activity that is represented by this notification.
|
11
|
+
attr_reader :activity
|
12
|
+
|
13
|
+
# The identity of the sender that can be used to discover the Identity
|
14
|
+
attr_reader :account
|
15
|
+
|
16
|
+
# Create an instance for a particular Nelumba::Activity.
|
17
|
+
def initialize activity, signature = nil, plaintext = nil
|
18
|
+
@activity = activity
|
19
|
+
@signature = signature
|
20
|
+
@plaintext = plaintext
|
21
|
+
|
22
|
+
account = activity.actor.uri
|
23
|
+
|
24
|
+
# XXX: Negotiate various weird uri schemes to find identity account
|
25
|
+
@account = account
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates an activity for following a particular Person.
|
29
|
+
def self.from_follow(user_author, followed_author)
|
30
|
+
activity = Nelumba::Activity.new(
|
31
|
+
:verb => :follow,
|
32
|
+
:object => followed_author,
|
33
|
+
:actor => user_author,
|
34
|
+
:title => "Now following #{followed_author.name}",
|
35
|
+
:content => "Now following #{followed_author.name}",
|
36
|
+
:content_type => "html"
|
37
|
+
)
|
38
|
+
|
39
|
+
self.new(activity)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates an activity for unfollowing a particular Person.
|
43
|
+
def self.from_unfollow(user_author, followed_author)
|
44
|
+
activity = Nelumba::Activity.new(
|
45
|
+
:verb => "http://ostatus.org/schema/1.0/unfollow",
|
46
|
+
:object => followed_author,
|
47
|
+
:actor => user_author,
|
48
|
+
:title => "Stopped following #{followed_author.name}",
|
49
|
+
:content => "Stopped following #{followed_author.name}",
|
50
|
+
:content_type => "html"
|
51
|
+
)
|
52
|
+
|
53
|
+
self.new(activity)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Creates an activity for a profile update.
|
57
|
+
def self.from_profile_update(user_author)
|
58
|
+
activity = Nelumba::Activity.new(
|
59
|
+
:verb => "http://ostatus.org/schema/1.0/update-profile",
|
60
|
+
:actor => user_author,
|
61
|
+
:title => "#{user_author.name} changed their profile information.",
|
62
|
+
:content => "#{user_author.name} changed their profile information.",
|
63
|
+
:content_type => "html"
|
64
|
+
)
|
65
|
+
|
66
|
+
self.new(activity)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Will pull a Nelumba::Activity from the given payload and MIME type.
|
70
|
+
def self.from_data(content, content_type)
|
71
|
+
case content_type
|
72
|
+
when 'xml',
|
73
|
+
'magic-envelope+xml',
|
74
|
+
'application/xml',
|
75
|
+
'application/text+xml',
|
76
|
+
'application/magic-envelope+xml'
|
77
|
+
self.from_xml content
|
78
|
+
when 'json',
|
79
|
+
'magic-envelope+json',
|
80
|
+
'application/json',
|
81
|
+
'application/text+json',
|
82
|
+
'application/magic-envelope+json'
|
83
|
+
self.from_json content
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Will pull a Nelumba::Activity from a magic envelope described by the JSON.
|
88
|
+
def self.from_json(source)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Will pull a Nelumba::Activity from a magic envelope described by the XML.
|
92
|
+
def self.from_xml(source)
|
93
|
+
if source.is_a?(String)
|
94
|
+
if source.length == 0
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
|
98
|
+
source = XML::Document.string(source,
|
99
|
+
:options => XML::Parser::Options::NOENT)
|
100
|
+
else
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
|
104
|
+
# Retrieve the envelope
|
105
|
+
envelope = source.find('/me:env',
|
106
|
+
'me:http://salmon-protocol.org/ns/magic-env').first
|
107
|
+
|
108
|
+
return nil unless envelope
|
109
|
+
|
110
|
+
data = envelope.find('me:data',
|
111
|
+
'me:http://salmon-protocol.org/ns/magic-env').first
|
112
|
+
return nil unless data
|
113
|
+
|
114
|
+
data_type = data.attributes["type"]
|
115
|
+
if data_type.nil?
|
116
|
+
data_type = 'application/atom+xml'
|
117
|
+
armored_data_type = ''
|
118
|
+
else
|
119
|
+
armored_data_type = Base64::urlsafe_encode64(data_type)
|
120
|
+
end
|
121
|
+
|
122
|
+
encoding = envelope.find('me:encoding',
|
123
|
+
'me:http://salmon-protocol.org/ns/magic-env').first
|
124
|
+
|
125
|
+
algorithm = envelope.find(
|
126
|
+
'me:alg',
|
127
|
+
'me:http://salmon-protocol.org/ns/magic-env').first
|
128
|
+
|
129
|
+
signature = source.find('me:sig',
|
130
|
+
'me:http://salmon-protocol.org/ns/magic-env').first
|
131
|
+
|
132
|
+
# Parse fields
|
133
|
+
|
134
|
+
# Well, if we cannot verify, we don't accept
|
135
|
+
return nil unless signature
|
136
|
+
|
137
|
+
# XXX: Handle key_id attribute
|
138
|
+
signature = signature.content
|
139
|
+
signature = Base64::urlsafe_decode64(signature)
|
140
|
+
|
141
|
+
if encoding.nil?
|
142
|
+
# When the encoding is omitted, use base64url
|
143
|
+
# Cite: Magic Envelope Draft Spec Section 3.3
|
144
|
+
armored_encoding = ''
|
145
|
+
encoding = 'base64url'
|
146
|
+
else
|
147
|
+
armored_encoding = Base64::urlsafe_encode64(encoding.content)
|
148
|
+
encoding = encoding.content.downcase
|
149
|
+
end
|
150
|
+
|
151
|
+
if algorithm.nil?
|
152
|
+
# When algorithm is omitted, use 'RSA-SHA256'
|
153
|
+
# Cite: Magic Envelope Draft Spec Section 3.3
|
154
|
+
armored_algorithm = ''
|
155
|
+
algorithm = 'rsa-sha256'
|
156
|
+
else
|
157
|
+
armored_algorithm = Base64::urlsafe_encode64(algorithm.content)
|
158
|
+
algorithm = algorithm.content.downcase
|
159
|
+
end
|
160
|
+
|
161
|
+
# Retrieve and decode data payload
|
162
|
+
|
163
|
+
data = data.content
|
164
|
+
armored_data = data
|
165
|
+
|
166
|
+
case encoding
|
167
|
+
when 'base64url'
|
168
|
+
data = Base64::urlsafe_decode64(data)
|
169
|
+
else
|
170
|
+
# Unsupported data encoding
|
171
|
+
return nil
|
172
|
+
end
|
173
|
+
|
174
|
+
# Signature plaintext
|
175
|
+
plaintext = "#{armored_data}.#{armored_data_type}.#{armored_encoding}.#{armored_algorithm}"
|
176
|
+
|
177
|
+
# Interpret data payload
|
178
|
+
payload = XML::Reader.string(data)
|
179
|
+
self.new Nelumba::Atom::Entry.new(payload).to_canonical, signature, plaintext
|
180
|
+
end
|
181
|
+
|
182
|
+
# Generate the xml for this notice and sign with the given private key.
|
183
|
+
def to_xml private_key
|
184
|
+
# Generate magic envelope
|
185
|
+
magic_envelope = XML::Document.new
|
186
|
+
|
187
|
+
magic_envelope.root = XML::Node.new 'env'
|
188
|
+
|
189
|
+
me_ns = XML::Namespace.new(magic_envelope.root,
|
190
|
+
'me', 'http://salmon-protocol.org/ns/magic-env')
|
191
|
+
|
192
|
+
magic_envelope.root.namespaces.namespace = me_ns
|
193
|
+
|
194
|
+
# Armored Data <me:data>
|
195
|
+
data = @activity.to_atom
|
196
|
+
@plaintext = data
|
197
|
+
data_armored = Base64::urlsafe_encode64(data)
|
198
|
+
elem = XML::Node.new 'data', data_armored, me_ns
|
199
|
+
elem.attributes['type'] = 'application/atom+xml'
|
200
|
+
data_type_armored = 'YXBwbGljYXRpb24vYXRvbSt4bWw='
|
201
|
+
magic_envelope.root << elem
|
202
|
+
|
203
|
+
# Encoding <me:encoding>
|
204
|
+
magic_envelope.root << XML::Node.new('encoding', 'base64url', me_ns)
|
205
|
+
encoding_armored = 'YmFzZTY0dXJs'
|
206
|
+
|
207
|
+
# Signing Algorithm <me:alg>
|
208
|
+
magic_envelope.root << XML::Node.new('alg', 'RSA-SHA256', me_ns)
|
209
|
+
algorithm_armored = 'UlNBLVNIQTI1Ng=='
|
210
|
+
|
211
|
+
# Signature <me:sig>
|
212
|
+
plaintext =
|
213
|
+
"#{data_armored}.#{data_type_armored}.#{encoding_armored}.#{algorithm_armored}"
|
214
|
+
|
215
|
+
# Assign @signature to the signature generated from the plaintext
|
216
|
+
@signature = Nelumba::Crypto.emsa_sign(plaintext, private_key)
|
217
|
+
|
218
|
+
signature_armored = Base64::urlsafe_encode64(@signature)
|
219
|
+
magic_envelope.root << XML::Node.new('sig', signature_armored, me_ns)
|
220
|
+
|
221
|
+
magic_envelope.to_s :indent => true, :encoding => XML::Encoding::UTF_8
|
222
|
+
end
|
223
|
+
|
224
|
+
# Check the origin of this notification.
|
225
|
+
def verified? key
|
226
|
+
Nelumba::Crypto.emsa_verify(@plaintext, @signature, key)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
module Nelumba
|
2
|
+
module Object
|
3
|
+
require 'json'
|
4
|
+
require 'cgi'
|
5
|
+
|
6
|
+
# Determines what constitutes a username inside an update text
|
7
|
+
USERNAME_REGULAR_EXPRESSION = /(^|[ \t\n\r\f"'\(\[{]+)@([^ \t\n\r\f&?=@%\/\#]*[^ \t\n\r\f&?=@%\/\#.!:;,"'\]}\)])(?:@([^ \t\n\r\f&?=@%\/\#]*[^ \t\n\r\f&?=@%\/\#.!:;,"'\]}\)]))?/
|
8
|
+
|
9
|
+
attr_reader :title
|
10
|
+
attr_reader :author
|
11
|
+
attr_reader :display_name
|
12
|
+
attr_reader :uid
|
13
|
+
attr_reader :url
|
14
|
+
|
15
|
+
# Natural-language description of this object
|
16
|
+
attr_reader :summary
|
17
|
+
|
18
|
+
# The image representation of this object
|
19
|
+
attr_reader :image
|
20
|
+
|
21
|
+
# Natural-language text content
|
22
|
+
attr_reader :content
|
23
|
+
|
24
|
+
attr_reader :published
|
25
|
+
attr_reader :updated
|
26
|
+
|
27
|
+
# Holds the content as plain text.
|
28
|
+
attr_reader :text
|
29
|
+
|
30
|
+
# Holds the content as html.
|
31
|
+
attr_reader :html
|
32
|
+
|
33
|
+
def initialize(options = {}, &blk)
|
34
|
+
init(options, &blk)
|
35
|
+
end
|
36
|
+
|
37
|
+
def init(options = {}, &blk)
|
38
|
+
@author = options[:author]
|
39
|
+
@content = options[:content]
|
40
|
+
@display_name = options[:display_name]
|
41
|
+
@uid = options[:uid]
|
42
|
+
@url = options[:url]
|
43
|
+
@summary = options[:summary]
|
44
|
+
@published = options[:published] || Time.now
|
45
|
+
@updated = options[:updated] || Time.now
|
46
|
+
@title = options[:title] || "Untitled"
|
47
|
+
|
48
|
+
options[:published] = @published
|
49
|
+
options[:updated] = @updated
|
50
|
+
options[:title] = @title
|
51
|
+
|
52
|
+
# Alternative representations of 'content'
|
53
|
+
@text = options[:text] || @content || ""
|
54
|
+
@content = @content || options[:text]
|
55
|
+
options[:text] = @text
|
56
|
+
|
57
|
+
@html = options[:html] || to_html(&blk)
|
58
|
+
@content = @content || @html
|
59
|
+
options[:html] = @html
|
60
|
+
end
|
61
|
+
|
62
|
+
# TODO: Convert html to safe text
|
63
|
+
def to_text()
|
64
|
+
return @text if @text
|
65
|
+
|
66
|
+
return "" if @html.nil?
|
67
|
+
|
68
|
+
""
|
69
|
+
end
|
70
|
+
|
71
|
+
# Produces an HTML string representing the Object's content.
|
72
|
+
#
|
73
|
+
# Requires a block that is given two arguments: the username and the domain
|
74
|
+
# that should return a Nelumba::Person that matches when a @username tag
|
75
|
+
# is found.
|
76
|
+
def to_html(&blk)
|
77
|
+
return @html if @html
|
78
|
+
|
79
|
+
return "" if @text.nil?
|
80
|
+
|
81
|
+
out = CGI.escapeHTML(@text)
|
82
|
+
|
83
|
+
# Replace any absolute addresses with a link
|
84
|
+
# Note: Do this first! Otherwise it will add anchors inside anchors!
|
85
|
+
out.gsub!(/(http[s]?:\/\/\S+[a-zA-Z0-9\/}])/, "<a href='\\1'>\\1</a>")
|
86
|
+
|
87
|
+
# we let almost anything be in a username, except those that mess with urls.
|
88
|
+
# but you can't end in a .:;, or !
|
89
|
+
# also ignore container chars [] () "" '' {}
|
90
|
+
# XXX: the _correct_ solution will be to use an email validator
|
91
|
+
out.gsub!(USERNAME_REGULAR_EXPRESSION) do |match|
|
92
|
+
if blk
|
93
|
+
author = blk.call($2, $3)
|
94
|
+
end
|
95
|
+
|
96
|
+
if author
|
97
|
+
"#{$1}<a href='#{author.uri}'>@#{$2}</a>"
|
98
|
+
else
|
99
|
+
"#{$1}@#{$2}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
out.gsub!( /(^|\s+)#(\p{Word}+)/ ) do |match|
|
104
|
+
"#{$1}<a href='/search?search=%23#{$2}'>##{$2}</a>"
|
105
|
+
end
|
106
|
+
|
107
|
+
out.gsub!(/\n/, "<br/>")
|
108
|
+
|
109
|
+
out
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns a list of Nelumba::Person's for those replied by the object.
|
113
|
+
#
|
114
|
+
# Requires a block that is given two arguments: the username and the domain
|
115
|
+
# that should return a Nelumba::Person that matches when a @username tag
|
116
|
+
# is found.
|
117
|
+
#
|
118
|
+
# Usage:
|
119
|
+
#
|
120
|
+
# note = Nelumba::Note.new(:text => "Hello @foo")
|
121
|
+
# note.reply_to do |username, domain|
|
122
|
+
# i = identities.select {|e| e.username == username && e.domain == domain }.first
|
123
|
+
# i.author if i
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# With a persistence backend:
|
127
|
+
# note.reply_to do |username, domain|
|
128
|
+
# i = Identity.first(:username => /^#{Regexp.escape(username)}$/i
|
129
|
+
# i.author if i
|
130
|
+
# end
|
131
|
+
def reply_to(&blk)
|
132
|
+
out = CGI.escapeHTML(@text)
|
133
|
+
|
134
|
+
# we let almost anything be in a username, except those that mess with urls.
|
135
|
+
# but you can't end in a .:;, or !
|
136
|
+
# also ignore container chars [] () "" '' {}
|
137
|
+
# XXX: the _correct_ solution will be to use an email validator
|
138
|
+
ret = []
|
139
|
+
out.match(/^#{USERNAME_REGULAR_EXPRESSION}/) do |beginning, username, domain|
|
140
|
+
ret << blk.call(username, domain) if blk
|
141
|
+
end
|
142
|
+
|
143
|
+
ret
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_hash(scheme = 'https', domain = 'example.org', port = nil)
|
147
|
+
url_start = "#{scheme}://#{domain}#{port.nil? ? "" : ":#{port}"}"
|
148
|
+
|
149
|
+
uid = self.uid
|
150
|
+
url = self.url
|
151
|
+
|
152
|
+
if uid && uid.start_with?("/")
|
153
|
+
uid = "#{url_start}#{uid}"
|
154
|
+
end
|
155
|
+
|
156
|
+
if url && url.start_with?("/")
|
157
|
+
url = "#{url_start}#{url}"
|
158
|
+
end
|
159
|
+
|
160
|
+
{
|
161
|
+
:author => self.author,
|
162
|
+
:summary => self.summary,
|
163
|
+
:content => self.content,
|
164
|
+
:display_name => self.display_name,
|
165
|
+
:uid => uid,
|
166
|
+
:url => url,
|
167
|
+
:published => self.published,
|
168
|
+
:updated => self.updated,
|
169
|
+
:title => self.title,
|
170
|
+
:text => self.text,
|
171
|
+
:html => self.html,
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
def to_json_hash(scheme = 'https', domain = 'example.org', port = nil)
|
176
|
+
url_start = "#{scheme}://#{domain}#{port.nil? ? "" : ":#{port}"}"
|
177
|
+
|
178
|
+
uid = self.uid
|
179
|
+
url = self.url
|
180
|
+
|
181
|
+
if uid && uid.start_with?("/")
|
182
|
+
uid = "#{url_start}#{uid}"
|
183
|
+
end
|
184
|
+
|
185
|
+
if url && url.start_with?("/")
|
186
|
+
url = "#{url_start}#{url}"
|
187
|
+
end
|
188
|
+
|
189
|
+
{
|
190
|
+
:author => self.author,
|
191
|
+
:content => self.content,
|
192
|
+
:summary => self.summary,
|
193
|
+
:displayName => self.display_name,
|
194
|
+
:id => uid,
|
195
|
+
:url => url,
|
196
|
+
:title => self.title,
|
197
|
+
:published => (self.published ? self.published.to_date.rfc3339 + 'Z' : nil),
|
198
|
+
:updated => (self.updated ? self.updated.to_date.rfc3339 + 'Z' : nil),
|
199
|
+
}
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns a list of Nelumba::Person's for those mentioned within the object.
|
203
|
+
#
|
204
|
+
# Requires a block that is given two arguments: the username and the domain
|
205
|
+
# that should return a Nelumba::Person that matches when a @username tag
|
206
|
+
# is found.
|
207
|
+
#
|
208
|
+
# Usage:
|
209
|
+
#
|
210
|
+
# note = Nelumba::Note.new(:text => "Hello @foo")
|
211
|
+
# note.mentions do |username, domain|
|
212
|
+
# i = identities.select {|e| e.username == username && e.domain == domain }.first
|
213
|
+
# i.author if i
|
214
|
+
# end
|
215
|
+
#
|
216
|
+
# With a persistence backend:
|
217
|
+
# note.mentions do |username, domain|
|
218
|
+
# i = Identity.first(:username => /^#{Regexp.escape(username)}$/i)
|
219
|
+
# i.author if i
|
220
|
+
# end
|
221
|
+
def mentions(&blk)
|
222
|
+
if self.respond_to? :text
|
223
|
+
out = self.text || ""
|
224
|
+
else
|
225
|
+
out = self.content || ""
|
226
|
+
end
|
227
|
+
|
228
|
+
out = CGI.escapeHTML(out)
|
229
|
+
|
230
|
+
# we let almost anything be in a username, except those that mess with urls.
|
231
|
+
# but you can't end in a .:;, or !
|
232
|
+
# also ignore container chars [] () "" '' {}
|
233
|
+
# XXX: the _correct_ solution will be to use an email validator
|
234
|
+
ret = []
|
235
|
+
out.scan(USERNAME_REGULAR_EXPRESSION) do |beginning, username, domain|
|
236
|
+
ret << blk.call(username, domain) if blk
|
237
|
+
end
|
238
|
+
|
239
|
+
ret
|
240
|
+
end
|
241
|
+
|
242
|
+
# Returns a string containing the JSON representation of this Object.
|
243
|
+
def to_json(*args)
|
244
|
+
to_json_hash.delete_if{|k,v| v.nil?}.to_json(*args)
|
245
|
+
end
|
246
|
+
|
247
|
+
def to_as1(*args)
|
248
|
+
to_json_hash.delete_if{|k,v| v.nil?}.to_json(*args)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|