tilde.news/script/send_new_webmentions

103 lines
3.1 KiB
Ruby
Executable File

#!/usr/bin/env ruby
ENV["RAILS_ENV"] ||= "production"
APP_PATH = File.expand_path("../../config/application", __FILE__)
require File.expand_path("../../config/boot", __FILE__)
require APP_PATH
require "webrick"
Rails.application.require_environment!
LAST_STORY_KEY = "webmentions:last_story_id".freeze
def endpoint_from_body(html)
doc = Nokogiri::HTML(html)
if !doc.css('[rel~="webmention"]').css("[href]").empty?
doc.css('[rel~="webmention"]').css("[href]").attribute("href").value
elsif !doc.css('[rel="http://webmention.org/"]').css("[href]").empty?
doc.css('[rel="http://webmention.org/"]').css("[href]").attribute("href").value
elsif !doc.css('[rel="http://webmention.org"]').css("[href]").empty?
doc.css('[rel="http://webmention.org"]').css("[href]").attribute("href").value
end
end
def endpoint_from_headers(header)
return unless header
if (matches = header.match(/<([^>]+)>; rel="[^"]*\s?webmention\s?[^"]*"/))
matches[1]
elsif (matches = header.match(/<([^>]+)>; rel=webmention/))
matches[1]
elsif (matches = header.match(/rel="[^"]*\s?webmention\s?[^"]*"; <([^>]+)>/))
matches[1]
elsif (matches = header.match(/rel=webmention; <([^>]+)>/))
matches[1]
elsif (matches = header.match(/<([^>]+)>; rel="http:\/\/webmention\.org\/?"/))
matches[1]
elsif (matches = header.match(/rel="http:\/\/webmention\.org\/?"; <([^>]+)>/))
matches[1]
end
end
# Some pages could return a relative link as their webmention endpoint.
# We need to translate this relative likn to an absolute one.
def uri_to_absolute(uri, req_uri)
abs_uri = URI.parse(uri)
if abs_uri.host
# Already absolute.
uri
else
abs_uri.host = req_uri.host
abs_uri.scheme = req_uri.scheme
abs_uri.port = req_uri.port
abs_uri
end
end
def send_webmention(source, target, endpoint)
sp = Sponge.new
sp.timeout = 10
# Don't check SSL certificate here for backward compatibility, security risk
# is minimal.
sp.ssl_verify = false
sp.fetch(endpoint.to_s, :post, {
"source" => URI.encode_www_form_component(source),
"target" => URI.encode_www_form_component(target)
}, nil, {}, 3)
end
if __FILE__ == $PROGRAM_NAME
last_story_id = (
Keystore.value_for(LAST_STORY_KEY) ||
Story.order("id desc").limit(1).offset(1).ids.try(:first)
).to_i
Story.where("id > ? AND is_deleted = ?", last_story_id, false).order(:id).each do |s|
# mark it done so we don't hit them again if we or they crash
Keystore.put(LAST_STORY_KEY, s.id)
next if s.url.blank?
sp = Sponge.new
sp.timeout = 10
begin
response = sp.fetch(WEBrick::HTTPUtils.escape(s.url), :get, nil, nil, {
"User-agent" => "#{Rails.application.domain} webmention endpoint lookup"
}, 3)
rescue NoIPsError, DNSError
# other people's DNS issues (usually transient); just skip the webmention
next
end
next unless response
wm_endpoint_raw = endpoint_from_headers(response["link"]) ||
endpoint_from_body(response.body.to_s)
next unless wm_endpoint_raw
wm_endpoint = uri_to_absolute(wm_endpoint_raw, URI.parse(s.url))
send_webmention(s.short_id_url, s.url, wm_endpoint)
last_story_id = s.id
end
end