webmention 5.0.0 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -3
- 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 +30 -0
- data/lib/webmention/parsers/html_parser.rb +36 -28
- data/lib/webmention/parsers/json_parser.rb +31 -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 +17 -14
- metadata +29 -62
- data/.editorconfig +0 -10
- data/.gitignore +0 -34
- data/.reek.yml +0 -8
- data/.rubocop +0 -3
- data/.rubocop.yml +0 -30
- data/.ruby-version +0 -1
- data/.simplecov +0 -12
- data/.travis.yml +0 -15
- data/Gemfile +0 -17
- 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,52 +17,57 @@ 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
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
29
|
+
|
30
|
+
# @return [Array<String>] An array of absolute URLs.
|
22
31
|
def results
|
23
|
-
@results ||=
|
32
|
+
@results ||=
|
33
|
+
extract_urls_from(*url_attributes).map { |url| response_uri.join(url).to_s }
|
34
|
+
.grep(Parser::URI_REGEXP)
|
24
35
|
end
|
25
36
|
|
26
37
|
private
|
27
38
|
|
39
|
+
# @return [Nokogiri::HTML5::Document]
|
28
40
|
def doc
|
29
|
-
|
41
|
+
Nokogiri.HTML5(response_body)
|
30
42
|
end
|
31
43
|
|
32
|
-
|
33
|
-
|
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
|
34
55
|
end
|
35
56
|
|
57
|
+
# @return [Nokogiri::XML::Element]
|
36
58
|
def root_node
|
37
|
-
doc.at_css(
|
59
|
+
doc.at_css(*ROOT_NODE_SELECTORS_ARRAY)
|
38
60
|
end
|
39
61
|
|
62
|
+
# @return [Array<Nokogiri::XML::Attr>]
|
40
63
|
def url_attributes
|
41
|
-
url_nodes.flat_map(&:attribute_nodes).
|
64
|
+
url_nodes.flat_map(&:attribute_nodes).find_all { |attribute| HTML_ATTRIBUTES_MAP.key?(attribute.name) }
|
42
65
|
end
|
43
66
|
|
67
|
+
# @return [Nokogiri::XML::NodeSet]
|
44
68
|
def url_nodes
|
45
69
|
root_node.css(*CSS_SELECTORS_ARRAY)
|
46
70
|
end
|
47
|
-
|
48
|
-
module UrlAttributesParser
|
49
|
-
# @param attributes [Array<Nokogiri::XML::Attr>]
|
50
|
-
# @return [Array<String>]
|
51
|
-
def self.parse(*attributes)
|
52
|
-
attributes.flat_map { |attribute| value_from(attribute) }
|
53
|
-
end
|
54
|
-
|
55
|
-
# @param attribute [Nokogiri::XML::Attr]
|
56
|
-
# @return [String, Array<String>]
|
57
|
-
def self.value_from(attribute)
|
58
|
-
return attribute.value unless attribute.name == 'srcset'
|
59
|
-
|
60
|
-
attribute.value.split(',').map { |value| value.strip.match(/^\S+/).to_s }
|
61
|
-
end
|
62
|
-
end
|
63
71
|
end
|
64
72
|
end
|
65
73
|
end
|
@@ -0,0 +1,31 @@
|
|
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
|
@@ -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::DEFAULT_PARSER.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
|
+
# @option options [String] :vouch
|
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.7', '< 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
|
-
spec.description = 'A Ruby gem for sending Webmention notifications.'
|
14
|
+
spec.description = 'A Ruby gem for sending and verifying 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', '~> 5.0'
|
29
|
-
spec.add_runtime_dependency 'nokogiri', '~> 1.10'
|
30
|
+
spec.add_runtime_dependency 'http', '~> 5.0'
|
31
|
+
spec.add_runtime_dependency 'indieweb-endpoints', '~> 8.0'
|
32
|
+
spec.add_runtime_dependency 'nokogiri', '>= 1.13'
|
30
33
|
end
|