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 +4 -4
- data/CHANGELOG.md +41 -0
- data/CONTRIBUTING.md +2 -2
- data/README.md +27 -69
- data/USAGE.md +153 -0
- data/lib/webmention/client.rb +116 -37
- data/lib/webmention/error_response.rb +40 -0
- data/lib/webmention/parser.rb +28 -0
- data/lib/webmention/parsers/html_parser.rb +33 -19
- data/lib/webmention/parsers/json_parser.rb +43 -0
- data/lib/webmention/parsers/plaintext_parser.rb +17 -0
- data/lib/webmention/request.rb +107 -0
- data/lib/webmention/response.rb +64 -0
- data/lib/webmention/url.rb +46 -0
- data/lib/webmention/verification.rb +79 -0
- data/lib/webmention/version.rb +3 -1
- data/lib/webmention.rb +85 -23
- data/webmention.gemspec +16 -13
- metadata +24 -57
- data/.editorconfig +0 -10
- data/.gitignore +0 -34
- data/.reek.yml +0 -8
- data/.rubocop +0 -3
- data/.rubocop.yml +0 -28
- data/.ruby-version +0 -1
- data/.simplecov +0 -12
- data/.travis.yml +0 -16
- data/Gemfile +0 -15
- data/Rakefile +0 -21
- data/lib/webmention/exceptions.rb +0 -15
- data/lib/webmention/parsers/base_parser.rb +0 -27
- data/lib/webmention/parsers.rb +0 -11
- data/lib/webmention/services/http_request_service.rb +0 -47
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Webmention
|
2
4
|
module Parsers
|
3
|
-
|
5
|
+
# @api private
|
6
|
+
class HtmlParser < Parser
|
4
7
|
@mime_types = ['text/html']
|
5
8
|
|
6
|
-
|
9
|
+
Client.register_parser(self)
|
7
10
|
|
8
11
|
HTML_ATTRIBUTES_MAP = {
|
9
12
|
'cite' => %w[blockquote del ins q],
|
@@ -14,47 +17,58 @@ module Webmention
|
|
14
17
|
'srcset' => %w[img source]
|
15
18
|
}.freeze
|
16
19
|
|
17
|
-
CSS_SELECTORS_ARRAY = HTML_ATTRIBUTES_MAP.flat_map
|
20
|
+
CSS_SELECTORS_ARRAY = HTML_ATTRIBUTES_MAP.flat_map do |attribute, names|
|
21
|
+
names.map { |name| "#{name}[#{attribute}]" }
|
22
|
+
end.freeze
|
23
|
+
|
24
|
+
ROOT_NODE_SELECTORS_ARRAY = ['.h-entry .e-content', '.h-entry', 'body'].freeze
|
25
|
+
|
26
|
+
private_constant :HTML_ATTRIBUTES_MAP
|
27
|
+
private_constant :CSS_SELECTORS_ARRAY
|
28
|
+
private_constant :ROOT_NODE_SELECTORS_ARRAY
|
18
29
|
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# @return [Array<String>] Unique external URLs whose scheme matches http/https
|
30
|
+
# @return [Array<String>] An array of absolute URLs.
|
22
31
|
def results
|
23
|
-
@results ||=
|
32
|
+
@results ||=
|
33
|
+
UrlExtractor.extract(*url_attributes)
|
34
|
+
.map { |url| response_uri.join(url).to_s }
|
35
|
+
.grep(Parser::URI_REGEXP)
|
24
36
|
end
|
25
37
|
|
26
38
|
private
|
27
39
|
|
40
|
+
# @return [Nokogiri::HTML5::Document]
|
28
41
|
def doc
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
def resolved_urls
|
33
|
-
UrlAttributesParser.parse(*url_attributes).map { |url| Absolutely.to_abs(base: response_url, relative: url) }
|
42
|
+
Nokogiri.HTML5(response_body)
|
34
43
|
end
|
35
44
|
|
45
|
+
# @return [Nokogiri::XML::Element]
|
36
46
|
def root_node
|
37
|
-
doc.at_css(
|
47
|
+
doc.at_css(*ROOT_NODE_SELECTORS_ARRAY)
|
38
48
|
end
|
39
49
|
|
50
|
+
# @return [Array<Nokogiri::XML::Attr>]
|
40
51
|
def url_attributes
|
41
|
-
url_nodes.flat_map(&:attribute_nodes).
|
52
|
+
url_nodes.flat_map(&:attribute_nodes).find_all { |attribute| HTML_ATTRIBUTES_MAP.key?(attribute.name) }
|
42
53
|
end
|
43
54
|
|
55
|
+
# @return [Nokogiri::XML::NodeSet]
|
44
56
|
def url_nodes
|
45
57
|
root_node.css(*CSS_SELECTORS_ARRAY)
|
46
58
|
end
|
47
59
|
|
48
|
-
module
|
49
|
-
# @param attributes [Array<Nokogiri::XML::Attr>]
|
60
|
+
module UrlExtractor
|
61
|
+
# @param *attributes [Array<Nokogiri::XML::Attr>]
|
62
|
+
#
|
50
63
|
# @return [Array<String>]
|
51
|
-
def self.
|
52
|
-
attributes.flat_map { |attribute|
|
64
|
+
def self.extract(*attributes)
|
65
|
+
attributes.flat_map { |attribute| values_from(attribute) }
|
53
66
|
end
|
54
67
|
|
55
68
|
# @param attribute [Nokogiri::XML::Attr]
|
69
|
+
#
|
56
70
|
# @return [String, Array<String>]
|
57
|
-
def self.
|
71
|
+
def self.values_from(attribute)
|
58
72
|
return attribute.value unless attribute.name == 'srcset'
|
59
73
|
|
60
74
|
attribute.value.split(',').map { |value| value.strip.match(/^\S+/).to_s }
|
@@ -0,0 +1,43 @@
|
|
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 ||= UrlExtractor.extract(doc)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# @return [Array, Hash]
|
19
|
+
def doc
|
20
|
+
@doc ||= JSON.parse(response_body)
|
21
|
+
end
|
22
|
+
|
23
|
+
module UrlExtractor
|
24
|
+
# @param *objs [Array<Hash, Array, String, Integer, Boolean, nil>]
|
25
|
+
#
|
26
|
+
# @return [Array<String>]
|
27
|
+
def self.extract(*objs)
|
28
|
+
objs.flat_map { |obj| values_from(obj) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param obj [Hash, Array, String, Integer, Boolean, nil]
|
32
|
+
#
|
33
|
+
# @return [Array<String>, String, nil]
|
34
|
+
def self.values_from(obj)
|
35
|
+
return obj.flat_map { |value| extract(value) }.compact if obj.is_a?(Array)
|
36
|
+
return extract(obj.values) if obj.is_a?(Hash)
|
37
|
+
|
38
|
+
obj if obj.is_a?(String) && obj.match?(Parser::URI_REGEXP)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,17 @@
|
|
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.extract(response_body, %w[http https])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Webmention
|
4
|
+
class Request
|
5
|
+
# Defaults derived from Webmention specification examples.
|
6
|
+
# @see https://www.w3.org/TR/webmention/#limits-on-get-requests
|
7
|
+
HTTP_CLIENT_OPTS = {
|
8
|
+
follow: {
|
9
|
+
max_hops: 20
|
10
|
+
},
|
11
|
+
headers: {
|
12
|
+
accept: '*/*',
|
13
|
+
user_agent: 'Webmention Client (https://rubygems.org/gems/webmention)'
|
14
|
+
},
|
15
|
+
timeout_options: {
|
16
|
+
connect_timeout: 5,
|
17
|
+
read_timeout: 5
|
18
|
+
}
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
private_constant :HTTP_CLIENT_OPTS
|
22
|
+
|
23
|
+
# @return [Symbol]
|
24
|
+
attr_reader :method
|
25
|
+
|
26
|
+
# @return [HTTP::URI]
|
27
|
+
attr_reader :uri
|
28
|
+
|
29
|
+
# @return [Hash]
|
30
|
+
attr_reader :options
|
31
|
+
|
32
|
+
# Send an HTTP GET request to the supplied URL.
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# Request.get('https://jgarber.example/posts/100')
|
36
|
+
#
|
37
|
+
# @param url [String]
|
38
|
+
#
|
39
|
+
# @return [Webmention::Response, Webmention::ErrorResponse]
|
40
|
+
def self.get(url)
|
41
|
+
new(:get, url).perform
|
42
|
+
end
|
43
|
+
|
44
|
+
# Send an HTTP POST request with form-encoded data to the supplied URL.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# Request.post(
|
48
|
+
# 'https://aaronpk.example/webmention',
|
49
|
+
# source: 'https://jgarber.examples/posts/100',
|
50
|
+
# target: 'https://aaronpk.example/notes/1',
|
51
|
+
# vouch: 'https://tantek.example/notes/1'
|
52
|
+
# )
|
53
|
+
#
|
54
|
+
# @param url [String]
|
55
|
+
# @param options [Hash{Symbol => String}]
|
56
|
+
# @option options [String] :source
|
57
|
+
# An absolute URL representing a source document.
|
58
|
+
# @option options [String] :target
|
59
|
+
# An absolute URL representing a target document.
|
60
|
+
# @param vouch [String]
|
61
|
+
# An absolute URL representing a document vouching for the source document.
|
62
|
+
# See https://indieweb.org/Vouch for additional details.
|
63
|
+
#
|
64
|
+
# @return [Webmention::Response, Webmention::ErrorResponse]
|
65
|
+
def self.post(url, **options)
|
66
|
+
new(:post, url, form: options.slice(:source, :target, :vouch)).perform
|
67
|
+
end
|
68
|
+
|
69
|
+
# Create a new Webmention::Request.
|
70
|
+
#
|
71
|
+
# @param method [Symbol]
|
72
|
+
# @param url [String]
|
73
|
+
# @param options [Hash{Symbol => String}]
|
74
|
+
#
|
75
|
+
# @return [Webmention::Request]
|
76
|
+
def initialize(method, url, **options)
|
77
|
+
@method = method.to_sym
|
78
|
+
@uri = HTTP::URI.parse(url.to_s)
|
79
|
+
@options = options
|
80
|
+
end
|
81
|
+
|
82
|
+
# :nocov:
|
83
|
+
# @return [String]
|
84
|
+
def inspect
|
85
|
+
"#<#{self.class}:#{format('%#0x', object_id)} " \
|
86
|
+
"method: #{method.upcase}, " \
|
87
|
+
"url: #{uri}>"
|
88
|
+
end
|
89
|
+
# :nocov:
|
90
|
+
|
91
|
+
# Submit the Webmention::Request.
|
92
|
+
#
|
93
|
+
# @return [Webmention::Response, Webmention::ErrorResponse]
|
94
|
+
def perform
|
95
|
+
Response.new(client.request(method, uri, options), self)
|
96
|
+
rescue HTTP::Error,
|
97
|
+
OpenSSL::SSL::SSLError => e
|
98
|
+
ErrorResponse.new(e.message, self)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def client
|
104
|
+
@client ||= HTTP::Client.new(HTTP_CLIENT_OPTS)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Webmention
|
4
|
+
class Response
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
# @return [Webmention::Request]
|
8
|
+
attr_reader :request
|
9
|
+
|
10
|
+
# @!method
|
11
|
+
# @return [HTTP::Headers]
|
12
|
+
def_delegator :@response, :headers
|
13
|
+
|
14
|
+
# @!method
|
15
|
+
# @return [HTTP::Response::Body]
|
16
|
+
def_delegator :@response, :body
|
17
|
+
|
18
|
+
# @!method
|
19
|
+
# @return [Integer]
|
20
|
+
def_delegator :@response, :code
|
21
|
+
|
22
|
+
# @!method
|
23
|
+
# @return [String]
|
24
|
+
def_delegator :@response, :reason
|
25
|
+
|
26
|
+
# !@method
|
27
|
+
# @return [String]
|
28
|
+
def_delegator :@response, :mime_type
|
29
|
+
|
30
|
+
# !@method
|
31
|
+
# @return [HTTP::URI]
|
32
|
+
def_delegator :@response, :uri
|
33
|
+
|
34
|
+
# Create a new Webmention::Response.
|
35
|
+
#
|
36
|
+
# Instances of this class represent completed HTTP requests, the details
|
37
|
+
# of which may be accessed using the delegated <code>#code</code> and
|
38
|
+
# <code>#reason</code>) instance methods.
|
39
|
+
#
|
40
|
+
# @param response [HTTP::Response]
|
41
|
+
# @param request [Webmention::Request]
|
42
|
+
#
|
43
|
+
# @return [Webmention::Response]
|
44
|
+
def initialize(response, request)
|
45
|
+
@response = response
|
46
|
+
@request = request
|
47
|
+
end
|
48
|
+
|
49
|
+
# :nocov:
|
50
|
+
# @return [String]
|
51
|
+
def inspect
|
52
|
+
"#<#{self.class}:#{format('%#0x', object_id)} " \
|
53
|
+
"code: #{code.inspect}, " \
|
54
|
+
"reason: #{reason}, " \
|
55
|
+
"url: #{request.uri}>"
|
56
|
+
end
|
57
|
+
# :nocov:
|
58
|
+
|
59
|
+
# @return [Boolean]
|
60
|
+
def ok?
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Webmention
|
4
|
+
class Url
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
# @return [HTTP::URI]
|
8
|
+
attr_reader :uri
|
9
|
+
|
10
|
+
# @!method
|
11
|
+
# @return [String]
|
12
|
+
def_delegator :uri, :to_s
|
13
|
+
|
14
|
+
# Create a new Webmention::Url.
|
15
|
+
#
|
16
|
+
# @param url [String, HTTP::URI, #to_s] An absolute URL.
|
17
|
+
#
|
18
|
+
# @return [Webmention::Url]
|
19
|
+
def initialize(url)
|
20
|
+
@uri = HTTP::URI.parse(url.to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
# :nocov:
|
24
|
+
# @return [String]
|
25
|
+
def inspect
|
26
|
+
"#<#{self.class}:#{format('%#0x', object_id)} " \
|
27
|
+
"uri: #{uri}>"
|
28
|
+
end
|
29
|
+
# :nocov:
|
30
|
+
|
31
|
+
# @return [Webmention::Response, Webmention::ErrorResponse]
|
32
|
+
def response
|
33
|
+
@response ||= Request.get(uri)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String, nil]
|
37
|
+
def webmention_endpoint
|
38
|
+
@webmention_endpoint ||= IndieWeb::Endpoints::Parser.new(response).results[:webmention] if response.ok?
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Boolean]
|
42
|
+
def webmention_endpoint?
|
43
|
+
!webmention_endpoint.nil?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Webmention
|
4
|
+
class Verification
|
5
|
+
# @param source_url [Webmention::Url]
|
6
|
+
# @param target_url [Webmention::Url]
|
7
|
+
# @param vouch_url [Webmention::Url]
|
8
|
+
def initialize(source_url, target_url, vouch_url: nil)
|
9
|
+
@source_url = source_url
|
10
|
+
@target_url = target_url
|
11
|
+
@vouch_url = vouch_url
|
12
|
+
end
|
13
|
+
|
14
|
+
# :nocov:
|
15
|
+
# @return [String]
|
16
|
+
def inspect
|
17
|
+
"#<#{self.class}:#{format('%#0x', object_id)} " \
|
18
|
+
"source_url: #{source_url} " \
|
19
|
+
"target_url: #{target_url} " \
|
20
|
+
"vouch_url: #{vouch_url}>"
|
21
|
+
end
|
22
|
+
# :nocov:
|
23
|
+
|
24
|
+
# @return [Boolean]
|
25
|
+
def source_mentions_target?
|
26
|
+
@source_mentions_target ||= mentioned_urls(source_url.response).any?(target_url.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Boolean]
|
30
|
+
def verified?
|
31
|
+
return source_mentions_target? unless verify_vouch?
|
32
|
+
|
33
|
+
source_mentions_target? && vouch_mentions_source?
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Boolean]
|
37
|
+
def verify_vouch?
|
38
|
+
!vouch_url.nil? && !vouch_url.to_s.strip.empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Boolean]
|
42
|
+
def vouch_mentions_source?
|
43
|
+
@vouch_mentions_source ||=
|
44
|
+
verify_vouch? && mentioned_domains(vouch_url.response).any?(source_url.uri.host)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @return [Webmention::Url]
|
50
|
+
attr_reader :source_url
|
51
|
+
|
52
|
+
# @return [Webmention::Url]
|
53
|
+
attr_reader :target_url
|
54
|
+
|
55
|
+
# @return [Webmention::Url]
|
56
|
+
attr_reader :vouch_url
|
57
|
+
|
58
|
+
# @param response [Webmention::Response]
|
59
|
+
#
|
60
|
+
# @raise (see Webmention::Client#mentioned_urls)
|
61
|
+
#
|
62
|
+
# @return [Array<String>]
|
63
|
+
def mentioned_domains(response)
|
64
|
+
mentioned_urls(response).map { |url| HTTP::URI.parse(url).host }.uniq
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param response [Webmention::Response]
|
68
|
+
#
|
69
|
+
# @raise (see Webmention::Client#mentioned_urls)
|
70
|
+
#
|
71
|
+
# @return [Array<String>]
|
72
|
+
def mentioned_urls(response)
|
73
|
+
Client.registered_parsers[response.mime_type]
|
74
|
+
.new(response.body, response.uri)
|
75
|
+
.results
|
76
|
+
.uniq
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/webmention/version.rb
CHANGED
data/lib/webmention.rb
CHANGED
@@ -1,38 +1,100 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
require 'http'
|
4
6
|
require 'indieweb/endpoints'
|
5
7
|
require 'nokogiri'
|
6
8
|
|
7
|
-
|
8
|
-
require 'webmention/exceptions'
|
9
|
-
|
10
|
-
require 'webmention/client'
|
9
|
+
require_relative 'webmention/version'
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
require_relative 'webmention/client'
|
12
|
+
require_relative 'webmention/url'
|
13
|
+
require_relative 'webmention/request'
|
14
|
+
require_relative 'webmention/response'
|
15
|
+
require_relative 'webmention/error_response'
|
16
|
+
require_relative 'webmention/verification'
|
15
17
|
|
16
|
-
|
18
|
+
require_relative 'webmention/parser'
|
19
|
+
require_relative 'webmention/parsers/html_parser'
|
20
|
+
require_relative 'webmention/parsers/json_parser'
|
21
|
+
require_relative 'webmention/parsers/plaintext_parser'
|
17
22
|
|
18
23
|
module Webmention
|
19
|
-
#
|
20
|
-
#
|
24
|
+
# Retrieve unique URLs mentioned by the provided URL.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# Webmention.mentioned_urls('https://jgarber.example/posts/100')
|
28
|
+
#
|
29
|
+
# @param url [String, HTTP::URI, #to_s] An absolute URL.
|
21
30
|
#
|
22
|
-
#
|
31
|
+
# @raise [NoMethodError]
|
32
|
+
# Raised when response is a Webmention::ErrorResponse or response is of an
|
33
|
+
# unsupported MIME type.
|
23
34
|
#
|
24
|
-
# @
|
25
|
-
|
26
|
-
|
27
|
-
Client.new(source)
|
35
|
+
# @return [Array<String>]
|
36
|
+
def self.mentioned_urls(url)
|
37
|
+
Client.new(url).mentioned_urls
|
28
38
|
end
|
29
39
|
|
30
|
-
# Send a webmention from
|
40
|
+
# Send a webmention from a source URL to a target URL.
|
41
|
+
#
|
42
|
+
# @example Send a webmention
|
43
|
+
# source = 'https://jgarber.example/posts/100'
|
44
|
+
# target = 'https://aaronpk.example/notes/1'
|
45
|
+
# Webmention.send_webmention(source, target)
|
46
|
+
#
|
47
|
+
# @example Send a webmention with a vouch URL
|
48
|
+
# source = 'https://jgarber.example/posts/100'
|
49
|
+
# target = 'https://aaronpk.example/notes/1'
|
50
|
+
# Webmention.send_webmention(source, target, vouch: 'https://tantek.example/notes/1')
|
51
|
+
#
|
52
|
+
# @param source [String, HTTP::URI, #to_s]
|
53
|
+
# An absolute URL representing a source document.
|
54
|
+
# @param target [String, HTTP::URI, #to_s]
|
55
|
+
# An absolute URL representing a target document.
|
56
|
+
# @param vouch [String, HTTP::URI, #to_s]
|
57
|
+
# An absolute URL representing a document vouching for the source document.
|
58
|
+
# See https://indieweb.org/Vouch for additional details.
|
59
|
+
#
|
60
|
+
# @return [Webmention::Response, Webmention::ErrorResponse]
|
61
|
+
def self.send_webmention(source, target, vouch: nil)
|
62
|
+
Client.new(source, vouch: vouch).send_webmention(target)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Send webmentions from a source URL to multiple target URLs.
|
66
|
+
#
|
67
|
+
# @example Send multiple webmentions
|
68
|
+
# source = 'https://jgarber.example/posts/100'
|
69
|
+
# targets = ['https://aaronpk.example/notes/1', 'https://adactio.example/notes/1']
|
70
|
+
# Webmention.send_webmentions(source, targets)
|
71
|
+
#
|
72
|
+
# @example Send multiple webmentions with a vouch URL
|
73
|
+
# source = 'https://jgarber.example/posts/100'
|
74
|
+
# targets = ['https://aaronpk.example/notes/1', 'https://adactio.example/notes/1']
|
75
|
+
# Webmention.send_webmentions(source, targets, vouch: 'https://tantek.example/notes/1')
|
76
|
+
#
|
77
|
+
# @param source [String, HTTP::URI, #to_s]
|
78
|
+
# An absolute URL representing a source document.
|
79
|
+
# @param *targets [Array<String, HTTP::URI, #to_s>]
|
80
|
+
# An array of absolute URLs representing multiple target documents.
|
81
|
+
# @param vouch [String, HTTP::URI, #to_s]
|
82
|
+
# An absolute URL representing a document vouching for the source document.
|
83
|
+
# See https://indieweb.org/Vouch for additional details.
|
84
|
+
#
|
85
|
+
# @return [Array<Webmention::Response, Webmention::ErrorResponse>]
|
86
|
+
def self.send_webmentions(source, *targets, vouch: nil)
|
87
|
+
Client.new(source, vouch: vouch).send_webmentions(*targets)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Verify that a source URL links to a target URL.
|
91
|
+
#
|
92
|
+
# @param (see Webmention.send_webmention)
|
93
|
+
#
|
94
|
+
# @raise (see Webmention::Client#mentioned_urls)
|
31
95
|
#
|
32
|
-
# @
|
33
|
-
|
34
|
-
|
35
|
-
def self.send_mention(source, target)
|
36
|
-
client(source).send_mention(target)
|
96
|
+
# @return [Boolean]
|
97
|
+
def self.verify_webmention(source, target, vouch: nil)
|
98
|
+
Client.new(source, vouch: vouch).verify_webmention(target)
|
37
99
|
end
|
38
100
|
end
|
data/webmention.gemspec
CHANGED
@@ -1,30 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'lib/webmention/version'
|
2
4
|
|
3
5
|
Gem::Specification.new do |spec|
|
4
|
-
spec.required_ruby_version =
|
6
|
+
spec.required_ruby_version = '>= 2.6', '< 4'
|
5
7
|
|
6
8
|
spec.name = 'webmention'
|
7
9
|
spec.version = Webmention::VERSION
|
8
|
-
spec.authors = ['
|
9
|
-
spec.email = ['
|
10
|
+
spec.authors = ['Jason Garber']
|
11
|
+
spec.email = ['jason@sixtwothree.org']
|
10
12
|
|
11
13
|
spec.summary = 'Webmention notification client'
|
12
14
|
spec.description = 'A Ruby gem for sending Webmention notifications.'
|
13
15
|
spec.homepage = 'https://github.com/indieweb/webmention-client-ruby'
|
14
16
|
spec.license = 'Apache-2.0'
|
15
17
|
|
16
|
-
spec.files = Dir.
|
17
|
-
|
18
|
-
|
18
|
+
spec.files = Dir['lib/**/*'].reject { |f| File.directory?(f) }
|
19
|
+
spec.files += %w[LICENSE CHANGELOG.md CONTRIBUTING.md README.md USAGE.md]
|
20
|
+
spec.files += %w[webmention.gemspec]
|
19
21
|
|
20
22
|
spec.require_paths = ['lib']
|
21
23
|
|
22
|
-
spec.metadata
|
23
|
-
|
24
|
+
spec.metadata = {
|
25
|
+
'bug_tracker_uri' => "#{spec.homepage}/issues",
|
26
|
+
'changelog_uri' => "#{spec.homepage}/blob/v#{spec.version}/CHANGELOG.md",
|
27
|
+
'rubygems_mfa_required' => 'true'
|
28
|
+
}
|
24
29
|
|
25
|
-
spec.add_runtime_dependency '
|
26
|
-
spec.add_runtime_dependency '
|
27
|
-
spec.add_runtime_dependency '
|
28
|
-
spec.add_runtime_dependency 'indieweb-endpoints', '~> 3.0'
|
29
|
-
spec.add_runtime_dependency 'nokogiri', '~> 1.10'
|
30
|
+
spec.add_runtime_dependency 'http', '~> 5.0'
|
31
|
+
spec.add_runtime_dependency 'indieweb-endpoints', '~> 7.1'
|
32
|
+
spec.add_runtime_dependency 'nokogiri', '~> 1.13'
|
30
33
|
end
|