vaccine-spotter 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/bin/vaccine-spotter +63 -1
  3. data/lib/vaccine-spotter.rb +114 -54
  4. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef4472babd675e97bdda96d6a52bccee08799429753ea166542b34069c9ae150
4
- data.tar.gz: ff3875ce3d6bef34cc1941e9990159ba7dffe048de981176b07163424a1f9fc7
3
+ metadata.gz: 205ae00407441cac0a54d0f3bc5105ba6d59258a170370e1ba9f0188d534d908
4
+ data.tar.gz: a645c74b497746a9988ed478804d904456fd33f9a89019fdd3ceb191e6b179d2
5
5
  SHA512:
6
- metadata.gz: 0c2783d31fc893a1a192523b2fad3d32db6d2e44525a252a44665cb68371c57bb0a73fac999c513787f41c349857fe2ee082e786fdb63bd0ac1049c77022053b
7
- data.tar.gz: 33c9366a86cdef1c192c2ff11598517400ba3177109a47763a27478cb6e904aec32f749fc57662763d4ce3abe7058b47a1589f0eb2bbd6f016fa6ff3115bf252
6
+ metadata.gz: 47ea8cd56a85627e89029a28491b7c82f596a8ab7e979f9dd7d5caa351dcf0e061fee8c965d7058e372966186aa59cb566d1d581a7377b3ad764ae5c3c2c717b
7
+ data.tar.gz: 256f8e366e846df15f250591d38ce1af6a5cf2906c92f11b9624e8e311a50394fdf0a389fafc0dc1d1ac8d131c5fad3b1417cbea6d4b2eb46e0713948bd41e4e
data/bin/vaccine-spotter CHANGED
@@ -1,3 +1,65 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'vaccine-spotter'
3
+ require "thor"
4
+
5
+ class MyCLI < Thor
6
+
7
+ desc "start", "Start vaccine-spotter — the default task when no command or options are given"
8
+ def start
9
+ begin
10
+ require 'vaccine-spotter'
11
+ rescue LoadError
12
+ require_relative './../lib/vaccine-spotter'
13
+ end
14
+ end
15
+
16
+ desc "check", "Run vaccine-spotter once instead of looping"
17
+ def check
18
+ $loop = false
19
+ begin
20
+ require 'vaccine-spotter'
21
+ rescue LoadError
22
+ require_relative './../lib/vaccine-spotter'
23
+ end
24
+ end
25
+
26
+ desc "config", "Open config file (~/.config/vaccine-spotter.toml) with default editor"
27
+ def config
28
+ system("open ~/.config/vaccine-spotter.toml")
29
+ end
30
+
31
+ desc "version", "Print version and OS information"
32
+ def version
33
+ begin
34
+ puts "vaccinespotter.org CLI notifier thing v#{Gem.loaded_specs["vaccine-spotter"].version}"
35
+ rescue
36
+ puts "(!) vaccine-spotter: error loading version"
37
+ end
38
+ begin
39
+ require 'os'
40
+ rescue
41
+ puts "(!) vaccine-spotter: couldn't load OS gem"
42
+ end
43
+ begin
44
+ puts OS.report
45
+ puts "ruby_bin: #{OS.ruby_bin}"
46
+ rescue
47
+ puts "(!) vaccine-spotter: couldn't print `OS.report`"
48
+ end
49
+ end
50
+
51
+ default_task :start
52
+
53
+ end
54
+
55
+ # The following is from https://stackoverflow.com/a/49044225/
56
+ help_commands = Thor::HELP_MAPPINGS + ["help"]
57
+ if help_commands.any? { |cmd| ARGV.include? cmd }
58
+ help_commands.each do |cmd|
59
+ if match = ARGV.delete(cmd)
60
+ ARGV.unshift match
61
+ end
62
+ end
63
+ end
64
+
65
+ MyCLI.start(ARGV)
@@ -12,43 +12,66 @@ require "tty-config"
12
12
  require "tty-prompt"
13
13
  require "launchy"
14
14
  require "feep"
15
+ require "os"
15
16
 
16
- pastel = Pastel.new
17
+ # Supports https://no-color.org
18
+ env_no_color = false if ENV['NO_COLOR']
19
+
20
+ pastel = Pastel.new(enabled: env_no_color)
17
21
  config = TTY::Config.new
