south_african_air_quality_index 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d697dae6b8a2a2239caa16f7107edfca19a6fe4ca3b13dbdf35ed2cd1220a55
4
- data.tar.gz: cbb4c1a357e70556c6a1e778a24e740ac853de86f0a7780469fb240ab87a9f3f
3
+ metadata.gz: a23dbb1a42530463ec42613307c880649753409ab4fcc31d5a6e7ae9a1bb7c41
4
+ data.tar.gz: 3a8e0dc764b6c1487477893b5246829be7080f0a22a9ff399eef04cdd65cc8af
5
5
  SHA512:
6
- metadata.gz: 7b94a2be9950e13b4f8b363e297a236271144801b436cb02e0b9ef55b011eea2714c85427bdd8df476f71e1839297b46689a4687492fc762200ffb1bc4959cbb
7
- data.tar.gz: 15583299e9d8660eb9391642cc64f8d0495e1287368225e35c651c9b6d703cb681e6ab67ef46b0a6b6f79409c9d20641a23a76f838dcd24a842612f7724aa535
6
+ metadata.gz: 76ec747444e003f82a48c9ae1cd01ef0a54b2bdade29eb24d683e0b6e5103fc960b08d5c8fabc1c5ded3d299e716b909da7197fd7b31f21742e18461ae299172
7
+ data.tar.gz: 5213adc33c04ab0f81757a91592c92c56b4caf11748d53dfd8c6556ae2cc08bce10f8a3b73a21c2d00610d60e04034b729ebeb9a1eb10af7784f344aa0ae56e6
data/.gitignore CHANGED
@@ -54,3 +54,5 @@ build-iPhoneSimulator/
54
54
 
55
55
  # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
56
  # .rubocop-https?--*
57
+
58
+ .DS_Store
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.0
data/Gemfile.lock CHANGED
@@ -1,110 +1,121 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- south_african_air_quality_index (0.0.1)
5
- active_attr (~> 0.15)
6
- httparty (~> 0.20)
4
+ south_african_air_quality_index (0.0.3)
5
+ active_attr (~> 0.16.0)
6
+ httparty (~> 0.21.0)
7
+ nokogiri (~> 1.16.3)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
11
- actionpack (7.0.3)
12
- actionview (= 7.0.3)
13
- activesupport (= 7.0.3)
12
+ actionpack (7.0.4)
13
+ actionview (= 7.0.4)
14
+ activesupport (= 7.0.4)
14
15
  rack (~> 2.0, >= 2.2.0)
15
16
  rack-test (>= 0.6.3)
16
17
  rails-dom-testing (~> 2.0)
17
18
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
18
- actionview (7.0.3)
19
- activesupport (= 7.0.3)
19
+ actionview (7.0.4)
20
+ activesupport (= 7.0.4)
20
21
  builder (~> 3.1)
21
22
  erubi (~> 1.4)
22
23
  rails-dom-testing (~> 2.0)
23
24
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
24
- active_attr (0.15.4)
25
- actionpack (>= 3.0.2, < 7.1)
26
- activemodel (>= 3.0.2, < 7.1)
27
- activesupport (>= 3.0.2, < 7.1)
28
- activemodel (7.0.3)
29
- activesupport (= 7.0.3)
30
- activesupport (7.0.3)
25
+ active_attr (0.16.0)
26
+ actionpack (>= 3.0.2, < 7.2)
27
+ activemodel (>= 3.0.2, < 7.2)
28
+ activesupport (>= 3.0.2, < 7.2)
29
+ activemodel (7.0.4)
30
+ activesupport (= 7.0.4)
31
+ activesupport (7.0.4)
31
32
  concurrent-ruby (~> 1.0, >= 1.0.2)
32
33
  i18n (>= 1.6, < 2)
33
34
  minitest (>= 5.1)
34
35
  tzinfo (~> 2.0)
35
- addressable (2.8.0)
36
- public_suffix (>= 2.0.2, < 5.0)
36
+ addressable (2.8.6)
37
+ public_suffix (>= 2.0.2, < 6.0)
37
38
  ansi (1.5.0)
39
+ bigdecimal (3.1.7)
38
40
  builder (3.2.4)
39
41
  coderay (1.1.3)
40
42
  concurrent-ruby (1.1.10)
41
- crack (0.4.5)
43
+ crack (1.0.0)
44
+ bigdecimal
42
45
  rexml
43
46
  crass (1.0.6)
44
- erubi (1.10.0)
45
- hashdiff (1.0.1)
46
- httparty (0.20.0)
47
- mime-types (~> 3.0)
47
+ erubi (1.11.0)
48
+ hashdiff (1.1.0)
49
+ httparty (0.21.0)
50
+ mini_mime (>= 1.0.0)
48
51
  multi_xml (>= 0.5.2)
49
- i18n (1.10.0)
52
+ i18n (1.12.0)
50
53
  concurrent-ruby (~> 1.0)
51
- loofah (2.18.0)
54
+ loofah (2.19.1)
52
55
  crass (~> 1.0.2)
53
56
  nokogiri (>= 1.5.9)
54
57
  method_source (1.0.0)
55
- mime-types (3.4.1)
56
- mime-types-data (~> 3.2015)
57
- mime-types-data (3.2022.0105)
58
- minitest (5.15.0)
59
- minitest-focus (1.1.2)
58
+ mini_mime (1.1.5)
59
+ mini_portile2 (2.8.5)
60
+ minitest (5.22.3)
61
+ minitest-focus (1.4.0)
60
62
  minitest (>= 4, < 6)
61
- minitest-reporters (1.4.3)
63
+ minitest-reporters (1.6.1)
62
64
  ansi
63
65
  builder
64
66
  minitest (>= 5.0)
65
67
  ruby-progressbar
66
- mocha (1.11.2)
68
+ mocha (2.1.0)
69
+ ruby2_keywords (>= 0.0.5)
67
70
  multi_xml (0.6.0)
68
- nokogiri (1.13.6-arm64-darwin)
71
+ nokogiri (1.16.3)
72
+ mini_portile2 (~> 2.8.2)
69
73
  racc (~> 1.4)
70
- pry (0.14.1)
74
+ nokogiri (1.16.3-arm64-darwin)
75
+ racc (~> 1.4)
76
+ nokogiri (1.16.3-x86_64-linux)
77
+ racc (~> 1.4)
78
+ pry (0.14.2)
71
79
  coderay (~> 1.1)
72
80
  method_source (~> 1.0)
73
- public_suffix (4.0.7)
74
- racc (1.6.0)
75
- rack (2.2.3.1)
76
- rack-test (1.1.0)
77
- rack (>= 1.0, < 3)
81
+ public_suffix (5.0.4)
82
+ racc (1.6.1)
83
+ rack (2.2.4)
84
+ rack-test (2.0.2)
85
+ rack (>= 1.3)
78
86
  rails-dom-testing (2.0.3)
79
87
  activesupport (>= 4.2.0)
80
88
  nokogiri (>= 1.6)
81
- rails-html-sanitizer (1.4.2)
82
- loofah (~> 2.3)
83
- rake (13.0.6)
84
- rexml (3.2.5)
85
- ruby-progressbar (1.11.0)
86
- timecop (0.9.5)
87
- tzinfo (2.0.4)
89
+ rails-html-sanitizer (1.4.4)
90
+ loofah (~> 2.19, >= 2.19.1)
91
+ rake (13.1.0)
92
+ rexml (3.2.6)
93
+ ruby-progressbar (1.13.0)
94
+ ruby2_keywords (0.0.5)
95
+ timecop (0.9.8)
96
+ tzinfo (2.0.5)
88
97
  concurrent-ruby (~> 1.0)
89
- webmock (3.8.3)
90
- addressable (>= 2.3.6)
98
+ webmock (3.23.0)
99
+ addressable (>= 2.8.0)
91
100
  crack (>= 0.3.2)
92
101
  hashdiff (>= 0.4.0, < 2.0.0)
93
102
 
94
103
  PLATFORMS
95
104
  arm64-darwin-20
105
+ arm64-darwin-23
106
+ ruby
107
+ x86_64-linux
96
108
 
97
109
  DEPENDENCIES
98
- bundler (~> 2.3.5)
99
- minitest (~> 5.0)
100
- minitest-focus (~> 1.1.2)
101
- minitest-reporters (~> 1.4.2)
102
- mocha (~> 1.11.2)
103
- pry (~> 0.14.1)
104
- rake (~> 13.0)
110
+ minitest (~> 5.22.3)
111
+ minitest-focus (~> 1.4.0)
112
+ minitest-reporters (~> 1.6.1)
113
+ mocha (~> 2.1.0)
114
+ pry (~> 0.14.2)
115
+ rake (~> 13.1.0)
105
116
  south_african_air_quality_index!
106
- timecop (~> 0.9.1)
107
- webmock (~> 3.8.3)
117
+ timecop (~> 0.9.8)
118
+ webmock (~> 3.23.0)
108
119
 
109
120
  BUNDLED WITH
110
- 2.3.5
121
+ 2.4.17
data/README.md CHANGED
@@ -46,7 +46,7 @@ Or install it yourself as:
46
46
  end_date = Time.now
47
47
  client.station_report(station_name, start_date, end_date)
48
48
 
49
- # To get a report from multiple stations at once
49
+ # To get a report from multiple stations at once (HTML is returned for now)
50
50
  # Some defaults that are used:
51
51
  # interval: DEFAULT_INTERVAL (60)
52
52
  # report_type: REPORT_TYPE (average)
@@ -4,9 +4,10 @@ module SouthAfricanAirQualityIndex
4
4
 
5
5
  attr_reader :base_path, :port, :stations
6
6
 
7
- def initialize(base_path: BASE_PATH, port: 80)
7
+ def initialize(base_path: BASE_PATH, port: BASE_PORT)
8
8
  @base_path = base_path
9
9
  @port = port
10
+ @cookie = authorise
10
11
  end
11
12
 
12
13
  def self.compatible_api_version
@@ -15,55 +16,91 @@ module SouthAfricanAirQualityIndex
15
16
 
16
17
  # This is the version of the API docs this client was built off-of
17
18
  def self.api_version
18
- 'v1 2022-06-08'
19
+ 'v1 2024-03-18'
19
20
  end
20
21
 
21
22
  # Endpoints
23
+ def regions
24
+ return @regions if @regions
25
+
26
+ response = send_request(http_method: :get, path: '/v1/envista/regions', port: DATA_SOURCE_PORT, port_in_path: true)
27
+
28
+ if response["code"] != 200
29
+ puts "Fallback SAAQIS region fetch!"
30
+ @regions ||= fetch_regions_from_html
31
+ else
32
+ @regions ||= response["body"]
33
+ end
34
+ end
35
+
36
+ def fetch_regions_from_html
37
+ # https://saaqis.environment.gov.za/Report/HourlyReports
38
+ response = send_request(http_method: :get, path: 'Report/HourlyReports')
39
+ doc = Nokogiri::HTML(response["body"])
40
+
41
+ # JSON.parse(content).map { |j| j.dig("stations")}.flatten
42
+ # alex = JSON.parse(content).map { |j| j.dig("stations")}.flatten.select { |st| st["name"] == "Alexandra-NAQI" }.first
43
+ find_and_parse_m_regions(doc)
44
+ end
22
45
 
23
46
  # Memoize stations as they are unlikely to change often
24
47
  def stations
25
- @stations ||= send(http_method: :get, path: 'ajax/getAllStationsNew')
48
+ # This endpoint is no longer active:
49
+ # @stations ||= send_request(http_method: :get, path: 'ajax/getAllStations')
50
+
51
+ @stations ||= regions.map { |regions| regions["stations"] }.flatten.uniq
26
52
  end
27
53
 
28
54
  def stations_from_code(codes, build_for_response: false)
29
- stations['body']['Stations'].select do |station|
30
- codes.map(&:to_s).include?(station["serialCode"].to_s)
55
+ unless codes.is_a?(Array)
56
+ codes = [codes]
57
+ end
58
+
59
+ stations.select do |station|
60
+ codes.map(&:to_s).include?(station["stationId"].to_s)
31
61
  end
32
62
  end
33
63
 
34
64
  def selected_stations(station_names)
35
- stations['body']['Stations'].select do |station|
65
+ unless station_names.is_a?(Array)
66
+ station_names = [station_names]
67
+ end
68
+
69
+ stations.select do |station|
36
70
  station_matches?(station_names, station)
37
71
  end
38
72
  end
39
73
 
40
- def station_report(station_name, start_date, end_date, interval: DEFAULT_INTERVAL, report_type: REPORT_TYPE)
74
+ def station_report(station_name, start_date, end_date, interval: DEFAULT_INTERVAL, report_type: REPORT_TYPE, precent_valid: DEFAULT_PERCENT_VALID)
41
75
  station = selected_stations([station_name]).first
42
76
  return if station.empty?
43
77
 
44
- monitor_ids = fetch_monitor_ids(station)
45
-
46
- body = {
47
- "StationId": station['serialCode'],
48
- "MonitorsChannels": monitor_ids,
49
- "reportName": STATION_REPORT,
50
- "startDateAbsolute": start_date.to_s,
51
- "endDateAbsolute": end_date.to_s,
52
- "startDate": start_date.to_s,
53
- "endDate": end_date.to_s,
54
- "reportType": report_type,
55
- "fromTb": 60,
56
- "toTb": 60
57
- }.to_json
78
+ params = {
79
+ filterChannels: fetch_monitor_ids(station),
80
+ from: parse_time(start_date.to_s),
81
+ to: parse_time(end_date.to_s),
82
+ fromTimebase: interval,
83
+ toTimebase: interval,
84
+ precentValid: precent_valid,
85
+ timeBeginning: false,
86
+ useBackWard: true,
87
+ unitConversion: false,
88
+ includeSummary: true,
89
+ onlySummary: false,
90
+ }
58
91
 
59
- send(http_method: :get, path: 'report/GetStationReportData', body: body, params: { 'InPopUp': false })
92
+ path = "v1/envista/stations/#{station['stationId']}/#{report_type}"
93
+ send_request(http_method: :get, path: path, params: params, port: DATA_SOURCE_PORT, port_in_path: true)
60
94
  end
61
95
 
96
+ # This now will only return HTML
97
+ # TODO: Parse the HTML
98
+ # May not work:
62
99
  def multi_station_report(station_names, start_date, end_date, interval: DEFAULT_INTERVAL, report_type: REPORT_TYPE)
63
100
  extracted_stations = selected_stations(station_names)
64
101
 
65
102
  monitor_channels_by_station_id = extracted_stations.map { |station|
66
- [station['serialCode'].to_s, fetch_monitor_ids(station)]
103
+ [station['stationId'].to_s, fetch_monitor_ids(station)]
67
104
  }.to_h
68
105
 
69
106
  body = {
@@ -87,19 +124,21 @@ module SouthAfricanAirQualityIndex
87
124
  body = body.merge(hsh)
88
125
  end
89
126
 
90
- send(http_method: :get, path: 'report/GetMultiStationReportData', body: body.to_json)
127
+ send_request(http_method: :get, path: 'report/MultiStationTable', body: body.to_json)
91
128
  end
92
129
 
93
130
  private
94
131
 
95
- def send(http_method:, path:, body: {}, params: {})
132
+ def send_request(http_method:, path:, body: {}, params: {}, headers: {}, port: @port, port_in_path: false)
96
133
  start_time = micro_second_time
97
134
 
135
+ headers["Cookie"] = @cookie
136
+
98
137
  response = HTTParty.send(
99
138
  http_method.to_sym,
100
- construct_base_path(path, params),
139
+ construct_base_path(path, params, port, port_in_path),
101
140
  body: body,
102
- headers: { 'Content-Type': 'application/json' },
141
+ headers: headers.merge({ 'Content-Type': 'application/json' }),
103
142
  port: port,
104
143
  format: :json
105
144
  )
@@ -108,9 +147,19 @@ module SouthAfricanAirQualityIndex
108
147
  construct_response_object(response, path, start_time, end_time)
109
148
  end
110
149
 
150
+ # Quick n dirty time parsing
151
+ def parse_time(str_time)
152
+ time_components = str_time.to_s.split(' ')
153
+
154
+ # Have to leave out the timezone - #{time_components[2]}
155
+ "#{time_components[0]}T#{time_components[1]}"
156
+ end
157
+
111
158
  def construct_response_object(response, path, start_time, end_time)
112
159
  {
113
160
  'body' => parse_body(response, path),
161
+ 'code' => response.code,
162
+ 'cookies' => response.headers.dig('set-cookie'),
114
163
  'headers' => response.headers,
115
164
  'metadata' => construct_metadata(response, start_time, end_time)
116
165
  }
@@ -130,8 +179,12 @@ module SouthAfricanAirQualityIndex
130
179
  (Time.now.to_f * 1_000_000).to_i
131
180
  end
132
181
 
133
- def construct_base_path(path, params)
134
- constructed_path = "#{base_path}/#{path}"
182
+ def construct_base_path(path, params, port, port_in_path)
183
+ if port_in_path
184
+ constructed_path = "#{base_path}:#{port}/#{path}"
185
+ else
186
+ constructed_path = "#{base_path}/#{path}"
187
+ end
135
188
 
136
189
  if params == {}
137
190
  constructed_path
@@ -156,7 +209,8 @@ module SouthAfricanAirQualityIndex
156
209
  .any? do |station_name|
157
210
  processed_name = process_station(station_name)
158
211
 
159
- process_station(station['city']) == processed_name ||
212
+ process_station(station['ShortName']) == processed_name ||
213
+ process_station(station['city']) == processed_name ||
160
214
  process_station(station['DisplayName']) == processed_name ||
161
215
  process_station(station['name']) == processed_name ||
162
216
  process_station(station['owner']) == processed_name ||
@@ -166,12 +220,89 @@ module SouthAfricanAirQualityIndex
166
220
 
167
221
  def fetch_monitor_ids(station)
168
222
  station['monitors'].map do |monitor|
169
- monitor['channel']
223
+ monitor['channelId']
170
224
  end
171
225
  end
172
226
 
173
227
  def process_station(name)
174
- name.downcase.gsub(' ', ' ')
228
+ name.to_s.downcase.gsub(' ', ' ')
229
+ end
230
+
231
+ def find_and_parse_m_regions(doc)
232
+ doc.xpath('//script').each do |script|
233
+ if script.content.include?("m_Regions")
234
+ content = script.content[/let\s+m_Regions\s*=\s*(.*?)\s*;/, 1]
235
+ return JSON.parse(content) if content
236
+ end
237
+ end
238
+
239
+ nil
240
+ end
241
+
242
+ def authorise
243
+ generate_token(get_api_token)
244
+ end
245
+
246
+ # This is terrible. Unsure why there is a JWT, since there is no secret
247
+ # and this is public information.
248
+ def generate_token(api_token)
249
+ headers = {
250
+ 'Authorization': "ApiToken #{api_token}",
251
+ 'Accept': "application/json",
252
+ 'Content-Length': '0',
253
+ 'Host': BASE_URI,
254
+ # 'envi-data-source': 'SAAQIS_ENVISTA',
255
+ # 'Access-Control-Allow-Origin': 'https://saaqis.environment.gov.za',
256
+ # 'Access-Control-Allow-Credentials': 'true',
257
+ # 'Authority': "#{BASE_URI}:#{DATA_SOURCE_PORT}",
258
+ }
259
+
260
+ res = send_request(http_method: :post, path: 'v1/GenerateToken', headers: headers, port: DATA_SOURCE_PORT, port_in_path: true)
261
+
262
+ @cookie ||= res["cookies"].first
263
+ end
264
+
265
+ def get_api_token
266
+ # Account/GetApiToken
267
+ body = { "userName": "Guest" }
268
+ res = send_request(http_method: :post, path: 'Account/GetApiToken', body: body.to_json)
269
+
270
+ res.dig("body")
271
+ end
272
+
273
+ # Experimental stuff
274
+ def generate_jwt
275
+ # header = {
276
+ # "alg": "HS256",
277
+ # "typ": "JWT"
278
+ # }
279
+
280
+ time_now = Time.now
281
+
282
+ payload = {
283
+ "unique_name": "Guest",
284
+ "nbf": time_now.to_i,
285
+ "exp": (time_now + 3600).to_i,
286
+ "iat": time_now.to_i
287
+ }
288
+
289
+ # IMPORTANT: set nil as password parameter
290
+ # token = JWT.encode payload, nil, 'none'
291
+
292
+ # hmac_secret = ""
293
+ # token = JWT.encode payload, hmac_secret, 'HS256'
294
+
295
+ # token = JWT.encode payload, nil, 'none', { typ: 'JWT' }
296
+ # token = JWT.encode payload, nil, 'HS256', { typ: 'JWT' }
297
+
298
+ hmac_secret = 'ApiToken '
299
+ _token = JWT.encode payload, hmac_secret, 'HS256', { typ: 'JWT' }
300
+
301
+
302
+ # Account/GetApiToken
303
+ # v1/GenerateToken
304
+
305
+ authorization = "ApiToken " + res
175
306
  end
176
307
  end
177
308
  end
@@ -1,8 +1,13 @@
1
1
  module SouthAfricanAirQualityIndex
2
2
  module Constants
3
- BASE_PATH = 'https://saaqis.environment.gov.za'
3
+ BASE_URI = 'saaqis.environment.gov.za'
4
+ BASE_PATH = "https://#{BASE_URI}"
5
+
6
+ BASE_PORT = 443
7
+ DATA_SOURCE_PORT = 44301
4
8
 
5
9
  DEFAULT_INTERVAL = 60
10
+ DEFAULT_PERCENT_VALID = 75
6
11
  STATION_REPORT = 'station report'.freeze
7
12
  HOURLY_REPORT = 'hourly report'.freeze
8
13
  REPORT_TYPE = 'Average'.freeze
@@ -1,3 +1,3 @@
1
1
  module SouthAfricanAirQualityIndex
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -1,4 +1,6 @@
1
1
  require 'httparty'
2
+ # require 'jwt'
3
+ require 'nokogiri'
2
4
 
3
5
  require 'south_african_air_quality_index/constants'
4
6
  require 'south_african_air_quality_index/version'
data/notes.txt CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  https://aqicn.org/city/south-africa/ekurhuleni/bedfordview/
2
3
  https://saaqis.environment.gov.za/
3
4
 
@@ -173,4 +174,169 @@ Types of SA Air Quality reports
173
174
  - Index Report (AQI)
174
175
  - Matrix Report (1 station 1 metric gives stats)
175
176
 
177
+ March 2024
178
+ https://saaqis.environment.gov.za/ajax/getAllStationsNew
179
+
180
+ curl 'https://saaqis.environment.gov.za/Report/stationreport?InPopUp=True' \
181
+ -H 'authority: saaqis.environment.gov.za' \
182
+ -H 'accept: */*' \
183
+ -H 'accept-language: en-GB,en-US;q=0.9,en;q=0.8' \
184
+ -H 'cookie: ASP.NET_SessionId=vldu0doftifewm5f1wrubrme; __RequestVerificationToken=mb7uM--kGvUTsScjOUsGIopNIJCn7BBY1kXuniYsvniLixU_TfQNWBc9nbi9uMbSX9lpIIHVf0s_-zMXNPjEKnMD6ZjVJvCHsS8thCvNVnI1; __FormVerificationToken=owO78ynW81P2O9ZDkqK0K30cT-MmY8bISAkF4yseane_nFfruM3Uvc1AVhk-3PDDnL4JcVatG1ob8Yy3wuAkid1LqvzuXoOmON7cMwv1nRY1; X-Access-Token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ikd1ZXN0IiwibmJmIjoxNzEwNTE3MTIzLCJleHAiOjE3MTA1MjA3MjMsImlhdCI6MTcxMDUxNzEyM30.aE39-GM-zc0PNlUJAukb37wGn7DuKUup2Qirq6UT3pA' \
185
+ -H 'dnt: 1' \
186
+ -H 'referer: https://saaqis.environment.gov.za/' \
187
+ -H 'sec-ch-ua: "Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"' \
188
+ -H 'sec-ch-ua-mobile: ?0' \
189
+ -H 'sec-ch-ua-platform: "macOS"' \
190
+ -H 'sec-fetch-dest: empty' \
191
+ -H 'sec-fetch-mode: cors' \
192
+ -H 'sec-fetch-site: same-origin' \
193
+ -H 'sec-gpc: 1' \
194
+ -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36' \
195
+ -H 'x-requested-with: XMLHttpRequest' \
196
+ -H 'x-requestverificationtoken: owO78ynW81P2O9ZDkqK0K30cT-MmY8bISAkF4yseane_nFfruM3Uvc1AVhk-3PDDnL4JcVatG1ob8Yy3wuAkid1LqvzuXoOmON7cMwv1nRY1' \
197
+ --compressed
198
+
199
+ curl 'https://saaqis.environment.gov.za/home/getPage?pageName=Hourly%20Monitoring%20Data&InPopUp=true' \
200
+ -H 'authority: saaqis.environment.gov.za' \
201
+ -H 'accept: */*' \
202
+ -H 'accept-language: en-GB,en-US;q=0.9,en;q=0.8' \
203
+ -H 'cookie: ASP.NET_SessionId=vldu0doftifewm5f1wrubrme; __RequestVerificationToken=VMM7f3rhky_AQ7cea4DpzOpnLa6VwibqHEiuFpB7QvpIKIhA3iweLsxtcUaFDTcfzRwBJAA477qe82Q1xELh1W5wNJ_g1QvRmUz4E4YYZEs1; __FormVerificationToken=CPVZjs1Bzr7xBI9fECT96iKK8auAmA21WS2j9o_ZiV58YoIcOoBqWSAyvmfG-HfcWFRsjpik55_672rbNNx3Jd-bADn0Zk17pkgGzQk1K7I1; X-Access-Token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ikd1ZXN0IiwibmJmIjoxNzEwNTE3MjMwLCJleHAiOjE3MTA1MjA4MzAsImlhdCI6MTcxMDUxNzIzMH0.5JNuKp15f0w9BQIpMwOiTc6Gx_3Gug3YD7i2efBwFE0' \
204
+ -H 'dnt: 1' \
205
+ -H 'referer: https://saaqis.environment.gov.za/' \
206
+ -H 'sec-ch-ua: "Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"' \
207
+ -H 'sec-ch-ua-mobile: ?0' \
208
+ -H 'sec-ch-ua-platform: "macOS"' \
209
+ -H 'sec-fetch-dest: empty' \
210
+ -H 'sec-fetch-mode: cors' \
211
+ -H 'sec-fetch-site: same-origin' \
212
+ -H 'sec-gpc: 1' \
213
+ -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36' \
214
+ -H 'x-requested-with: XMLHttpRequest' \
215
+ -H 'x-requestverificationtoken: CPVZjs1Bzr7xBI9fECT96iKK8auAmA21WS2j9o_ZiV58YoIcOoBqWSAyvmfG-HfcWFRsjpik55_672rbNNx3Jd-bADn0Zk17pkgGzQk1K7I1' \
216
+ --compressed
217
+
218
+
219
+ Needs cookie and JWT:
176
220
 
221
+ curl 'https://saaqis.environment.gov.za:44301/v1/envista/regions' \
222
+ -H 'authority: saaqis.environment.gov.za:44301' \
223
+ -H 'accept: application/json' \
224
+ -H 'accept-language: en-GB,en-US;q=0.9,en;q=0.8' \
225
+ -H 'access-control-allow-credentials: true' \
226
+ -H 'access-control-allow-origin: https://saaqis.environment.gov.za' \
227
+ -H 'authorization: JwtToken' \
228
+ -H 'cache-control: no-cache' \
229
+ -H 'compression: gzip' \
230
+ -H 'content-type: application/json; charset=utf-8' \
231
+ -H 'contentlength: 0' \
232
+ -H 'cookie: ASP.NET_SessionId=vldu0doftifewm5f1wrubrme; __RequestVerificationToken=u6thajdYfxMhX6czeh1L60HP3r_pGsGLqyXPwpBxYVuXLGbHqLEs0vyA0TX4kv3xHD9JcuP4cLxxWZvvVzwCG1V1F8H6VrUjuxWw7-qaMhg1; __FormVerificationToken=bpDKkVnfQ8bgG159kuknJXr9GvkzplWNAB18bKodU7O5oAJn0nQ1Q3rGyAsAydvMo1LM0EB6sCmOF9uGTrMeyFRcYyehUA03ZJbvLij_oBI1; X-Access-Token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ikd1ZXN0IiwibmJmIjoxNzEwNTE4MjMzLCJleHAiOjE3MTA1MjE4MzMsImlhdCI6MTcxMDUxODIzM30.ESillaQmk8cJBuUFUcOCoC5c0HeOAT5ix9m98zKiXLc' \
233
+ -H 'dnt: 1' \
234
+ -H 'domainname;' \
235
+ -H 'envi-data-source: SAAQIS_ENVISTA' \
236
+ -H 'origin: https://saaqis.environment.gov.za' \
237
+ -H 'pragma: no-cache' \
238
+ -H 'referer: https://saaqis.environment.gov.za/' \
239
+ -H 'sec-ch-ua: "Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"' \
240
+ -H 'sec-ch-ua-mobile: ?0' \
241
+ -H 'sec-ch-ua-platform: "macOS"' \
242
+ -H 'sec-fetch-dest: empty' \
243
+ -H 'sec-fetch-mode: cors' \
244
+ -H 'sec-fetch-site: same-site' \
245
+ -H 'sec-gpc: 1' \
246
+ -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36' \
247
+ -H 'x-requestverificationtoken: bpDKkVnfQ8bgG159kuknJXr9GvkzplWNAB18bKodU7O5oAJn0nQ1Q3rGyAsAydvMo1LM0EB6sCmOF9uGTrMeyFRcYyehUA03ZJbvLij_oBI1' \
248
+ --compressed
249
+
250
+
251
+ [
252
+ {
253
+ "regionId": 0,
254
+ "name": "None",
255
+ "stations": []
256
+ },
257
+ {
258
+ "regionId": 1,
259
+ "name": "Mpumalanga",
260
+ "stations": [
261
+ {
262
+ "stationId": 363,
263
+ "name": "Eskom-Wilge",
264
+ "shortName": "Wilge",
265
+ "stationsTag": "(null)",
266
+ "location": {
267
+ "latitude": -25.975444444,
268
+ "longitude": 28.985611
269
+ },
270
+ "timebase": 1,
271
+ "active": true,
272
+ "owner": "Eskom",
273
+ "ownerId": 6,
274
+ "regionId": 1,
275
+ "monitors": [
276
+ {
277
+ "channelId": 1,
278
+ "name": "Amb Pressure",
279
+ "alias": null,
280
+ "active": false,
281
+ "typeId": 71,
282
+ "pollutantId": 36,
283
+ "units": "mBar",
284
+ "unitID": 40,
285
+ "description": null,
286
+ "mapView": true,
287
+ "isIndex": false,
288
+ "PollutantCategory": 2,
289
+ "NumericFormat": "5.0",
290
+ "LowRange": 0.0,
291
+ "HighRange": 100.0,
292
+ "state": 1,
293
+ "PctValid": 75,
294
+ "MonitorTitle": "tl_BP",
295
+ "MON_StartDate": "10/17/2023 2:57:58 PM",
296
+ "MON_EndDate": "12/31/9999 11:59:59 PM"
297
+ },
298
+ {
299
+ "channelId": 2,
300
+ "name": "Amb RelHum",
301
+ "alias": null,
302
+ "active": false,
303
+ "typeId": 69,
304
+ "pollutantId": 18,
305
+ "units": "%",
306
+ "unitID": 1,
307
+ "description": null,
308
+ "mapView": true,
309
+ "isIndex": false,
310
+ "PollutantCategory": 2,
311
+ "NumericFormat": "5.2",
312
+ "LowRange": 0.0,
313
+ "HighRange": 100.0,
314
+ "state": 1,
315
+ "PctValid": 75,
316
+ "MonitorTitle": "tl_RH",
317
+ "MON_StartDate": "10/17/2023 2:57:58 PM",
318
+ "MON_EndDate": "12/31/9999 11:59:59 PM"
319
+ },
320
+
321
+
322
+ curl 'https://saaqis.environment.gov.za/report/MultiStationTable' \
323
+ -H 'authority: saaqis.environment.gov.za' \
324
+ -H 'accept: */*' \
325
+ -H 'accept-language: en-GB,en-US;q=0.9,en;q=0.8' \
326
+ -H 'content-type: application/json; charset=UTF-8' \
327
+ -H 'cookie: ASP.NET_SessionId=vldu0doftifewm5f1wrubrme; __RequestVerificationToken=u6thajdYfxMhX6czeh1L60HP3r_pGsGLqyXPwpBxYVuXLGbHqLEs0vyA0TX4kv3xHD9JcuP4cLxxWZvvVzwCG1V1F8H6VrUjuxWw7-qaMhg1; __FormVerificationToken=bpDKkVnfQ8bgG159kuknJXr9GvkzplWNAB18bKodU7O5oAJn0nQ1Q3rGyAsAydvMo1LM0EB6sCmOF9uGTrMeyFRcYyehUA03ZJbvLij_oBI1; X-Access-Token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ikd1ZXN0IiwibmJmIjoxNzEwNTE4MjMzLCJleHAiOjE3MTA1MjE4MzMsImlhdCI6MTcxMDUxODIzM30.ESillaQmk8cJBuUFUcOCoC5c0HeOAT5ix9m98zKiXLc' \
328
+ -H 'dnt: 1' \
329
+ -H 'origin: https://saaqis.environment.gov.za' \
330
+ -H 'referer: https://saaqis.environment.gov.za/' \
331
+ -H 'sec-ch-ua: "Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"' \
332
+ -H 'sec-ch-ua-mobile: ?0' \
333
+ -H 'sec-ch-ua-platform: "macOS"' \
334
+ -H 'sec-fetch-dest: empty' \
335
+ -H 'sec-fetch-mode: cors' \
336
+ -H 'sec-fetch-site: same-origin' \
337
+ -H 'sec-gpc: 1' \
338
+ -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36' \
339
+ -H 'x-requested-with: XMLHttpRequest' \
340
+ -H 'x-requestverificationtoken: bpDKkVnfQ8bgG159kuknJXr9GvkzplWNAB18bKodU7O5oAJn0nQ1Q3rGyAsAydvMo1LM0EB6sCmOF9uGTrMeyFRcYyehUA03ZJbvLij_oBI1' \
341
+ --data-raw '{"m_ReportConfig":{"reportName":"station report","reportType":"Average","actualReportType":"Average","startDate":"14/03/2024 00:01","endDate":"15/03/2024 00:00","startDateAbsolute":"14/03/2024 00:01","endDateAbsolute":"15/03/2024 00:00","fromTb":60,"toTb":60,"MonitorsChannels":[1,7,8,9,23,25,27,28,29,31],"StationId":73,"monitorChannelsByStationId":{"73":[1,7,8,9,23,25,27,28,29,31]},"monitorChannelsByStationId[0].Key":"73","monitorChannelsByStationId[0].Value":[1,7,8,9,23,25,27,28,29,31]},"reportConfig":{"reportName":"station report","reportType":"Average","actualReportType":"Average","startDate":"14/03/2024 00:01","endDate":"15/03/2024 00:00","startDateAbsolute":"14/03/2024 00:01","endDateAbsolute":"15/03/2024 00:00","fromTb":60,"toTb":60,"MonitorsChannels":[1,7,8,9,23,25,27,28,29,31],"StationId":73,"monitorChannelsByStationId":{"73":[1,7,8,9,23,25,27,28,29,31]},"monitorChannelsByStationId[0].Key":"73","monitorChannelsByStationId[0].Value":[1,7,8,9,23,25,27,28,29,31]},"InPopUp":true,"isSummaryOnBottomTable":true}' \
342
+ --compressed
@@ -22,17 +22,18 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.add_dependency "httparty", "~> 0.20"
26
- spec.add_dependency "active_attr", "~> 0.15"
25
+ spec.add_dependency "httparty", "~> 0.21.0"
26
+ spec.add_dependency "active_attr", "~> 0.16.0"
27
+ # spec.add_dependency "jwt", "~> 2.8.1"
28
+ spec.add_dependency "nokogiri", "~> 1.16.3"
27
29
 
28
30
  # Development dependancies
29
- spec.add_development_dependency "bundler", "~> 2.3.5"
30
- spec.add_development_dependency "rake", "~> 13.0"
31
- spec.add_development_dependency "minitest", "~> 5.0"
32
- spec.add_development_dependency "minitest-focus", "~> 1.1.2"
33
- spec.add_development_dependency "minitest-reporters", "~> 1.4.2"
34
- spec.add_development_dependency "timecop", "~> 0.9.1"
35
- spec.add_development_dependency "mocha", "~> 1.11.2"
36
- spec.add_development_dependency "pry", "~> 0.14.1"
37
- spec.add_development_dependency "webmock", "~> 3.8.3"
31
+ spec.add_development_dependency "rake", "~> 13.1.0"
32
+ spec.add_development_dependency "minitest", "~> 5.22.3"
33
+ spec.add_development_dependency "minitest-focus", "~> 1.4.0"
34
+ spec.add_development_dependency "minitest-reporters", "~> 1.6.1"
35
+ spec.add_development_dependency "timecop", "~> 0.9.8"
36
+ spec.add_development_dependency "mocha", "~> 2.1.0"
37
+ spec.add_development_dependency "pry", "~> 0.14.2"
38
+ spec.add_development_dependency "webmock", "~> 3.23.0"
38
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: south_african_air_quality_index
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - trex22
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-08 00:00:00.000000000 Z
11
+ date: 2024-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -16,154 +16,154 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.20'
19
+ version: 0.21.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.20'
26
+ version: 0.21.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: active_attr
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.15'
33
+ version: 0.16.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.15'
40
+ version: 0.16.0
41
41
  - !ruby/object:Gem::Dependency
42
- name: bundler
42
+ name: nokogiri
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 2.3.5
48
- type: :development
47
+ version: 1.16.3
48
+ type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 2.3.5
54
+ version: 1.16.3
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '13.0'
61
+ version: 13.1.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '13.0'
68
+ version: 13.1.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '5.0'
75
+ version: 5.22.3
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '5.0'
82
+ version: 5.22.3
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: minitest-focus
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 1.1.2
89
+ version: 1.4.0
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 1.1.2
96
+ version: 1.4.0
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: minitest-reporters
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 1.4.2
103
+ version: 1.6.1
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 1.4.2
110
+ version: 1.6.1
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: timecop
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 0.9.1
117
+ version: 0.9.8
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 0.9.1
124
+ version: 0.9.8
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: mocha
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 1.11.2
131
+ version: 2.1.0
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: 1.11.2
138
+ version: 2.1.0
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: pry
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: 0.14.1
145
+ version: 0.14.2
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: 0.14.1
152
+ version: 0.14.2
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: webmock
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: 3.8.3
159
+ version: 3.23.0
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: 3.8.3
166
+ version: 3.23.0
167
167
  description: A client for using the South African Air Quality Index API in Ruby. This
168
168
  is an unofficial project.
169
169
  email:
@@ -173,6 +173,7 @@ extensions: []
173
173
  extra_rdoc_files: []
174
174
  files:
175
175
  - ".gitignore"
176
+ - ".ruby-version"
176
177
  - CODE_OF_CONDUCT.md
177
178
  - Gemfile
178
179
  - Gemfile.lock
@@ -206,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
207
  - !ruby/object:Gem::Version
207
208
  version: '0'
208
209
  requirements: []
209
- rubygems_version: 3.3.4
210
+ rubygems_version: 3.5.3
210
211
  signing_key:
211
212
  specification_version: 4
212
213
  summary: A client for using the South African Air Quality Index API in Ruby.