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.
- checksums.yaml +4 -4
- data/bin/vaccine-spotter +63 -1
- data/lib/vaccine-spotter.rb +114 -54
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 205ae00407441cac0a54d0f3bc5105ba6d59258a170370e1ba9f0188d534d908
|
4
|
+
data.tar.gz: a645c74b497746a9988ed478804d904456fd33f9a89019fdd3ceb191e6b179d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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)
|
data/lib/vaccine-spotter.rb
CHANGED
@@ -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
|
-
|
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
|
28
|
+
if OS.windows?
|
25
29
|
puts
|
26
|
-
|
27
|
-
puts "
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
puts "
|
35
|
-
|
36
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
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 =
|
127
|
+
excluded = Hash.new
|
128
|
+
|
129
|
+
begin
|
100
130
|
|
101
|
-
loop do
|
131
|
+
loop do
|
102
132
|
|
103
|
-
|
133
|
+
initial_number_excluded = excluded.length
|
104
134
|
|
105
|
-
|
106
|
-
|
135
|
+
json = Net::HTTP.get(URI("https://www.vaccinespotter.org/api/v0/states/#{state}.json"))
|
136
|
+
parsed_json = JSON.parse(json)
|
107
137
|
|
108
|
-
|
138
|
+
puts
|
109
139
|
|
110
|
-
|
111
|
-
excluded.clear
|
112
|
-
puts "List of excluded/checked stores cleared"
|
113
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
127
|
-
excluded.append current["id"].to_i
|
186
|
+
}
|
128
187
|
end
|
129
|
-
end
|
130
188
|
|
131
|
-
|
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
|
-
|
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.
|
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-
|
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.
|