turbovax 0.0.2pre
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 +7 -0
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.rubocop.yml +23 -0
- data/CHANGELOG.md +2 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +15 -0
- data/LICENSE +661 -0
- data/README.md +91 -0
- data/Rakefile +12 -0
- data/bin/console +20 -0
- data/bin/setup +8 -0
- data/lib/turbovax/appointment.rb +48 -0
- data/lib/turbovax/constants.rb +8 -0
- data/lib/turbovax/data_fetcher.rb +95 -0
- data/lib/turbovax/handlers/location_handler.rb +133 -0
- data/lib/turbovax/location.rb +77 -0
- data/lib/turbovax/portal.rb +178 -0
- data/lib/turbovax/twitter_client.rb +23 -0
- data/lib/turbovax/version.rb +5 -0
- data/lib/turbovax.rb +63 -0
- data/turbovax.gemspec +36 -0
- metadata +116 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Turbovax
|
6
|
+
# Captures configuration required to fetch and process data from a specific vaccine website
|
7
|
+
class Portal
|
8
|
+
class << self
|
9
|
+
# @!macro [attach] definte_parameter
|
10
|
+
# @method $1
|
11
|
+
# $3
|
12
|
+
# @return [$2]
|
13
|
+
# @example $4
|
14
|
+
# $5
|
15
|
+
def self.definte_parameter(
|
16
|
+
attribute, _doc_return_type, _explanation = nil, _explanation = nil,
|
17
|
+
_example = nil
|
18
|
+
)
|
19
|
+
# metaprogramming magic so that
|
20
|
+
# 1) attributes can be defined via DSL
|
21
|
+
# 2) attributes can be fetched when method is called without any parameters
|
22
|
+
# 3) attributes can saved static variables or blocks that can be called (for dynamic)
|
23
|
+
# might be better to refactor in the future
|
24
|
+
define_method attribute do |argument = nil, &block|
|
25
|
+
variable = nil
|
26
|
+
block_exists =
|
27
|
+
begin
|
28
|
+
variable = instance_variable_get("@#{attribute}")
|
29
|
+
variable.is_a?(Proc)
|
30
|
+
rescue StandardError
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
if !variable.nil?
|
35
|
+
block_exists ? variable.call(argument) : variable
|
36
|
+
else
|
37
|
+
instance_variable_set("@#{attribute}", argument || block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
definte_parameter :name, String, "Full name of portal", "Name",
|
43
|
+
"'New York City Vaccine Website'"
|
44
|
+
definte_parameter :key, String, "Unique identifier for portal", "Key", "'nyc_vax'"
|
45
|
+
definte_parameter :public_url,
|
46
|
+
String,
|
47
|
+
"Link to public facing website", "Full URL",
|
48
|
+
"'https://www.turbovax.info/'"
|
49
|
+
|
50
|
+
definte_parameter :request_headers, Hash, "Key:value mapping of HTTP request headers",
|
51
|
+
"Specify user agent and cookies", "{ 'user-agent': 'Mozilla/5.0', " \
|
52
|
+
"'cookies': 'ABC' }"
|
53
|
+
definte_parameter :request_http_method, Symbol,
|
54
|
+
"Turbovax::Constants::GET_REQUEST_METHOD or " \
|
55
|
+
"Turbovax::Constants::POST_REQUEST_METHOD"
|
56
|
+
definte_parameter :api_url, String, "Full API URL", "Example Turbovax endpoint",
|
57
|
+
"'https://api.turbovax.info/v1/dashboard'"
|
58
|
+
definte_parameter :api_url_variables, Hash,
|
59
|
+
"Hash or block that is interpolated ",
|
60
|
+
'
|
61
|
+
api_url_variables do |extra_params|
|
62
|
+
{
|
63
|
+
site_id: NAME_TO_ID_MAPPING[extra_params[:name]],
|
64
|
+
date: extra_params.strftime("%F"),
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
# before api_url_variables interpolation
|
69
|
+
api_url "https://api.turbovax.info/v1/sites/%{site_id}/%{date}"
|
70
|
+
|
71
|
+
# after api_url_variables interpolation
|
72
|
+
api_url "https://api.turbovax.info/v1/sites/8888/2021-08-08"
|
73
|
+
'
|
74
|
+
# definte_parameter :request_body, Hash,
|
75
|
+
# "Hash (or block evaluates to a hash) that is used to in a POST request",
|
76
|
+
# '
|
77
|
+
# request_body do |extra_params|
|
78
|
+
# {
|
79
|
+
# site_id: NAME_TO_ID_MAPPING[extra_params[:name]],
|
80
|
+
# date: extra_params.strftime("%F"),
|
81
|
+
# }
|
82
|
+
# end
|
83
|
+
# '
|
84
|
+
|
85
|
+
# Block that will called after raw data is fetched from API. Must return list of Location
|
86
|
+
# instances
|
87
|
+
# @param [Array] args passed from [Turbovax::DataFetcher]
|
88
|
+
# @param [Block] block stored as a class instance variable
|
89
|
+
# @return [Array<Turbovax::Location>]
|
90
|
+
# @example Parse API responses from turbovax.info
|
91
|
+
# parse_response do |response|
|
92
|
+
# response_json = JSON.parse(response)
|
93
|
+
# response_json["locations"].map do |location|
|
94
|
+
# Turbovax::Location.new(
|
95
|
+
# name: location["name"],
|
96
|
+
# time_zone: "America/New_York",
|
97
|
+
# data: {
|
98
|
+
# is_available: location["is_available"],
|
99
|
+
# appointment_count: location["appointments"]["count"],
|
100
|
+
# appointments: [{
|
101
|
+
# time: "2021-04-19T17:21:15-04:00",
|
102
|
+
# }]
|
103
|
+
# }
|
104
|
+
# )
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
def parse_response(*args, &block)
|
108
|
+
if args.size.positive? && !@parse_response.nil?
|
109
|
+
@parse_response.call(*args)
|
110
|
+
elsif !block.nil?
|
111
|
+
@parse_response = block
|
112
|
+
else
|
113
|
+
{}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Block that will be executed and then appended to API url path. The extra_params variable is
|
118
|
+
# provided by Turbovax::DataFetcher.
|
119
|
+
# When specified, this will overwrite any query string that is already present in api_url
|
120
|
+
#
|
121
|
+
# @param [Array] args passed from [Turbovax::DataFetcher]
|
122
|
+
# @param [Block] block stored as a class instance variable
|
123
|
+
# @return [Hash]
|
124
|
+
# @example Append date and noCache to URL
|
125
|
+
# # result: /path?date=2021-08-08&noCache=0.123
|
126
|
+
# api_query_params do |extra_params|
|
127
|
+
# {
|
128
|
+
# date: extra_params[:date].strftime("%F"),
|
129
|
+
# noCache: rand,
|
130
|
+
# }
|
131
|
+
# end
|
132
|
+
def api_query_params(*args, &block)
|
133
|
+
if args.size.positive? && !@api_query_params.nil?
|
134
|
+
@api_query_params.call(*args)
|
135
|
+
elsif !block.nil?
|
136
|
+
@api_query_params = block
|
137
|
+
else
|
138
|
+
{}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def request_body(*args, &block)
|
143
|
+
if args.size.positive? && !@request_body.nil?
|
144
|
+
@request_body.call(*args)
|
145
|
+
elsif !block.nil?
|
146
|
+
@request_body = block
|
147
|
+
else
|
148
|
+
{}.to_json
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns base API URL (used when creating Faraday connection)
|
153
|
+
def api_base_url
|
154
|
+
"#{api_uri_object.scheme}://#{api_uri_object.hostname}"
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns API URL path (used when making Faraday requests)
|
158
|
+
def api_path
|
159
|
+
"#{api_uri_object.path}?#{api_uri_object.query}"
|
160
|
+
end
|
161
|
+
|
162
|
+
# Calls parse_response and assigns portal to each location so user doesn't need to do
|
163
|
+
# this by themselves
|
164
|
+
def parse_response_with_portal(response, extra_params)
|
165
|
+
parse_response(response, extra_params).map do |location|
|
166
|
+
location.portal ||= self
|
167
|
+
location
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def api_uri_object
|
174
|
+
@api_uri_object ||= URI(api_url % api_url_variables)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "twitter"
|
4
|
+
|
5
|
+
module Turbovax
|
6
|
+
# Helper class that wraps around Twitter gem
|
7
|
+
class TwitterClient
|
8
|
+
def self.client
|
9
|
+
@client ||= Twitter::REST::Client.new do |config|
|
10
|
+
config.consumer_key = Turbovax.twitter_credentials[:consumer_key]
|
11
|
+
config.consumer_secret = Turbovax.twitter_credentials[:consumer_secret]
|
12
|
+
config.access_token = Turbovax.twitter_credentials[:access_token]
|
13
|
+
config.access_token_secret = Turbovax.twitter_credentials[:access_token_secret]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.send_tweet(message, reply_to_id: nil)
|
18
|
+
response = client.update(message, in_reply_to_status_id: reply_to_id)
|
19
|
+
Turbovax.logger.info("[Turbovax::Twitter::Client] send_tweet (#{response.id})")
|
20
|
+
response
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/turbovax.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
|
5
|
+
require_relative "turbovax/constants"
|
6
|
+
require_relative "turbovax/version"
|
7
|
+
require_relative "turbovax/portal"
|
8
|
+
require_relative "turbovax/location"
|
9
|
+
require_relative "turbovax/appointment"
|
10
|
+
require_relative "turbovax/data_fetcher"
|
11
|
+
|
12
|
+
require_relative "turbovax/twitter_client"
|
13
|
+
require_relative "turbovax/handlers/location_handler"
|
14
|
+
|
15
|
+
# Turbovax gem
|
16
|
+
module Turbovax
|
17
|
+
class InvalidRequestTypeError < StandardError; end
|
18
|
+
|
19
|
+
def self.configure
|
20
|
+
yield self
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.logger
|
24
|
+
@logger ||= Logger.new($stdout, level: Logger::INFO)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.logger=(logger)
|
28
|
+
if logger.nil?
|
29
|
+
self.logger.level = Logger::FATAL
|
30
|
+
return self.logger
|
31
|
+
end
|
32
|
+
|
33
|
+
@logger = logger
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.twitter_enabled
|
37
|
+
# enable twitter by default
|
38
|
+
@twitter_enabled = true if @twitter_enabled.nil?
|
39
|
+
@twitter_enabled
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.twitter_enabled=(twitter_enabled)
|
43
|
+
@twitter_enabled = twitter_enabled
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.twitter_credentials
|
47
|
+
raise NotImplementedError, "no twitter credentials provided" if @twitter_credentials.nil?
|
48
|
+
|
49
|
+
@twitter_credentials
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.twitter_credentials=(twitter_credentials)
|
53
|
+
@twitter_credentials = twitter_credentials
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.faraday_logging_config
|
57
|
+
@faraday_logging_config ||= { headers: false, bodies: false, log_level: :info }
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.faraday_logging_config=(faraday_logging_config)
|
61
|
+
@faraday_logging_config = faraday_logging_config
|
62
|
+
end
|
63
|
+
end
|
data/turbovax.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/turbovax/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "turbovax"
|
7
|
+
spec.version = Turbovax::VERSION
|
8
|
+
spec.authors = ["Huge Ma"]
|
9
|
+
spec.email = ["huge@turbovax.info"]
|
10
|
+
spec.license = "AGPLv3"
|
11
|
+
|
12
|
+
spec.summary = "Quickly build vaccine twitter bots"
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = "https://github.com/hugem/turbovax-gem"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/hugem/turbovax-gem"
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/hugem/turbovax-gem/CHANGELOG.md"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "activesupport", "~> 6.0", ">= 6.0.0.1"
|
31
|
+
spec.add_dependency "faraday", "~> 0.17"
|
32
|
+
spec.add_dependency "twitter", "~> 7.0"
|
33
|
+
|
34
|
+
# For more information and examples about making a new gem, checkout our
|
35
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: turbovax
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Huge Ma
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-04-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 6.0.0.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '6.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 6.0.0.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: faraday
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.17'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0.17'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: twitter
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '7.0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '7.0'
|
61
|
+
description: Quickly build vaccine twitter bots
|
62
|
+
email:
|
63
|
+
- huge@turbovax.info
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- ".github/workflows/main.yml"
|
69
|
+
- ".gitignore"
|
70
|
+
- ".rspec"
|
71
|
+
- ".rubocop.yml"
|
72
|
+
- CHANGELOG.md
|
73
|
+
- CODE_OF_CONDUCT.md
|
74
|
+
- Gemfile
|
75
|
+
- LICENSE
|
76
|
+
- README.md
|
77
|
+
- Rakefile
|
78
|
+
- bin/console
|
79
|
+
- bin/setup
|
80
|
+
- lib/turbovax.rb
|
81
|
+
- lib/turbovax/appointment.rb
|
82
|
+
- lib/turbovax/constants.rb
|
83
|
+
- lib/turbovax/data_fetcher.rb
|
84
|
+
- lib/turbovax/handlers/location_handler.rb
|
85
|
+
- lib/turbovax/location.rb
|
86
|
+
- lib/turbovax/portal.rb
|
87
|
+
- lib/turbovax/twitter_client.rb
|
88
|
+
- lib/turbovax/version.rb
|
89
|
+
- turbovax.gemspec
|
90
|
+
homepage: https://github.com/hugem/turbovax-gem
|
91
|
+
licenses:
|
92
|
+
- AGPLv3
|
93
|
+
metadata:
|
94
|
+
homepage_uri: https://github.com/hugem/turbovax-gem
|
95
|
+
source_code_uri: https://github.com/hugem/turbovax-gem
|
96
|
+
changelog_uri: https://github.com/hugem/turbovax-gem/CHANGELOG.md
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: 2.5.0
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.3.1
|
111
|
+
requirements: []
|
112
|
+
rubygems_version: 3.1.4
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Quickly build vaccine twitter bots
|
116
|
+
test_files: []
|