hpanot (3645B)
1 #!/usr/bin/env ruby 2 # frozen_string_literal: true 3 4 require 'open-uri' 5 require 'json' 6 7 # hypothes.is interface 8 class Hypothesis 9 # a hypothes.is annotation 10 class Annotation 11 attr_reader :uri, :title 12 def initialize(text:, comment:, title:, uri:) 13 @text = text 14 @comment = comment.empty? ? nil : comment 15 @title = title 16 @uri = uri 17 end 18 19 def to_markdown 20 str = @text.split("\n").reduce('') { |acc, line| acc + "> #{line.gsub(/[‘’]/, %q{'}).gsub(/[“”]/, %q{"})}\n" } 21 str += "\n" 22 str += "#{@comment}\n" if @comment 23 str 24 end 25 26 def to_markdown_title_url 27 "[#{@title}](#{@uri})\n" 28 end 29 end 30 31 def initialize 32 %w[HYPOTHESIS_API_KEY HYPOTHESIS_USERNAME].each do |var| 33 die "Please set the #{var} environment variable" if ENV[var].nil? 34 end 35 36 @apikey = ENV['HYPOTHESIS_API_KEY'] 37 @username = ENV['HYPOTHESIS_USERNAME'] 38 @headers = { 'Host' => 'hypothes.is', 'Accept' => 'application/json', 'Authorization' => "Bearer #{@apikey}" } 39 @baseurl = 'https://hypothes.is/api' 40 41 ping || die("Could not access the API #{@baseurl}") 42 end 43 44 def search_host(host) 45 annotations "wildcard_uri=http://#{host}/*&limit=200&order=asc" 46 end 47 48 def search_uri(uri) 49 annotations "uri=#{uri}&limit=200&order=asc" 50 end 51 52 def recent_urls(num_urls = 2) 53 # FIXME: this is a very roundabout, inefficient method. Waiting for an API for this from hypothes.is 54 grouped_annotations = annotations('sort=created&order=desc&limit=200').group_by(&:uri) 55 n_recent = grouped_annotations.keys[0, num_urls] 56 n_recent.inject([]) do |arr, uri| 57 arr << { uri: uri, title: grouped_annotations[uri].first.title } 58 end 59 end 60 61 private 62 63 def ping 64 URI.open("#{@baseurl}/", @headers) 65 true 66 rescue OpenURI::HTTPError 67 false 68 end 69 70 def die(msg) 71 warn msg 72 exit 1 73 end 74 75 def request(endpoint) 76 URI.open(@baseurl + endpoint, @headers) do |response| 77 JSON.parse(response.read)['rows'] 78 end 79 rescue OpenURI::HTTPError 80 warn "Error getting data from #{endpoint}" 81 [] 82 end 83 84 def annotations(uri_search_str) 85 data = request "/search?user=acct:#{@username}@hypothes.is&#{uri_search_str}" 86 data.reduce([]) do |arr, annot| 87 arr << Annotation.new(text: annot['target'].first['selector'] 88 .select { |f| f['type'] == 'TextQuoteSelector' }.first['exact'], 89 comment: annot['text'], 90 title: annot['document']['title'].first, 91 uri: annot['uri']) 92 end 93 end 94 end 95 96 def print_usage 97 puts <<~HEREDOC 98 Usage: hpanot [command] [arg1, [arg2...]] 99 100 Commands: 101 site URL get annotations for a website 102 recent [n] get n (default 2) most recently annotated websites 103 104 HEREDOC 105 end 106 107 ARGV.each do |opt| 108 case opt 109 when '-h' || '--help' 110 print_usage 111 exit 0 112 end 113 end 114 115 params = ARGV.reject { |arg| arg.start_with? '-' } 116 if params.empty? 117 print_usage 118 exit 1 119 end 120 121 case params[0] 122 when 'site' 123 die 'No website specified, use pass -h to print usage.' unless params[1] 124 h = Hypothesis.new 125 site = params[1] 126 annotations = site.start_with?('http') ? h.search_uri(site) : h.search_host(site) 127 puts annotations.first.to_markdown_title_url + "\n" + annotations.reduce('') { |str, annot| str + annot.to_markdown } 128 when 'recent' 129 h = Hypothesis.new 130 begin 131 recent_urls = params[1] ? h.recent_urls(Integer(params[1])) : h.recent_urls 132 recent_urls.each { |u| puts "#{u[:uri]}\t#{u[:title]}" } 133 rescue ArgumentError 134 die 'Argument 2 must be an integer.' 135 end 136 else 137 print_usage 138 exit 1 139 end