ewelink 4.1.0 → 5.0.0

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/VERSION +1 -1
  3. data/lib/ewelink/api.rb +79 -55
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8cba69a24adaf18a0be08cbf1618b612d6d0e90fd788d8b9f2360f7d19f67406
4
- data.tar.gz: adc650eac1533d343320490132302827a79f0fc4a0571d0dcba91ef926e5f682
3
+ metadata.gz: f2f26d6875823958f38a894377c7dfbce3531d7f98b1c1c69b18bd88caf44c1d
4
+ data.tar.gz: d25335eb91529c57d2780c42e8cef5856cb7bfa7f222e5282816bff122854b08
5
5
  SHA512:
6
- metadata.gz: 39ab9a66085401bb70f34f74b9220e70ad691c5db65b180816d5a284462162064364d6e6ed3f2b5d990f9c3fa3450472fb0b13ba0b3ecbca1a22042de3926d28
7
- data.tar.gz: 19b1627d07a411ef4fe04578f924a7d339413d65d7132b8cd7614877b1e72300b5cbd51f1f9a6469807fe3179e37a65c048f1e699601c4eb3b4f05b82b0e663f
6
+ metadata.gz: 42786625ddc6d8482a5d85922e25b4b650e080866148b378aeb579ad25e0dcee00d4f12b8ebf74492e207d80e6ed0d65b6f0c71ccdaf5c3f81280b1efbd4bc7b
7
+ data.tar.gz: 8eb9cc344312a30a7b0924ff02b052516bf5123a7d706acb15e000b91db2c91ecf56a2173d22543403f7fbbc31773e30d97d3c5379ad37ab42e34b55f8f67366
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.1.0
1
+ 5.0.0
data/lib/ewelink/api.rb CHANGED
@@ -2,13 +2,14 @@ module Ewelink
2
2
 
3
3
  class Api
4
4
 
5
- APP_ID = 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq'.freeze
6
- APP_SECRET = '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM'.freeze
5
+ APP_ID = 'Uw83EKZFxdif7XFXEsrpduz5YyjP7nTl'.freeze
6
+ APP_SECRET = 'mXLOjea0woSMvK9gw7Fjsy7YlFO4iSu6'.freeze
7
+ DEFAULT_COUNTRY_CODE = '+44'.freeze
7
8
  DEFAULT_REGION = 'us'.freeze
8
9
  REQUEST_TIMEOUT = 10.seconds
9
10
  RF_BRIDGE_DEVICE_UIID = 28
10
11
  SWITCH_DEVICES_UIIDS = [1, 5, 6, 24].freeze
11
- URL = 'https://#{region}-api.coolkit.cc:8080'.freeze
12
+ URL = 'https://#{region}-apia.coolkit.cc'.freeze
12
13
  VERSION = 8
13
14
  WEB_SOCKET_CHECK_AUTHENTICATION_TIMEOUT = 30.seconds
14
15
  WEB_SOCKET_PING_TOLERANCE_FACTOR = 1.5
@@ -40,23 +41,21 @@ module Ewelink
40
41
  process_action do
41
42
  synchronize(:press_rf_bridge_button) do
42
43
  button = find_rf_bridge_button!(uuid)
43
- web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
44
- params = {
45
- 'action' => 'update',
46
- 'apikey' => button[:api_key],
47
- 'deviceid' => button[:device_id],
48
- 'params' => {
49
- 'cmd' => 'transmit',
50
- 'rfChl' => button[:channel],
51
- },
52
- 'sequence' => web_socket_sequence,
53
- 'ts' => 0,
54
- 'userAgent' => 'app',
55
- }
56
- Ewelink.logger.debug(self.class.name) { "Pressing RF bridge button #{button[:uuid].inspect}" }
57
- send_to_web_socket(JSON.generate(params))
58
- true
59
- end
44
+ headers = authentication_headers.merge(
45
+ 'X-CK-Appid' => APP_ID,
46
+ 'X-CK-Nonce' => nonce,
47
+ )
48
+ params = {
49
+ 'type' => 1,
50
+ 'id' => button[:device_id],
51
+ 'params' => {
52
+ 'cmd' => 'transmit',
53
+ 'rfChl' => button[:channel],
54
+ },
55
+ }
56
+ body = JSON.generate(params)
57
+ response = rest_request(:post, '/v2/device/thing/status', headers:, body:)
58
+ response['error'] == 0
60
59
  end
61
60
  end
62
61
  end
