git-fit 0.12.0 → 0.13.0
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 +4 -4
- data/lib/git-fit.rb +1 -1
- data/lib/git_fit/auth/garmin/login_session.rb +1 -1
- data/lib/git_fit/auth/garmin/strategy_b.rb +4 -4
- data/lib/git_fit/auth/garmin.rb +3 -1
- data/lib/git_fit/auth/garmin_token.rb +1 -1
- data/lib/git_fit/auth/paths.rb +14 -0
- data/lib/git_fit/cli/strava.rb +2 -1
- data/lib/git_fit/config_template.rb +4 -1
- data/lib/git_fit/sync/garmin_base.rb +56 -46
- data/lib/git_fit/sync/garmin_base_di.rb +6 -2
- data/lib/git_fit/sync/garmin_cn.rb +4 -4
- data/lib/git_fit/sync/{garmin.rb → garmin_com.rb} +8 -2
- data/lib/git_fit/sync/xingzhe.rb +2 -1
- data/lib/git_fit/sync/xoss.rb +2 -1
- data/lib/git_fit/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c3e935c4e4949dfd80ef84c659f9f64a727d9c1d3aab7c2b6303d6571f42156b
|
|
4
|
+
data.tar.gz: 771f645e5ae8a369c89237d3df03c00b06d5887f5331e7cfec306af0c57f8eb6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 926a648129246714657066e5d7a21b9663682af63516a98d21646e9c58167b4c962150697a8d1d9e14068c834d1d010f9d3696628f8edae9b6ee8f2c614d36cc
|
|
7
|
+
data.tar.gz: 7bebd70ae9b51e18fc97278f7e691466ea5ca4aec07594c83bc7ca2c9ff913f85566dcf9b98b6013a279a3c2927216fd60ad82d860d8185b8fb133a873fbc5fe
|
data/lib/git-fit.rb
CHANGED
|
@@ -63,7 +63,7 @@ require_relative 'git_fit/util'
|
|
|
63
63
|
require_relative 'git_fit/sync/lorem'
|
|
64
64
|
require_relative 'git_fit/sync/garmin_base'
|
|
65
65
|
require_relative 'git_fit/sync/garmin_base_di'
|
|
66
|
-
require_relative 'git_fit/sync/
|
|
66
|
+
require_relative 'git_fit/sync/garmin_com'
|
|
67
67
|
require_relative 'git_fit/sync/garmin_cn'
|
|
68
68
|
require_relative 'git_fit/sync/strava'
|
|
69
69
|
require_relative 'git_fit/sync/keep'
|
|
@@ -15,7 +15,7 @@ module GitFit
|
|
|
15
15
|
# Persists the pre-auth login session created during an interrupted MFA
|
|
16
16
|
# flow. This is a *temporary* session (30-minute TTL matching the MFA code
|
|
17
17
|
# validity window), distinct from the long-lived persistent session cookie
|
|
18
|
-
# (
|
|
18
|
+
# (data/auth/garmin/session.json) that Strategy B keeps after success.
|
|
19
19
|
module LoginSession
|
|
20
20
|
DIR = 'data/cache'
|
|
21
21
|
TTL_SECONDS = 30 * 60
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'di_exchange'
|
|
4
4
|
require_relative 'login_session'
|
|
5
|
-
require_relative '../../sync/
|
|
5
|
+
require_relative '../../sync/garmin_com'
|
|
6
|
+
require_relative '../paths'
|
|
6
7
|
require 'json'
|
|
7
8
|
require 'fileutils'
|
|
8
9
|
|
|
@@ -18,7 +19,6 @@ module GitFit
|
|
|
18
19
|
MOBILE_SERVICE = 'https://mobile.integration.garmin.com/gcm/android'
|
|
19
20
|
LOGIN_DELAY_MIN = 30.0
|
|
20
21
|
LOGIN_DELAY_MAX = 45.0
|
|
21
|
-
SESSION_CACHE_DIR = 'data/cache'
|
|
22
22
|
REDIRECT_STATUSES = [301, 302, 303, 307, 308].freeze
|
|
23
23
|
AUTH_FLAG = '-B'
|
|
24
24
|
|
|
@@ -204,8 +204,8 @@ module GitFit
|
|
|
204
204
|
sleep(delay)
|
|
205
205
|
end
|
|
206
206
|
|
|
207
|
-
def _session_file_path(
|
|
208
|
-
|
|
207
|
+
def _session_file_path(_domain)
|
|
208
|
+
Auth.file(GitFit::Sync::GarminCom.source_prefix, 'session.json')
|
|
209
209
|
end
|
|
210
210
|
|
|
211
211
|
def _load_session(session_path)
|
data/lib/git_fit/auth/garmin.rb
CHANGED
|
@@ -4,6 +4,8 @@ require_relative 'garmin/strategy_a'
|
|
|
4
4
|
require_relative 'garmin/strategy_b'
|
|
5
5
|
require_relative 'garmin/di_exchange'
|
|
6
6
|
require_relative 'garmin/login_session'
|
|
7
|
+
require_relative 'paths'
|
|
8
|
+
require_relative '../sync/garmin_com'
|
|
7
9
|
require_relative '../config'
|
|
8
10
|
require_relative '../cli/gh_cli'
|
|
9
11
|
require 'json'
|
|
@@ -189,7 +191,7 @@ module GitFit
|
|
|
189
191
|
|
|
190
192
|
def token_path
|
|
191
193
|
garmin_cfg['token_path'] ||
|
|
192
|
-
|
|
194
|
+
Auth.file(GitFit::Sync::GarminCom.source_prefix, 'tokens.json')
|
|
193
195
|
end
|
|
194
196
|
|
|
195
197
|
def gh_ladder(seed)
|
|
@@ -10,7 +10,7 @@ module GitFit
|
|
|
10
10
|
# commands, CI token checks).
|
|
11
11
|
class GarminToken
|
|
12
12
|
def self.for_intl(config)
|
|
13
|
-
authenticate_with(GitFit::Sync::
|
|
13
|
+
authenticate_with(GitFit::Sync::GarminCom, config, 'garmin.com')
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def self.for_cn(config)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitFit
|
|
4
|
+
module Auth
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
# Canonical per-source auth storage: data/auth/{source}/{name}. All sync
|
|
8
|
+
# adapters, the auth orchestrator and strategies must resolve token/session
|
|
9
|
+
# defaults through this helper so they can never drift apart.
|
|
10
|
+
def file(source, name)
|
|
11
|
+
File.join('data', 'auth', source, name)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/git_fit/cli/strava.rb
CHANGED
|
@@ -4,6 +4,7 @@ require 'thor'
|
|
|
4
4
|
require 'fileutils'
|
|
5
5
|
require 'tzinfo'
|
|
6
6
|
require 'stravaweb'
|
|
7
|
+
require_relative '../auth/paths'
|
|
7
8
|
|
|
8
9
|
module GitFit
|
|
9
10
|
class StravaCLI < Thor
|
|
@@ -60,7 +61,7 @@ module GitFit
|
|
|
60
61
|
FileUtils.mkdir_p(output_dir)
|
|
61
62
|
cache_dir = File.join('data', 'cache', 'strava_downloads')
|
|
62
63
|
FileUtils.mkdir_p(cache_dir)
|
|
63
|
-
cookie_path =
|
|
64
|
+
cookie_path = web_cfg['cookie_path'] || GitFit::Auth.file('strava', 'session.yml')
|
|
64
65
|
|
|
65
66
|
if activity_ids.empty?
|
|
66
67
|
activity_ids = pending_ids(config, output_dir)
|
|
@@ -19,6 +19,7 @@ module GitFit
|
|
|
19
19
|
# web_auth: # web original-file fetch (git fit strava fetch)
|
|
20
20
|
# jwt: "" # env: GIT_FIT_STRAVA_WEB_AUTH_JWT (strava_remember_token)
|
|
21
21
|
# auth_seed: "" # env: GIT_FIT_STRAVA_WEB_AUTH_AUTH_SEED (base64 JWT)
|
|
22
|
+
# cookie_path: "" # default data/auth/strava/session.yml
|
|
22
23
|
|
|
23
24
|
# garmin: # env: GIT_FIT_GARMIN_EMAIL
|
|
24
25
|
# email: "" # env: GIT_FIT_GARMIN_PASSWORD
|
|
@@ -44,12 +45,14 @@ module GitFit
|
|
|
44
45
|
# xoss: # env: GIT_FIT_XOSS_EMAIL
|
|
45
46
|
# email: "" # env: GIT_FIT_XOSS_PASSWORD
|
|
46
47
|
# password: ""
|
|
48
|
+
# token_path: "" # env: GIT_FIT_XOSS_TOKEN_PATH
|
|
49
|
+
# # default data/auth/xoss/tokens.json
|
|
47
50
|
|
|
48
51
|
# xingzhe: # env: GIT_FIT_XINGZHE_EMAIL
|
|
49
52
|
# email: "" # env: GIT_FIT_XINGZHE_PASSWORD
|
|
50
53
|
# password: ""
|
|
51
54
|
# session_path: "" # env: GIT_FIT_XINGZHE_SESSION_PATH
|
|
52
|
-
# # default data/
|
|
55
|
+
# # default data/auth/xingzhe/session.json
|
|
53
56
|
|
|
54
57
|
export:
|
|
55
58
|
json:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
require_relative '../fit/decoder'
|
|
5
|
+
require_relative '../auth/paths'
|
|
5
6
|
require 'ostruct'
|
|
6
7
|
require 'base64'
|
|
7
8
|
require 'oauth'
|
|
@@ -29,12 +30,17 @@ module GitFit
|
|
|
29
30
|
OAUTH_CONSUMER_URL = 'https://thegarth.s3.amazonaws.com/oauth_consumer.json'
|
|
30
31
|
|
|
31
32
|
SSO_HEADERS = {
|
|
32
|
-
'User-Agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X)
|
|
33
|
+
'User-Agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) ' \
|
|
34
|
+
'AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148',
|
|
33
35
|
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
34
36
|
'Accept-Language' => 'en-US,en;q=0.9',
|
|
35
37
|
}.freeze
|
|
36
38
|
|
|
37
39
|
def initialize(...)
|
|
40
|
+
if instance_of?(GarminBase)
|
|
41
|
+
raise NotImplementedError,
|
|
42
|
+
'GarminBase is abstract — instantiate a concrete subclass (Garmin, GarminCN)'
|
|
43
|
+
end
|
|
38
44
|
super
|
|
39
45
|
@domain = 'garmin.com'
|
|
40
46
|
@email = @config['email']
|
|
@@ -53,19 +59,18 @@ module GitFit
|
|
|
53
59
|
def authenticate
|
|
54
60
|
@oauth_consumer = fetch_oauth_consumer
|
|
55
61
|
|
|
56
|
-
if @secret
|
|
57
|
-
if
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
if @secret && parse_secret_string(@secret)
|
|
63
|
+
if access_token_valid?
|
|
64
|
+
@auth_method = 'secret'
|
|
65
|
+
return true
|
|
66
|
+
end
|
|
67
|
+
begin
|
|
68
|
+
if refresh_oauth2_via_oauth1
|
|
69
|
+
@auth_method = 'refreshed'
|
|
60
70
|
return true
|
|
61
71
|
end
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
@auth_method = 'refreshed'
|
|
65
|
-
return true
|
|
66
|
-
end
|
|
67
|
-
rescue AuthError
|
|
68
|
-
end
|
|
72
|
+
rescue AuthError => e
|
|
73
|
+
warn e.message
|
|
69
74
|
end
|
|
70
75
|
end
|
|
71
76
|
|
|
@@ -144,6 +149,14 @@ module GitFit
|
|
|
144
149
|
attrs
|
|
145
150
|
end
|
|
146
151
|
|
|
152
|
+
def self.source_prefix
|
|
153
|
+
raise NotImplementedError, "#{self} must implement source_prefix"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def source_prefix
|
|
157
|
+
self.class.source_prefix
|
|
158
|
+
end
|
|
159
|
+
|
|
147
160
|
private
|
|
148
161
|
|
|
149
162
|
def access_token_valid?
|
|
@@ -164,7 +177,7 @@ module GitFit
|
|
|
164
177
|
oauth_token: o1['oauth_token'],
|
|
165
178
|
oauth_token_secret: o1['oauth_token_secret'],
|
|
166
179
|
mfa_token: o1['mfa_token'],
|
|
167
|
-
domain: @domain
|
|
180
|
+
domain: @domain,
|
|
168
181
|
)
|
|
169
182
|
@access_token = o2
|
|
170
183
|
true
|
|
@@ -190,7 +203,7 @@ module GitFit
|
|
|
190
203
|
return unless File.exist?(path)
|
|
191
204
|
|
|
192
205
|
cfg = YAML.safe_load(File.read(path)) || {}
|
|
193
|
-
source =
|
|
206
|
+
source = source_prefix
|
|
194
207
|
cfg['sync'] ||= {}
|
|
195
208
|
cfg['sync'][source] ||= {}
|
|
196
209
|
cfg['sync'][source]['secret'] = @generated_secret
|
|
@@ -222,8 +235,8 @@ module GitFit
|
|
|
222
235
|
save_tokens
|
|
223
236
|
true
|
|
224
237
|
rescue StandardError => e
|
|
225
|
-
raise AuthError, "Garmin OAuth refresh failed: #{e.message}"
|
|
226
|
-
|
|
238
|
+
raise AuthError, "Garmin OAuth refresh failed: #{e.message}" \
|
|
239
|
+
" — #{reauth_hint}"
|
|
227
240
|
end
|
|
228
241
|
|
|
229
242
|
def fetch_oauth_consumer
|
|
@@ -258,16 +271,14 @@ module GitFit
|
|
|
258
271
|
mfaVerificationCode: mfa_code,
|
|
259
272
|
rememberMyBrowser: false,
|
|
260
273
|
reconsentList: [],
|
|
261
|
-
mfaSetup: false
|
|
274
|
+
mfaSetup: false,
|
|
262
275
|
)
|
|
263
276
|
resp = sso_post(sso_base, '/mobile/api/mfa/verifyCode', login_params, mfa_body, jar)
|
|
264
277
|
body = JSON.parse(resp.body)
|
|
265
278
|
status = body.dig('responseStatus', 'type')
|
|
266
279
|
end
|
|
267
280
|
|
|
268
|
-
unless status == 'SUCCESSFUL'
|
|
269
|
-
raise "Garmin login failed (#{body.dig("responseStatus", "message") || status})"
|
|
270
|
-
end
|
|
281
|
+
raise "Garmin login failed (#{body.dig('responseStatus', 'message') || status})" unless status == 'SUCCESSFUL'
|
|
271
282
|
|
|
272
283
|
body['serviceTicketId']
|
|
273
284
|
end
|
|
@@ -314,7 +325,8 @@ module GitFit
|
|
|
314
325
|
def fetch_oauth1_token(ticket)
|
|
315
326
|
base_url = "https://connectapi.#{@domain}/oauth-service/oauth/"
|
|
316
327
|
login_url = "https://mobile.integration.#{@domain}/gcm/android"
|
|
317
|
-
url = "#{base_url}preauthorized?ticket=#{CGI.escape(ticket)}
|
|
328
|
+
url = "#{base_url}preauthorized?ticket=#{CGI.escape(ticket)}" \
|
|
329
|
+
"&login-url=#{CGI.escape(login_url)}&accepts-mfa-tokens=true"
|
|
318
330
|
|
|
319
331
|
oauth_consumer = OAuth::Consumer.new(
|
|
320
332
|
@oauth_consumer['consumer_key'], @oauth_consumer['consumer_secret'],
|
|
@@ -328,7 +340,7 @@ module GitFit
|
|
|
328
340
|
req['User-Agent'] = 'com.garmin.android.apps.connectmobile'
|
|
329
341
|
oauth_consumer.sign!(req, nil)
|
|
330
342
|
|
|
331
|
-
resp =
|
|
343
|
+
resp = retry_on_rate_limit(3, 5) { send_http(URI(url), req) }
|
|
332
344
|
raise "OAuth1 error: #{resp.code}" unless resp.code.to_i == 200
|
|
333
345
|
|
|
334
346
|
parsed = CGI.parse(resp.body).transform_keys(&:to_s).transform_values(&:first)
|
|
@@ -337,7 +349,7 @@ module GitFit
|
|
|
337
349
|
oauth_token: parsed['oauth_token'],
|
|
338
350
|
oauth_token_secret: parsed['oauth_token_secret'],
|
|
339
351
|
mfa_token: parsed['mfa_token'],
|
|
340
|
-
domain: @domain
|
|
352
|
+
domain: @domain,
|
|
341
353
|
)
|
|
342
354
|
end
|
|
343
355
|
|
|
@@ -359,7 +371,7 @@ module GitFit
|
|
|
359
371
|
req.body = URI.encode_www_form(body_params)
|
|
360
372
|
oauth_consumer.sign!(req, token)
|
|
361
373
|
|
|
362
|
-
resp =
|
|
374
|
+
resp = retry_on_rate_limit { send_http(URI(url), req) }
|
|
363
375
|
raise "OAuth2 exchange error: #{resp.code}" unless resp.code.to_i == 200
|
|
364
376
|
|
|
365
377
|
result = JSON.parse(resp.body)
|
|
@@ -374,7 +386,7 @@ module GitFit
|
|
|
374
386
|
|
|
375
387
|
def existing_run_ids
|
|
376
388
|
@db[:activities].where(source: source_prefix).select_map(:run_id)
|
|
377
|
-
|
|
389
|
+
.map { |id| id.sub("#{source_prefix}_", '') }
|
|
378
390
|
end
|
|
379
391
|
|
|
380
392
|
def fetch_activity_list(start, limit)
|
|
@@ -388,9 +400,9 @@ module GitFit
|
|
|
388
400
|
def api_get(path)
|
|
389
401
|
uri = URI("#{connectapi_base}#{path}")
|
|
390
402
|
req = Net::HTTP::Get.new(uri)
|
|
391
|
-
req['Authorization'] = "Bearer #{@access_token[
|
|
403
|
+
req['Authorization'] = "Bearer #{@access_token['access_token']}"
|
|
392
404
|
req['User-Agent'] = 'GCM-iOS-5.22.1.4'
|
|
393
|
-
resp =
|
|
405
|
+
resp = retry_on_rate_limit { send_http(uri, req) }
|
|
394
406
|
return nil unless resp.code.to_i == 200
|
|
395
407
|
JSON.parse(resp.body)
|
|
396
408
|
end
|
|
@@ -408,14 +420,14 @@ module GitFit
|
|
|
408
420
|
http.start { |h| h.request(request) }
|
|
409
421
|
end
|
|
410
422
|
|
|
411
|
-
def
|
|
423
|
+
def retry_on_rate_limit(max_retries = 3, base_delay = 2)
|
|
412
424
|
retries = 0
|
|
413
425
|
loop do
|
|
414
426
|
resp = yield
|
|
415
427
|
return resp unless resp.code.to_i == 429 && retries < max_retries
|
|
416
428
|
|
|
417
429
|
retries += 1
|
|
418
|
-
sleep(base_delay * (2
|
|
430
|
+
sleep(base_delay * (2**retries))
|
|
419
431
|
end
|
|
420
432
|
end
|
|
421
433
|
|
|
@@ -424,15 +436,17 @@ module GitFit
|
|
|
424
436
|
end
|
|
425
437
|
|
|
426
438
|
def resolve_local_time(start_utc, activity_id, polyline = nil)
|
|
427
|
-
|
|
439
|
+
nil
|
|
428
440
|
|
|
429
441
|
lat, lng = first_gps_from_l2(activity_id)
|
|
430
442
|
|
|
431
443
|
if lat.nil? && polyline
|
|
432
|
-
decoded =
|
|
433
|
-
|
|
434
|
-
|
|
444
|
+
decoded = begin
|
|
445
|
+
GitFit::Geo::Polyline.decode(polyline)
|
|
446
|
+
rescue StandardError
|
|
447
|
+
nil
|
|
435
448
|
end
|
|
449
|
+
lat, lng = decoded.first if decoded&.any?
|
|
436
450
|
end
|
|
437
451
|
|
|
438
452
|
tz_string = tz_resolver.resolve(lat, lng)
|
|
@@ -450,11 +464,11 @@ module GitFit
|
|
|
450
464
|
l2 = JSON.parse(File.read(l2_path))
|
|
451
465
|
return [nil, nil] unless l2.is_a?(Array) && l2.any?
|
|
452
466
|
|
|
453
|
-
first = l2.find
|
|
467
|
+
first = l2.find do |r|
|
|
454
468
|
lat = r['positionLat']
|
|
455
469
|
lng = r['positionLong']
|
|
456
470
|
lat && lng && lat.to_f != 0.0 && lng.to_f != 0.0
|
|
457
|
-
|
|
471
|
+
end
|
|
458
472
|
return [nil, nil] unless first
|
|
459
473
|
|
|
460
474
|
[first['positionLat'].to_f, first['positionLong'].to_f]
|
|
@@ -462,10 +476,6 @@ module GitFit
|
|
|
462
476
|
[nil, nil]
|
|
463
477
|
end
|
|
464
478
|
|
|
465
|
-
def source_prefix
|
|
466
|
-
'garmin'
|
|
467
|
-
end
|
|
468
|
-
|
|
469
479
|
def reauth_hint
|
|
470
480
|
"Re-auth: set sync.#{source_prefix}.secret in config/config.yml"
|
|
471
481
|
end
|
|
@@ -560,7 +570,7 @@ module GitFit
|
|
|
560
570
|
oauth_token: o1['oauth_token'],
|
|
561
571
|
oauth_token_secret: o1['oauth_token_secret'],
|
|
562
572
|
mfa_token: o1['mfa_token'],
|
|
563
|
-
domain: @domain
|
|
573
|
+
domain: @domain,
|
|
564
574
|
)
|
|
565
575
|
@access_token = data['oauth2']
|
|
566
576
|
@saved_expires_at = @access_token&.dig('expires_at')
|
|
@@ -571,7 +581,7 @@ module GitFit
|
|
|
571
581
|
end
|
|
572
582
|
|
|
573
583
|
def token_path
|
|
574
|
-
@config['token_path'] ||
|
|
584
|
+
@config['token_path'] || GitFit::Auth.file(source_prefix, 'tokens.json')
|
|
575
585
|
end
|
|
576
586
|
|
|
577
587
|
FIT_EPOCH = 631_065_600
|
|
@@ -598,9 +608,9 @@ module GitFit
|
|
|
598
608
|
def download_fit_raw(activity_id)
|
|
599
609
|
uri = URI("#{connectapi_base}/download-service/files/activity/#{activity_id}")
|
|
600
610
|
req = Net::HTTP::Get.new(uri)
|
|
601
|
-
req['Authorization'] = "Bearer #{@access_token.fetch(
|
|
611
|
+
req['Authorization'] = "Bearer #{@access_token.fetch('access_token')}"
|
|
602
612
|
req['User-Agent'] = 'GCM-iOS-5.22.1.4'
|
|
603
|
-
resp =
|
|
613
|
+
resp = retry_on_rate_limit { send_http(uri, req) }
|
|
604
614
|
return false unless resp.code.to_i == 200
|
|
605
615
|
|
|
606
616
|
zip_data = resp.body
|
|
@@ -652,13 +662,13 @@ module GitFit
|
|
|
652
662
|
nil
|
|
653
663
|
end
|
|
654
664
|
|
|
655
|
-
def write_standardized_json(
|
|
656
|
-
return unless
|
|
665
|
+
def write_standardized_json(l2_data, activity_id)
|
|
666
|
+
return unless l2_data
|
|
657
667
|
|
|
658
668
|
dir = std_dir
|
|
659
669
|
FileUtils.mkdir_p(dir)
|
|
660
670
|
tmp = File.join(dir, ".#{activity_id}.json.tmp")
|
|
661
|
-
File.write(tmp, JSON.generate(
|
|
671
|
+
File.write(tmp, JSON.generate(l2_data))
|
|
662
672
|
File.rename(tmp, std_path(activity_id))
|
|
663
673
|
end
|
|
664
674
|
end
|
|
@@ -5,6 +5,10 @@ require_relative 'garmin_base'
|
|
|
5
5
|
module GitFit
|
|
6
6
|
module Sync
|
|
7
7
|
class GarminBaseDI < GarminBase
|
|
8
|
+
def self.source_prefix
|
|
9
|
+
'garmin'
|
|
10
|
+
end
|
|
11
|
+
|
|
8
12
|
DI_CLIENT_IDS = %w[
|
|
9
13
|
GARMIN_CONNECT_MOBILE_ANDROID_DI_2025Q2
|
|
10
14
|
GARMIN_CONNECT_MOBILE_ANDROID_DI_2024Q4
|
|
@@ -131,10 +135,10 @@ module GitFit
|
|
|
131
135
|
req.body = URI.encode_www_form(
|
|
132
136
|
grant_type: 'refresh_token',
|
|
133
137
|
client_id: client_id,
|
|
134
|
-
refresh_token: @access_token['refresh_token']
|
|
138
|
+
refresh_token: @access_token['refresh_token'],
|
|
135
139
|
)
|
|
136
140
|
|
|
137
|
-
resp =
|
|
141
|
+
resp = retry_on_rate_limit { send_http(uri, req) }
|
|
138
142
|
unless resp.code.to_i == 200
|
|
139
143
|
msg = "DI refresh error: #{resp.code}"
|
|
140
144
|
if resp.code.to_i == 400
|
|
@@ -8,6 +8,10 @@ module GitFit
|
|
|
8
8
|
register_adapter
|
|
9
9
|
register_config :email, :password, :secret, :token_path
|
|
10
10
|
|
|
11
|
+
def self.source_prefix
|
|
12
|
+
'garmin_cn'
|
|
13
|
+
end
|
|
14
|
+
|
|
11
15
|
def initialize(config:, db:, activity_filter: nil, privacy: nil, time_budget: nil)
|
|
12
16
|
super
|
|
13
17
|
@domain = 'garmin.cn'
|
|
@@ -21,10 +25,6 @@ module GitFit
|
|
|
21
25
|
def std_dir
|
|
22
26
|
File.join('data', 'std', 'garmin_cn')
|
|
23
27
|
end
|
|
24
|
-
|
|
25
|
-
def source_prefix
|
|
26
|
-
'garmin_cn'
|
|
27
|
-
end
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -4,15 +4,21 @@ require_relative 'garmin_base_di'
|
|
|
4
4
|
|
|
5
5
|
module GitFit
|
|
6
6
|
module Sync
|
|
7
|
-
class
|
|
7
|
+
class GarminCom < GarminBaseDI
|
|
8
8
|
register_adapter
|
|
9
|
-
register_config :email, :password, :secret, :auth_seed, :token_path
|
|
10
9
|
config_key 'garmin'
|
|
10
|
+
register_config :email, :password, :secret, :auth_seed, :token_path
|
|
11
11
|
|
|
12
12
|
def initialize(config:, db:, activity_filter: nil, privacy: nil, time_budget: nil)
|
|
13
13
|
super
|
|
14
14
|
@domain = 'garmin.com'
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
protected
|
|
18
|
+
|
|
19
|
+
def source_label
|
|
20
|
+
'Garmin'
|
|
21
|
+
end
|
|
16
22
|
end
|
|
17
23
|
end
|
|
18
24
|
end
|
data/lib/git_fit/sync/xingzhe.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'base'
|
|
4
|
+
require_relative '../auth/paths'
|
|
4
5
|
require 'fileutils'
|
|
5
6
|
require 'openssl'
|
|
6
7
|
require 'tzinfo'
|
|
@@ -32,7 +33,7 @@ module GitFit
|
|
|
32
33
|
super
|
|
33
34
|
@email = @config['email']
|
|
34
35
|
@password = @config['password']
|
|
35
|
-
@session_path = @config['session_path'] ||
|
|
36
|
+
@session_path = @config['session_path'] || GitFit::Auth.file('xingzhe', 'session.json')
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
def before_call
|
data/lib/git_fit/sync/xoss.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'base'
|
|
4
|
+
require_relative '../auth/paths'
|
|
4
5
|
require 'fileutils'
|
|
5
6
|
require 'tzinfo'
|
|
6
7
|
|
|
@@ -32,7 +33,7 @@ module GitFit
|
|
|
32
33
|
super
|
|
33
34
|
@email = @config['email']
|
|
34
35
|
@password = @config['password']
|
|
35
|
-
@token_path = @config['token_path'] ||
|
|
36
|
+
@token_path = @config['token_path'] || GitFit::Auth.file('xoss', 'tokens.json')
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
def before_call
|
data/lib/git_fit/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lax
|
|
@@ -252,6 +252,7 @@ files:
|
|
|
252
252
|
- lib/git_fit/auth/garmin/strategy_a.rb
|
|
253
253
|
- lib/git_fit/auth/garmin/strategy_b.rb
|
|
254
254
|
- lib/git_fit/auth/garmin_token.rb
|
|
255
|
+
- lib/git_fit/auth/paths.rb
|
|
255
256
|
- lib/git_fit/auth/strava.rb
|
|
256
257
|
- lib/git_fit/cli.rb
|
|
257
258
|
- lib/git_fit/cli/checkpointable.rb
|
|
@@ -312,10 +313,10 @@ files:
|
|
|
312
313
|
- lib/git_fit/std/resolver.rb
|
|
313
314
|
- lib/git_fit/strava_web/file_check.rb
|
|
314
315
|
- lib/git_fit/sync/base.rb
|
|
315
|
-
- lib/git_fit/sync/garmin.rb
|
|
316
316
|
- lib/git_fit/sync/garmin_base.rb
|
|
317
317
|
- lib/git_fit/sync/garmin_base_di.rb
|
|
318
318
|
- lib/git_fit/sync/garmin_cn.rb
|
|
319
|
+
- lib/git_fit/sync/garmin_com.rb
|
|
319
320
|
- lib/git_fit/sync/igpsport.rb
|
|
320
321
|
- lib/git_fit/sync/keep.rb
|
|
321
322
|
- lib/git_fit/sync/lorem.rb
|