git-fit 0.10.3 → 0.10.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e28cf553804e9da6cdf7ed898602802136f6c9689a826b9719e63e2143d3211a
4
- data.tar.gz: c4de8625a28667946149d9685de1f880bbe4eb56ec00c18ca1f1c7e3c49d9ba2
3
+ metadata.gz: 69674e0fc800279e2099fe879318f4b8f3ce751325515192474dc40223438293
4
+ data.tar.gz: f91fd18620742288f923f302917667121d310a5707ac71cc185f77da5fc154a6
5
5
  SHA512:
6
- metadata.gz: 32c53064259112990880d3bf91ab648bb06b6e27261e020b6fbf154c0f7e372737d5175c4e760216d1c62e0f15fe3f48a2d5546620d1ba7c6d31f17bf6c5e553
7
- data.tar.gz: 4eff014203d8c5dcf8f68fb6f7c93e239a4ff3dfb7aea444ddd058023532122eb65f9aeaa75d509435b43d3b8a599884d71650e682cded206365ef92cd7675ac
6
+ metadata.gz: f204350fdd449be0942ba5d42917a54460198f61ffdfde00b6c51fa39cdb379c0e056851c05e0bae6577b2d3dca9a0428bc967247245528c65c94b1b9f32d591
7
+ data.tar.gz: 9d4bf7e3c1c72ebfc4a35c05e2b5c150b87c17cfd67ca634ede7e4026b72978c89360434baac73b9e36addd1f88b8c61b8820c0089712086f9cc9dee8777e989
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'zlib'
5
+ require 'openssl'
6
+ require 'json'
7
+ require 'time'
8
+ require_relative '../geo/coord_transform'
9
+
10
+ module GitFit
11
+ module Source
12
+ module Keep
13
+ PLACEHOLDER_POLYLINE = 'gqqrFurkeU??'
14
+
15
+ AES_KEY = Base64.decode64('NTZmZTU5OzgyZzpkODczYw==').freeze
16
+ AES_IV = Base64.decode64('MjM0Njg5MjQzMjkyMDMwMA==').freeze
17
+
18
+ TIMESTAMP_THRESHOLD = 3_600_000
19
+ HR_THRESHOLD = 100
20
+
21
+ module_function
22
+
23
+ def decode_geo_points(geo_data)
24
+ return nil unless geo_data
25
+ raw = Base64.decode64(geo_data)
26
+ decrypted = aes_decrypt(raw)
27
+ decompressed = zlib_inflate(decrypted)
28
+ JSON.parse(decompressed)
29
+ rescue StandardError
30
+ nil
31
+ end
32
+
33
+ def decode_hr_data(hr_data)
34
+ return nil unless hr_data
35
+ raw = Base64.decode64(hr_data)
36
+ decompressed = zlib_inflate(raw)
37
+ JSON.parse(decompressed)
38
+ rescue StandardError
39
+ nil
40
+ end
41
+
42
+ def aes_decrypt(data)
43
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
44
+ cipher.decrypt
45
+ cipher.key = AES_KEY
46
+ cipher.iv = AES_IV
47
+ cipher.update(data) + cipher.final
48
+ end
49
+
50
+ def zlib_inflate(data)
51
+ [-Zlib::MAX_WBITS, Zlib::MAX_WBITS, Zlib::MAX_WBITS + 16].each do |wbits|
52
+ inflater = Zlib::Inflate.new(wbits)
53
+ result = inflater.inflate(data)
54
+ begin
55
+ inflater.finish
56
+ rescue StandardError
57
+ nil
58
+ end
59
+ inflater.close
60
+ return result
61
+ rescue Zlib::DataError
62
+ next
63
+ end
64
+ raise Zlib::DataError, 'cannot inflate data'
65
+ end
66
+
67
+ def compute_wgs_points(payload, run_data)
68
+ geo_points = payload[:geo_points]
69
+ hr_data = payload[:hr_data]
70
+ return [] unless geo_points&.any?
71
+
72
+ start_time = run_data['startTime']
73
+ use_absolute_ts = geo_points.first['timestamp'].to_i > TIMESTAMP_THRESHOLD
74
+ effective_start = use_absolute_ts ? 0 : start_time
75
+
76
+ geo_points.map do |p|
77
+ lat, lng = Geo::CoordTransform.gcj02_to_wgs84_exact(p['latitude'], p['longitude'])
78
+ ts = p['timestamp'] || p['unixTimestamp']
79
+ pt_time = ts ? Time.at(effective_start / 1000.0 + ts.to_i / 10.0).utc : nil
80
+ hr = find_nearest_hr(hr_data, ts, start_time)
81
+ [lat, lng, p['altitude'], pt_time, hr]
82
+ end
83
+ end
84
+
85
+ def find_nearest_hr(hr_data, geo_ts, start_time, threshold: HR_THRESHOLD)
86
+ return nil unless hr_data&.any? && geo_ts
87
+
88
+ normalize = lambda { |ts|
89
+ if ts > TIMESTAMP_THRESHOLD
90
+ ts - start_time / 100
91
+ else
92
+ ts
93
+ end
94
+ }
95
+
96
+ target = normalize.call(geo_ts)
97
+
98
+ nearest = nil
99
+ min_diff = Float::INFINITY
100
+
101
+ hr_data.each do |item|
102
+ ts = item['timestamp']
103
+ next unless ts
104
+
105
+ diff = (normalize.call(ts) - target).abs
106
+ if diff <= threshold && diff < min_diff
107
+ nearest = item
108
+ min_diff = diff
109
+ end
110
+ end
111
+
112
+ hr = nearest&.dig('beatsPerMinute')
113
+ hr&.positive? ? hr : nil
114
+ end
115
+ end
116
+ end
117
+ end
@@ -7,5 +7,6 @@ end
7
7
 
