vaccine-spotter 0.2.0 → 0.2.5
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.
- checksums.yaml +4 -4
- data/bin/vaccine-spotter +64 -1
- data/lib/vaccine-spotter.rb +141 -48
- metadata +51 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54c43d4e7a183668e99aa9ca2e33ee581ca275cf625022e899c85ea1343ad374
|
4
|
+
data.tar.gz: dfe2031626184c0cd6559b38db0b48acbfd5611396b1a9addb7a9bb985ecd21e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c479b10fa6e637dc99b11c5799e9ed374047a43135c66fd9d2c0818a032d074ce81b5bb15030158cc6724e1e93d592ba4552fc3f70f90ea1a772ab77790b4528
|
7
|
+
data.tar.gz: dc801614d28a967ede5139e5851aa0ce512de9904647e88ccab6c51aec7b176fb1d990c6c7a97a3994e80594617e2bcf9a6eb6537864e5b4977a07ad1244858b
|
data/bin/vaccine-spotter
CHANGED
@@ -1,3 +1,66 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
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
|
+
map %w[--version -v] => :version
|
51
|
+
|
52
|
+
default_task :start
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
# The following is from https://stackoverflow.com/a/49044225/
|
57
|
+
help_commands = Thor::HELP_MAPPINGS + ["help"]
|
58
|
+
if help_commands.any? { |cmd| ARGV.include? cmd }
|
59
|
+
help_commands.each do |cmd|
|
60
|
+
if match = ARGV.delete(cmd)
|
61
|
+
ARGV.unshift match
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
MyCLI.start(ARGV)
|
data/lib/vaccine-spotter.rb
CHANGED
@@ -11,37 +11,67 @@ require "toml"
|
|
11
11
|
require "tty-config"
|
12
12
|
require "tty-prompt"
|
13
13
|
require "launchy"
|
14
|
+
require "feep"
|
15
|
+
require "os"
|
14
16
|
|
15
|
-
|
17
|
+
# Supports https://no-color.org
|
18
|
+
env_no_color = false if ENV['NO_COLOR']
|
19
|
+
|
20
|
+
pastel = Pastel.new(enabled: env_no_color)
|
16
21
|
config = TTY::Config.new
|
17
|
-
prompt = TTY::Prompt.new
|
22
|
+
prompt = TTY::Prompt.new(enable_color: env_no_color)
|
18
23
|
|
19
24
|
config.filename = "vaccine-spotter"
|
20
25
|
config.extname = ".toml"
|
21
26
|
config.append_path "#{Dir.home}/.config"
|
22
27
|
|
23
|
-
if
|
28
|
+
if OS.windows?
|
24
29
|
puts
|
25
|
-
|
26
|
-
puts "
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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."
|
31
43
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
36
68
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
config.
|
41
|
-
|
42
|
-
|
43
|
-
puts "Perfect; writing to `~/.config/vaccine-spotter.toml`…"
|
44
|
-
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
|
45
75
|
end
|
46
76
|
end
|
47
77
|
|
@@ -49,6 +79,27 @@ zips = config.read["zips"]
|
|
49
79
|
state = config.read["state"]
|
50
80
|
refresh_rate = config.read["refresh_rate"].to_i
|
51
81
|
vaccine_types = config.read["vaccine_types"]
|
82
|
+
play_sound = config.read["sound"]["play"]
|
83
|
+
repeat_sound = config.read["sound"]["repeat"].to_i
|
84
|
+
|
85
|
+
def pluralize(number, text)
|
86
|
+
return text + 's' if number != 1
|
87
|
+
text
|
88
|
+
end
|
89
|
+
|
90
|
+
def blues_scale
|
91
|
+
Feep::Base.new(
|
92
|
+
:freq_or_note => 'E4',
|
93
|
+
:scale => 'blues',
|
94
|
+
:waveform => 'triangle',
|
95
|
+
:volume => 0.8,
|
96
|
+
:duration => 100,
|
97
|
+
:save => false,
|
98
|
+
:verbose => false,
|
99
|
+
:visual_cue => false,
|
100
|
+
:usage => nil
|
101
|
+
)
|
102
|
+
end
|
52
103
|
|
53
104
|
puts
|
54
105
|
print pastel.bold "vaccinespotter.org CLI notifier thing"
|
@@ -59,44 +110,86 @@ rescue
|
|
59
110
|
end
|
60
111
|
puts pastel.dim ", by Jack MapelLentz (jltml.me)"
|
61
112
|
puts pastel.dim "This script uses the wonderful vaccinespotter.org, by Nick Muerdter (github.com/GUI)"
|
62
|
-
|
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
|
63
119
|
puts "Filtering appointments for #{pastel.underline vaccine_types.to_s.tr("[]\"", "")}"
|
120
|
+
if play_sound
|
121
|
+
puts "Playing a sound #{pastel.underline repeat_sound} #{pluralize(repeat_sound, "time")} when availability is found"
|
122
|
+
else
|
123
|
+
puts "#{pastel.underline "Not"} playing a sound when availability is found"
|
124
|
+
end
|
64
125
|
puts pastel.dim "Press control-C to quit"
|
65
126
|
|
66
|
-
excluded =
|
127
|
+
excluded = Hash.new
|
67
128
|
|
68
|
-
|
129
|
+
begin
|
69
130
|
|
70
|
-
|
131
|
+
loop do
|
71
132
|
|
72
|
-
|
73
|
-
parsed_json = JSON.parse(json)
|
133
|
+
initial_number_excluded = excluded.length
|
74
134
|
|
75
|
-
|
135
|
+
json = Net::HTTP.get(URI("https://www.vaccinespotter.org/api/v0/states/#{state}.json"))
|
136
|
+
parsed_json = JSON.parse(json)
|
76
137
|
|
77
|
-
|
78
|
-
excluded.clear
|
79
|
-
puts "List of excluded/checked stores cleared"
|
80
|
-
end
|
138
|
+
puts
|
81
139
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
140
|
+
new_appointment = false
|
141
|
+
|
142
|
+
# This is the main area thing — it loops through each zip code and finds matches
|
143
|
+
parsed_json["features"].each_index do |i|
|
144
|
+
current = parsed_json["features"][i]["properties"]
|
145
|
+
|
146
|
+
# Checks if locations that previously had appointments no longer have availability
|
147
|
+
if (excluded.keys.include? current["id"]) && (current["appointments_available"] == false)
|
148
|
+
print "- "
|
149
|
+
puts pastel.red "#{pastel.bold current["city"].split.map(&:capitalize).join(' ') + ' ' + current["provider_brand_name"]}: Appointments no longer available as of #{Time.parse(current["appointments_last_fetched"]).localtime.strftime("%H:%M:%S")}"
|
150
|
+
excluded.delete current["id"]
|
151
|
+
initial_number_excluded = excluded.length
|
152
|
+
end
|
153
|
+
|
154
|
+
# Finds matches
|
155
|
+
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?)
|
156
|
+
|
157
|
+
# Logs matches to terminal (and uniformly capitalizes location names)
|
158
|
+
puts "- #{pastel.green.bold current["city"].split.map(&:capitalize).join(' ')} #{pastel.green.bold current["provider_brand_name"]}: #{current["url"]}"
|
159
|
+
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")}"
|
160
|
+
|
161
|
+
# Sends a notification if on macOS (also makes location names all the same title case/capitalization)
|
162
|
+
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
|
163
|
+
|
164
|
+
excluded[current["id"].to_i] = current["appointments_last_fetched"] # Add key & value of {"id" => "appointments_last_fetched"} to excluded hash
|
165
|
+
new_appointment = true
|
166
|
+
end
|
89
167
|
end
|
90
|
-
end
|
91
168
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
169
|
+
print pastel.dim "Checked at #{Time.now.strftime("%H:%M:%S")} on #{Time.now.strftime("%Y-%m-%d")}"
|
170
|
+
if initial_number_excluded != 0
|
171
|
+
print pastel.dim " (#{initial_number_excluded} checked #{pluralize(initial_number_excluded, "location")} excluded)"
|
172
|
+
end
|
173
|
+
puts
|
174
|
+
|
175
|
+
# 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
|
176
|
+
if play_sound && new_appointment
|
177
|
+
Thread.new {
|
178
|
+
repeat_sound.times do
|
179
|
+
blues_scale
|
180
|
+
sleep 0.5
|
181
|
+
end
|
182
|
+
}
|
183
|
+
end
|
99
184
|
|
100
|
-
|
185
|
+
break if $loop == false
|
186
|
+
|
187
|
+
sleep refresh_rate
|
188
|
+
|
189
|
+
end
|
101
190
|
|
191
|
+
rescue Interrupt
|
192
|
+
print " "
|
193
|
+
puts pastel.underline "Stopping vaccine-spotter…"
|
194
|
+
exit
|
102
195
|
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.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jack MapelLentz
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-04-
|
11
|
+
date: 2021-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tty-logger
|
@@ -108,12 +108,52 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '2.5'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: feep
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.2.2
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
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
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: thor
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '1.0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '1.0'
|
111
153
|
description: This gem will notify you when COVID-19 vaccine appointments are available
|
112
|
-
matching certain criteria (a list of zip codes, type of vaccine, etc)
|
113
|
-
|
114
|
-
|
115
|
-
times.
|
116
|
-
email:
|
154
|
+
matching certain criteria (a list of zip codes, type of vaccine, etc) using the
|
155
|
+
very beta API from the absolutely wonderful vaccinespotter.org.
|
156
|
+
email:
|
117
157
|
executables:
|
118
158
|
- vaccine-spotter
|
119
159
|
extensions: []
|
@@ -125,7 +165,7 @@ homepage: https://github.com/jltml/vaccine-spotter-cli
|
|
125
165
|
licenses:
|
126
166
|
- MIT
|
127
167
|
metadata: {}
|
128
|
-
post_install_message:
|
168
|
+
post_install_message:
|
129
169
|
rdoc_options: []
|
130
170
|
require_paths:
|
131
171
|
- lib
|
@@ -140,8 +180,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
180
|
- !ruby/object:Gem::Version
|
141
181
|
version: '0'
|
142
182
|
requirements: []
|
143
|
-
rubygems_version: 3.
|
144
|
-
signing_key:
|
183
|
+
rubygems_version: 3.0.3
|
184
|
+
signing_key:
|
145
185
|
specification_version: 4
|
146
186
|
summary: Get notified of vaccine availability
|
147
187
|
test_files: []
|