Module: Nelumba::Discover

Defined in:
lib/nelumba/discover.rb

Constant Summary collapse

MIME_ORDER =

The order to respect atom links

['application/atom+xml',
'application/rss+xml',
'application/xml']

Class Method Summary collapse

Class Method Details

.activity(url) ⇒ Object



286
287
288
# File 'lib/nelumba/discover.rb', line 286

def self.activity(url)
  self.activity_from_url(url)
end

.activity_from_string(string, content_type = "application/atom+xml") ⇒ Object

Yield a Nelumba::Activity from the given string content.



309
310
311
312
313
314
315
316
# File 'lib/nelumba/discover.rb', line 309

def self.activity_from_string(string, content_type = "application/atom+xml")
  content_type ||= "application/atom+xml"

  case content_type
  when 'application/atom+xml', 'application/rss+xml', 'application/xml'
    Nelumba::Atom::Entry.new(XML::Reader.string(string)).to_canonical
  end
end

.activity_from_url(url, content_type = nil) ⇒ Object

Yield a Nelumba::Activity from the given url.



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/nelumba/discover.rb', line 291

def self.activity_from_url(url, content_type = nil)
  # Atom is default type to attempt to retrieve
  content_type ||= "application/atom+xml"

  response = Nelumba::Discover.pull_url(url, content_type)

  return nil unless response.is_a?(Net::HTTPSuccess)

  content_type = response.content_type

  case content_type
  when 'application/atom+xml', 'application/rss+xml', 'application/xml'
    xml_str = response.body
    self.entry_from_string(xml_str, response.content_type)
  end
end

.feed(url_or_identity, content_type = nil) ⇒ Object

Will yield a Nelumba::Feed object representing the feed at the given url or identity.

Usage:

feed = Nelumba::Discover.feed("https://rstat.us/users/wilkieii/feed")

i = Nelumba::Discover.identity("[email protected]")
feed = Nelumba::Discover.feed(i)


197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/nelumba/discover.rb', line 197

def self.feed(url_or_identity, content_type = nil)
  if url_or_identity =~ /^(?:acct:)?[^@]+@[^@]+\.[^@]+$/
    url_or_identity = Nelumba::Discover.identity(url_or_identity)
  end

  if url_or_identity.is_a? Nelumba::Identity
    return Nelumba::Discover.feed(url_or_identity.profile_page)
  end

  # Atom is default type to attempt to retrieve
  content_type ||= ["application/atom+xml", "text/html"]
  accept = content_type

  url = url_or_identity

  if url =~ /^http[s]?:\/\//
    # Url is an internet resource
    response = Nelumba::Discover.pull_url(url, accept)

    return nil unless response.is_a?(Net::HTTPSuccess)

    content_type = response.content_type
    str = response.body
  else
    str = open(url).read
  end

  case content_type
  when 'application/atom+xml', 'application/rss+xml', 'application/xml',
       'xml', 'atom', 'rss', 'atom+xml', 'rss+xml'
    xml_str = str

    self.feed_from_string(xml_str, content_type)
  when 'text/html'
    html_str = str

    # Discover the feed
    doc = Nokogiri::HTML::Document.parse(html_str)
    links = doc.xpath("//link[@rel='alternate']").map {|el|
      {:type => el.attributes['type'].to_s,
       :href => el.attributes['href'].to_s}
    }.select{|e|
      MIME_ORDER.include? e[:type]
    }.sort {|a, b|
      MIME_ORDER.index(a[:type]) <=>
      MIME_ORDER.index(b[:type])
    }

    return nil if links.empty?

    href = links.first[:href]
    if href.start_with? "/"
      # Append domain from what we know
      uri = URI::parse(url) rescue URI.new
      href = "#{uri.scheme}://#{uri.host}#{uri.port == 80 ? "" : ":#{uri.port}"}#{href}"
    end

    # Resolve relative links
    link = URI::parse(href) rescue URI.new

    unless link.scheme
      link.scheme = URI::parse(url).scheme
    end

    unless link.host
      link.host = URI::parse(url).host rescue nil
    end

    unless link.absolute?
      link.path = File::dirname(URI::parse(url).path) \
        + '/' + link.path rescue nil
    end

    url = link.to_s
    Nelumba::Discover.feed(url, links.first[:type])
  end
end

.feed_from_string(string, content_type = nil) ⇒ Object

Yield a Nelumba::Feed from the given string content.



276
277
278
279
280
281
282
283
284
# File 'lib/nelumba/discover.rb', line 276

def self.feed_from_string(string, content_type = nil)
  # Atom is default type to attempt to retrieve
  content_type ||= "application/atom+xml"

  case content_type
  when 'application/atom+xml', 'application/rss+xml', 'application/xml'
    Nelumba::Atom::Feed.new(XML::Reader.string(string)).to_canonical
  end
end

.identity(name) ⇒ Object

Will yield an OStatus::Identity for the given fully qualified name (i.e. “[email protected]”)



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/nelumba/discover.rb', line 14

