module Jekyll::WebmentionIO

Constants

EXCEPTIONS
TIMEFRAMES
VERSION

Attributes

api_suffix[W]
cache_files[R]

define simple getters and setters

cache_folder[R]

define simple getters and setters

config[R]

define simple getters and setters

file_prefix[R]

define simple getters and setters

jekyll_config[R]

define simple getters and setters

js_handler[R]

define simple getters and setters

supported_templates[R]

define simple getters and setters

types[R]

define simple getters and setters

Public Class Methods

api_path=(path) click to toggle source

Setter

# File lib/jekyll/webmention_io.rb, line 74
def self.api_path=(path)
  @api_endpoint = "#{@api_url}/#{path}"
end
bootstrap(site) click to toggle source
# File lib/jekyll/webmention_io.rb, line 50
def self.bootstrap(site)
  @site = site
  @jekyll_config = site.config
  @config = @jekyll_config["webmentions"] || {}

  # Set up the cache folder & files
  @cache_folder = site.in_source_dir(@config["cache_folder"] || ".jekyll-cache")
  Dir.mkdir(@cache_folder) unless File.exist?(@cache_folder)
  @file_prefix = ""
  @file_prefix = "webmention_io_" unless @cache_folder.include? "webmention"
  @cache_files = {
    "incoming" => cache_file("received.yml"),
    "outgoing" => cache_file("outgoing.yml"),
    "bad_uris" => cache_file("bad_uris.yml"),
    "lookups"  => cache_file("lookups.yml")
  }
  @cache_files.each_value do |file|
    dump_yaml(file) unless File.exist?(file)
  end

  @js_handler = WebmentionIO::JSHandler.new(site)
end
cache_file(filename) click to toggle source

Helpers

# File lib/jekyll/webmention_io.rb, line 79
def self.cache_file(filename)
  Jekyll.sanitized_path(@cache_folder, "#{@file_prefix}#{filename}")
end
cache_lookup_dates(lookups) click to toggle source
# File lib/jekyll/webmention_io.rb, line 144
def self.cache_lookup_dates(lookups)
  cache_file = get_cache_file_path "lookups"
  dump_yaml(cache_file, lookups)

  log "msg", "Lookups have been cached."
end
cache_webmentions(which, webmentions) click to toggle source
# File lib/jekyll/webmention_io.rb, line 94
def self.cache_webmentions(which, webmentions)
  if %w(incoming outgoing).include? which
    cache_file = get_cache_file_path which
    dump_yaml(cache_file, webmentions)

    log "msg", "#{which.capitalize} webmentions have been cached."
  end
end
dump_yaml(file, data = {}) click to toggle source

Utility Method Caches given data to memory and then proceeds to write data as YAML string into file path.

Returns nothing.

# File lib/jekyll/webmention_io.rb, line 315
def self.dump_yaml(file, data = {})
  @webmention_data_cache[file] = data
  File.open(file, "wb") { |f| f.puts YAML.dump(data) }
end
gather_documents(site) click to toggle source
# File lib/jekyll/webmention_io.rb, line 103
def self.gather_documents(site)
  documents = site.posts.docs.clone

  if @config.dig("pages") == true
    log "info", "Including site pages."
    documents.concat site.pages.clone
  end

  collections = @config.dig("collections")
  if collections
    log "info", "Adding collections."
    site.collections.each do |name, collection|
      # skip _posts
      next if name == "posts"

      unless collections.is_a?(Array) && !collections.include?(name)
        documents.concat collection.docs.clone
      end
    end
  end

  return documents
end
get_cache_file_path(key) click to toggle source
# File lib/jekyll/webmention_io.rb, line 83
def self.get_cache_file_path(key)
  @cache_files[key] || false
end
get_date_from_string(text) click to toggle source

supported: daily, weekly, monthly, yearly, every X days|weeks|months|years

