garminclimb 1.1.6 → 1.2.2

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.
@@ -20,7 +20,10 @@
20
20
  "Bash(node bin/index.js charts 2>&1 | grep -A 5 \"Romanian Deadlift\")",
21
21
  "Bash(chmod +x /Users/johnottenlips/garminclimb/bin/index.js)",
22
22
  "Bash(grep -r \"Cycling\" /Users/johnottenlips/garminclimb/node_modules/garmin-connect/dist/garmin/types/activity* 2>/dev/null | head -20)",
23
- "Bash(node -e \"const { ActivityType } = require\\('garmin-connect/dist/garmin/types/activity'\\); console.log\\('ActivityType.Cycling =', ActivityType.Cycling\\); console.log\\('All types:', JSON.stringify\\(ActivityType, null, 2\\)\\);\")"
23
+ "Bash(node -e \"const { ActivityType } = require\\('garmin-connect/dist/garmin/types/activity'\\); console.log\\('ActivityType.Cycling =', ActivityType.Cycling\\); console.log\\('All types:', JSON.stringify\\(ActivityType, null, 2\\)\\);\")",
24
+ "Bash(ls:*)",
25
+ "Bash(npm run:*)",
26
+ "Bash(python3 -c ':*)"
24
27
  ]
25
28
  }
26
29
  }
package/bin/download.js CHANGED
@@ -48,15 +48,39 @@ const fs = __importStar(require("fs"));
48
48
  const garmin_connect_1 = require("garmin-connect");
49
49
  const _1 = require(".");
50
50
  const activity_1 = require("garmin-connect/dist/garmin/types/activity");
