vaccine-spotter 0.2.0 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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: []