require "openname/version" require 'net/http' require 'json' require 'bitcoin' ## # A toolkit for the Openname distributed identity & naming system module Openname DEFAULT_ENDPOINT = "https://api.nametiles.co/v1/users/" SCHEMA_VERSION = "0.2" USERAGENT = "openname-ruby #{VERSION}" # https://github.com/openname/openname-specifications#usernames OPENNAME_REGEX = /^[a-z0-9_]{1,60}$/ @@endpoint = nil @@suffix = "" @@profile_only = false @@username = nil @@password = nil ## # Current endpoint used by the library def self.endpoint if @@endpoint.nil? return DEFAULT_ENDPOINT else return @@endpoint end end ## # Send basic authentication # Openname.auth(nil,nil) disables basic auth # Disabled by default # def self.auth(username, password) @@username = username @@password = password end ## # Profile only # if +profile_only+ is true, Openname profile will not be wrapped in # { "profile": {} } # def self.profile_only(profile_only) @@profile_only = profile_only end def self.profile_only? @@profile_only end ## # Set suffix appended to openname to +suffix+ # if +suffix+ is +nil+, no suffix will be appended def self.suffix=(suffix) @@suffix = suffix end ## # Suffix appended to openname on each endpoint request def self.suffix if @@suffix.nil? return "" else return @@suffix end end ## # Set endpoint to +url+ # if +url+ is +nil+, +DEFAULT_ENDPOINT+ is used as the endpoint def self.endpoint=(url) @@endpoint = url end ## # Check if the given +openname+ is in proper format # Does not downcase input def self.valid?(openname) Openname::OPENNAME_REGEX.match(openname).nil? ? false : true end ## # Retrieve JSON data stored in Openname record def self.get_json(openname) raise ArgumentError.new("#{openname} is not a valid Openname") if !self.valid?(openname) uri = URI(self.endpoint + "/#{openname.downcase}#{self.suffix}") http = Net::HTTP.new(uri.host,uri.port) http.use_ssl = uri.scheme == "https" ? true : false req = Net::HTTP::Get.new(uri.path, {'User-Agent' => USERAGENT}) req.basic_auth(@@username, @@password) if @@username && @@password res = http.request(req) case res.code.to_s when "404" then raise NameError.new("Openname \"#{openname}\" does not exist") when "200" then json_body = JSON.parse(res.body) if(json_body["status"] && json_body["status"] == "reserved") NameError.new("Openname \"#{openname}\" does not exist. It is reserved.") else # Current ONS resolver always returns 200 # so we need to manually detect names that don't exist if (json_body["error"]) raise NameError.new("Openname \"#{openname}\" does not exist") end # Current ONS resolver wraps profile # so as to also return proof verification # results. openname-ruby ignores verifications if (profile_only? && json_body["profile"]) json_body["profile"] else json_body end end else error = JSON.parse(res.body) raise RuntimeError.new("Openname endpoint returned error: #{error["error"]}") end end ## # Return a +User+ representing the given openname def self.get(openname) User.from_json(self.get_json(openname),openname) end ## # Takes either a bitcoin address or a openname # Returns the bitcoin address associated with the openname or passes through address provided def self.get_bitcoin_address(openname_or_address) return openname_or_address if Bitcoin.valid_address?(openname_or_address) raise ArgumentError.new("#{openname_or_address} is not a valid Openname or Bitcoin address") if !self.valid?(openname_or_address) user = get(openname_or_address) raise NameError.new("Openname user #{openname_or_address} does not have a Bitcoin address") if !Bitcoin.valid_address?(user.bitcoin_address) return user.bitcoin_address end class User def self.from_json(json,openname) User.new(json,openname) end attr_reader :username attr_reader :name_formatted attr_reader :avatar_url attr_reader :cover_url attr_reader :location_formatted attr_reader :website attr_reader :bio attr_reader :angellist_username attr_reader :github_username attr_reader :facebook_username attr_reader :twitter_username attr_reader :instagram_username attr_reader :linkedin_url attr_reader :bitcoin_address attr_reader :bitmessage_address attr_reader :bitcoinotc_username attr_reader :pgp_fingerprint attr_reader :pgp_url attr_reader :orgs attr_reader :schema_version def initialize(json,username) if(json["profile"]) json = json["profile"] end @username = username @name_formatted = json["name"]["formatted"] if json["name"] @avatar_url = json["avatar"]["url"] if json["avatar"] @cover_url = json["cover"]["url"] if json["cover"] @location_formatted = json["location"]["formatted"] if json["location"] @website = json["website"] @bio = json["bio"] @angellist_username = json["angellist"]["username"] if json["angellist"] @github_username = json["github"]["username"] if json["github"] @facebook_username = json["facebook"]["username"] if json["facebook"] @twitter_username = json["twitter"]["username"] if json["twitter"] @instagram_username = json["instagram"]["username"] if json["instagram"] @linkedin_url = json["linkedin"]["url"] if json["linkedin"] @bitcoin_address = json["bitcoin"]["address"] if json["bitcoin"] @bitmessage_address = json["bitmessage"]["address"] if json["bitmessage"] @bitcoinotc_username = json["bitcoinotc"]["username"] if json["bitcoinotc"] @pgp_fingerprint = json["pgp"]["fingerprint"] if json["pgp"] @pgp_url = json["pgp"]["url"] if json["pgp"] @schema_version = json["v"] @orgs = parse_orgs(json["orgs"]) end def openname warn "[DEPRECATION] `openname` is deprecated. Please use `username` instead." username end protected def parse_orgs(orgs_json) orgs = Array.new if orgs_json for org_json in orgs_json orgs << Org.new(org_json) end end orgs end end class Org def self.from_json(json) Org.new(json) end attr_reader :url attr_reader :relationship attr_reader :name def initialize(json) @url = json["url"] if json["url"] @relationship = json["relationship"] if json["relationship"] @name = json["name"] if json["name"] end end protected def self.check_schema_version(json_result) if json_result["v"] != SCHEMA_VERSION warn "Openname gem only supports Openname schema version #{SCHEMA_VERSION}" end end end