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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/bin/vaccine-spotter +64 -1
  3. data/lib/vaccine-spotter.rb +141 -48
  4. metadata +51 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee6ea9690334fd97f64cccafb4d4a22a59d36ba4e6b999a52b77f5f4128d9cd8
4
- data.tar.gz: afd75efd6acd1ce70c8a73cbd7ed401e1cf4a2fe650efabec901c599a2a012fd
3
+ metadata.gz: 54c43d4e7a183668e99aa9ca2e33ee581ca275cf625022e899c85ea1343ad374
4
+ data.tar.gz: dfe2031626184c0cd6559b38db0b48acbfd5611396b1a9addb7a9bb985ecd21e
5
5
  SHA512:
6
- metadata.gz: 12ddd6dcf1befc1d1a2379e9b614e7508988194304016c80115788c546bab9d85f43c89f663dee02d85244abd9c2c2920d615e406529bf7c2b2c93b10c1a6127
7
- data.tar.gz: d7552b9a5621c004d52a21c8eff20cb3e2663a0139d6fe47126a460c82d85685a37b820ca72e910b540a4a8ee827cee36ba43cc09ed5b1e9bfd4bc271327c3d7
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 '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
+ 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)
@@ -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
- 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)
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 !config.exist?
28
+ if OS.windows?
24
29
  puts
25
- puts pastel.bold "Hi, welcome to the unofficial vaccinespotter.org CLI notifier thing!"
26
- puts "It looks like you don't have a configuration file yet. Would you like to create one?"
27
- create_config = prompt.yes? "Create `~/.config/vaccine-spotter.toml`?"
28
- if create_config
29
- set_state = prompt.ask("What state would you like to monitor? (Two-letter postal code, please)", required: true) do |q|
30
- q.modify :up, :remove
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
- config.set(:state, value: set_state)
33
- puts "Ok, time to set zip codes…"
34
- if prompt.yes? "freemaptools.com has a really helpful tool for getting the zip codes around a radius. Would you like to open it?"
35
- Launchy.open "https://www.freemaptools.com/find-zip-codes-inside-radius.htm"
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
- 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)
38
- config.set(:zips, value: set_zips)
39
- 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)
40
- config.set(:vaccine_types, value: set_vaccine_types)
41
- set_refresh_rate = prompt.ask("How often should the script check for updates? (seconds)", convert: :int, required: true)
42
- config.set(:refresh_rate, value: set_refresh_rate)
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
- 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
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 = Array.new
127
+ excluded = Hash.new
67
128
 
68
- loop do
129
+ begin
69
130
 
70
- number_excluded = excluded.length
131
+ loop do
71
132
 
72
- json = Net::HTTP.get(URI("https://www.vaccinespotter.org/api/v0/states/#{state}.json"))
73
- parsed_json = JSON.parse(json)
133
+ initial_number_excluded = excluded.length
74
134
 
75
- puts
135
+ json = Net::HTTP.get(URI("https://www.vaccinespotter.org/api/v0/states/#{state}.json"))
136
+ parsed_json = JSON.parse(json)
76
137
 
77
- if (Time.now.strftime("%M").to_i%5 == 0) && (Time.now.strftime("%S").to_i < refresh_rate)
78
- excluded.clear
79
- puts "List of excluded/checked stores cleared"
80
- end
138
+ puts
81
139
 
82
- parsed_json["features"].each_index do |i|
83
- current = parsed_json["features"][i]["properties"]
84
- 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?)
85
- puts "- #{pastel.green.bold current["city"]} #{pastel.green.bold current["provider_brand_name"]}: #{current["url"]}"
86
- puts " - #{current["appointment_vaccine_types"]} as of #{Time.parse(current["appointments_last_fetched"]).localtime.strftime("%H:%M:%S")}"
87
- TerminalNotifier.notify("#{current["provider_brand_name"]} - #{current["city"]}", :title => "vaccine-spotter", :open => "#{current["url"]}")
88
- excluded.append current["id"].to_i
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
- print pastel.dim "Checked at #{Time.now.strftime("%H:%M:%S")} on #{Time.now.strftime("%Y-%m-%d")}"
93
- if number_excluded == 1
94
- print pastel.dim " (#{number_excluded} checked location excluded)"
95
- elsif number_excluded != 0
96
- print pastel.dim " (#{number_excluded} checked locations excluded)"
97
- end
98
- puts
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
- sleep refresh_rate
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.0
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-15 00:00:00.000000000 Z
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). It currently
113
- pretty much just wraps the very beta API from the absolutely wonderful vaccinespotter.org,
114
- though I hope to add my own website scraping soon too so as to improve response
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.2.16
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: []