Gisle Aune
12 months ago
commit
35f7241738
8 changed files with 830 additions and 0 deletions
-
18.drone.yml
-
2.gitignore
-
5Dockerfile
-
71index.mjs
-
56lib/client.mjs
-
119lib/tcx.mjs
-
553package-lock.json
-
6package.json
@ -0,0 +1,18 @@ |
|||
name: garmsync-build |
|||
kind: pipeline |
|||
type: docker |
|||
steps: |
|||
- name: docker-tag |
|||
image: plugins/docker |
|||
settings: |
|||
auto_tag: true |
|||
username: |
|||
from_secret: docker_username |
|||
password: |
|||
from_secret: docker_password |
|||
repo: r.vmaple.dev/gisle/garmsync |
|||
registry: r.vmaple.dev |
|||
dockerfile: Dockerfile |
|||
when: |
|||
event: |
|||
- tag |
@ -0,0 +1,2 @@ |
|||
/node_modules |
|||
*.tcx |
@ -0,0 +1,5 @@ |
|||
FROM node:21.5 |
|||
WORKDIR /garmsync |
|||
COPY . . |
|||
RUN npm install |
|||
CMD node index.mjs |
@ -0,0 +1,71 @@ |
|||
import gc from 'garmin-connect'; |
|||
import Trimlog from './lib/client.mjs'; |
|||
import { generateTrimlogInput, today } from './lib/tcx.mjs'; |
|||
|
|||
async function main() { |
|||
const garmin = new gc.GarminConnect({ |
|||
username: process.env.GARMIN_USERNAME, |
|||
password: process.env.GARMIN_PASSWORD, |
|||
}); |
|||
const trimlog = new Trimlog({ |
|||
username: process.env.TRIMLOG_USERNAME, |
|||
password: process.env.TRIMLOG_PASSWORD, |
|||
}) |
|||
|
|||
await trimlog.refreshToken(); |
|||
await garmin.login(); |
|||
|
|||
const blackList = {}; |
|||
|
|||
setInterval(async() => { |
|||
const looseActivities = await trimlog.getLooseActivities(); |
|||
const recentWorkouts = await trimlog.getWorkouts(); |
|||
|
|||
const activities = await garmin.getActivities(); |
|||
const todayDate = today(); |
|||
for (const act of activities) { |
|||
const id = Math.round(act.activityId).toFixed(0); |
|||
|
|||
if (blackList[id]) { |
|||
continue; |
|||
} |
|||
|
|||
if (!act.startTimeLocal.startsWith(todayDate)) { |
|||
console.error("Skipping", act.activityId, "(not today)"); |
|||
blackList[id] = true; |
|||
continue; |
|||
} |
|||
if (looseActivities.find(l => l.tags.find(t => t.key === "gc:ActivityID" && t.value === id))) { |
|||
console.error("Skipping", act.activityId, "(loose activity exists)"); |
|||
blackList[id] = true; |
|||
continue; |
|||
} |
|||
if (recentWorkouts.find(l => l.tags.find(t => t.key === "gc:ActivityID" && t.value === id))) { |
|||
console.error("Skipping", act.activityId, "(workout exists)"); |
|||
blackList[id] = true; |
|||
continue; |
|||
} |
|||
|
|||
await garmin.downloadOriginalActivityData(act, "/tmp/", "tcx") |
|||
const input = await generateTrimlogInput(`/tmp/${act.activityId}.tcx`, act.activityName) |
|||
|
|||
input.tags.push({key: "gc:ActivityID", value: id}) |
|||
|
|||
await trimlog.postWorkout(input); |
|||
|
|||
blackList[id] = true; |
|||
} |
|||
}, 300000); |
|||
|
|||
process.on('SIGINT', function() { |
|||
console.log("INTERRUPTED"); |
|||
process.exit(0); |
|||
}); |
|||
|
|||
process.on('SIGTERM', function() { |
|||
console.log("TERMINATED"); |
|||
process.exit(0); |
|||
}); |
|||
} |
|||
|
|||
main(); |
@ -0,0 +1,56 @@ |
|||
export default class Trimlog { |
|||
constructor({username, password}) { |
|||
this.token = null; |
|||
this.tokenExpiry = new Date(0); |
|||
this.username = username; |
|||
this.password = password; |
|||
} |
|||
|
|||
async getLooseActivities() { |
|||
return this.fetch("GET", "activities"); |
|||
} |
|||
|
|||
async getWorkouts() { |
|||
return this.fetch("GET", `workouts?from=${new Date(Date.now() - 86400000 * 3).toISOString().slice(0, 10)}`); |
|||
} |
|||
|
|||
async postWorkout(data) { |
|||
return this.fetch("POST", "workouts", data); |
|||
} |
|||
|
|||
async fetch(method, path, body) { |
|||
if (new Date() > this.tokenExpiry) { |
|||
await this.refreshToken(); |
|||
} |
|||
|
|||
const res = await fetch("https://i.stifred.dev/api/"+path, { |
|||
method: method, |
|||
headers: { |
|||
"content-type": body ? "application/json" : void(0), |
|||
"authorization": `Bearer ${this.token}` |
|||
}, |
|||
body: body ? JSON.stringify(body) : void(0), |
|||
}) |
|||
|
|||
if (!res.ok) { |
|||
throw new Error(res.statusText); |
|||
} |
|||
|
|||
return (await res.json()).data; |
|||
} |
|||
|
|||
async refreshToken() { |
|||
const res = await fetch("https://stifred.auth.eu-west-1.amazoncognito.com/oauth2/token", { |
|||
method: "POST", |
|||
headers: { |
|||
"content-type": "application/x-www-form-urlencoded", |
|||
"authorization": `Basic ${Buffer.from(`${this.username}:${this.password}`).toString("base64")}` |
|||
}, |
|||
body: "grant_type=client_credentials&scope=indigo/api", |
|||
}) |
|||
const data = await res.json(); |
|||
|
|||
this.token = data.access_token; |
|||
this.tokenExpiry = new Date(Date.now() + (data.expires_in * 1000 * 0.95)); |
|||
} |
|||
} |
@ -0,0 +1,119 @@ |
|||
import * as fs from "fs/promises"; |
|||
import { parseString } from "xml2js"; |
|||
|
|||
export function generateTrimlogInput(filename, name = "Walk") { |
|||
return new Promise(async(resolve, reject) => { |
|||
parseString(await fs.readFile(filename, "utf-8"), (err, res) => { |
|||
if (err != null) { |
|||
reject(err); |
|||
} |
|||
|
|||
const data = res.TrainingCenterDatabase.Activities[0].Activity[0]; |
|||
const map = {}; |
|||
|
|||
const date = new Date(res.TrainingCenterDatabase.Activities[0].Activity[0].Id[0]); |
|||
const dateStr = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` |
|||
|
|||
let totalCalories = 0; |
|||
let minAltitude = null; |
|||
let maxAltitude = null; |
|||
|
|||
for (const lap of data.Lap) { |
|||
totalCalories += parseFloat(lap.Calories[0]) || 0; |
|||
|
|||
for (const tp of lap.Track[0].Trackpoint) { |
|||
const point = { |
|||
time: tp.Time[0], |
|||
longitude: parseFloat(tp.Position?.[0]?.LongitudeDegrees[0]), |
|||
latitude: parseFloat(tp.Position?.[0]?.LatitudeDegrees[0]), |
|||
altitude: parseFloat(tp.AltitudeMeters[0]), |
|||
distance: parseFloat(tp.DistanceMeters[0]), |
|||
heartrate: parseFloat(tp.HeartRateBpm?.[0].Value?.[0]), |
|||
}; |
|||
|
|||
if (minAltitude == null || point.altitude < minAltitude) { |
|||
minAltitude = point.altitude; |
|||
} |
|||
if (maxAltitude == null || point.altitude > maxAltitude) { |
|||
maxAltitude = point.altitude; |
|||
} |
|||
|
|||
map[tp.Time[0]] = point; |
|||
} |
|||
} |
|||
|
|||
const out = { |
|||
date: dateStr, |
|||
contents: [{rawActivity: { |
|||
kind: "Walking", |
|||
sets: [], |
|||
effortScale: 1, |
|||
weight: 0, |
|||
measurements: Object.keys(map).sort().map(k => map[k]).map(p => ({ |
|||
seconds: Math.round((new Date(p.time) - date) / 1000), |
|||
meters: p.distance, |
|||
pulse: p.heartrate, |
|||
})) |
|||
}}], |
|||
description: name, |
|||
tags: [ |
|||
{ key: "tcx:Time", value: res.TrainingCenterDatabase.Activities[0].Activity[0].Id[0] }, |
|||
{ key: "tcx:TotalCalories", value: totalCalories.toFixed(1) }, |
|||
{ key: "tcx:MaximumAltitude", value: maxAltitude.toFixed(1) }, |
|||
{ key: "tcx:MinimumAltitude", value: minAltitude.toFixed(1) }, |
|||
] |
|||
} |
|||
|
|||
resolve(out); |
|||
}); |
|||
}) |
|||
} |
|||
|
|||
export function today() { |
|||
const date = new Date(); |
|||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; |
|||
} |
|||
|
|||
function pad(n) {return n > 9 ? n.toString() : `0${n}`} |
|||
|
|||
/* |
|||
|
|||
{ |
|||
"date": "2023-12-31", |
|||
"contents": [ |
|||
{ |
|||
"rawActivity": { |
|||
"kind": "Walking", |
|||
"sets": [], |
|||
"measurements": [ |
|||
{ |
|||
"seconds": 1234, |
|||
"calories": 4321, |
|||
"meters": 432553 |
|||
}, |
|||
{ |
|||
"seconds": 2468, |
|||
"calories": 42342, |
|||
"meters": 3424 |
|||
}, |
|||
{ |
|||
"seconds": 3702, |
|||
"calories": 23423, |
|||
"meters": 2342 |
|||
}, |
|||
{ |
|||
"seconds": 4936, |
|||
"calories": 23423, |
|||
"meters": 2342 |
|||
} |
|||
], |
|||
"effortScale": 1, |
|||
"weight": 0 |
|||
} |
|||
} |
|||
], |
|||
"description": "asdfasdfasdf", |
|||
"tags": [] |
|||
} |
|||
|
|||
*/ |
@ -0,0 +1,553 @@ |
|||
{ |
|||
"name": "garmsync", |
|||
"lockfileVersion": 2, |
|||
"requires": true, |
|||
"packages": { |
|||
"": { |
|||
"dependencies": { |
|||
"garmin-connect": "^1.6.1", |
|||
"xml2js": "^0.6.2" |
|||
} |
|||
}, |
|||
"node_modules/app-root-path": { |
|||
"version": "3.1.0", |
|||
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", |
|||
"integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", |
|||
"engines": { |
|||
"node": ">= 6.0.0" |
|||
} |
|||
}, |
|||
"node_modules/asynckit": { |
|||
"version": "0.4.0", |
|||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", |
|||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" |
|||
}, |
|||
"node_modules/axios": { |
|||
"version": "1.6.3", |
|||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", |
|||
"integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", |
|||
"dependencies": { |
|||
"follow-redirects": "^1.15.0", |
|||
"form-data": "^4.0.0", |
|||
"proxy-from-env": "^1.1.0" |
|||
} |
|||
}, |
|||
"node_modules/call-bind": { |
|||
"version": "1.0.5", |
|||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", |
|||
"integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", |
|||
"dependencies": { |
|||
"function-bind": "^1.1.2", |
|||
"get-intrinsic": "^1.2.1", |
|||
"set-function-length": "^1.1.1" |
|||
}, |
|||
"funding": { |
|||
"url": "https://github.com/sponsors/ljharb" |
|||
} |
|||
}, |
|||
"node_modules/combined-stream": { |
|||
"version": "1.0.8", |
|||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", |
|||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", |
|||
"dependencies": { |
|||
"delayed-stream": "~1.0.0" |
|||
}, |
|||
"engines": { |
|||
"node": ">= 0.8" |
|||
} |
|||
}, |
|||
"node_modules/crypto": { |
|||
"version": "1.0.1", |
|||
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", |
|||
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", |
|||
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." |
|||
}, |
|||
"node_modules/define-data-property": { |
|||
"version": "1.1.1", |
|||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", |
|||
"integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", |
|||
"dependencies": { |
|||
"get-intrinsic": "^1.2.1", |
|||
"gopd": "^1.0.1", |
|||
"has-property-descriptors": "^1.0.0" |
|||
}, |
|||
"engines": { |
|||
"node": ">= 0.4" |
|||
} |
|||
}, |
|||
"node_modules/delayed-stream": { |
|||
"version": "1.0.0", |
|||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", |
|||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", |
|||
"engines": { |
|||
"node": ">=0.4.0" |
|||
} |
|||
}, |
|||
"node_modules/follow-redirects": { |
|||
"version": "1.15.3", |
|||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", |
|||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", |
|||
"funding": [ |
|||
{ |
|||
"type": "individual", |
|||
"url": "https://github.com/sponsors/RubenVerborgh" |
|||
} |
|||
], |
|||
"engines": { |
|||
"node": ">=4.0" |
|||
}, |
|||
"peerDependenciesMeta": { |
|||
"debug": { |
|||
"optional": true |
|||
} |
|||
} |
|||
}, |
|||
"node_modules/form-data": { |
|||
"version": "4.0.0", |
|||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", |
|||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", |
|||
"dependencies": { |
|||
"asynckit": "^0.4.0", |
|||
"combined-stream": "^1.0.8", |
|||
"mime-types": "^2.1.12" |
|||
}, |
|||
"engines": { |
|||
"node": ">= 6" |
|||
} |
|||
}, |
|||
"node_modules/function-bind": { |
|||
"version": "1.1.2", |
|||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", |
|||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", |
|||
"funding": { |
|||
"url": "https://github.com/sponsors/ljharb" |
|||
} |
|||
}, |
|||
"node_modules/garmin-connect": { |
|||
"version": "1.6.1", |
|||
"resolved": "https://registry.npmjs.org/garmin-connect/-/garmin-connect-1.6.1.tgz", |
|||
"integrity": "sha512-ILYhV1G6UNEXiAj+pbPfhOPb9J8mi/QJnGiJOkg2DkRutpFJrwnG73JzzlBY5hbU1R/KpcDNNC1Sl/zilnZhZg==", |
|||
"dependencies": { |
|||
"app-root-path": "^3.1.0", |
|||
"axios": "^1.5.1", |
|||
"crypto": "^1.0.1", |
|||
"form-data": "^4.0.0", |
|||
"lodash": "^4.17.21", |
|||
"luxon": "^3.4.3", |
|||
"oauth-1.0a": "^2.2.6", |
|||
"qs": "^6.11.2" |
|||
} |
|||
}, |
|||
"node_modules/get-intrinsic": { |
|||
"version": "1.2.2", |
|||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", |
|||
"integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", |
|||
"dependencies": { |
|||
"function-bind": "^1.1.2", |
|||
"has-proto": "^1.0.1", |
|||
"has-symbols": "^1.0.3", |
|||
"hasown": "^2.0.0" |
|||
}, |
|||
"funding": { |
|||
"url": "https://github.com/sponsors/ljharb" |
|||
} |
|||
}, |
|||
"node_modules/gopd": { |
|||
"version": "1.0.1", |
|||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", |
|||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", |
|||
"dependencies": { |
|||
"get-intrinsic": "^1.1.3" |
|||
}, |
|||
"funding": { |
|||
"url": "https://github.com/sponsors/ljharb" |
|||
} |
|||
}, |
|||
"node_modules/has-property-descriptors": { |
|||
"version": "1.0.1", |
|||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", |
|||
"integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", |
|||
"dependencies": { |
|||
"get-intrinsic": "^1.2.2" |
|||
}, |
|||
"funding": { |
|||
"url": "https://github.com/sponsors/ljharb" |
|||
} |
|||
}, |
|||
"node_modules/has-proto": { |
|||
"version": "1.0.1", |
|||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", |
|||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", |
|||
"engines": { |
|||
"node": ">= 0.4" |
|||
}, |
|||
"funding": { |
|||
"url": "https://github.com/sponsors/ljharb" |
|||
} |
|||
}, |
|||
"node_modules/has-symbols": { |
|||
"version": "1.0.3", |
|||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", |
|||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", |
|||
"engines": { |
|||
"node": ">= 0.4" |
|||
}, |
|||
"funding": { |
|||
"url": "https://github.com/sponsors/ljharb" |
|||
} |
|||
}, |
|||
"node_modules/hasown": { |
|||
"version": "2.0.0", |
|||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", |
|||
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", |
|||
"dependencies": { |
|||
"function-bind": "^1.1.2" |
|||
}, |
|||
"engines": { |
|||
"node": ">= 0.4" |
|||
} |
|||
}, |
|||
"node_modules/lodash": { |
|||
"version": "4.17.21", |
|||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", |
|||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" |
|||
}, |
|||
"node_modules/luxon": { |
|||
"version": "3.4.4", |
|||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", |
|||
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", |
|||
"engines": { |
|||
"node": ">=12" |
|||
} |
|||
}, |
|||
"node_modules/mime-db": { |
|||
"version": "1.52.0", |
|||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", |
|||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", |
|||
"engines": { |
|||
"node": ">= 0.6" |
|||
} |
|||
}, |
|||
"node_modules/mime-types": { |
|||
"version": "2.1.35", |
|||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", |
|||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", |
|||
"dependencies": { |
|||
"mime-db": "1.52.0" |
|||
}, |
|||
"engines": { |
|||
"node": ">= 0.6" |
|||
} |
|||
}, |
|||
"node_modules/oauth-1.0a": { |
|||
"version": "2.2.6", |
|||
"resolved": "https://registry.npmjs.org/oauth-1.0a/-/oauth-1.0a-2.2.6.tgz", |
|||
"integrity": "sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ==" |
|||
}, |
|||
"node_modules/object-inspect": { |
|||
"version": "1.13.1", |
|||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", |
|||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", |
|||
"funding": { |
|||
"url": "https://github.com/sponsors/ljharb" |
|||
} |
|||
}, |
|||
"node_modules/proxy-from-env": { |
|||
"version": "1.1.0", |
|||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", |
|||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" |
|||
}, |
|||
"node_modules/qs": { |
|||
"version": "6.11.2", |
|||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", |
|||
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", |
|||
"dependencies": { |
|||
"side-channel": "^1.0.4" |
|||
}, |
|||
"engines": { |
|||
"node": ">=0.6" |
|||
}, |
|||
"funding": { |
|||
"url": "https://github.com/sponsors/ljharb" |
|||
} |
|||
}, |
|||
"node_modules/sax": { |
|||
"version": "1.3.0", |
|||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", |
|||
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" |
|||
}, |
|||
"node_modules/set-function-length": { |
|||
"version": "1.1.1", |
|||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", |
|||
"integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", |
|||
"dependencies": { |
|||
"define-data-property": "^1.1.1", |
|||
"get-intrinsic": "^1.2.1", |
|||
"gopd": "^1.0.1", |
|||
"has-property-descriptors": "^1.0.0" |
|||
}, |
|||
"engines": { |
|||
"node": ">= 0.4" |
|||
} |
|||
}, |
|||
"node_modules/side-channel": { |
|||
"version": "1.0.4", |
|||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", |
|||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", |
|||
"dependencies": { |
|||
"call-bind": "^1.0.0", |
|||
"get-intrinsic": "^1.0.2", |
|||
"object-inspect": "^1.9.0" |
|||
}, |
|||
"funding": { |
|||
"url": "https://github.com/sponsors/ljharb" |
|||
} |
|||
}, |
|||
"node_modules/xml2js": { |
|||
"version": "0.6.2", |
|||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", |
|||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", |
|||
"dependencies": { |
|||
"sax": ">=0.6.0", |
|||
"xmlbuilder": "~11.0.0" |
|||
}, |
|||
"engines": { |
|||
"node": ">=4.0.0" |
|||
} |
|||
}, |
|||
"node_modules/xmlbuilder": { |
|||
"version": "11.0.1", |
|||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", |
|||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", |
|||
"engines": { |
|||
"node": ">=4.0" |
|||
} |
|||
} |
|||
}, |
|||
"dependencies": { |
|||
"app-root-path": { |
|||
"version": "3.1.0", |
|||
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", |
|||
"integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==" |
|||
}, |
|||
"asynckit": { |
|||
"version": "0.4.0", |
|||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", |
|||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" |
|||
}, |
|||
"axios": { |
|||
"version": "1.6.3", |
|||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", |
|||
"integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", |
|||
"requires": { |
|||
"follow-redirects": "^1.15.0", |
|||
"form-data": "^4.0.0", |
|||
"proxy-from-env": "^1.1.0" |
|||
} |
|||
}, |
|||
"call-bind": { |
|||
"version": "1.0.5", |
|||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", |
|||
"integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", |
|||
"requires": { |
|||
"function-bind": "^1.1.2", |
|||
"get-intrinsic": "^1.2.1", |
|||
"set-function-length": "^1.1.1" |
|||
} |
|||
}, |
|||
"combined-stream": { |
|||
"version": "1.0.8", |
|||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", |
|||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", |
|||
"requires": { |
|||
"delayed-stream": "~1.0.0" |
|||
} |
|||
}, |
|||
"crypto": { |
|||
"version": "1.0.1", |
|||
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", |
|||
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" |
|||
}, |
|||
"define-data-property": { |
|||
"version": "1.1.1", |
|||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", |
|||
"integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", |
|||
"requires": { |
|||
"get-intrinsic": "^1.2.1", |
|||
"gopd": "^1.0.1", |
|||
"has-property-descriptors": "^1.0.0" |
|||
} |
|||
}, |
|||
"delayed-stream": { |
|||
"version": "1.0.0", |
|||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", |
|||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" |
|||
}, |
|||
"follow-redirects": { |
|||
"version": "1.15.3", |
|||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", |
|||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" |
|||
}, |
|||
"form-data": { |
|||
"version": "4.0.0", |
|||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", |
|||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", |
|||
"requires": { |
|||
"asynckit": "^0.4.0", |
|||
"combined-stream": "^1.0.8", |
|||
"mime-types": "^2.1.12" |
|||
} |
|||
}, |
|||
"function-bind": { |
|||
"version": "1.1.2", |
|||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", |
|||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" |
|||
}, |
|||
"garmin-connect": { |
|||
"version": "1.6.1", |
|||
"resolved": "https://registry.npmjs.org/garmin-connect/-/garmin-connect-1.6.1.tgz", |
|||
"integrity": "sha512-ILYhV1G6UNEXiAj+pbPfhOPb9J8mi/QJnGiJOkg2DkRutpFJrwnG73JzzlBY5hbU1R/KpcDNNC1Sl/zilnZhZg==", |
|||
"requires": { |
|||
"app-root-path": "^3.1.0", |
|||
"axios": "^1.5.1", |
|||
"crypto": "^1.0.1", |
|||
"form-data": "^4.0.0", |
|||
"lodash": "^4.17.21", |
|||
"luxon": "^3.4.3", |
|||
"oauth-1.0a": "^2.2.6", |
|||
"qs": "^6.11.2" |
|||
} |
|||
}, |
|||
"get-intrinsic": { |
|||
"version": "1.2.2", |
|||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", |
|||
"integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", |
|||
"requires": { |
|||
"function-bind": "^1.1.2", |
|||
"has-proto": "^1.0.1", |
|||
"has-symbols": "^1.0.3", |
|||
"hasown": "^2.0.0" |
|||
} |
|||
}, |
|||
"gopd": { |
|||
"version": "1.0.1", |
|||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", |
|||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", |
|||
"requires": { |
|||
"get-intrinsic": "^1.1.3" |
|||
} |
|||
}, |
|||
"has-property-descriptors": { |
|||
"version": "1.0.1", |
|||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", |
|||
"integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", |
|||
"requires": { |
|||
"get-intrinsic": "^1.2.2" |
|||
} |
|||
}, |
|||
"has-proto": { |
|||
"version": "1.0.1", |
|||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", |
|||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" |
|||
}, |
|||
"has-symbols": { |
|||
"version": "1.0.3", |
|||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", |
|||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" |
|||
}, |
|||
"hasown": { |
|||
"version": "2.0.0", |
|||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", |
|||
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", |
|||
"requires": { |
|||
"function-bind": "^1.1.2" |
|||
} |
|||
}, |
|||
"lodash": { |
|||
"version": "4.17.21", |
|||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", |
|||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" |
|||
}, |
|||
"luxon": { |
|||
"version": "3.4.4", |
|||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", |
|||
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==" |
|||
}, |
|||
"mime-db": { |
|||
"version": "1.52.0", |
|||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", |
|||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" |
|||
}, |
|||
"mime-types": { |
|||
"version": "2.1.35", |
|||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", |
|||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", |
|||
"requires": { |
|||
"mime-db": "1.52.0" |
|||
} |
|||
}, |
|||
"oauth-1.0a": { |
|||
"version": "2.2.6", |
|||
"resolved": "https://registry.npmjs.org/oauth-1.0a/-/oauth-1.0a-2.2.6.tgz", |
|||
"integrity": "sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ==" |
|||
}, |
|||
"object-inspect": { |
|||
"version": "1.13.1", |
|||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", |
|||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" |
|||
}, |
|||
"proxy-from-env": { |
|||
"version": "1.1.0", |
|||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", |
|||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" |
|||
}, |
|||
"qs": { |
|||
"version": "6.11.2", |
|||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", |
|||
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", |
|||
"requires": { |
|||
"side-channel": "^1.0.4" |
|||
} |
|||
}, |
|||
"sax": { |
|||
"version": "1.3.0", |
|||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", |
|||
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" |
|||
}, |
|||
"set-function-length": { |
|||
"version": "1.1.1", |
|||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", |
|||
"integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", |
|||
"requires": { |
|||
"define-data-property": "^1.1.1", |
|||
"get-intrinsic": "^1.2.1", |
|||
"gopd": "^1.0.1", |
|||
"has-property-descriptors": "^1.0.0" |
|||
} |
|||
}, |
|||
"side-channel": { |
|||
"version": "1.0.4", |
|||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", |
|||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", |
|||
"requires": { |
|||
"call-bind": "^1.0.0", |
|||
"get-intrinsic": "^1.0.2", |
|||
"object-inspect": "^1.9.0" |
|||
} |
|||
}, |
|||
"xml2js": { |
|||
"version": "0.6.2", |
|||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", |
|||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", |
|||
"requires": { |
|||
"sax": ">=0.6.0", |
|||
"xmlbuilder": "~11.0.0" |
|||
} |
|||
}, |
|||
"xmlbuilder": { |
|||
"version": "11.0.1", |
|||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", |
|||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"dependencies": { |
|||
"garmin-connect": "^1.6.1", |
|||
"xml2js": "^0.6.2" |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue