webmention 3.0.0 → 6.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 39599574ee4962a000832e7a47c48fd78d58797181a12a554ca26065b2d860c1
4
- data.tar.gz: aaf047a87adf3b2c171622725aabb9a5f5fc64267a98077c7f2eb8b8e5db0c55
3
+ metadata.gz: df8e59b193b673f72a5fecba8c0e381aa28fbb648c6a1b2fdddcb29f9824573e
4
+ data.tar.gz: 982c8060b33da7a7ca2e5aa4dd905d6bd49f4a16fd3390b1b6f79ff902b741af
5
5
  SHA512:
6
- metadata.gz: db45f488f6e2f527b075764ad580817294ac8971248365270102ee244820c04a7f044612eebe6b7ebc52dbc1fe754b7b997e0da01f2a7b9994d4d51a77e92f82
7
- data.tar.gz: 254fe4bda676161657ff674d75a0866f7711dca89d5f7b5f0f961178efbbd9915a3b89ac94497442906b73ee572ababc90fedf3909c85b8e39c2ed53b897689b
6
+ metadata.gz: 4e8dafddec503fba0d36ea31444de7cd6a162dcc1cc76ae88e7587a9f7e2222cfcf8a06b09e071f92aabd4836f9c58438d7bebbf21aed33655b33f2941bbed07
7
+ data.tar.gz: 18fe26ed4639d359dd705ae574b02b2bc406a6f74582e6a3fe6f042a061c884e5aefed90280eb3e58a4eefcfca73d4edb8f50553fe2235987dd218e323a3bed3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
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
+
34
+ ## v5.0.0 / 2020-12-13
35
+
36
+ - Update absolutely and indieweb-endpoints gems to v5.0 (89f4ea8)
37
+
38
+ ## v4.0.0 / 2020-08-23
39
+
40
+ - **Breaking change:** Update minimum supported Ruby version to 2.5 (b2bc62f)
41
+ - Update indieweb-endpoints to 4.0 (e61588f)
42
+ - Update project Ruby version to 2.5.8 (2a626a6)
43
+
3
44
  ## v3.0.0 / 2020-05-19
4
45
 
5
46
  - Reject "internal" URLs when sending webmentions (#24) (ccc82c8)
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.4.10 and is additionally tested against Ruby 2.5, 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.4.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.4.10 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.4 (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.4.10 and is additionally tested against Ruby 2.5, 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