8
8
  require_relative 'util/tally'
9
9
  require_relative 'source/base'
10
+ require_relative 'source/keep'
10
11
 
11
12
  GitFit::Source::Tally = GitFit::Util::Tally
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base'
4
- require 'base64'
5
- require 'zlib'
6
- require 'openssl'
7
4
  require 'time'
8
5
  require 'tzinfo'
9
6
 
@@ -14,11 +11,6 @@ module GitFit
14
11
  STATS_URL = 'https://api.gotokeep.com/pd/v3/stats/detail?dateUnit=all&type=%{sport}&lastDate=%{last_date}'
15
12
  LOG_URL = 'https://api.gotokeep.com/pd/v3/%{sport}log/%{run_id}'
16
13
 
17
- AES_KEY = Base64.decode64('NTZmZTU5OzgyZzpkODczYw==')
18
- AES_IV = Base64.decode64('MjM0Njg5MjQzMjkyMDMwMA==')
19
-
20
- PLACEHOLDER_POLYLINE = 'gqqrFurkeU??'
21
-
22
14
  KEEP_TYPES = %w[running hiking cycling].freeze
23
15
  KEEP_RAW_EXT = 'json'
24
16
 
@@ -38,9 +30,6 @@ module GitFit
38
30
  'mountaineering' => 'Hiking',
39
31
  }.freeze
40
32
 
41
- TIMESTAMP_THRESHOLD = 3_600_000
42
- HR_THRESHOLD = 100
43
-
44
33
  register_adapter
45
34
  register_config :phone, :password
46
35
 
@@ -231,7 +220,7 @@ module GitFit
231
220
  heartRates: payload[:hr_data],
232
221
  }, keep_id)
233
222
 
234
- wgs_points = compute_wgs_points(payload, run_data)
223
+ wgs_points = Source::Keep.compute_wgs_points(payload, run_data)
235
224
  write_standardized_json(wgs_points, payload, run_data, keep_id)
236
225
 
237
226
  attrs = build_activity_from_wgs(run_data, wgs_points)
@@ -273,35 +262,17 @@ module GitFit
273
262
  else
274
263
  payload = { geo_points: raw['geoPoints'], hr_data: raw['heartRates'] }
275
264
  run_data = raw['meta']
276
- compute_wgs_points(payload, run_data)
265
+ Source::Keep.compute_wgs_points(payload, run_data)
277
266
  end
278
267
  end
279
268
 
280
269
  def decrypt_raw_payload(run_data)
281
270
  {
282
- geo_points: decode_geo_points(run_data['geoPoints']),
283
- hr_data: decode_hr_data(run_data.dig('heartRate', 'heartRates')),
271
+ geo_points: Source::Keep.decode_geo_points(run_data['geoPoints']),
272
+ hr_data: Source::Keep.decode_hr_data(run_data.dig('heartRate', 'heartRates')),
284
273
  }
285
274
  end