51
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
52
+ const DELAY = 3000; // 3s between bulk requests
53
+ function withRetry(fn_1) {
54
+ return __awaiter(this, arguments, void 0, function* (fn, retries = 3) {
55
+ var _a, _b;
56
+ for (let i = 0; i < retries; i++) {
57
+ try {
58
+ return yield fn();
59
+ }
60
+ catch (e) {
61
+ const is429 = ((_a = e === null || e === void 0 ? void 0 : e.message) === null || _a === void 0 ? void 0 : _a.includes("429")) || ((_b = e === null || e === void 0 ? void 0 : e.message) === null || _b === void 0 ? void 0 : _b.includes("Rate"));
62
+ if (is429 && i < retries - 1) {
63
+ const wait = DELAY * (i + 2);
64
+ console.log(`Rate limited, retrying in ${wait / 1000}s...`);
65
+ yield sleep(wait);
66
+ }
67
+ else {
68
+ throw e;
69
+ }
70
+ }
71
+ }
72
+ throw new Error("Unreachable");
73
+ });
74
+ }
51
75
  const download = (username, password) => __awaiter(void 0, void 0, void 0, function* () {
52
76
  const GCClient = new garmin_connect_1.GarminConnect({
53
77
  username,
54
78
  password,
55
79
  });
56
80
  yield GCClient.login(username, password);
57
- const activities = yield GCClient.getActivities(0, 6000, activity_1.ActivityType.FitnessEquipment,
81
+ const activities = yield withRetry(() => GCClient.getActivities(0, 6000, activity_1.ActivityType.FitnessEquipment,
58
82
  // @ts-ignore
59
- "indoor_climbing");
83
+ "indoor_climbing"));
60
84
  const indoorClimbingActivities = activities === null || activities === void 0 ? void 0 : activities.filter((activity) => { var _a; return ((_a = activity === null || activity === void 0 ? void 0 : activity.activityType) === null || _a === void 0 ? void 0 : _a.typeKey) === "indoor_climbing"; });
61
85
  if (!fs.existsSync(_1.garminDataFolder)) {
62
86
  fs.mkdirSync(_1.garminDataFolder);
@@ -66,16 +90,18 @@ const download = (username, password) => __awaiter(void 0, void 0, void 0, funct
66
90
  fs.writeFileSync(`${_1.garminDataFolder}/boulderingActivities.json`, JSON.stringify(boulderingActivities, null, 2));
67
91
  console.log("Downloaded climbing activities from Garmin Connect");
68
92
  // Download strength training activities
69
- const strengthActivities = yield GCClient.getActivities(0, 6000, activity_1.ActivityType.FitnessEquipment,
93
+ yield sleep(DELAY);
94
+ const strengthActivities = yield withRetry(() => GCClient.getActivities(0, 6000, activity_1.ActivityType.FitnessEquipment,
70
95
  // @ts-ignore
71
- "strength_training");
96
+ "strength_training"));
72
97
  const strengthTrainingActivities = strengthActivities === null || strengthActivities === void 0 ? void 0 : strengthActivities.filter((activity) => { var _a; return ((_a = activity === null || activity === void 0 ? void 0 : activity.activityType) === null || _a === void 0 ? void 0 : _a.typeKey) === "strength_training"; });
73
98
  // Fetch detailed data for each strength activity to get exercise sets
74
99
  console.log(`Found ${(strengthTrainingActivities === null || strengthTrainingActivities === void 0 ? void 0 : strengthTrainingActivities.length) || 0} strength training activities, fetching details...`);
75
100
  const detailedActivities = [];
76
101
  for (const activity of strengthTrainingActivities || []) {
77
102
  try {
78
- const detail = yield GCClient.getActivity({ activityId: activity.activityId });
103
+ yield sleep(1000);
104
+ const detail = yield withRetry(() => GCClient.getActivity({ activityId: activity.activityId }));
79
105
  detailedActivities.push(Object.assign(Object.assign({}, activity), { exerciseSets: (detail === null || detail === void 0 ? void 0 : detail.exerciseSets) || (detail === null || detail === void 0 ? void 0 : detail.summarizedExerciseSets) || (activity === null || activity === void 0 ? void 0 : activity.summarizedExerciseSets) || [], fullSummarizedExerciseSets: (detail === null || detail === void 0 ? void 0 : detail.summarizedExerciseSets) || (activity === null || activity === void 0 ? void 0 : activity.summarizedExerciseSets) || [] }));
80
106
  }
81
107
  catch (e) {
@@ -86,9 +112,10 @@ const download = (username, password) => __awaiter(void 0, void 0, void 0, funct
86
112
  fs.writeFileSync(`${_1.garminDataFolder}/strengthTrainingActivities.json`, JSON.stringify(detailedActivities, null, 2));
87
113
  console.log("Downloaded strength training activities from Garmin Connect");
88
114
  // Download running activities (street_running gets all running subtypes)
89
- const runningActivities = yield GCClient.getActivities(0, 6000,
115
+ yield sleep(DELAY);
116
+ const runningActivities = yield withRetry(() => GCClient.getActivities(0, 6000,
90
117
  // @ts-ignore
91
- "running");
118
+ "running"));
92
119
  const filteredRunning = (runningActivities || []).filter((a) => {
93
120
  var _a;
94
121
  const key = ((_a = a === null || a === void 0 ? void 0 : a.activityType) === null || _a === void 0 ? void 0 : _a.typeKey) || "";
@@ -97,16 +124,28 @@ const download = (username, password) => __awaiter(void 0, void 0, void 0, funct
97
124
  fs.writeFileSync(`${_1.garminDataFolder}/runningActivities.json`, JSON.stringify(filteredRunning, null, 2));
98
125
  console.log(`Downloaded ${filteredRunning.length} running activities from Garmin Connect`);
99
126
  // Download cycling activities
100
- const cyclingActivities = yield GCClient.getActivities(0, 6000,
127
+ yield sleep(DELAY);
128
+ const cyclingActivities = yield withRetry(() => GCClient.getActivities(0, 6000,
129
+ // @ts-ignore
130
+ "cycling"));
131
+ // Download mountain biking activities using correct subtype
132
+ yield sleep(DELAY);
133
+ const mtbActivities = yield withRetry(() => GCClient.getActivities(0, 6000,
101
134
  // @ts-ignore
102
- "cycling");
103
- const filteredCycling = (cyclingActivities || []).filter((a) => {
135
+ "cycling", "mountain_biking"));
136
+ const allCycling = [...(cyclingActivities || []), ...(mtbActivities || [])];
137
+ const seen = new Set();
138
+ const filteredCycling = allCycling.filter((a) => {
104
139
  var _a;
105
140
  const key = ((_a = a === null || a === void 0 ? void 0 : a.activityType) === null || _a === void 0 ? void 0 : _a.typeKey) || "";
106
- return key.includes("cycling") || key.includes("biking") || key === "virtual_ride";
141
+ const id = a === null || a === void 0 ? void 0 : a.activityId;
142
+ if (seen.has(id))
143
+ return false;
144
+ seen.add(id);
145
+ return key.includes("cycling") || key.includes("biking") || key === "virtual_ride" || key.includes("mtb");
107
146
  });
108
147
  fs.writeFileSync(`${_1.garminDataFolder}/cyclingActivities.json`, JSON.stringify(filteredCycling, null, 2));
109
- console.log(`Downloaded ${filteredCycling.length} cycling activities from Garmin Connect`);
148
+ console.log(`Downloaded ${filteredCycling.length} cycling + MTB activities from Garmin Connect`);
110
149
  });
111
150
  exports.download = download;
112
151
  //# sourceMappingURL=download.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"download.js","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"download.js","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "garminclimb",
3
- "version": "1.1.6",
3
+ "version": "1.2.2",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/download.ts CHANGED
@@ -6,6 +6,28 @@ import {
6
6
  ActivitySubType,
7
7
  ActivityType,
8
8
  } from "garmin-connect/dist/garmin/types/activity";
9
+
10
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
11
+ const DELAY = 3000; // 3s between bulk requests
12
+
13
+ async function withRetry<T>(fn: () => Promise<T>, retries = 3): Promise<T> {
14
+ for (let i = 0; i < retries; i++) {
15
+ try {
16
+ return await fn();
17
+ } catch (e: any) {
18
+ const is429 = e?.message?.includes("429") || e?.message?.includes("Rate");
19
+ if (is429 && i < retries - 1) {
20
+ const wait = DELAY * (i + 2);
21
+ console.log(`Rate limited, retrying in ${wait / 1000}s...`);
22
+ await sleep(wait);
23
+ } else {
24
+ throw e;
25
+ }
26
+ }
27
+ }
28
+ throw new Error("Unreachable");
29
+ }
30
+
9
31
  export const download = async (
10
32
  username: string,
11
33
  password: string
@@ -17,13 +39,13 @@ export const download = async (
17
39
 
18
40
  await GCClient.login(username, password);
19
41
 
20
- const activities = await GCClient.getActivities(
42
+ const activities = await withRetry(() => GCClient.getActivities(
21
43
  0,
22
44
  6000,
23
45
  ActivityType.FitnessEquipment,
24
46
  // @ts-ignore
25
47
  "indoor_climbing"
26
- );
48
+ ));
27
49
 
28
50
  const indoorClimbingActivities = activities?.filter(
29
51
  (activity) => activity?.activityType?.typeKey === "indoor_climbing"
@@ -48,13 +70,14 @@ export const download = async (
48
70
  console.log("Downloaded climbing activities from Garmin Connect");
49
71
 
50
72
  // Download strength training activities
51
- const strengthActivities = await GCClient.getActivities(
73
+ await sleep(DELAY);
74
+ const strengthActivities = await withRetry(() => GCClient.getActivities(
52
75
  0,
53
76
  6000,
54
77
  ActivityType.FitnessEquipment,
55
78
  // @ts-ignore
56
79
  "strength_training"
57
- );
80
+ ));
58
81
 
59
82
  const strengthTrainingActivities = strengthActivities?.filter(
60
83
  (activity: any) => activity?.activityType?.typeKey === "strength_training"
@@ -65,7 +88,8 @@ export const download = async (
65
88
  const detailedActivities: any[] = [];
66
89
  for (const activity of strengthTrainingActivities || []) {
67
90
  try {
68
- const detail: any = await GCClient.getActivity({ activityId: activity.activityId });
91
+ await sleep(1000);
92
+ const detail: any = await withRetry(() => GCClient.getActivity({ activityId: activity.activityId }));
69
93
  detailedActivities.push({
70
94
  ...activity,
71
95
  exerciseSets: detail?.exerciseSets || detail?.summarizedExerciseSets || (activity as any)?.summarizedExerciseSets || [],
@@ -84,12 +108,13 @@ export const download = async (
84
108
  console.log("Downloaded strength training activities from Garmin Connect");
85
109
 
86
110
  // Download running activities (street_running gets all running subtypes)
87
- const runningActivities = await GCClient.getActivities(
111
+ await sleep(DELAY);
112
+ const runningActivities = await withRetry(() => GCClient.getActivities(
88
113
  0,
89
114
  6000,
90
115
  // @ts-ignore
91
116
  "running",
92
- );
117
+ ));
93
118
 
94
119
  const filteredRunning = (runningActivities || []).filter(
95
120
  (a: any) => {
@@ -105,17 +130,33 @@ export const download = async (
105
130
  console.log(`Downloaded ${filteredRunning.length} running activities from Garmin Connect`);
106
131
 
107
132
  // Download cycling activities
108
- const cyclingActivities = await GCClient.getActivities(
133
+ await sleep(DELAY);
134
+ const cyclingActivities = await withRetry(() => GCClient.getActivities(
109
135
  0,
110
136
  6000,
111
137
  // @ts-ignore
112
138
  "cycling",
113
- );
139
+ ));
140
+
141
+ // Download mountain biking activities using correct subtype
142
+ await sleep(DELAY);
143
+ const mtbActivities = await withRetry(() => GCClient.getActivities(
144
+ 0,
145
+ 6000,
146
+ // @ts-ignore
147
+ "cycling",
148
+ "mountain_biking",
149
+ ));
114
150
 
115
- const filteredCycling = (cyclingActivities || []).filter(
151
+ const allCycling = [...(cyclingActivities || []), ...(mtbActivities || [])];
152
+ const seen = new Set<number>();
153
+ const filteredCycling = allCycling.filter(
116
154
  (a: any) => {
117
155
  const key = a?.activityType?.typeKey || "";
118
- return key.includes("cycling") || key.includes("biking") || key === "virtual_ride";
156
+ const id = a?.activityId;
157
+ if (seen.has(id)) return false;
158
+ seen.add(id);
159
+ return key.includes("cycling") || key.includes("biking") || key === "virtual_ride" || key.includes("mtb");
119
160
  }
120
161
  );
121
162
 
@@ -123,5 +164,5 @@ export const download = async (
123
164
  `${garminDataFolder}/cyclingActivities.json`,
124
165
  JSON.stringify(filteredCycling, null, 2)
125
166
  );
126
- console.log(`Downloaded ${filteredCycling.length} cycling activities from Garmin Connect`);
167
+ console.log(`Downloaded ${filteredCycling.length} cycling + MTB activities from Garmin Connect`);
127
168
  };