18
- prompt = TTY::Prompt.new
22
+ prompt = TTY::Prompt.new(enable_color: env_no_color)
19
23
 
20
24
  config.filename = "vaccine-spotter"
21
25
  config.extname = ".toml"
22
26
  config.append_path "#{Dir.home}/.config"
23
27
 
24
- if !config.exist?
28
+ if OS.windows?
25
29
  puts
26
- puts pastel.bold "Hi, welcome to the unofficial vaccinespotter.org CLI notifier thing!"
27
- puts "It looks like you don't have a configuration file yet. Would you like to create one?"
28
- create_config = prompt.yes? "Create `~/.config/vaccine-spotter.toml`?"
29
- if create_config
30
- set_state = prompt.ask("What state would you like to monitor? (Two-letter postal code, please)", required: true) do |q|
31
- q.modify :up, :remove
32
- end
33
- config.set(:state, value: set_state)
34
- puts "Ok, time to set zip codes…"
35
- if prompt.yes? "freemaptools.com has a really helpful tool for getting the zip codes around a radius. Would you like to open it?"
36
- Launchy.open "https://www.freemaptools.com/find-zip-codes-inside-radius.htm"
30
+ print pastel.bold.red "(!) Note: Windows is unsupported"
31
+ puts pastel.red " (I don't have a Windows computer and I barely know how it works)."
32
+ puts pastel.red "Notifications don't work, I think, and sounds require installing `sounder`. Read more at the project homepage: https://github.com/jltml/vaccine-spotter-cli"
33
+ end
34
+
35
+ if !config.exist?
36
+ begin
37
+ puts
38
+ puts pastel.bold "Hi, welcome to the unofficial vaccinespotter.org CLI notifier thing!"
39
+ puts "It looks like you don't have a configuration file yet. Would you like to create one?"
40
+ create_config = prompt.yes? "Create `~/.config/vaccine-spotter.toml`?"
41
+ if !create_config
42
+ puts pastel.bold.red "You'll need to create a configuration file at `~/.config/vaccine-spotter.toml` to use this."
37
43
  end
38
- set_zips = prompt.ask("What zip codes would you like to track? You can paste here from freemaptools.com, if you used it (comma-separated list of integers, please)", convert: :int_list, required: true)
39
- config.set(:zips, value: set_zips)
40
- set_vaccine_types = prompt.multi_select("Which types of vaccines would you like to include?", %w(pfizer moderna jj unknown), min: 1, show_help: :always)
41
- config.set(:vaccine_types, value: set_vaccine_types)
42
- set_refresh_rate = prompt.ask("How often should the script check for updates? (seconds)", convert: :int, required: true)
43
- config.set(:refresh_rate, value: set_refresh_rate)
44
- set_play_sound = prompt.yes?("Would you like to play a sound when availability is found?", required: true)
45
- config.set(:sound, :play, value: set_play_sound)
46
- if set_play_sound
47
- set_repeat_sound = prompt.ask("How many times should the sound repeat?", convert: :int, required: true)
48
- config.set(:sound, :repeat, value: set_repeat_sound)
44
+ if create_config
45
+ set_state = prompt.ask("What #{pastel.bold.underline "state"} would you like to monitor? (Two-letter postal code, please)", required: true) do |q|
46
+ q.modify :up, :remove
47
+ # q.validate(^(?-i:A[LKSZRAEP]|C[AOT]|D[EC]|F[LM]|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEHINOPST]|N[CDEHJMVY]|O[HKR]|P[ARW]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])) # This is supposed to validate but doesn't work # Validates state abbreviation, apparently
48
+ end
49
+ config.set(:state, value: set_state)
50
+ puts "Ok, time to set zip codes…"
51
+ if prompt.yes? "freemaptools.com has a really helpful tool for getting the zip codes around a radius. Would you like to open it?"
52
+ Launchy.open "https://www.freemaptools.com/find-zip-codes-inside-radius.htm"
53
+ end
54
+ set_zips = prompt.ask("What #{pastel.bold.underline "zip codes"} would you like to track? You can paste here from freemaptools.com, if you used it (comma-separated list of integers, please)", convert: :int_list, required: true)
55
+ config.set(:zips, value: set_zips)
56
+ set_vaccine_types = prompt.multi_select("Which types of vaccines would you like to include?", %w(pfizer moderna jj unknown), min: 1, show_help: :always)
57
+ config.set(:vaccine_types, value: set_vaccine_types)
58
+ set_refresh_rate = prompt.ask("How often should the script check for updates? (seconds)", convert: :int, required: true)
59
+ config.set(:refresh_rate, value: set_refresh_rate)
60
+ set_play_sound = prompt.yes?("Would you like to play a sound when availability is found?", required: true)
61
+ config.set(:sound, :play, value: set_play_sound)
62
+ if set_play_sound
63
+ set_repeat_sound = prompt.ask("How many times should the sound repeat?", convert: :int, required: true)
64
+ config.set(:sound, :repeat, value: set_repeat_sound)
65
+ end
66
+ puts "Perfect; writing to `~/.config/vaccine-spotter.toml`…"
67
+ config.write
49
68
  end