# File lib/jekyll/webmention_io.rb, line 186
def self.get_date_from_string(text)
  today = Date.today
  pattern = /every\s(?:(\d+)\s)?(day|week|month|year)s?/
  matches = text.match(pattern)
  unless matches
    text = if text == "daily"
             "every 1 day"
           else
             "every 1 #{text.sub("ly", "")}"
           end
    matches = text.match(pattern)
  end
  n = matches[1] ? matches[1].to_i : 1
  unit = matches[2]
  # weeks aren't natively supported in Ruby
  if unit == "week"
    n *= 7
    unit = "day"
  end
  # dynamic method call
  return today.send "prev_#{unit}", n
end
get_response(api_params) click to toggle source
# File lib/jekyll/webmention_io.rb, line 127
def self.get_response(api_params)
  api_params << @api_suffix
  url = "#{@api_endpoint}?#{api_params}"
  log "info", "Sending request to #{url}."
  source = get_uri_source(url)
  if source
    JSON.parse(source)
  else
    {}
  end
end
get_template_contents(template) click to toggle source
# File lib/jekyll/webmention_io.rb, line 252
def self.get_template_contents(template)
  template_file = template_file(template)
  @template_content_cache[template_file] ||= begin
    log "info", "Template file: #{template_file}"
    File.read(template_file)
  end
end
get_timeframe_from_date(time) click to toggle source
# File lib/jekyll/webmention_io.rb, line 172
def self.get_timeframe_from_date(time)
  date = time.to_date
  timeframe = nil
  TIMEFRAMES.each do |key, value|
    if date.to_date > get_date_from_string(value)
      timeframe = key
      break
    end
  end
  timeframe ||= "older"
  return timeframe
end
get_uri_source(uri, redirect_limit = 10, original_uri = false) click to toggle source

Connections

# File lib/jekyll/webmention_io.rb, line 278
def self.get_uri_source(uri, redirect_limit = 10, original_uri = false)
  original_uri ||= uri
  return false unless uri_ok?(uri)

  if redirect_limit.positive?
    response = get_http_response(uri)
    case response
    when Net::HTTPSuccess then
      return response.body.force_encoding("UTF-8")
    when Net::HTTPRedirection then
      redirect_to = URI.parse(URI.encode(response["location"]))
      redirect_to = redirect_to.relative? ? "#{original_uri.scheme}://#{original_uri.host}" + redirect_to.to_s : redirect_to.to_s
      return get_uri_source(redirect_to, redirect_limit - 1, original_uri)
    else
      uri_is_not_ok(uri)
      return false
    end
  else
    log("warn", "too many redirects for #{original_uri}") if original_uri
    uri_is_not_ok(uri)
    return false
  end
end
get_webmention_endpoint(uri) click to toggle source
# File lib/jekyll/webmention_io.rb, line 209
def self.get_webmention_endpoint(uri)
  # log "info", "Looking for webmention endpoint at #{uri}"
  begin
    endpoint = Webmention::Client.supports_webmention?(uri)
    unless endpoint
      log("info", "Could not find a webmention endpoint at #{uri}")
      uri_is_not_ok(uri)
    end
  rescue StandardError => e
    log "info", "Endpoint lookup failed for #{uri}: #{e.message}"
    uri_is_not_ok(uri)
    endpoint = false
  end
  endpoint
end
html_templates() click to toggle source
# File lib/jekyll/webmention_io.rb, line 260
def self.html_templates
  proofer = if @config['html_proofer'] == true
              ' data-proofer-ignore'
            else
              ''
            end
  @html_templates ||= begin
    templates = +"" # unfrozen String
    supported_templates.each do |template|
      templates << "<template style=\"display:none\" id=\"webmention-#{template}\"#{proofer}>"
      templates << get_template_contents(template)
      templates << "</template>"
    end
    templates
  end
end
load_yaml(file) click to toggle source

Utility Method Attempts to first load data cached in memory and then proceeds to safely parse given YAML file path and return data.

Returns empty hash if parsing fails to return data

# File lib/jekyll/webmention_io.rb, line 325
def self.load_yaml(file)
  @webmention_data_cache[file] || SafeYAML.load_file(file) || {}
end
log(type, message) click to toggle source
# File lib/jekyll/webmention_io.rb, line 302
def self.log(type, message)
  debug = !!@config.dig("debug")
  if debug || %w(error msg).include?(type)
    type = "info" if type == "msg"
    Jekyll.logger.method(type).call("#{@logger_prefix} #{message}")
  end
