webmention 9.1.0 → 10.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.
- checksums.yaml +4 -4
- data/.yardopts +6 -0
- data/CONTRIBUTING.md +2 -2
- data/README.md +3 -3
- data/USAGE.md +2 -4
- data/lib/webmention/client.rb +25 -42
- data/lib/webmention/error_response.rb +19 -8
- data/lib/webmention/request.rb +21 -36
- data/lib/webmention/response.rb +57 -10
- data/lib/webmention/url.rb +7 -9
- data/lib/webmention/verification.rb +16 -46
- data/lib/webmention.rb +16 -25
- data/webmention.gemspec +3 -2
- metadata +20 -9
- data/lib/webmention/parser.rb +0 -32
- data/lib/webmention/parsers/html_parser.rb +0 -73
- data/lib/webmention/parsers/json_parser.rb +0 -31
- data/lib/webmention/parsers/plaintext_parser.rb +0 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 04d6dfabcd47d82e0191f80b7dbbf7b0c78c29e019c1994514f3ac4eb96d025a
|
|
4
|
+
data.tar.gz: a340c6d249f9e16f453be9f1346518268dc2c66f2f85568c1bd8ae15158e5b12
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 22d043cdb6500397afc4c5c9b52464ff5026937883fac4b272e277fbb6c208b1856254604b4effd60c87d94c7621550bf0f6103fd6593a6214b51e7f24317b0f
|
|
7
|
+
data.tar.gz: 7a71592e478ab9322c622115ec4327516a472cff31136b48b5b531b5eddc6bc193e5369964246583d9e25a1654f29f78e42e9504014c14937c09884a19abc8ee
|
data/.yardopts
ADDED
data/CONTRIBUTING.md
CHANGED
|
@@ -8,12 +8,12 @@ There are a couple ways you can help improve webmention-client-ruby:
|
|
|
8
8
|
|
|
9
9
|
## Getting Started
|
|
10
10
|
|
|
11
|
-
webmention-client-ruby is developed using Ruby
|
|
11
|
+
webmention-client-ruby is developed using Ruby 4.0 and is tested against additional Ruby versions using [GitHub Actions](https://github.com/indieweb/webmention-client-ruby/actions).
|
|
12
12
|
|
|
13
13
|
> [!TIP]
|
|
14
14
|
> This project is configured with a [Dev Container](https://containers.dev) which includes everything you'd need to contribute to webmention-client-ruby. If you use a supported code editor or IDE, you're encouraged to use the existing Dev Container setup.
|
|
15
15
|
|
|
16
|
-
Before making changes to webmention-client-ruby, you'll want to install Ruby
|
|
16
|
+
Before making changes to webmention-client-ruby, you'll want to install Ruby 4.0. Using a Ruby version managment tool like [rv](https://github.com/spinel-coop/rv) or [rbenv](https://github.com/rbenv/rbenv) is recommended. Once you've installed Ruby 4.0 using your method of choice, install the project's gems by running:
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
19
|
bundle install
|
data/README.md
CHANGED
|
@@ -16,9 +16,9 @@ A Ruby gem for sending and verifying [Webmention](https://indieweb.org/Webmentio
|
|
|
16
16
|
|
|
17
17
|
## Getting Started
|
|
18
18
|
|
|
19
|
-
Before installing and using webmention-client-ruby, you'll want to have [Ruby](https://www.ruby-lang.org) 2.7 (or newer) installed. Using a Ruby version managment tool like [
|
|
19
|
+
Before installing and using webmention-client-ruby, you'll want to have [Ruby](https://www.ruby-lang.org) 2.7 (or newer) installed. Using a Ruby version managment tool like [rv](https://github.com/spinel-coop/rv) or [rbenv](https://github.com/rbenv/rbenv) is recommended.
|
|
20
20
|
|
|
21
|
-
webmention-client-ruby is developed using Ruby
|
|
21
|
+
webmention-client-ruby is developed using Ruby 4.0 and is tested against additional Ruby versions using [GitHub Actions](https://github.com/indieweb/webmention-client-ruby/actions).
|
|
22
22
|
|
|
23
23
|
## Installation
|
|
24
24
|
|
|
@@ -36,7 +36,7 @@ See [USAGE.md](USAGE.md) for documentation of webmention-client-ruby's features.
|
|
|
36
36
|
|
|
37
37
|
## Contributing
|
|
38
38
|
|
|
39
|
-
See [CONTRIBUTING.md](
|
|
39
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for more on how to contribute to webmention-client-ruby. Your help is greatly appreciated!
|
|
40
40
|
|
|
41
41
|
By contributing to and participating in the development of webmention-client-ruby, you acknowledge that you have read and agree to the [IndieWeb Code of Conduct](https://indieweb.org/code-of-conduct).
|
|
42
42
|
|
data/USAGE.md
CHANGED
|
@@ -29,7 +29,7 @@ response.class
|
|
|
29
29
|
#=> Webmention::ErrorResponse
|
|
30
30
|
|
|
31
31
|
response.message
|
|
32
|
-
#=> "No
|
|
32
|
+
#=> "No Webmention endpoint found for target URL https://aaronpk.example/post/100"
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
A `Webmention::Response` will be returned in all other cases.
|
|
@@ -147,6 +147,4 @@ verification.vouch_mentions_source?
|
|
|
147
147
|
|
|
148
148
|
## Exception Handling
|
|
149
149
|
|
|
150
|
-
webmention-client-ruby avoids raising exceptions when making HTTP requests. As noted above, a `Webmention::ErrorResponse` should be returned in cases where an HTTP request triggers an exception.
|
|
151
|
-
|
|
152
|
-
When crawling the supplied URL, `Webmention.mentioned_urls` _may_ raise a `NoMethodError` if a `Webmention::ErrorResponse` is returned, or the response is of an unsupported MIME type.
|
|
150
|
+
webmention-client-ruby avoids raising exceptions when making HTTP requests. As noted above, a `Webmention::ErrorResponse` should be returned in cases where an HTTP request triggers an exception. `Webmention.mentioned_urls` will raise a `Burly::UnsupportedMimeType` exception when the response is of an unsupported MIME type, though.
|
data/lib/webmention/client.rb
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Webmention
|
|
4
|
+
# A client interface to send and verify Webmention notifications.
|
|
4
5
|
class Client
|
|
5
|
-
@registered_parsers = {}
|
|
6
|
-
|
|
7
|
-
class << self
|
|
8
|
-
# @api private
|
|
9
|
-
attr_reader :registered_parsers
|
|
10
|
-
end
|
|
11
|
-
|
|
12
6
|
# @return [Url]
|
|
13
7
|
attr_reader :source_url
|
|
14
8
|
|
|
15
9
|
# @return [Url]
|
|
16
10
|
attr_reader :vouch_url
|
|
17
11
|
|
|
18
|
-
# @api private
|
|
19
|
-
def self.register_parser(klass)
|
|
20
|
-
klass.mime_types.each { |mime_type| @registered_parsers[mime_type] = klass }
|
|
21
|
-
end
|
|
22
|
-
|
|
23
12
|
# Create a new {Client}.
|
|
24
13
|
#
|
|
25
14
|
# @example
|
|
@@ -28,52 +17,47 @@ module Webmention
|
|
|
28
17
|
# @example
|
|
29
18
|
# Webmention::Client.new("https://jgarber.example/posts/100", vouch: "https://tantek.example/notes/1")
|
|
30
19
|
#
|
|
20
|
+
# @see https://indieweb.org/Vouch
|
|
21
|
+
#
|
|
31
22
|
# @param source [String, HTTP::URI, #to_s]
|
|
32
23
|
# An absolute URL representing a source document.
|
|
33
24
|
# @param vouch [String, HTTP::URI, #to_s]
|
|
34
25
|
# An absolute URL representing a document vouching for the source document.
|
|
35
|
-
# See https://indieweb.org/Vouch for additional details.
|
|
36
26
|
def initialize(source, vouch: nil)
|
|
37
27
|
@source_url = Url.new(source)
|
|
38
28
|
@vouch_url = Url.new(vouch)
|
|
39
29
|
end
|
|
40
30
|
|
|
41
|
-
#
|
|
42
|
-
#
|
|
31
|
+
# @!visibility private
|
|
32
|
+
# simplecov:disable
|
|
43
33
|
def inspect
|
|
44
34
|
"#<#{self.class}:#{format("%#0x", object_id)} " \
|
|
45
35
|
"source_url: #{source_url} " \
|
|
46
36
|
"vouch_url: #{vouch_url}>"
|
|
47
37
|
end
|
|
48
|
-
# :
|
|
38
|
+
# simplecov:enable
|
|
49
39
|
|
|
50
|
-
#
|
|
40
|
+
# Unique URLs mentioned by {source_url}.
|
|
51
41
|
#
|
|
52
42
|
# @example
|
|
53
43
|
# client = Webmention::Client.new("https://jgarber.example/posts/100")
|
|
54
44
|
# client.mentioned_urls
|
|
55
45
|
#
|
|
56
|
-
# @raise [
|
|
57
|
-
# Raised when response is an
|
|
58
|
-
# unsupported MIME type.
|
|
46
|
+
# @raise [Burly::UnsupportedMimeType]
|
|
47
|
+
# Raised when response is of an unsupported MIME type.
|
|
59
48
|
#
|
|
60
49
|
# @return [Array<String>]
|
|
61
50
|
def mentioned_urls
|
|
62
51
|
response = source_url.response
|
|
63
52
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
urls.reject! { |url| url.match(/^#{response.uri}(?:#.*)?$/) }
|
|
72
|
-
|
|
73
|
-
urls.to_a.sort
|
|
53
|
+
response
|
|
54
|
+
.mentioned_urls
|
|
55
|
+
.lazy
|
|
56
|
+
.reject { |url| url.match(/^#{response.uri}(?:#.*)?$/) }
|
|
57
|
+
.sort
|
|
74
58
|
end
|
|
75
59
|
|
|
76
|
-
# Send a webmention from this client's
|
|
60
|
+
# Send a webmention from this client's {source_url} to a target URL.
|
|
77
61
|
#
|
|
78
62
|
# @example
|
|
79
63
|
# client = Webmention::Client.new("https://jgarber.example/posts/100")
|
|
@@ -85,20 +69,20 @@ module Webmention
|
|
|
85
69
|
# @return [Response, ErrorResponse]
|
|
86
70
|
def send_webmention(target)
|
|
87
71
|
target_url = Url.new(target)
|
|
72
|
+
endpoint = target_url.webmention_endpoint
|
|
73
|
+
response = target_url.response
|
|
88
74
|
|
|
89
75
|
# A Webmention endpoint exists. Send the request and return the response.
|
|
90
|
-
|
|
91
|
-
return Request.post(target_url.webmention_endpoint, **request_options_for(target))
|
|
92
|
-
end
|
|
76
|
+
return Request.new(:post, endpoint, **request_options_for(target)).perform unless endpoint.nil?
|
|
93
77
|
|
|
94
78
|
# An error was encountered fetching the target URL. Return the response.
|
|
95
|
-
return
|
|
79
|
+
return response unless response.ok?
|
|
96
80
|
|
|
97
81
|
# No Webmention endpoint exists. Return a new ErrorResponse.
|
|
98
|
-
ErrorResponse.new("No
|
|
82
|
+
ErrorResponse.new("No Webmention endpoint found for target URL #{target}", response.request)
|
|
99
83
|
end
|
|
100
84
|
|
|
101
|
-
# Send webmentions from
|
|
85
|
+
# Send webmentions from {source_url} to multiple target URLs.
|
|
102
86
|
#
|
|
103
87
|
# @example
|
|
104
88
|
# client = Webmention::Client.new("https://jgarber.example/posts/100")
|
|
@@ -113,10 +97,9 @@ module Webmention
|
|
|
113
97
|
targets.map { |target| send_webmention(target) }
|
|
114
98
|
end
|
|
115
99
|
|
|
116
|
-
# Verify that
|
|
100
|
+
# Verify that {source_url} links to a target URL.
|
|
117
101
|
#
|
|
118
|
-
# @param target
|
|
119
|
-
# An absolute URL representing a target document.
|
|
102
|
+
# @param target (see #send_webmention)
|
|
120
103
|
#
|
|
121
104
|
# @raise (see Client#mentioned_urls)
|
|
122
105
|
#
|
|
@@ -127,7 +110,7 @@ module Webmention
|
|
|
127
110
|
|
|
128
111
|
private
|
|
129
112
|
|
|
130
|
-
# @param target
|
|
113
|
+
# @param target (see #send_webmention)
|
|
131
114
|
#
|
|
132
115
|
# @return [Hash{Symbol => String}]
|
|
133
116
|
def request_options_for(target)
|
|
@@ -140,7 +123,7 @@ module Webmention
|
|
|
140
123
|
opts.transform_values! { |value| value.to_s.strip }
|
|
141
124
|
opts.reject! { |_, value| value.empty? }
|
|
142
125
|
|
|
143
|
-
opts
|
|
126
|
+
{ form: opts }
|
|
144
127
|
end
|
|
145
128
|
end
|
|
146
129
|
end
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Webmention
|
|
4
|
+
# An interface to instances of the {Request} class.
|
|
5
|
+
#
|
|
6
|
+
# An {ErrorResponse} represents {Request}s that generated errors (e.g.
|
|
7
|
+
# connection error, SSL error) or that could not locate a Webmention endpoint.
|
|
8
|
+
# The nature of the error is captured in the {#message} instance method.
|
|
4
9
|
class ErrorResponse
|
|
5
10
|
# @return [String]
|
|
6
11
|
attr_reader :message
|
|
@@ -10,10 +15,7 @@ module Webmention
|
|
|
10
15
|
|
|
11
16
|
# Create a new {ErrorResponse}.
|
|
12
17
|
#
|
|
13
|
-
#
|
|
14
|
-
# (e.g. connection error, SSL error) or that could not locate a Webmention
|
|
15
|
-
# endpoint. The nature of the error is captured in the {#message} instance
|
|
16
|
-
# method.
|
|
18
|
+
# @api private
|
|
17
19
|
#
|
|
18
20
|
# @param message [String]
|
|
19
21
|
# @param request [Request]
|
|
@@ -22,15 +24,24 @@ module Webmention
|
|
|
22
24
|
@request = request
|
|
23
25
|
end
|
|
24
26
|
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
+
# @!visibility private
|
|
28
|
+
# simplecov:disable
|
|
27
29
|
def inspect
|
|
28
30
|
"#<#{self.class}:#{format("%#0x", object_id)} " \
|
|
29
31
|
"message: #{message}>"
|
|
30
32
|
end
|
|
31
|
-
# :
|
|
33
|
+
# simplecov:enable
|
|
32
34
|
|
|
33
|
-
#
|
|
35
|
+
# An {ErrorResponse} has no mentioned URLs.
|
|
36
|
+
#
|
|
37
|
+
# @return [Set]
|
|
38
|
+
def mentioned_urls
|
|
39
|
+
Set.new
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# An {ErrorResponse} is never okay!
|
|
43
|
+
#
|
|
44
|
+
# @return [false]
|
|
34
45
|
def ok?
|
|
35
46
|
false
|
|
36
47
|
end
|
data/lib/webmention/request.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Webmention
|
|
4
|
+
# An interface for executing HTTP requests and generating {Response}s and
|
|
5
|
+
# {ErrorResponse}s.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
4
8
|
class Request
|
|
5
9
|
# Set defaults derived from Webmention specification examples.
|
|
6
10
|
#
|
|
@@ -31,64 +35,45 @@ module Webmention
|
|
|
31
35
|
# @return [Hash]
|
|
32
36
|
attr_reader :options
|
|
33
37
|
|
|
34
|
-
#
|
|
38
|
+
# Create a new {Request}.
|
|
35
39
|
#
|
|
36
40
|
# @example
|
|
37
|
-
# Webmention::Request.get
|
|
38
|
-
#
|
|
39
|
-
# @param url [String]
|
|
40
|
-
#
|
|
41
|
-
# @return [Response, ErrorResponse]
|
|
42
|
-
def self.get(url)
|
|
43
|
-
new(:get, url).perform
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Send an HTTP POST request with form-encoded data to the supplied URL.
|
|
41
|
+
# Webmention::Request.new(:get, "https://jgarber.example/posts/100")
|
|
47
42
|
#
|
|
48
43
|
# @example
|
|
49
|
-
# Webmention::Request.
|
|
44
|
+
# Webmention::Request.new(
|
|
45
|
+
# :post,
|
|
50
46
|
# "https://aaronpk.example/webmention",
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
47
|
+
# form: {
|
|
48
|
+
# source: "https://jgarber.examples/posts/100",
|
|
49
|
+
# target: "https://aaronpk.example/notes/1",
|
|
50
|
+
# vouch: "https://tantek.example/notes/1",
|
|
51
|
+
# },
|
|
54
52
|
# )
|
|
55
53
|
#
|
|
56
|
-
# @
|
|
57
|
-
# @param options [Hash{Symbol => String}]
|
|
58
|
-
# @option options [String] :source
|
|
59
|
-
# An absolute URL representing a source document.
|
|
60
|
-
# @option options [String] :target
|
|
61
|
-
# An absolute URL representing a target document.
|
|
62
|
-
# @option options [String] :vouch
|
|
63
|
-
# An absolute URL representing a document vouching for the source document.
|
|
64
|
-
# See https://indieweb.org/Vouch for additional details.
|
|
65
|
-
#
|
|
66
|
-
# @return [Response, ErrorResponse]
|
|
67
|
-
def self.post(url, **options)
|
|
68
|
-
new(:post, url, form: options.slice(:source, :target, :vouch)).perform
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Create a new {Request}.
|
|
54
|
+
# @see https://indieweb.org/Vouch
|
|
72
55
|
#
|
|
73
56
|
# @param method [Symbol]
|
|
74
57
|
# @param url [String]
|
|
75
|
-
# @param options [Hash{Symbol => String}]
|
|
58
|
+
# @param options [Hash{Symbol => Hash{Symbol => String}}]
|
|
59
|
+
# @option options [Hash{Symbol => String}] :form
|
|
60
|
+
# A Hash of options to serialize as form-encoded.
|
|
76
61
|
def initialize(method, url, **options)
|
|
77
62
|
@method = method.to_sym
|
|
78
63
|
@uri = HTTP::URI.parse(url.to_s)
|
|
79
64
|
@options = options
|
|
80
65
|
end
|
|
81
66
|
|
|
82
|
-
#
|
|
83
|
-
#
|
|
67
|
+
# @!visibility private
|
|
68
|
+
# simplecov:disable
|
|
84
69
|
def inspect
|
|
85
70
|
"#<#{self.class}:#{format("%#0x", object_id)} " \
|
|
86
71
|
"method: #{method.upcase}, " \
|
|
87
72
|
"url: #{uri}>"
|
|
88
73
|
end
|
|
89
|
-
# :
|
|
74
|
+
# simplecov:enable
|
|
90
75
|
|
|
91
|
-
#
|
|
76
|
+
# Execute the {Request}.
|
|
92
77
|
#
|
|
93
78
|
# @return [Response, ErrorResponse]
|
|
94
79
|
def perform
|
data/lib/webmention/response.rb
CHANGED
|
@@ -1,33 +1,50 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Webmention
|
|
4
|
+
# An interface to instances of the {Request} and `HTTP::Response` classes.
|
|
5
|
+
#
|
|
6
|
+
# A {Response} represents the results of a successful {Request}.
|
|
4
7
|
class Response
|
|
5
8
|
extend Forwardable
|
|
6
9
|
|
|
10
|
+
# Consider the first h-entry to be the authoritative content container when
|
|
11
|
+
# parsing a document for mentioned URLs. Fall back to the `<body>` element
|
|
12
|
+
# when no h-entry is found.
|
|
13
|
+
#
|
|
14
|
+
# @see http://microformats.org/wiki/h-entry
|
|
15
|
+
PARSE_CONTEXT = [".h-entry .e-content", ".h-entry", "body"].freeze
|
|
16
|
+
|
|
17
|
+
URI_REGEXP = URI::RFC2396_Parser.new.make_regexp(["http", "https"]).freeze
|
|
18
|
+
|
|
19
|
+
public_constant :PARSE_CONTEXT
|
|
20
|
+
private_constant :URI_REGEXP
|
|
21
|
+
|
|
22
|
+
# The {Request} associated with this {Response}.
|
|
23
|
+
#
|
|
7
24
|
# @return [Request]
|
|
8
25
|
attr_reader :request
|
|
9
26
|
|
|
10
|
-
# @!method
|
|
27
|
+
# @!method headers
|
|
11
28
|
# @return [HTTP::Headers]
|
|
12
29
|
def_delegator :@response, :headers
|
|
13
30
|
|
|
14
|
-
# @!method
|
|
31
|
+
# @!method body
|
|
15
32
|
# @return [HTTP::Response::Body]
|
|
16
33
|
def_delegator :@response, :body
|
|
17
34
|
|
|
18
|
-
# @!method
|
|
35
|
+
# @!method code
|
|
19
36
|
# @return [Integer]
|
|
20
37
|
def_delegator :@response, :code
|
|
21
38
|
|
|
22
|
-
# @!method
|
|
39
|
+
# @!method reason
|
|
23
40
|
# @return [String]
|
|
24
41
|
def_delegator :@response, :reason
|
|
25
42
|
|
|
26
|
-
#
|
|
43
|
+
# @!method mime_type
|
|
27
44
|
# @return [String]
|
|
28
45
|
def_delegator :@response, :mime_type
|
|
29
46
|
|
|
30
|
-
#
|
|
47
|
+
# @!method uri
|
|
31
48
|
# @return [HTTP::URI]
|
|
32
49
|
def_delegator :@response, :uri
|
|
33
50
|
|
|
@@ -37,6 +54,8 @@ module Webmention
|
|
|
37
54
|
# of which may be accessed using the delegated {#code} and {#reason}
|
|
38
55
|
# instance methods.
|
|
39
56
|
#
|
|
57
|
+
# @api private
|
|
58
|
+
#
|
|
40
59
|
# @param response [HTTP::Response]
|
|
41
60
|
# @param request [Request]
|
|
42
61
|
def initialize(response, request)
|
|
@@ -44,17 +63,45 @@ module Webmention
|
|
|
44
63
|
@request = request
|
|
45
64
|
end
|
|
46
65
|
|
|
47
|
-
#
|
|
48
|
-
#
|
|
66
|
+
# @!visibility private
|
|
67
|
+
# simplecov:disable
|
|
49
68
|
def inspect
|
|
50
69
|
"#<#{self.class}:#{format("%#0x", object_id)} " \
|
|
51
70
|
"code: #{code.inspect}, " \
|
|
52
71
|
"reason: #{reason}, " \
|
|
53
72
|
"url: #{request.uri}>"
|
|
54
73
|
end
|
|
55
|
-
# :
|
|
74
|
+
# simplecov:enable
|
|
56
75
|
|
|
57
|
-
#
|
|
76
|
+
# Unique host names parsed from the response body.
|
|
77
|
+
#
|
|
78
|
+
# The returned `Set` contains the unique host names ("domain names") derived
|
|
79
|
+
# from the `Set` of {mentioned_urls}. Order is not guaranteed.
|
|
80
|
+
#
|
|
81
|
+
# @return [Set<String>]
|
|
82
|
+
def mentioned_hosts
|
|
83
|
+
@mentioned_hosts ||= Set.new(mentioned_urls.map { |url| HTTP::URI.parse(url).host })
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Unique mentioned URLs parsed from the response body.
|
|
87
|
+
#
|
|
88
|
+
# The returned `Set` contains the unique URLs parsed from the
|
|
89
|
+
# {PARSE_CONTEXT}. Order is not guaranteed.
|
|
90
|
+
#
|
|
91
|
+
# @return [Set<String>]
|
|
92
|
+
def mentioned_urls
|
|
93
|
+
@mentioned_urls ||=
|
|
94
|
+
Set.new(
|
|
95
|
+
Burly
|
|
96
|
+
.parse(@response.body, context: PARSE_CONTEXT, mime_type: @response.mime_type)
|
|
97
|
+
.map { |url| @response.uri.join(url).to_s }
|
|
98
|
+
.grep(URI_REGEXP)
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# A {Response} is always okay!
|
|
103
|
+
#
|
|
104
|
+
# @return [true]
|
|
58
105
|
def ok?
|
|
59
106
|
true
|
|
60
107
|
end
|
data/lib/webmention/url.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Webmention
|
|
4
|
+
# An interface representing a parsed URL and its {Response}.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
4
7
|
class Url
|
|
5
8
|
extend Forwardable
|
|
6
9
|
|
|
@@ -18,27 +21,22 @@ module Webmention
|
|
|
18
21
|
@uri = HTTP::URI.parse(url.to_s)
|
|
19
22
|
end
|
|
20
23
|
|
|
21
|
-
#
|
|
22
|
-
#
|
|
24
|
+
# @!visibility private
|
|
25
|
+
# simplecov:disable
|
|
23
26
|
def inspect
|
|
24
27
|
"#<#{self.class}:#{format("%#0x", object_id)} " \
|
|
25
28
|
"uri: #{uri}>"
|
|
26
29
|
end
|
|
27
|
-
# :
|
|
30
|
+
# simplecov:enable
|
|
28
31
|
|
|
29
32
|
# @return [Response, ErrorResponse]
|
|
30
33
|
def response
|
|
31
|
-
@response ||= Request.get
|
|
34
|
+
@response ||= Request.new(:get, uri).perform
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
# @return [String, nil]
|
|
35
38
|
def webmention_endpoint
|
|
36
39
|
@webmention_endpoint ||= IndieWeb::Endpoints::Parser.new(response).to_h[:webmention] if response.ok?
|
|
37
40
|
end
|
|
38
|
-
|
|
39
|
-
# @return [Boolean]
|
|
40
|
-
def webmention_endpoint?
|
|
41
|
-
!webmention_endpoint.nil?
|
|
42
|
-
end
|
|
43
41
|
end
|
|
44
42
|
end
|
|
@@ -1,86 +1,56 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Webmention
|
|
4
|
+
# Verify that a source URL links to a target URL.
|
|
4
5
|
class Verification
|
|
5
6
|
# Create a new {Verification}.
|
|
6
7
|
#
|
|
8
|
+
# @api private
|
|
9
|
+
#
|
|
10
|
+
# @see https://indieweb.org/Vouch
|
|
11
|
+
#
|
|
7
12
|
# @param source_url [Url]
|
|
8
13
|
# An {URL} representing a source document.
|
|
9
14
|
# @param target_url [Url]
|
|
10
15
|
# An {URL} representing a target document.
|
|
11
16
|
# @param vouch_url [Url]
|
|
12
17
|
# An {URL} representing a document vouching for the source document.
|
|
13
|
-
# See https://indieweb.org/Vouch for additional details.
|
|
14
18
|
def initialize(source_url, target_url, vouch_url: nil)
|
|
15
19
|
@source_url = source_url
|
|
16
20
|
@target_url = target_url
|
|
17
21
|
@vouch_url = vouch_url
|
|
18
22
|
end
|
|
19
23
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
24
|
+
# @!visibility private
|
|
25
|
+
# simplecov:disable
|
|
22
26
|
def inspect
|
|
23
27
|
"#<#{self.class}:#{format("%#0x", object_id)} " \
|
|
24
|
-
"source_url: #{source_url} " \
|
|
25
|
-
"target_url: #{target_url} " \
|
|
26
|
-
"vouch_url: #{vouch_url}>"
|
|
28
|
+
"source_url: #{@source_url} " \
|
|
29
|
+
"target_url: #{@target_url} " \
|
|
30
|
+
"vouch_url: #{@vouch_url}>"
|
|
27
31
|
end
|
|
28
|
-
# :
|
|
32
|
+
# simplecov:enable
|
|
29
33
|
|
|
30
34
|
# @return [Boolean]
|
|
31
35
|
def source_mentions_target?
|
|
32
|
-
@source_mentions_target ||=
|
|
36
|
+
@source_mentions_target ||= @source_url.response.mentioned_urls.any?(@target_url.to_s)
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
# @return [Boolean]
|
|
36
40
|
def verified?
|
|
37
|
-
return source_mentions_target?
|
|
41
|
+
return source_mentions_target? && vouch_mentions_source? if verify_vouch?
|
|
38
42
|
|
|
39
|
-
source_mentions_target?
|
|
43
|
+
source_mentions_target?
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
# @return [Boolean]
|
|
43
47
|
def verify_vouch?
|
|
44
|
-
|
|
48
|
+
!@vouch_url.to_s.strip.empty?
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
# @return [Boolean]
|
|
48
52
|
def vouch_mentions_source?
|
|
49
|
-
@vouch_mentions_source ||=
|
|
50
|
-
verify_vouch? && mentioned_domains(vouch_url.response).any?(source_url.uri.host)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
private
|
|
54
|
-
|
|
55
|
-
# @return [Url]
|
|
56
|
-
attr_reader :source_url
|
|
57
|
-
|
|
58
|
-
# @return [Url]
|
|
59
|
-
attr_reader :target_url
|
|
60
|
-
|
|
61
|
-
# @return [Url]
|
|
62
|
-
attr_reader :vouch_url
|
|
63
|
-
|
|
64
|
-
# @param response [Response]
|
|
65
|
-
#
|
|
66
|
-
# @raise (see Client#mentioned_urls)
|
|
67
|
-
#
|
|
68
|
-
# @return [Array<String>]
|
|
69
|
-
def mentioned_domains(response)
|
|
70
|
-
Set.new(mentioned_urls(response).map { |url| HTTP::URI.parse(url).host }).to_a
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# @param response [Response]
|
|
74
|
-
#
|
|
75
|
-
# @raise (see Client#mentioned_urls)
|
|
76
|
-
#
|
|
77
|
-
# @return [Array<String>]
|
|
78
|
-
def mentioned_urls(response)
|
|
79
|
-
Set.new(
|
|
80
|
-
Client.registered_parsers[response.mime_type]
|
|
81
|
-
.new(response.body, response.uri)
|
|
82
|
-
.results
|
|
83
|
-
).to_a
|
|
53
|
+
@vouch_mentions_source ||= verify_vouch? && @vouch_url.response.mentioned_hosts.any?(@source_url.uri.host)
|
|
84
54
|
end
|
|
85
55
|
end
|
|
86
56
|
end
|
data/lib/webmention.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
|
|
5
|
+
require "burly"
|
|
5
6
|
require "http"
|
|
6
7
|
require "indieweb/endpoints"
|
|
7
8
|
require "nokogiri"
|
|
@@ -13,11 +14,9 @@ require_relative "webmention/response"
|
|
|
13
14
|
require_relative "webmention/error_response"
|
|
14
15
|
require_relative "webmention/verification"
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
require_relative "webmention/parsers/plaintext_parser"
|
|
20
|
-
|
|
17
|
+
# Send and verify Webmention notifications.
|
|
18
|
+
#
|
|
19
|
+
# @see https://indieweb.org/Webmention
|
|
21
20
|
module Webmention
|
|
22
21
|
# Retrieve unique URLs mentioned by the provided URL.
|
|
23
22
|
#
|
|
@@ -26,11 +25,9 @@ module Webmention
|
|
|
26
25
|
#
|
|
27
26
|
# @param url [String, HTTP::URI, #to_s] An absolute URL.
|
|
28
27
|
#
|
|
29
|
-
# @raise
|
|
30
|
-
# Raised when response is an {ErrorResponse} or response is of an
|
|
31
|
-
# unsupported MIME type.
|
|
28
|
+
# @raise (see Client#mentioned_urls)
|
|
32
29
|
#
|
|
33
|
-
# @return
|
|
30
|
+
# @return (see Client#mentioned_urls)
|
|
34
31
|
def self.mentioned_urls(url)
|
|
35
32
|
Client.new(url).mentioned_urls
|
|
36
33
|
end
|
|
@@ -47,15 +44,13 @@ module Webmention
|
|
|
47
44
|
# target = "https://aaronpk.example/notes/1"
|
|
48
45
|
# Webmention.send_webmention(source, target, vouch: "https://tantek.example/notes/1")
|
|
49
46
|
#
|
|
50
|
-
# @
|
|
51
|
-
# An absolute URL representing a source document.
|
|
52
|
-
# @param target [String, HTTP::URI, #to_s]
|
|
53
|
-
# An absolute URL representing a target document.
|
|
54
|
-
# @param vouch [String, HTTP::URI, #to_s]
|
|
55
|
-
# An absolute URL representing a document vouching for the source document.
|
|
56
|
-
# See https://indieweb.org/Vouch for additional details.
|
|
47
|
+
# @see https://indieweb.org/Vouch
|
|
57
48
|
#
|
|
58
|
-
# @
|
|
49
|
+
# @param source (see Client#initialize)
|
|
50
|
+
# @param target (see Client#send_webmention)
|
|
51
|
+
# @param vouch (see Client#initialize)
|
|
52
|
+
#
|
|
53
|
+
# @return (see Client#send_webmention)
|
|
59
54
|
def self.send_webmention(source, target, vouch: nil)
|
|
60
55
|
Client.new(source, vouch: vouch).send_webmention(target)
|
|
61
56
|
end
|
|
@@ -72,13 +67,9 @@ module Webmention
|
|
|
72
67
|
# targets = ["https://aaronpk.example/notes/1", "https://adactio.example/notes/1"]
|
|
73
68
|
# Webmention.send_webmentions(source, targets, vouch: "https://tantek.example/notes/1")
|
|
74
69
|
#
|
|
75
|
-
# @
|
|
76
|
-
#
|
|
77
|
-
# @param
|
|
78
|
-
# An array of absolute URLs representing multiple target documents.
|
|
79
|
-
# @param vouch [String, HTTP::URI, #to_s]
|
|
80
|
-
# An absolute URL representing a document vouching for the source document.
|
|
81
|
-
# See https://indieweb.org/Vouch for additional details.
|
|
70
|
+
# @see https://indieweb.org/Vouch
|
|
71
|
+
#
|
|
72
|
+
# @param (see .send_webmention)
|
|
82
73
|
#
|
|
83
74
|
# @return [Array<Response, ErrorResponse>]
|
|
84
75
|
def self.send_webmentions(source, *targets, vouch: nil)
|
|
@@ -87,7 +78,7 @@ module Webmention
|
|
|
87
78
|
|
|
88
79
|
# Verify that a source URL links to a target URL.
|
|
89
80
|
#
|
|
90
|
-
# @param (see
|
|
81
|
+
# @param (see .send_webmention)
|
|
91
82
|
#
|
|
92
83
|
# @raise (see Client#mentioned_urls)
|
|
93
84
|
#
|
data/webmention.gemspec
CHANGED
|
@@ -4,7 +4,7 @@ Gem::Specification.new do |spec|
|
|
|
4
4
|
spec.required_ruby_version = ">= 2.7"
|
|
5
5
|
|
|
6
6
|
spec.name = "webmention"
|
|
7
|
-
spec.version = "
|
|
7
|
+
spec.version = "10.0.1"
|
|
8
8
|
spec.authors = ["Jason Garber"]
|
|
9
9
|
spec.email = ["jason@sixtwothree.org"]
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
|
14
14
|
spec.license = "Apache-2.0"
|
|
15
15
|
|
|
16
16
|
spec.files = Dir["lib/**/*"].reject { |f| File.directory?(f) }
|
|
17
|
-
spec.files += ["LICENSE", "CHANGELOG.md", "CONTRIBUTING.md", "README.md", "USAGE.md"]
|
|
17
|
+
spec.files += [".yardopts", "LICENSE", "CHANGELOG.md", "CONTRIBUTING.md", "README.md", "USAGE.md"]
|
|
18
18
|
spec.files += ["webmention.gemspec"]
|
|
19
19
|
|
|
20
20
|
spec.require_paths = ["lib"]
|
|
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
|
|
|
28
28
|
"source_code_uri" => "#{spec.homepage}/tree/v#{spec.version}",
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
spec.add_dependency "burly", "~> 0.4"
|
|
31
32
|
spec.add_dependency "http", ">= 5.3"
|
|
32
33
|
spec.add_dependency "indieweb-endpoints", "~> 10.1"
|
|
33
34
|
spec.add_dependency "nokogiri", ">= 1.14"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: webmention
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 10.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jason Garber
|
|
@@ -9,6 +9,20 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: burly
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.4'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.4'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: http
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,6 +72,7 @@ executables: []
|
|
|
58
72
|
extensions: []
|
|
59
73
|
extra_rdoc_files: []
|
|
60
74
|
files:
|
|
75
|
+
- ".yardopts"
|
|
61
76
|
- CHANGELOG.md
|
|
62
77
|
- CONTRIBUTING.md
|
|
63
78
|
- LICENSE
|
|
@@ -66,10 +81,6 @@ files:
|
|
|
66
81
|
- lib/webmention.rb
|
|
67
82
|
- lib/webmention/client.rb
|
|
68
83
|
- lib/webmention/error_response.rb
|
|
69
|
-
- lib/webmention/parser.rb
|
|
70
|
-
- lib/webmention/parsers/html_parser.rb
|
|
71
|
-
- lib/webmention/parsers/json_parser.rb
|
|
72
|
-
- lib/webmention/parsers/plaintext_parser.rb
|
|
73
84
|
- lib/webmention/request.rb
|
|
74
85
|
- lib/webmention/response.rb
|
|
75
86
|
- lib/webmention/url.rb
|
|
@@ -80,11 +91,11 @@ licenses:
|
|
|
80
91
|
- Apache-2.0
|
|
81
92
|
metadata:
|
|
82
93
|
bug_tracker_uri: https://github.com/indieweb/webmention-client-ruby/issues
|
|
83
|
-
changelog_uri: https://github.com/indieweb/webmention-client-ruby/releases/tag/
|
|
84
|
-
documentation_uri: https://rubydoc.info/gems/webmention/
|
|
94
|
+
changelog_uri: https://github.com/indieweb/webmention-client-ruby/releases/tag/v10.0.1
|
|
95
|
+
documentation_uri: https://rubydoc.info/gems/webmention/10.0.1
|
|
85
96
|
homepage_uri: https://github.com/indieweb/webmention-client-ruby
|
|
86
97
|
rubygems_mfa_required: 'true'
|
|
87
|
-
source_code_uri: https://github.com/indieweb/webmention-client-ruby/tree/
|
|
98
|
+
source_code_uri: https://github.com/indieweb/webmention-client-ruby/tree/v10.0.1
|
|
88
99
|
rdoc_options: []
|
|
89
100
|
require_paths:
|
|
90
101
|
- lib
|
|
@@ -99,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
99
110
|
- !ruby/object:Gem::Version
|
|
100
111
|
version: '0'
|
|
101
112
|
requirements: []
|
|
102
|
-
rubygems_version: 4.0.
|
|
113
|
+
rubygems_version: 4.0.20
|
|
103
114
|
specification_version: 4
|
|
104
115
|
summary: A Ruby gem for sending and verifying Webmention notifications.
|
|
105
116
|
test_files: []
|
data/lib/webmention/parser.rb
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Webmention
|
|
4
|
-
# @api private
|
|
5
|
-
class Parser
|
|
6
|
-
URI_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::Generic::DEFAULT_PARSER
|
|
7
|
-
|
|
8
|
-
URI_REGEXP = URI_PARSER.make_regexp(["http", "https"]).freeze
|
|
9
|
-
|
|
10
|
-
public_constant :URI_REGEXP
|
|
11
|
-
|
|
12
|
-
class << self
|
|
13
|
-
# @return [Array<String>]
|
|
14
|
-
attr_reader :mime_types
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# @param response_body [HTTP::Response::Body, String, #to_s]
|
|
18
|
-
# @param response_uri [String, HTTP::URI, #to_s]
|
|
19
|
-
def initialize(response_body, response_uri)
|
|
20
|
-
@response_body = response_body.to_s
|
|
21
|
-
@response_uri = HTTP::URI.parse(response_uri.to_s)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
# @return [String]
|
|
27
|
-
attr_reader :response_body
|
|
28
|
-
|
|
29
|
-
# @return [HTTP::URI]
|
|
30
|
-
attr_reader :response_uri
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Webmention
|
|
4
|
-
module Parsers
|
|
5
|
-
# @api private
|
|
6
|
-
class HtmlParser < Parser
|
|
7
|
-
@mime_types = ["text/html"]
|
|
8
|
-
|
|
9
|
-
Client.register_parser(self)
|
|
10
|
-
|
|
11
|
-
HTML_ATTRIBUTES_MAP = {
|
|
12
|
-
"cite" => ["blockquote", "del", "ins", "q"],
|
|
13
|
-
"data" => ["object"],
|
|
14
|
-
"href" => ["a", "area"],
|
|
15
|
-
"poster" => ["video"],
|
|
16
|
-
"src" => ["audio", "embed", "img", "source", "track", "video"],
|
|
17
|
-
"srcset" => ["img", "source"],
|
|
18
|
-
}.freeze
|
|
19
|
-
|
|
20
|
-
CSS_SELECTORS_ARRAY = HTML_ATTRIBUTES_MAP
|
|
21
|
-
.flat_map { |attribute, names| names.map { |name| "#{name}[#{attribute}]" } }.freeze
|
|
22
|
-
|
|
23
|
-
ROOT_NODE_SELECTORS_ARRAY = [".h-entry .e-content", ".h-entry", "body"].freeze
|
|
24
|
-
|
|
25
|
-
private_constant :HTML_ATTRIBUTES_MAP
|
|
26
|
-
private_constant :CSS_SELECTORS_ARRAY
|
|
27
|
-
private_constant :ROOT_NODE_SELECTORS_ARRAY
|
|
28
|
-
|
|
29
|
-
# @return [Array<String>] An array of absolute URLs.
|
|
30
|
-
def results
|
|
31
|
-
@results ||=
|
|
32
|
-
extract_urls_from(*url_attributes)
|
|
33
|
-
.map { |url| response_uri.join(url).to_s }
|
|
34
|
-
.grep(Parser::URI_REGEXP)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
# @return [Nokogiri::HTML5::Document]
|
|
40
|
-
def doc
|
|
41
|
-
Nokogiri.HTML5(response_body)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# @param attributes [Array<Nokogiri::XML::Attr>]
|
|
45
|
-
#
|
|
46
|
-
# @return [Array<String>]
|
|
47
|
-
def extract_urls_from(*attributes)
|
|
48
|
-
attributes.flat_map do |attribute|
|
|
49
|
-
if attribute.name == "srcset"
|
|
50
|
-
attribute.value.split(",").map { |value| value.strip.match(/^\S+/).to_s }
|
|
51
|
-
else
|
|
52
|
-
attribute.value
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# @return [Nokogiri::XML::Element]
|
|
58
|
-
def root_node
|
|
59
|
-
doc.at_css(*ROOT_NODE_SELECTORS_ARRAY)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# @return [Array<Nokogiri::XML::Attr>]
|
|
63
|
-
def url_attributes
|
|
64
|
-
url_nodes.flat_map(&:attribute_nodes).select { |attribute| HTML_ATTRIBUTES_MAP.key?(attribute.name) }
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# @return [Nokogiri::XML::NodeSet]
|
|
68
|
-
def url_nodes
|
|
69
|
-
root_node.css(*CSS_SELECTORS_ARRAY)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Webmention
|
|
4
|
-
module Parsers
|
|
5
|
-
# @api private
|
|
6
|
-
class JsonParser < Parser
|
|
7
|
-
@mime_types = ["application/json"]
|
|
8
|
-
|
|
9
|
-
Client.register_parser(self)
|
|
10
|
-
|
|
11
|
-
# @return [Array<String>] An array of absolute URLs.
|
|
12
|
-
def results
|
|
13
|
-
@results ||= extract_urls_from(JSON.parse(response_body))
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
# @param objs [Array<Hash, Array, String, Integer, Boolean, nil>]
|
|
19
|
-
#
|
|
20
|
-
# @return [Array<String>]
|
|
21
|
-
def extract_urls_from(*objs)
|
|
22
|
-
objs.flat_map do |obj|
|
|
23
|
-
return obj.flat_map { |value| extract_urls_from(value) }.compact if obj.is_a?(Array)
|
|
24
|
-
return extract_urls_from(obj.values) if obj.is_a?(Hash)
|
|
25
|
-
|
|
26
|
-
obj if obj.is_a?(String) && obj.match?(Parser::URI_REGEXP)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Webmention
|
|
4
|
-
module Parsers
|
|
5
|
-
# @api private
|
|
6
|
-
class PlaintextParser < Parser
|
|
7
|
-
@mime_types = ["text/plain"]
|
|
8
|
-
|
|
9
|
-
Client.register_parser(self)
|
|
10
|
-
|
|
11
|
-
# @return [Array<String>] An array of absolute URLs.
|
|
12
|
-
def results
|
|
13
|
-
@results ||= URI_PARSER.extract(response_body, ["http", "https"])
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|