50
- puts "Perfect; writing to `~/.config/vaccine-spotter.toml`…"
51
- config.write
69
+
70
+ rescue Interrupt
71
+ puts
72
+ print pastel.red.underline "vaccine-spotter was interrupted during config setup — ~/.config/vaccine-spotter.toml was "
73
+ puts pastel.red.underline.bold "not created"
74
+ exit 1
52
75
  end
53
76
  end
54
77
 
@@ -87,7 +110,12 @@ rescue
87
110
  end
88
111
  puts pastel.dim ", by Jack MapelLentz (jltml.me)"
89
112
  puts pastel.dim "This script uses the wonderful vaccinespotter.org, by Nick Muerdter (github.com/GUI)"
90
- puts "Checking #{pastel.underline zips.length} zip codes in #{pastel.underline state} every #{pastel.underline refresh_rate} seconds"
113
+ print "Checking #{pastel.underline zips.length} zip codes in #{pastel.underline state} "
114
+ if $loop != false
115
+ puts "every #{pastel.underline refresh_rate} seconds"
116
+ else
117
+ puts pastel.underline "once"
118
+ end
91
119
  puts "Filtering appointments for #{pastel.underline vaccine_types.to_s.tr("[]\"", "")}"
92
120
  if play_sound
93
121
  puts "Playing a sound #{pastel.underline repeat_sound} #{pluralize(repeat_sound, "time")} when availability is found"
@@ -96,44 +124,76 @@ else
96
124
  end
97
125
  puts pastel.dim "Press control-C to quit"
98
126
 
99
- excluded = Array.new
127
+ excluded = Hash.new
128
+
129
+ begin
100
130
 
101
- loop do
131
+ loop do
102
132
 
103
- number_excluded = excluded.length
133
+ initial_number_excluded = excluded.length
104
134
 
105
- json = Net::HTTP.get(URI("https://www.vaccinespotter.org/api/v0/states/#{state}.json"))
106
- parsed_json = JSON.parse(json)
135
+ json = Net::HTTP.get(URI("https://www.vaccinespotter.org/api/v0/states/#{state}.json"))
136
+ parsed_json = JSON.parse(json)
107
137
 
108
- puts
138
+ puts
109
139
 
110
- if (Time.now.strftime("%M").to_i%5 == 0) && (Time.now.strftime("%S").to_i < refresh_rate)
111
- excluded.clear
112
- puts "List of excluded/checked stores cleared"
113
- end
140
+ # if (Time.now.strftime("%M").to_i%5 == 0) && (Time.now.strftime("%S").to_i < refresh_rate)
141
+ # excluded.clear
142
+ # puts "List of excluded/checked stores cleared"
143
+ # number_excluded = excluded.length
144
+ # end
145
+
146
+ new_appointment = false
114
147
 
