webmention 5.0.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bdbd526bc4e17aa56447d83497ba225b902dd685581aab0934b9ef4ca4164c1e
4
- data.tar.gz: 12feb3e14687d066fada7d39ca1f6689328f2160610bac5e4301254e61afe630
3
+ metadata.gz: df8e59b193b673f72a5fecba8c0e381aa28fbb648c6a1b2fdddcb29f9824573e
4
+ data.tar.gz: 982c8060b33da7a7ca2e5aa4dd905d6bd49f4a16fd3390b1b6f79ff902b741af
5
5
  SHA512:
6
- metadata.gz: 407be30d3a706ade7b5a1bd7698aa6470e1962a657f17325b5a45356710ff67330650f9abf2923d2211eea59773d27a5140baaee7f2fd1fdd7e0da8d1ed33f13
7
- data.tar.gz: 1d5b4eb9c3b20ab8ad4fb0183482485b54dc5d1047c359e08ea6a63d9e277276b47b73ec429450c51be871046d8b1c057bbed718a286556d8566464ab3d2d8ef
6
+ metadata.gz: 4e8dafddec503fba0d36ea31444de7cd6a162dcc1cc76ae88e7587a9f7e2222cfcf8a06b09e071f92aabd4836f9c58438d7bebbf21aed33655b33f2941bbed07
7
+ data.tar.gz: 18fe26ed4639d359dd705ae574b02b2bc406a6f74582e6a3fe6f042a061c884e5aefed90280eb3e58a4eefcfca73d4edb8f50553fe2235987dd218e323a3bed3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## v6.0.0 / 2022-05-13
4
+
5
+ ### New Features
6
+
7
+ - Top-level module methods:
8
+ - `Webmention.send_webmention(source, target)`
9
+ - `Webmention.send_webmentions(source, *targets)`
10
+ - `Webmention.mentioned_urls(url)`
11
+ - New JSON and plaintext parsers
12
+ - [Vouch](https://indieweb.org/Vouch) URL support (9829269)
13
+ - Webmention verification support (5fe5f58 and 100644)
14
+ - Fewer exceptions! HTTP response handling updated to return similar objects (`Webmention::Response` and `Webmention::ErrorResponse`).
15
+ - Fewer runtime dependencies!
16
+
17
+ ### Breaking Changes
18
+
19
+ - `Webmention.send_mention` renamed to `Webmention.send_webmention`
20
+ - `Webmention.client` method removed
21
+ - `Webmention::Client#send_all_mentions` removed in favor of `Webmention.mentioned_urls` and `Webmention.send_webmentions`
22
+ - Response objects from `Webmention.send_webmention` and `Webmention.send_webmentions` have changed from instances of `HTTP::Response` to instances of `Webmention::Response` or `Webmention::ErrorResponse`
23
+ - Remove Absolutely and Addressable dependencies
24
+ - Add support for Ruby 3 (a31aae6)
25
+ - Update minimum supported Ruby version to 2.6 (e4fed8e)
26
+
27
+ ### Development Changes
28
+
29
+ - Remove Reek development dependency (806bbc7)
30
+ - Update development Ruby version to 2.6.10 (7e52ec9)
31
+ - Migrate test suite to RSpec (79ac684)
32
+ - Migrate to GitHub Actions (f5a3d7a)
33
+
3
34
  ## v5.0.0 / 2020-12-13
4
35
 
5
36
  - Update absolutely and indieweb-endpoints gems to v5.0 (89f4ea8)
data/CONTRIBUTING.md CHANGED
@@ -8,9 +8,9 @@ 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 2.5.8 and is additionally tested against Ruby 2.6 and 2.7 using [Travis CI](https://travis-ci.org/indieweb/webmention-client-ruby).
11
+ webmention-client-ruby is developed using Ruby 2.6.10 and is additionally tested against Ruby 2.7, 3.0, and 3.1 using [GitHub Actions](https://github.com/indieweb/webmention-client-ruby/actions).
12
12
 
13
- Before making changes to webmention-client-ruby, you'll want to install Ruby 2.5.8. It's recommended that you use a Ruby version managment tool like [rbenv](https://github.com/rbenv/rbenv), [chruby](https://github.com/postmodern/chruby), or [rvm](https://github.com/rvm/rvm). Once you've installed Ruby 2.5.8 using your method of choice, install the project's gems by running:
13
+ Before making changes to webmention-client-ruby, you'll want to install Ruby 2.6.10. It's recommended that you use a Ruby version managment tool like [rbenv](https://github.com/rbenv/rbenv), [chruby](https://github.com/postmodern/chruby), or [rvm](https://github.com/rvm/rvm). Once you've installed Ruby 2.6.10 using your method of choice, install the project's gems by running:
14
14
 
15
15
  ```sh
16
16
  bundle install
data/README.md CHANGED
@@ -1,110 +1,68 @@
1
1
  # webmention-client-ruby
2
2
 
3
- **A Ruby gem for sending [Webmention](https://indieweb.org/Webmention) notifications.**
3
+ **A Ruby gem for sending and verifying [Webmention](https://indieweb.org/Webmention) notifications.**
4
4
 
5
- [![Gem](https://img.shields.io/gem/v/webmention.svg?style=for-the-badge)](https://rubygems.org/gems/webmention)
6
- [![Downloads](https://img.shields.io/gem/dt/webmention.svg?style=for-the-badge)](https://rubygems.org/gems/webmention)
7
- [![Build](https://img.shields.io/travis/indieweb/webmention-client-ruby/master.svg?style=for-the-badge)](https://travis-ci.org/indieweb/webmention-client-ruby)
8
- [![Maintainability](https://img.shields.io/codeclimate/maintainability/indieweb/webmention-client-ruby.svg?style=for-the-badge)](https://codeclimate.com/github/indieweb/webmention-client-ruby)
9
- [![Coverage](https://img.shields.io/codeclimate/c/indieweb/webmention-client-ruby.svg?style=for-the-badge)](https://codeclimate.com/github/indieweb/webmention-client-ruby/code)
5
+ [![Gem](https://img.shields.io/gem/v/webmention.svg?logo=rubygems&style=for-the-badge)](https://rubygems.org/gems/webmention)
6
+ [![Downloads](https://img.shields.io/gem/dt/webmention.svg?logo=rubygems&style=for-the-badge)](https://rubygems.org/gems/webmention)
7
+ [![Build](https://img.shields.io/github/workflow/status/indieweb/webmention-client-ruby/CI?logo=github&style=for-the-badge)](https://github.com/indieweb/webmention-client-ruby/actions/workflows/ci.yml)
8
+ [![Maintainability](https://img.shields.io/codeclimate/maintainability/indieweb/webmention-client-ruby.svg?logo=code-climate&style=for-the-badge)](https://codeclimate.com/github/indieweb/webmention-client-ruby)
9
+ [![Coverage](https://img.shields.io/codeclimate/c/indieweb/webmention-client-ruby.svg?logo=code-climate&style=for-the-badge)](https://codeclimate.com/github/indieweb/webmention-client-ruby/code)
10
10
 
11
11
  ## Key Features
12
12
 
13
- - Crawls a given URL for mentioned URLs.
14
- - Performs [endpoint discovery](https://www.w3.org/TR/webmention/#sender-discovers-receiver-webmention-endpoint) on mentioned URLs.
15
- - Sends webmentions to mentioned URLs.
13
+ - Crawl a URL for mentioned URLs.
14
+ - Perform [endpoint discovery](https://www.w3.org/TR/webmention/#sender-discovers-receiver-webmention-endpoint) on mentioned URLs.
15
+ - Send webmentions to one or more mentioned URLs (and optionally include a [vouch](https://indieweb.org/Vouch) URL).
16
+ - Verify that a received webmention's source URL links to a target URL (and optionally verify that a vouch URL mentions the source URL's domain).
16
17
 
17
18
  ## Getting Started
18
19
 
19
- Before installing and using webmention-client-ruby, you'll want to have [Ruby](https://www.ruby-lang.org) 2.5 (or newer) installed. It's recommended that you use a Ruby version managment tool like [rbenv](https://github.com/rbenv/rbenv), [chruby](https://github.com/postmodern/chruby), or [rvm](https://github.com/rvm/rvm).
20
+ Before installing and using webmention-client-ruby, you'll want to have [Ruby](https://www.ruby-lang.org) 2.6 (or newer) installed. It's recommended that you use a Ruby version managment tool like [rbenv](https://github.com/rbenv/rbenv), [chruby](https://github.com/postmodern/chruby), or [rvm](https://github.com/rvm/rvm).
20
21
 
21
- webmention-client-ruby is developed using Ruby 2.5.8 and is additionally tested against Ruby 2.6 and 2.7 using [Travis CI](https://travis-ci.org/indieweb/webmention-client-ruby).
22
+ webmention-client-ruby is developed using Ruby 2.6.10 and is additionally tested against Ruby 2.7, 3.0, and 3.1 using [GitHub Actions](https://github.com/indieweb/webmention-client-ruby/actions).
22
23
 
23
24
  ## Installation
24
25
 
25
- If you're using [Bundler](https://bundler.io) to manage gem dependencies, add webmention-client-ruby to your project's Gemfile:
26
+ If you're using [Bundler](https://bundler.io) to manage gem dependencies, add webmention-client-ruby to your project's `Gemfile`:
26
27
 
27
28
  ```ruby
28
- source 'https://rubygems.org'
29
-
30
29
  gem 'webmention'
31
30
  ```
32
31
 
33
- …and then run:
32
+ …and run `bundle install` in your shell.
33
+
34
+ To install the gem manually, run the following in your shell:
34
35
 
35
36
  ```sh
36
- bundle install
37
+ gem install webmention
37
38
  ```
38
39
 
39
40
  ## Usage
40
41
 
41
- With webmention-client-ruby added to your project's `Gemfile` and installed, you may send a webmention from a source URL to a target URL:
42
-
43
- ```ruby
44
- require 'webmention'
45
-
46
- source = 'https://source.example.com/post/100' # A post on your website
47
- target = 'https://target.example.com/post/100' # A post on someone else's website
48
-
49
- Webmention.send_mention(source, target) # => #<HTTP::Response/1.1 200 OK {…}>
50
- ```
51
-
52
- If no Webmention endpoint is found for a given source URL, the `send_mention` method will return `nil`.
53
-
54
- **Note:** `HTTP::Response` objects may return a variety of status codes that will vary depending on the endpoint's capabilities and the success or failure of the request. See [the Webmention spec](https://www.w3.org/TR/webmention/) for more on status codes on their implications.
55
-
56
- ### Sending multiple webmentions
57
-
58
- To send webmentions to all URLs mentioned within a source URL's [h-entry](http://microformats.org/wiki/h-entry):
59
-
60
- ```ruby
61
- require 'webmention'
62
-
63
- client = Webmention.client('https://source.example.com/post/100')
64
-
65
- client.mentioned_urls # => Array
66
- client.send_all_mentions # => Hash
67
- ```
68
-
69
- This example will crawl `https://source.example.com/post/100`, parse its markup for the first h-entry, perform endpoint discovery on mentioned URLs, and attempt to send webmentions to those URLs.
70
-
71
- **Note:** If no h-entry is found at the provided source URL, the `send_all_mentions` method will search the source URL's `<body>` for mentioned URLs.
72
-
73
- The `send_all_mentions` method returns a hash of mentioned URLs and the associated HTTP response (an [`HTTP::Response` object](https://github.com/httprb/http/wiki/Response-Handling)):
74
-
75
- ```ruby
76
- {
77
- 'https://target.example.com/post/100' => #<HTTP::Response/1.1 200 OK {…}>,
78
- 'https://target.example.com/post/101' => #<HTTP::Response/1.1 200 OK {…}>
79
- }
80
- ```
81
-
82
- ### Exception Handling
42
+ See [USAGE.md](https://github.com/indieweb/webmention-client-ruby/blob/main/USAGE.md) for documentation of webmention-client-ruby's features.
83
43
 
84
- There are several exceptions that may be raised by webmention-client-ruby's underlying dependencies. These errors are raised as subclasses of `WebmentionClientError` (which itself is a subclass of `StandardError`).
44
+ ## Migrating to version 6
85
45
 
86
- From [sporkmonger/addressable](https://github.com/sporkmonger/addressable):
46
+ webmention-client-ruby was completely rewritten for version 6 to better support new features and future development. Some notes on migrating to the new version:
87
47
 
88
- - `Webmention::Client::InvalidURIError`
48
+ ♻️ **Renamed:** for clarity and consistency, the `Webmention.send_mention` method has been renamed `Webmention.send_webmention`. Both methods use the same interface.
89
49
 
90
- From [httprb/http](https://github.com/httprb/http):
50
+ **Removed:** the `Webmention.client` method has been removed in favor of the additional module methods [noted above](#usage). While the underlying `Webmention::Client` class still exists, its interface has changed and its direct usage is generally unnecessary.
91
51
 
92
- - `Webmention::Client::ConnectionError`
93
- - `Webmention::Client::TimeoutError`
94
- - `Webmention::Client::TooManyRedirectsError`
52
+ **Removed:** `Webmention::Client#send_all_mentions` has been removed in favor of `Webmention.send_webmentions`. Combine `Webmention.mentioned_urls` and `Webmention.send_webmentions` to achieve similar results.
95
53
 
96
- webmention-client-ruby will also raise a `Webmention::Client::UnsupportedMimeTypeError` when encountering an `HTTP::Response` instance with an unsupported MIME type.
54
+ 🛠 **Refactored:** Exception handling has been greatly improved [as noted above](#exception-handling).
97
55
 
98
56
  ## Contributing
99
57
 
100
- Interested in helping improve webmention-client-ruby? Awesome! Your help is greatly appreciated. See [CONTRIBUTING.md](https://github.com/indieweb/webmention-client-ruby/blob/master/CONTRIBUTING.md) for details.
58
+ Interested in helping improve webmention-client-ruby? Awesome! Your help is greatly appreciated. See [CONTRIBUTING.md](https://github.com/indieweb/webmention-client-ruby/blob/main/CONTRIBUTING.md) for details.
101
59
 
102
60
  ## Acknowledgments
103
61
 
104
- webmention-client-ruby is written and maintained by [Aaron Parecki](https://aaronparecki.com) ([@aaronpk](https://github.com/aaronpk)) and [Nat Welch](https://natwelch.com) ([@icco](https://github.com/icco)) with help from [these additional contributors](https://github.com/indieweb/webmention-client-ruby/graphs/contributors).
62
+ webmention-client-ruby is written and maintained by [Jason Garber](https://sixtwothree.org) ([@jgarber623](https://github.com/jgarber623)) with help from [these additional contributors](https://github.com/indieweb/webmention-client-ruby/graphs/contributors). Prior to 2018, webmention-client-ruby was written and maintained by [Aaron Parecki](https://aaronparecki.com) ([@aaronpk](https://github.com/aaronpk)) and [Nat Welch](https://natwelch.com) ([@icco](https://github.com/icco)).
105
63
 
106
64
  To learn more about Webmention, see [indieweb.org/Webmention](https://indieweb.org/Webmention) and [webmention.net](https://webmention.net).
107
65
 
108
66
  ## License
109
67
 
110
- webmention-client-ruby is freely available under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html). See [LICENSE](https://github.com/indieweb/webmention-client-ruby/blob/master/LICENSE) for more details.
68
+ webmention-client-ruby is freely available under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html). See [LICENSE](https://github.com/indieweb/webmention-client-ruby/blob/main/LICENSE) for more details.
data/USAGE.md ADDED
@@ -0,0 +1,153 @@
1
+ # Using webmention-client-ruby
2
+
3
+ Before using webmention-client-ruby, please read the [Getting Started](https://github.com/indieweb/webmention-client-ruby/blob/main/README.md#getting-started) and [Installation](https://github.com/indieweb/webmention-client-ruby/blob/main/README.md#installation) sections of the project's [README.md](https://github.com/indieweb/webmention-client-ruby/blob/main/README.md).
4
+
5
+ ## Sending a webmention
6
+
7
+ With webmention-client-ruby installed, you may send a webmention from a source URL to a target URL:
8
+
9
+ ```ruby
10
+ require 'webmention'
11
+
12
+ source = 'https://jgarber.example/post/100' # A post on your website
13
+ target = 'https://aaronpk.example/post/100' # A post on someone else's website
14
+
15
+ response = Webmention.send_webmention(source, target)
16
+ ```
17
+
18
+ `Webmention.send_webmention` will return either a `Webmention::Response` or a `Webmention::ErrorResponse`. Instances of both classes respond to `ok?`. Building on the examples above:
19
+
20
+ A `Webmention::ErrorResponse` may be returned when:
21
+
22
+ 1. The target URL does not advertise a Webmention endpoint.
23
+ 2. The request to the target URL raises an `HTTP::Error` or an `OpenSSL::SSL::SSLError`.
24
+
25
+ ```ruby
26
+ response.ok?
27
+ #=> false
28
+
29
+ response.class
30
+ #=> Webmention::ErrorResponse
31
+
32
+ response.message
33
+ #=> "No webmention endpoint found for target URL https://aaronpk.example/post/100"
34
+ ```
35
+
36
+ A `Webmention::Response` will be returned in all other cases.
37
+
38
+ ```ruby
39
+ response.ok?
40
+ #=> true
41
+
42
+ response.class
43
+ #=> Webmention::Response
44
+ ```
45
+
46
+ Instances of `Webmention::Response` include useful methods delegated to the underlying `HTTP::Response` object:
47
+
48
+ ```ruby
49
+ response.headers #=> HTTP::Headers
50
+ response.body #=> HTTP::Response::Body
51
+ response.code #=> Integer
52
+ response.reason #=> String
53
+ response.mime_type #=> String
54
+ response.uri #=> HTTP::URI
55
+ ```
56
+
57
+ 💡 **Note:** `Webmention::Response` objects may return a variety of status codes that will vary depending on the endpoint's capabilities and the success or failure of the request. See [the Webmention spec](https://www.w3.org/TR/webmention/) for more on status codes on their implications. A `Webmention::Response` responding affirmatively to `ok?` _may_ also have a non-successful HTTP status code (e.g. `404 Not Found`).
58
+
59
+ ## Sending multiple webmentions
60
+
61
+ To send webmentions to multiple target URLs mentioned by a source URL:
62
+
63
+ ```ruby
64
+ source = 'https://jgarber.example/post/100'
65
+ targets = ['https://aaronpk.example/notes/1', 'https://adactio.example/notes/1']
66
+
67
+ responses = Webmention.send_webmentions(source, targets)
68
+ ```
69
+
70
+ `Webmention.send_webmentions` will return an array of `Webmention::Response` and `Webmention::ErrorResponse` objects.
71
+
72
+ ## Including a vouch URL
73
+
74
+ webmention-client-ruby supports submitting a [vouch](https://indieweb.org/Vouch) URL when sending webmentions:
75
+
76
+ ```ruby
77
+ # Send a webmention with a vouch URL to a target URL
78
+ Webmention.send_webmention(source, target, vouch: 'https://tantek.example/notes/1')
79
+
80
+ # Send webmentions with a vouch URL to multiple target URLs
81
+ Webmention.send_webmentions(source, targets, vouch: 'https://tantek.example/notes/1')
82
+ ```
83
+
84
+ ## Discovering mentioned URLs
85
+
86
+ To retrieve unique URLs mentioned by a URL:
87
+
88
+ ```ruby
89
+ urls = Webmention.mentioned_urls('https://jgarber.example/post/100')
90
+ ```
91
+
92
+ `Webmention.mentioned_urls` will crawl the provided URL, parse the response body, and return a sorted list of unique URLs. Response bodies are parsed using MIME type-specific rules as noted in the [Verifying a webmention](#verifying-a-webmention) section below.
93
+
94
+ When parsing HTML documents, webmention-client-ruby will find the first [h-entry](https://microformats.org/wiki/h-entry) and search its markup for URLs. If no h-entry is found, the parser will search the document's `<body>`.
95
+
96
+ 💡 **Note:** Links pointing to the supplied URL (or those with internal fragment identifiers) will be rejected. You may wish to additionally filter the results returned by `Webmention.mentioned_urls` before sending webmentions.
97
+
98
+ ## Verifying a webmention
99
+
100
+ webmention-client-ruby verifies [HTML](https://www.w3.org/TR/html/), [JSON](https://json.org), and plaintext files in accordance with [Section 3.2.2](https://www.w3.org/TR/webmention/#webmention-verification) of [the W3C's Webmention Recommendation](https://www.w3.org/TR/webmention/):
101
+
102
+ > The receiver **should** use per-media-type rules to determine whether the source document mentions the target URL.
103
+
104
+ In plaintext documents, webmention-client-ruby will search the source URL for exact matches of the target URL. If the source URL is a JSON document, key/value pairs whose value equals the target URL are matched.
105
+
106
+ HTML documents are searched for a variety of elements and attributes whose values may be (or include) URLs:
107
+
108
+ | Element | Attributes |
109
+ |:-------------|:----------------|
110
+ | `a` | `href` |
111
+ | `area` | `href` |
112
+ | `audio` | `src` |
113
+ | `blockquote` | `cite` |
114
+ | `del` | `cite` |
115
+ | `embed` | `src` |
116
+ | `img` | `src`, `srcset` |
117
+ | `ins` | `cite` |
118
+ | `object` | `data` |
119
+ | `q` | `cite` |
120
+ | `source` | `src`, `srcset` |
121
+ | `track` | `src` |
122
+ | `video` | `src` |
123
+
124
+ To verify a received webmention:
125
+
126
+ ```ruby
127
+ # Verify that a source URL links to a target URL
128
+ verification = Webmention.verify_webmention(source, target)
129
+
130
+ # Verify that a source URL links to a target URL and that the vouch URL mentions
131
+ # the source URL's domain
132
+ verification = Webmention.verify_webmention(source, target, vouch: 'https://tantek.example/notes/1')
133
+ ```
134
+
135
+ `Webmention.verify_webmention` returns an instance of `Webmention::Verification` which includes the following methods (each returns either `true` or `false`):
136
+
137
+ ```ruby
138
+ verification.source_mentions_target?
139
+ verification.verified?
140
+ verification.verify_vouch?
141
+ verification.vouch_mentions_source?
142
+ ```
143
+
144
+ 💡 **Note:** `Webmention.verify_webmention` parses HTML documents using the same rules outlined in [Discovering mentioned URLs](#discovering-mentioned-urls).
145
+
146
+ ## Exception Handling
147
+
148
+ 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.
149
+
150
+ When crawling the supplied URL, `Webmention.mentioned_urls` _may_ raise a `NoMethodError` if:
151
+
152
+ - a `Webmention::ErrorResponse` is returned, or
153
+ - the response is of an unsupported MIME type.
@@ -1,64 +1,143 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Webmention
2
4
  class Client
3
- # Create a new Webmention::Client
4
- #
5
- # client = Webmention::Client.new('https://source.example.com/post/100')
6
- #
7
- # @param source [String] An absolute URL representing the source document
8
- def initialize(source)
9
- raise ArgumentError, "source must be a String (given #{source.class.name})" unless source.is_a?(String)
5
+ @registered_parsers = {}
10
6
 
11
- @source = source
7
+ class << self
8
+ # @api private
9
+ attr_reader :registered_parsers
10
+ end
12
11
 
13
- raise ArgumentError, 'source must be an absolute URL (e.g. https://example.com)' unless source_uri.absolute?
12
+ # @return [Webmention::Url]
13
+ attr_reader :source_url
14
+
15
+ # @return [Webmention::Url]
16
+ attr_reader :vouch_url
17
+
18
+ # @api private
19
+ def self.register_parser(klass)
20
+ klass.mime_types.each { |mime_type| @registered_parsers[mime_type] = klass }
14
21
  end
15
22
 
16
- # Send webmentions to all mentioned URLs in this client's source document
23
+ # Create a new Webmention::Client.
24
+ #
25
+ # @example
26
+ # Webmention::Client.new('https://jgarber.example/posts/100')
17
27
  #
18
- # @return [Hash{String => HTTP::Response, nil}]
19
- def send_all_mentions
20
- mentioned_urls.each_with_object({}) { |url, hash| hash[url] = send_mention(url) }
28
+ # @example
29
+ # Webmention::Client.new('https://jgarber.example/posts/100', vouch: 'https://tantek.example/notes/1')
30
+ #
31
+ # @param source [String, HTTP::URI, #to_s]
32
+ # An absolute URL representing a source document.
33
+ # @param vouch [String, HTTP::URI, #to_s]
34
+ # An absolute URL representing a document vouching for the source document.
35
+ # See https://indieweb.org/Vouch for additional details.
36
+ #
37
+ # @return [Webmention::Client]
38
+ def initialize(source, vouch: nil)
39
+ @source_url = Url.new(source)
40
+ @vouch_url = Url.new(vouch)
41
+ end
42
+
43
+ # :nocov:
44
+ # @return [String]
45
+ def inspect
46
+ "#<#{self.class}:#{format('%#0x', object_id)} " \
47
+ "source_url: #{source_url} " \
48
+ "vouch_url: #{vouch_url}>"
21
49
  end
50
+ # :nocov:
22
51
 
23
- # Extract mentioned URLs from this client's source document
52
+ # Retrieve unique URLs mentioned by this client's source URL.
53
+ #
54
+ # @example
55
+ # client = Webmention::Client.new('https://jgarber.example/posts/100')
56
+ # client.mentioned_urls
57
+ #
58
+ # @raise [NoMethodError]
59
+ # Raised when response is a Webmention::ErrorResponse or response is of an
60
+ # unsupported MIME type.
24
61
  #
25
62
  # @return [Array<String>]
26
- # @raise [Webmention::UnsupportedMimeTypeError]
27
63
  def mentioned_urls
28
- raise UnsupportedMimeTypeError, "Unsupported MIME Type: #{source_response.mime_type}" unless parser_for_mime_type
64
+ response = source_url.response
29
65
 
30
- @mentioned_urls ||= parser_for_mime_type.new(source_response).results
66
+ self.class
67
+ .registered_parsers[response.mime_type]
68
+ .new(response.body, response.uri)
69
+ .results
70
+ .uniq
71
+ .reject { |url| url.match(/^#{response.uri}(?:#.*)?$/) }
72
+ .sort
31
73
  end
32
74
 
33
- # Send a webmention from this client's source URL to the target URL
75
+ # Send a webmention from this client's source URL to a target URL.
76
+ #
77
+ # @example
78
+ # client = Webmention::Client.new('https://jgarber.example/posts/100')
79
+ # client.send_webmention('https://aaronpk.example/notes/1')
34
80
  #
35
- # @param target [String] An absolute URL representing the target document
36
- # @return [HTTP::Response, nil]
37
- # @raise [Webmention::ArgumentError, Webmention::ConnectionError, Webmention::InvalidURIError, Webmention::TimeoutError, Webmention::TooManyRedirectsError]
38
- def send_mention(target)
39
- endpoint = IndieWeb::Endpoints.get(target)[:webmention]
81
+ # @param target [String, HTTP::URI, #to_s]
82
+ # An absolute URL representing a target document.
83
+ #
84
+ # @return [Webmention::Response, Webmention::ErrorResponse]
85
+ def send_webmention(target)
86
+ target_url = Url.new(target)
40
87
 
41
- return unless endpoint
88
+ # A Webmention endpoint exists. Send the request and return the response.
89
+ if target_url.webmention_endpoint?
90
+ return Request.post(target_url.webmention_endpoint, **request_options_for(target))
91
+ end
42
92
 
43
- Services::HttpRequestService.post(Addressable::URI.parse(endpoint), source: @source, target: target)
44
- rescue IndieWeb::Endpoints::IndieWebEndpointsError => exception
45
- raise Webmention.const_get(exception.class.name.split('::').last), exception
46
- end
93
+ # An error was encountered fetching the target URL. Return the response.
94
+ return target_url.response unless target_url.response.ok?
47
95
 
48
- private
96
+ # No Webmention endpoint exists. Return a new ErrorResponse.
97
+ ErrorResponse.new("No webmention endpoint found for target URL #{target}", target_url.response.request)
98
+ end
49
99
 
50
- def parser_for_mime_type
51
- @parser_for_mime_type ||= Parsers.registered[source_response.mime_type]
100
+ # Send webmentions from this client's source URL to multiple target URLs.
101
+ #
102
+ # @example
103
+ # client = Webmention::Client.new('https://jgarber.example/posts/100')
104
+ # targets = ['https://aaronpk.example/notes/1', 'https://adactio.example/notes/1']
105
+ # client.send_webmentions(targets)
106
+ #
107
+ # @param *targets [Array<String, HTTP::URI, #to_s>]
108
+ # An array of absolute URLs representing multiple target documents.
109
+ #
110
+ # @return [Array<Webmention::Response, Webmention::ErrorResponse>]
111
+ def send_webmentions(*targets)
112
+ targets.map { |target| send_webmention(target) }
52
113
  end
53
114
 
54
- def source_response
55
- @source_response ||= Services::HttpRequestService.get(source_uri)
115
+ # Verify that this client's source URL links to a target URL.
116
+ #
117
+ # @param target [String, HTTP::URI, #to_s]
118
+ # An absolute URL representing a target document.
119
+ #
120
+ # @raise (see Webmention::Client#mentioned_urls)
121
+ #
122
+ # @return [Webmention::Verification]
123
+ def verify_webmention(target)
124
+ Verification.new(source_url, Url.new(target), vouch_url: vouch_url)
56
125
  end
57
126
 
58
- def source_uri
59
- @source_uri ||= Addressable::URI.parse(@source)
60
- rescue Addressable::URI::InvalidURIError => exception
61
- raise InvalidURIError, exception
127
+ private
128
+
129
+ # @param target [String, HTTP::URI, #to_s]
130
+ #
131
+ # @return [Hash{Symbol => String}]
132
+ def request_options_for(target)
133
+ opts = {
134
+ source: source_url,
135
+ target: target,
136
+ vouch: vouch_url
137
+ }
138
+
139
+ opts.transform_values { |value| value.to_s.strip }
140
+ .delete_if { |_, value| value.empty? }
62
141
  end
63
142
  end
64
143
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Webmention
4
+ class ErrorResponse
5
+ # @return [String]
6
+ attr_reader :message
7
+
8
+ # @return [Webmention::Request]
9
+ attr_reader :request
10
+
11
+ # Create a new Webmention::ErrorResponse.
12
+ #
13
+ # Instances of this class represent HTTP requests that generated errors
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 <code>#message</code>
16
+ # instance method.
17
+ #
18
+ # @param message [String]
19
+ # @param request [Webmention::Request]
20
+ #
21
+ # @return [Webmention::ErrorResponse]
22
+ def initialize(message, request)
23
+ @message = message
24
+ @request = request
25
+ end
26
+
27
+ # :nocov:
28
+ # @return [String]
29
+ def inspect
30
+ "#<#{self.class}:#{format('%#0x', object_id)} " \
31
+ "message: #{message}>"
32
+ end
33
+ # :nocov:
34
+
35
+ # @return [Boolean]
36
+ def ok?
37
+ false
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Webmention
4
+ # @api private
5
+ class Parser
6
+ URI_REGEXP = URI::DEFAULT_PARSER.make_regexp(%w[http https]).freeze
7
+
8
+ class << self
9
+ # @return [Array<String>]
10
+ attr_reader :mime_types
11
+ end
12
+
13
+ # @param response_body [HTTP::Response::Body, String, #to_s]
14
+ # @param response_uri [String, HTTP::URI, #to_s]
15
+ def initialize(response_body, response_uri)
16
+ @response_body = response_body.to_s
17
+ @response_uri = HTTP::URI.parse(response_uri.to_s)
18
+ end
19
+
20
+ private
21
+
22
+ # @return [String]
23
+ attr_reader :response_body
24
+
25
+ # @return [HTTP::URI]
26
+ attr_reader :response_uri
27
+ end
28
+ end