def self.identity(name)
  xrd = nil

  if name.match /^https?:\/\//
    url = name
    type = 'text/html'
    response = Nelumba::Discover.pull_url(url, type)

    # Look at HTTP link headers
    if response["Link"]
      link = response["Link"]

      new_url = link[/^<([^>]+)>/,1]
      rel     = link[/;\s*rel\s*=\s*"([^"]+)"/,1]
      type    = link[/;\s*type\s*=\s*"([^"]+)"/,1]

      if new_url.start_with? "/"
        domain = url[/^(http[s]?:\/\/[^\/]+)\//,1]
        new_url = "#{domain}#{new_url}"
      end

      if rel == "lrdd"
        Nelumba::Discover.identity_from_xml(new_url)
      else
        nil
      end
    end
  elsif name.match /@/
    Nelumba::Discover.identity_from_webfinger(name)
  end
end

.identity_from_webfinger(acct) ⇒ Object

Retrieves a Nelumba::Identity from the given webfinger account.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/nelumba/discover.rb', line 47

def self.identity_from_webfinger(acct)
  # We allow a port, unusually. Some exaxmples:
  #
  # acct: 'acct:[email protected]:9292'
  #   or: 'acct:[email protected]'
  #   or: '[email protected]'

  # Remove acct: prefix if it exists
  acct.gsub!(/^acct\:/, "")

  # Get domain and port
  matches = acct.match /([^@]+)@([^:]+)(:\d+)?$/
  username = matches[1]
  domain   = matches[2]
  port     = matches[3] || "" # will include the ':'

  accept = ['application/xml+xrd',
            'application/xml',
            'text/html']

  # Pull .well-known/host-meta
  scheme = 'https'
  url = "#{scheme}://#{domain}#{port}/.well-known/host-meta"
  host_meta = Nelumba::Discover.pull_url(url, accept)

  if host_meta.nil?
    # TODO: Should we do this? probably not. ugh. but we must.
    scheme = 'http'
    url = "#{scheme}://#{domain}#{port}/.well-known/host-meta"
    host_meta = Nelumba::Discover.pull_url(url, accept)
  end

  return nil if host_meta.nil?

  # Read xrd template location
  host_meta = host_meta.body
  host_meta = Nokogiri::XML(host_meta)
  links = host_meta.xpath("/xmlns:XRD/xmlns:Link")
  link = links.select{|link| link.attr('rel') == 'lrdd' }.first
  lrdd_template = link.attr('template') || link.attr('href')

  xrd_url = lrdd_template.gsub(/{uri}/, "acct:#{username}")

  xrd = Nelumba::Discover.pull_url(xrd_url, accept)
  return nil if xrd.nil?

  xrd = xrd.body
  xrd = Nokogiri::XML(xrd)

  unless xrd
    # TODO: Error
    return nil
  end

  # magic-envelope public key
  public_key = find_link(xrd, 'magic-public-key') || ""
  public_key = public_key.split(",")[1] || ""

  # ostatus notification endpoint
  salmon_url = find_link(xrd, 'salmon')

  # pump.io authentication endpoint
  dialback_url = find_link(xrd, 'dialback')

  # pump.io activity endpoints
  activity_inbox_endpoint = find_link(xrd, 'activity-inbox')
  activity_outbox_endpoint = find_link(xrd, 'activity-outbox')

  # profile page
  profile_page = find_link(xrd, 'http://webfinger.net/rel/profile-page')

  Identity.new(:public_key               => public_key,
               :profile_page             => profile_page,
               :salmon_endpoint          => salmon_url,
               :dialback_endpoint        => dialback_url,
               :activity_inbox_endpoint  => activity_inbox_endpoint,
               :activity_outbox_endpoint => activity_outbox_endpoint)
end

.identity_from_xml(url, content_type = nil) ⇒ Object

Retrieves a Nelumba::Identity from the given xml. You specify the xml as a url, which will be retrieved and then parsed.



128
129
130
131
132
133
134
135
136
# File 'lib/nelumba/discover.rb', line 128

def self.identity_from_xml(url, content_type = nil)
  content_type ||= ['application/xml+xrd',
                    'application/xml']

  xml = Nelumba::Discover.pull_url(url, content_type)
  return nil if xml.nil?

  Nelumba::Discover.identity_from_xml_string(xml)
end

.identity_from_xml_string(xml) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/nelumba/discover.rb', line 138

def self.identity_from_xml_string(xml)
  xrd = Nokogiri::XML(xml)
  unless xrd
    # TODO: Error
    return nil
  end

  # magic-envelope public key
  public_key = find_link(xrd, 'magic-public-key')
  public_key = public_key.split(",")[1] || ""

  # ostatus notification endpoint
  salmon_url = find_link(xrd, 'salmon')

  # pump.io authentication endpoint
  dialback_url = find_link(xrd, 'dialback')

  # pump.io activity endpoints
  activity_inbox_endpoint = find_link(xrd, 'activity-inbox')
  activity_outbox_endpoint = find_link(xrd, 'activity-outbox')

  # profile page
  profile_page = find_link(xrd, 'http://webfinger.net/rel/profile-page')

  Identity.new(:public_key               => public_key,
               :profile_page             => profile_page,
               :salmon_endpoint          => salmon_url,
               :dialback_endpoint        => dialback_url,
               :activity_inbox_endpoint  => activity_inbox_endpoint,
               :activity_outbox_endpoint => activity_outbox_endpoint)
end

.person(identity) ⇒ Object

Will yield an Nelumba::Person for the given person.

identity: Can be a String containing a fully qualified name (i.e. “[email protected]”) or a previously resolved Nelumba::Identity.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/nelumba/discover.rb', line 174

def self.person(identity)
  if identity.is_a? String
    identity = Nelumba::Discover.identity(identity)
  end

  return nil if identity.nil? || identity.profile_page.nil?

  # Discover Person information

  # Pull profile page
  # Look for a feed to pull
  feed = Nelumba::Discover.feed(identity.profile_page)
  feed.authors.first
end