286
275
 
287
- def compute_wgs_points(payload, run_data)
288
- geo_points = payload[:geo_points]
289
- hr_data = payload[:hr_data]
290
- return [] unless geo_points&.any?
291
-
292
- start_time = run_data['startTime']
293
- use_absolute_ts = geo_points.first['timestamp'].to_i > TIMESTAMP_THRESHOLD
294
- effective_start = use_absolute_ts ? 0 : start_time
295
-
296
- geo_points.map do |p|
297
- lat, lng = Geo::CoordTransform.gcj02_to_wgs84_exact(p['latitude'], p['longitude'])
298
- ts = p['timestamp'] || p['unixTimestamp']
299
- pt_time = ts ? Time.at(effective_start / 1000.0 + ts.to_i / 10.0).utc : nil
300
- hr = find_nearest_hr(hr_data, ts, start_time)
301
- [lat, lng, p['altitude'], pt_time, hr]
302
- end
303
- end
304
-
305
276
  def tz_resolver
306
277
  @tz_resolver ||= GitFit::Timezone::Resolver.new({})
307
278
  end
@@ -414,79 +385,6 @@ module GitFit
414
385
  id_str.split('_')[1]
415
386
  end
416
387
 
417
- def decode_geo_points(geo_data)
418
- return nil unless geo_data
419
- raw = Base64.decode64(geo_data)
420
- decrypted = aes_decrypt(raw)
421
- decompressed = zlib_inflate(decrypted)
422
- JSON.parse(decompressed)
423
- rescue StandardError
424
- nil
425
- end
426
-
427
- def decode_hr_data(hr_data)
428
- return nil unless hr_data
429
- raw = Base64.decode64(hr_data)
430
- decompressed = zlib_inflate(raw)
431
- JSON.parse(decompressed)
432
- rescue StandardError
433
- nil
434
- end
435
-
436
- def aes_decrypt(data)
437
- cipher = OpenSSL::Cipher.new('AES-128-CBC')
438
- cipher.decrypt
439
- cipher.key = AES_KEY
440
- cipher.iv = AES_IV
441
- cipher.update(data) + cipher.final
442
- end
443
-
444
- def zlib_inflate(data)
445
- [-Zlib::MAX_WBITS, Zlib::MAX_WBITS, Zlib::MAX_WBITS + 16].each do |wbits|
446
- begin
447
- inflater = Zlib::Inflate.new(wbits)
448
- result = inflater.inflate(data)
449
- inflater.finish rescue nil
450
- inflater.close
451
- return result
452
- rescue Zlib::DataError
453
- next
454
- end
455
- end
456
- raise Zlib::DataError, 'cannot inflate data'
457
- end
458
-
459
- def find_nearest_hr(hr_data, geo_ts, start_time, threshold: HR_THRESHOLD)
460
- return nil unless hr_data&.any? && geo_ts
461
-
462
- normalize = ->(ts) {
463
- if ts > TIMESTAMP_THRESHOLD
464
- ts - start_time / 100
465
- else
466
- ts
467
- end
468
- }
469
-
470
- target = normalize.call(geo_ts)
471
-
472
- nearest = nil
473
- min_diff = Float::INFINITY
474
-
475
- hr_data.each do |item|
476
- ts = item['timestamp']
477
- next unless ts
478
-
479
- diff = (normalize.call(ts) - target).abs
480
- if diff <= threshold && diff < min_diff
481
- nearest = item
482
- min_diff = diff
483
- end
484
- end
485
-
486
- hr = nearest&.dig('beatsPerMinute')
487
- hr&.positive? ? hr : nil
488
- end
489
-
490
388
  def compute_avg_hr(points)
491
389
  hrs = points.map { |p| p[4] }.compact
492
390
  return nil if hrs.empty?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitFit
4
- VERSION = '0.10.3'
4
+ VERSION = '0.10.4'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-fit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.3
4
+ version: 0.10.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lax
@@ -262,6 +262,7 @@ files:
262
262
  - lib/git_fit/privacy/polyline_filter.rb
263
263
  - lib/git_fit/source.rb
264
264
  - lib/git_fit/source/base.rb
265
+ - lib/git_fit/source/keep.rb
265
266
  - lib/git_fit/source/tally.rb
266
267
  - lib/git_fit/sport_mapper.rb
267
268
  - lib/git_fit/strava_web/file_check.rb