@@ -87,6 +86,7 @@ module Ewelink
87
86
  %i(
88
87
  @authentication_infos
89
88
  @devices
89
+ @homes_ids
90
90
  @last_web_socket_pong_at
91
91
  @region
92
92
  @rf_bridge_buttons
@@ -105,21 +105,21 @@ module Ewelink
105
105
  def rf_bridge_buttons
106
106
  synchronize(:rf_bridge_buttons) do
107
107
  @rf_bridge_buttons ||= [].tap do |buttons|
108
- rf_bridge_devices = devices.select { |device| device['uiid'] == RF_BRIDGE_DEVICE_UIID }.tap do |devices|
108
+ rf_bridge_devices = devices.select { |device| device['itemData']['extra']['uiid'] == RF_BRIDGE_DEVICE_UIID }.tap do |devices|
109
109
  Ewelink.logger.debug(self.class.name) { "Found #{devices.size} RF 433MHz bridge device(s)" }
110
110
  end
111
111
  rf_bridge_devices.each do |device|
112
- api_key = device['apikey'].presence || next
113
- device_id = device['deviceid'].presence || next
114
- device_name = device['name'].presence || next
115
- buttons = device['params']['rfList'].each do |rf|
112
+ api_key = device['itemData']['apikey'].presence || next
113
+ device_id = device['itemData']['deviceid'].presence || next
114
+ device_name = device['itemData']['name'].presence || next
115
+ buttons = device['itemData']['params']['rfList'].each do |rf|
116
116
  button = {
117
117
  api_key:,
118
118
  channel: rf['rfChl'],
119
119
  device_id:,
120
120
  device_name:,
121
121
  }
122
- remote_info = device['tags']['zyx_info'].find { |info| info['buttonName'].find { |data| data.key?(button[:channel].to_s) } }.presence || next
122
+ remote_info = device['itemData']['tags']['zyx_info'].find { |info| info['buttonName'].find { |data| data.key?(button[:channel].to_s) } }.presence || next
123
123
  remote_name = remote_info['name'].try(:squish).presence || next
124
124
  button_info = remote_info['buttonName'].find { |info| info.key?(button[:channel].to_s) }.presence || next
125
125
  button_name = button_info.values.first.try(:squish).presence || next
@@ -160,15 +160,15 @@ module Ewelink
160
160
  def switches
161
161
  synchronize(:switches) do
162
162
  @switches ||= [].tap do |switches|
163
- switch_devices = devices.select { |device| SWITCH_DEVICES_UIIDS.include?(device['uiid']) }
163
+ switch_devices = devices.select { |device| SWITCH_DEVICES_UIIDS.include?(device['itemData']['extra']['uiid']) }
164
164
  switch_devices.each do |device|
165
- api_key = device['apikey'].presence || next
166
- device_id = device['deviceid'].presence || next
167
- name = device['name'].presence || next
165
+ api_key = device['itemData']['apikey'].presence || next
166
+ device_id = device['itemData']['deviceid'].presence || next
167
+ name = device['itemData']['name'].presence || next
168
168
  switch = {
169
169
  api_key:,
170
170
  device_id:,
171
- model: device['productModel'],
171
+ model: device['itemData']['productModel'],
172
172
  name:,
173
173
  }
174
174
  switch[:uuid] = Digest::UUID.uuid_v5(Digest::UUID::DNS_NAMESPACE, switch[:device_id])
@@ -242,12 +242,8 @@ module Ewelink
242
242
  synchronize(:authentication_infos) do
243
243
  @authentication_infos ||= begin
244
244
  params = {
245
- 'appid' => APP_ID,
246
- 'imei' => SecureRandom.uuid.upcase,
247
- 'nonce' => nonce,
245
+ 'countryCode' => DEFAULT_COUNTRY_CODE,
248
246
  'password' => password,
249
- 'ts' => Time.now.to_i,
250
- 'version' => VERSION,
251
247
  }
252
248
  if email.present?
253
249
  params['email'] = email
@@ -255,12 +251,20 @@ module Ewelink
255
251
  params['phoneNumber'] = phone_number
256
252
  end
257
253
  body = JSON.generate(params)
258
- response = rest_request(:post, '/api/user/login', { body:, headers: { 'Authorization' => "Sign #{Base64.encode64(OpenSSL::HMAC.digest('SHA256', APP_SECRET, body))}" } })
259
- raise(Error.new('Authentication token not found')) if response['at'].blank?
260
- raise(Error.new('API key not found')) if response['user'].blank? || response['user']['apikey'].blank?
254
+ response = rest_request(:post, '/v2/user/login', {
255
+ body:,
256
+ headers: {
257
+ 'Authorization' => "Sign #{Base64.encode64(OpenSSL::HMAC.digest('SHA256', APP_SECRET, body))}",
258
+ 'X-CK-Appid': APP_ID,
259
+ 'X-CK-Nonce': nonce,
260
+ },
261
+ })
262
+ raise(Error.new('Invalid authentication response')) unless response['data'].is_a?(Hash) && response['data']['user'].is_a?(Hash)
263
+ raise(Error.new('Authentication token not found')) unless response['data']['at'].present?
264
+ raise(Error.new('API key not found')) unless response['data']['user']['apikey'].present?
261
265
  {
262
- authentication_token: response['at'].tap { Ewelink.logger.debug(self.class.name) { 'Authentication token found' } },
263
- api_key: response['user']['apikey'].tap { Ewelink.logger.debug(self.class.name) { 'API key found' } },
266
+ api_key: response['data']['user']['apikey'].tap { Ewelink.logger.debug(self.class.name) { 'API key found' } },
267
+ authentication_token: response['data']['at'].tap { Ewelink.logger.debug(self.class.name) { 'Authentication token found' } },
264
268
  }
265
269
  end
266
270
  end
@@ -273,15 +277,22 @@ module Ewelink
273
277
  def devices
274
278
  synchronize(:devices) do
275
279
  @devices ||= begin
276
- params = {
277
- 'appid' => APP_ID,
278
- 'getTags' => 1,
279
- 'nonce' => nonce,
280
- 'ts' => Time.now.to_i,
281
- 'version' => VERSION,
282
- }
283
- response = rest_request(:get, '/api/user/device', headers: authentication_headers, query: params)
284
- response['devicelist'].tap { |devices| Ewelink.logger.debug(self.class.name) { "Found #{devices.size} device(s)" } }
280
+ devices = []
281
+ homes_ids.each do |home_id|
282
+ params = {
283
+ 'familyid' => home_id,
284
+ 'num' => 0,
285
+ }
286
+ headers = authentication_headers.merge(
287
+ 'X-CK-Appid' => APP_ID,
288
+ 'X-CK-Nonce' => nonce,
289
+ )
290
+ response = rest_request(:get, '/v2/device/thing', headers:, query: params)
291
+ raise('Invalid devices response') unless response['data'].is_a?(Hash) && response['data']['thingList'].is_a?(Array)
292
+ devices += response['data']['thingList']
293
+ end
294
+ Ewelink.logger.debug(self.class.name) { "Found #{devices.size} device(s)" }
295
+ devices
285
296
  end
286
297
  end
287
298
  end
@@ -294,6 +305,20 @@ module Ewelink
294
305
  switches.find { |switch| switch[:uuid] == uuid } || raise(Error.new("No such switch with UUID: #{uuid.inspect}"))
295
306
  end
296
307
 
308
+ def homes_ids
309
+ synchronize(:homes_ids) do
310
+ @homes_ids ||= begin
311
+ headers = authentication_headers.merge(
312
+ 'X-CK-Appid' => APP_ID,
313
+ 'X-CK-Nonce' => nonce,
314
+ )
315
+ response = rest_request(:get, '/v2/family', headers:)
316
+ raise('Invalid homes response') unless response['data'].is_a?(Hash) && response['data']['familyList'].is_a?(Array)
317
+ response['data']['familyList'].map { |home| home['id'] }.compact.uniq
318
+ end
319
+ end
320
+ end
321
+
297
322
  def nonce
298
323
  SecureRandom.hex[0, 8]
299
324
  end
@@ -311,17 +336,17 @@ module Ewelink
311
336
 
312
337
  def rest_request(method, path, options = {})
313
338
  url = "#{URL.gsub('#{region}', region)}#{path}"
339
+ url.gsub!(/#{Regexp.escape("#{region}-api")}/, "#{region}-disp") if options[:dispatch].present?
314
340
  method = method.to_s.upcase
315
341
  headers = (options[:headers] || {}).reverse_merge('Content-Type' => 'application/json')
316
342
  Ewelink.logger.debug(self.class.name) { "#{method} #{url}" }
317
343
  response = HTTParty.send(method.downcase, url, options.merge(headers:).reverse_merge(timeout: REQUEST_TIMEOUT))
318
344
  raise(Error.new("#{method} #{url}: #{response.code}")) unless response.success?
319
- if response['error'] == 301 && response['region'].present?
320
- @region = response['region']
345
+ if response['error'] == 10_004 && response['data'].present? && response['data']['region'].present?
346
+ @region = response['data']['region']
321
347
  Ewelink.logger.debug(self.class.name) { "Switched to region #{region.inspect}" }
322
348
  return rest_request(method, path, options)
323
349
  end
324
- remove_instance_variable(:@authentication_infos) if instance_variable_defined?(:@authentication_infos) && [401, 403].include?(response['error'])
325
350
  raise(Error.new("#{method} #{url}: #{response['error']} #{response['msg']}".strip)) if response['error'].present? && response['error'] != 0
326
351
  response.to_h
327
352
  rescue Errno::ECONNREFUSED, OpenSSL::OpenSSLError, SocketError, Timeout::Error => e
@@ -468,13 +493,12 @@ module Ewelink
468
493
  synchronize(:web_socket_url) do
469
494
  @web_socket_url ||= begin
470
495
  params = {
471
- 'accept' => 'ws',
472
496
  'appid' => APP_ID,
473
497
  'nonce' => nonce,
474
498
  'ts' => Time.now.to_i,
475
499
  'version' => VERSION,
476
500
  }
477
- response = rest_request(:post, '/dispatch/app', body: JSON.generate(params), headers: authentication_headers)
501
+ response = rest_request(:post, '/dispatch/app', body: JSON.generate(params), dispatch: true, headers: authentication_headers)
478
502
  raise('Error while getting WebSocket URL') unless response['error'] == 0
479
503
  domain = response['domain'].presence || raise("Can't get WebSocket server domain")
480
504
  port = response['port'].presence || raise("Can't get WebSocket server port")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ewelink
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Toulotte
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-20 00:00:00.000000000 Z
11
+ date: 2022-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport