vaccine-spotter 0.2.1 → 0.2.2

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 +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.