115
- parsed_json["features"].each_index do |i|
116
- current = parsed_json["features"][i]["properties"]
117
- if (current["appointments_available"] == true) and (zips.include?(current["postal_code"].to_i) and (!excluded.include? current["id"].to_i) and !(vaccine_types & current["appointment_vaccine_types"].keys).empty?)
118
- puts "- #{pastel.green.bold current["city"]} #{pastel.green.bold current["provider_brand_name"]}: #{current["url"]}"
119
- puts " - #{current["appointment_vaccine_types"]} as of #{Time.parse(current["appointments_last_fetched"]).localtime.strftime("%H:%M:%S")}"
120
- TerminalNotifier.notify("#{current["provider_brand_name"]} - #{current["city"]}", :title => "vaccine-spotter", :open => "#{current["url"]}")
121
- if play_sound
148
+ # This is the main area thing — it loops through each zip code and finds matches
149
+ parsed_json["features"].each_index do |i|
150
+ current = parsed_json["features"][i]["properties"]
151
+
152
+ if (excluded.keys.include? current["id"]) && (current["appointments_available"] == false)
153
+ print "- "
154
+ puts pastel.red "Appointments no longer available at #{pastel.bold current["city"].split.map(&:capitalize).join(' ') + ' ' + current["provider_brand_name"]} as of #{Time.parse(current["appointments_last_fetched"]).localtime.strftime("%H:%M:%S")}"
155
+ excluded.delete current["id"]
156
+ end
157
+
158
+ # Finds matches
159
+ if (current["appointments_available"] == true) && (zips.include?(current["postal_code"].to_i) && (!excluded.include? current["id"].to_i) && (vaccine_types & current["appointment_vaccine_types"].keys).any?)
160
+
161
+ # Logs matches to terminal (and uniformly capitalizes location names)
162
+ puts "- #{pastel.green.bold current["city"].split.map(&:capitalize).join(' ')} #{pastel.green.bold current["provider_brand_name"]}: #{current["url"]}"
163
+ puts " - #{current["appointment_vaccine_types"].keys.to_s.tr("[]\"", "").split.map(&:capitalize).join(' ')} available as of #{Time.parse(current["appointments_last_fetched"]).localtime.strftime("%H:%M:%S")}"
164
+
165
+ # Sends a notification if on macOS (also makes location names all the same title case/capitalization)
166
+ if OS.mac? then TerminalNotifier.notify("#{current["provider_brand_name"]} - #{current["city"].split.map(&:capitalize).join(' ')} (#{current["appointment_vaccine_types"].keys.to_s.tr("[]\"", "").split.map(&:capitalize).join(' ')})", :title => "vaccine-spotter", :open => "#{current["url"]}") end
167
+
168
+ excluded[current["id"].to_i] = current["appointments_last_fetched"] # Add key & value of {"id" => "appointments_last_fetched"} to excluded hash
169
+ new_appointment = true
170
+ end
171
+ end
172
+
173
+ print pastel.dim "Checked at #{Time.now.strftime("%H:%M:%S")} on #{Time.now.strftime("%Y-%m-%d")}"
174
+ if initial_number_excluded != 0
175
+ print pastel.dim " (#{initial_number_excluded} checked #{pluralize(initial_number_excluded, "location")} excluded)"
176
+ end
177
+ puts
178
+
179
+ # Plays a sound (E blues scale) if there's a new appointment in a new thread so the whole program isn't delayed by the sound playing
180
+ if play_sound && new_appointment
181
+ Thread.new {
122
182
  repeat_sound.times do
123
183
  blues_scale
124
184
  sleep 0.5
125
185
  end
126
- end
127
- excluded.append current["id"].to_i
186
+ }
128
187
  end
129
- end
130
188
 
131
- print pastel.dim "Checked at #{Time.now.strftime("%H:%M:%S")} on #{Time.now.strftime("%Y-%m-%d")}"
132
- if number_excluded != 0
133
- print pastel.dim " (#{number_excluded} checked #{pluralize(number_excluded, "location")} excluded)"
134
- end
135
- puts
189
+ break if $loop == false
136
190
 
137
- sleep refresh_rate
191
+ sleep refresh_rate
192
+
193
+ end
138
194
 
195
+ rescue Interrupt
196
+ print " "
197
+ puts pastel.underline "Stopping vaccine-spotter…"
198
+ exit
139
199
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vaccine-spotter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jack MapelLentz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-15 00:00:00.000000000 Z
11
+ date: 2021-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-logger
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: 0.2.2
125
+ - !ruby/object:Gem::Dependency
126
+ name: os
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.9.6
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.9.6
125
139
  description: This gem will notify you when COVID-19 vaccine appointments are available
126
140
  matching certain criteria (a list of zip codes, type of vaccine, etc) using the
127
141
  very beta API from the absolutely wonderful vaccinespotter.org.