#!/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
