Class: Abqari::Indieweb::Sender

Inherits:
Object
  • Object
show all
Defined in:
lib/abqari/indieweb.rb

Overview

Sender — POSTs webmention notifications to external sites linked from our posts. The sent-log lives at vendor/webmentions/sent.yml so re-builds don't double-send.

Endpoint discovery follows the webmention spec:

1. HEAD the target; check `Link: <...>; rel="webmention"`
2. Fall back to GET; check `<link rel="webmention">` in <head>

Only posts published within send_window_days (default 30) are candidates — historical posts have already received their notifications in earlier builds, and replaying them on every CI run wastes outbound HTTP and can look like spam.

Constant Summary collapse

MAX_TARGET_BYTES =

1 MB cap for endpoint discovery HTML

1 * 1024 * 1024
/<(?<url>[^>]+)>\s*;[^,]*?rel\s*=\s*"?webmention"?/i.freeze
HTML_WEBMENTION_RE =
%r{<link\b[^>]*?rel=["']?webmention["']?[^>]*?href=["']([^"']+)["']|<link\b[^>]*?href=["']([^"']+)["'][^>]*?rel=["']?webmention["']?}i.freeze

Instance Method Summary collapse

Constructor Details

#initialize(site) ⇒ Sender

Returns a new instance of Sender.



297
298
299
300
# File 'lib/abqari/indieweb.rb', line 297

def initialize(site)
  @site = site
  @cfg  = Indieweb.config_for(site)
end

Instance Method Details

#enabled?Boolean

Returns:

  • (Boolean)


302
303
304
# File 'lib/abqari/indieweb.rb', line 302

def enabled?
  Indieweb.enabled?(@site) && @cfg['send_webmentions'] == true
end

#log_pathObject



306
307
308
# File 'lib/abqari/indieweb.rb', line 306

def log_path
  File.join(@site.site_root, 'vendor', 'webmentions', 'sent.yml')
end

#send_allObject

Walk recently-published posts, extract outbound external links from their rendered bodies, send a notification per (source, target) pair not already in the sent log.



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/abqari/indieweb.rb', line 317

def send_all
  return unless enabled?
  # Hard production gate — same rationale as the syndication
  # Dispatcher. Sending webmentions is an outbound POST to every
  # site a post links to; doing it from a dev build (which includes
  # drafts / future posts and resolves source URLs against the dev
  # `url`) would ping targets with unpublished, localhost-scoped
  # source URLs. Only fire in production.
  return unless @site.respond_to?(:env) && @site.env == :production

  FileUtils.mkdir_p(File.dirname(log_path))
  log = load_log
  cutoff = Date.today - send_window_days
  sent_count = 0

  recent_posts(cutoff).each do |post|
    source_url = absolute_url_for(post)
    next if source_url.nil?

    outbound_targets_for(post).each do |target|
      key = "#{source_url}|#{target}"
      # Skip only when previously SENT successfully. Prior
      # `'no-endpoint'` (transient DNS / 5xx) and `'failed'`
      # (target up but POST rejected) entries are recorded for
      # visibility but get retried on the next build — a single
      # bad build must not permanently exclude a (source, target)
      # pair. Same retry policy as the syndication Dispatcher.
      prev = log['sent'][key]
      next if prev && prev['status'] == 'sent'

      endpoint = discover_endpoint(target)
      unless endpoint
        log['sent'][key] = { 'status' => 'no-endpoint', 'at' => Time.now.utc.iso8601 }
        next
      end

      if post_notification(endpoint, source_url, target)
        log['sent'][key] = { 'status' => 'sent', 'at' => Time.now.utc.iso8601 }
        sent_count += 1
        Log.info "Webmention sent: #{source_url}#{target}"
      else
        log['sent'][key] = { 'status' => 'failed', 'at' => Time.now.utc.iso8601 }
      end
    end
  end

  save_log(log) if sent_count.positive? || log_dirty?(log)
  Log.info "Webmentions: sent #{sent_count} new notification(s)" if sent_count.positive?
end

#send_window_daysObject



310
311
312
# File 'lib/abqari/indieweb.rb', line 310

def send_window_days
  @cfg.fetch('send_window_days', 30).to_i
end