A library for handling DID identifiers used in Bluesky AT Protocol

added PLCImporter

+131
+2
lib/didkit.rb
··· 2 3 require_relative "didkit/did" 4 require_relative "didkit/document" 5 require_relative "didkit/resolver" 6 require_relative "didkit/version" 7
··· 2 3 require_relative "didkit/did" 4 require_relative "didkit/document" 5 + require_relative "didkit/plc_importer" 6 + require_relative "didkit/plc_operation" 7 require_relative "didkit/resolver" 8 require_relative "didkit/version" 9
+73
lib/didkit/plc_importer.rb
···
··· 1 + require 'json' 2 + require 'open-uri' 3 + require 'time' 4 + 5 + require_relative 'plc_operation' 6 + 7 + module DIDKit 8 + class PLCImporter 9 + PLC_SERVICE = 'plc.directory' 10 + MAX_PAGE = 1000 11 + 12 + attr_accessor :ignore_errors, :last_date 13 + 14 + def initialize(since: nil) 15 + if since.to_s == 'beginning' 16 + @last_date = nil 17 + elsif since.is_a?(String) 18 + @last_date = Time.parse(since) 19 + elsif since 20 + @last_date = since 21 + else 22 + @last_date = Time.now 23 + @eof = true 24 + end 25 + 26 + @ignore_errors = false 27 + end 28 + 29 + def plc_service 30 + PLC_SERVICE 31 + end 32 + 33 + def get_export(args = {}) 34 + url = URI("https://#{plc_service}/export") 35 + url.query = URI.encode_www_form(args) 36 + 37 + data = URI.open(url).read 38 + data.lines.map(&:strip).reject(&:empty?).map { |x| JSON.parse(x) } 39 + end 40 + 41 + def fetch_page 42 + request_time = Time.now 43 + 44 + query = @last_date ? { :after => @last_date.utc.iso8601(6) } : {} 45 + rows = get_export(query) 46 + 47 + operations = rows.filter_map do |json| 48 + begin 49 + PLCOperation.new(json) 50 + rescue PLCOperation::FormatError => e 51 + ignore_errors ? nil : raise 52 + end 53 + end 54 + 55 + @last_date = operations.last&.created_at || request_time 56 + @eof = (rows.length < MAX_PAGE) 57 + 58 + operations 59 + end 60 + 61 + def fetch(&block) 62 + loop do 63 + operations = fetch_page 64 + block.call(operations) 65 + break if eof? 66 + end 67 + end 68 + 69 + def eof? 70 + !!@eof 71 + end 72 + end 73 + end
+56
lib/didkit/plc_operation.rb
···
··· 1 + require 'time' 2 + 3 + module DIDKit 4 + class PLCOperation 5 + class FormatError < StandardError 6 + end 7 + 8 + attr_reader :did, :created_at, :type, :pds_endpoint, :handles 9 + 10 + def initialize(json) 11 + @did = json['did'] 12 + raise FormatError, "Missing DID" if @did.nil? 13 + raise FormatError, "Invalid DID" unless @did.is_a?(String) && @did.start_with?('did:') 14 + 15 + timestamp = json['createdAt'] 16 + raise FormatError, "Missing createdAt" if timestamp.nil? 17 + raise FormatError, "Invalid createdAt" unless timestamp.is_a?(String) 18 + 19 + @created_at = Time.parse(timestamp) 20 + 21 + operation = json['operation'] 22 + raise FormatError, "Missing operation key" if operation.nil? 23 + raise FormatError, "Invalid operation data" unless operation.is_a?(Hash) 24 + 25 + type = operation['type'] 26 + raise FormatError, "Missing type" if type.nil? 27 + 28 + @type = type.to_sym 29 + return unless @type == :plc_operation 30 + 31 + services = operation['services'] 32 + raise FormatError, "Missing services key" if services.nil? 33 + raise FormatError, "Invalid services data" unless services.is_a?(Hash) 34 + 35 + if pds = services['atproto_pds'] 36 + raise FormatError, "Invalid PDS data" unless pds.is_a?(Hash) 37 + raise FormatError, "Missing PDS type" unless pds['type'] 38 + raise FormatError, "Invalid PDS type" unless pds['type'] == 'AtprotoPersonalDataServer' 39 + raise FormatError, "Missing PDS endpoint" unless pds['endpoint'] 40 + raise FormatError, "Invalid PDS endpoint" unless pds['endpoint'].is_a?(String) && pds['endpoint'] =~ %r(://) 41 + 42 + @pds_endpoint = pds['endpoint'] 43 + end 44 + 45 + if aka = operation['alsoKnownAs'] 46 + raise FormatError, "Invalid alsoKnownAs" unless aka.is_a?(Array) 47 + raise FormatError, "Invalid alsoKnownAs" unless aka.all? { |x| x.is_a?(String) } 48 + raise FormatError, "Invalid alsoKnownAs" unless aka.all? { |x| x =~ %r(\Aat://[^/]+\z) } 49 + 50 + @handles = aka.map { |x| x.gsub('at://', '') } 51 + else 52 + @handles = [] 53 + end 54 + end 55 + end 56 + end