end
post_should_be_throttled?(post, item_date, last_lookup) click to toggle source

allowed throttles: last_week, last_month, last_year, older allowed values: daily, weekly, monthly, yearly, every X days|weeks|months|years

# File lib/jekyll/webmention_io.rb, line 153
def self.post_should_be_throttled?(post, item_date, last_lookup)
  throttles = @config.dig("throttle_lookups")
  if throttles && item_date && last_lookup
    age = get_timeframe_from_date(item_date)
    throttle = throttles.dig(age)
    if throttle && last_lookup >= get_date_from_string(throttle)
      log "info", "Throttling #{post.data["title"]} (Only checking it #{throttle})"
      return true
    end
  end
  return false
end
read_cached_webmentions(which) click to toggle source
# File lib/jekyll/webmention_io.rb, line 87
def self.read_cached_webmentions(which)
  return {} unless %w(incoming outgoing).include?(which)

  cache_file = get_cache_file_path which
  load_yaml(cache_file)
end
read_lookup_dates() click to toggle source
# File lib/jekyll/webmention_io.rb, line 139
def self.read_lookup_dates
  cache_file = get_cache_file_path "lookups"
  load_yaml(cache_file)
end
template_file(template) click to toggle source
# File lib/jekyll/webmention_io.rb, line 240
def self.template_file(template)
  @template_file_cache[template] ||= begin
    configured_template = @config.dig("templates", template)
    if configured_template
      log "info", "Using custom #{template} template from site source"
      @site.in_source_dir configured_template
    else
      File.expand_path("templates/#{template}.html", __dir__)
    end
  end
end
uri_ok?(uri) click to toggle source
# File lib/jekyll/webmention_io.rb, line 365
def self.uri_ok?(uri)
  uri = URI.parse(URI.encode(uri.to_s))
  now = Time.now.to_s
  bad_uris = load_yaml(@cache_files["bad_uris"])
  if bad_uris.key? uri.host
    last_checked = DateTime.parse(bad_uris[uri.host])
    cache_bad_uris_for = @config["cache_bad_uris_for"] || 1 # in days
    recheck_at = last_checked.next_day(cache_bad_uris_for).to_s
    return false if recheck_at > now
  end
  return true
end
webmention(source, target, endpoint) click to toggle source
# File lib/jekyll/webmention_io.rb, line 225
def self.webmention(source, target, endpoint)
  log "info", "Sending webmention of #{target} in #{source}"
  # return `curl -s -i -d \"source=#{source}&target=#{target}\" -o /dev/null #{endpoint}`
  mention = Webmention::Client.send_mention(endpoint, source, target, true)
  case mention.response
  when Net::HTTPOK, Net::HTTPCreated, Net::HTTPAccepted
    log "info", "Webmention successful!"
    mention.response.body
  else
    log "info", mention.inspect
    log "info", "Webmention failed, but will remain queued for next time"
    false
  end
end

Private Class Methods

get_http_response(uri) click to toggle source

Private Methods

# File lib/jekyll/webmention_io.rb, line 331
def self.get_http_response(uri)
  uri  = URI.parse(URI.encode(uri))
  http = Net::HTTP.new(uri.host, uri.port)
  http.read_timeout = 10

  if uri.scheme == "https"
    http.use_ssl = true
    http.ciphers = "ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:-LOW"
    http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  end

  begin
    request  = Net::HTTP::Get.new(uri.request_uri)
    response = http.request(request)
    return response
  rescue *EXCEPTIONS => e
    log "warn", "Got an error checking #{uri}: #{e}"
    uri_is_not_ok(uri)
    return false
  end
end
uri_is_not_ok(uri) click to toggle source

Cache bad URLs for a bit

# File lib/jekyll/webmention_io.rb, line 354
def self.uri_is_not_ok(uri)
  uri = URI.parse(URI.encode(uri.to_s))
  # Never cache webmention.io in here
  return if uri.host == "webmention.io"

  cache_file = @cache_files["bad_uris"]
  bad_uris = load_yaml(cache_file)
  bad_uris[uri.host] = Time.now.to_s
  dump_yaml(cache_file, bad_uris)
end