UNPKG

@gooin/garmin-connect

Version:

Makes it simple to interface with Garmin Connect to get or set any data point

469 lines 19 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const form_data_1 = __importDefault(require("form-data")); const lodash_1 = __importDefault(require("lodash")); const luxon_1 = require("luxon"); const node_fs_1 = require("node:fs"); const node_path_1 = __importDefault(require("node:path")); const HttpClient_1 = require("../common/HttpClient"); const utils_1 = require("../utils"); const UrlClass_1 = require("./UrlClass"); const DateUtils_1 = require("./common/DateUtils"); const HydrationUtils_1 = require("./common/HydrationUtils"); const WeightUtils_1 = require("./common/WeightUtils"); const types_1 = require("./types"); const Running_1 = __importDefault(require("./workouts/Running")); class GarminConnect { // private oauth1: OAuth; constructor(config, domain = 'garmin.com') { var _a, _b; const { username, password } = config; if (!username || !password) { throw new Error('Missing credentials'); } this.config = config; this.url = new UrlClass_1.UrlClass((_a = config === null || config === void 0 ? void 0 : config.domain) !== null && _a !== void 0 ? _a : domain); this.domain = (_b = config === null || config === void 0 ? void 0 : config.domain) !== null && _b !== void 0 ? _b : domain; this._userHash = undefined; this.listeners = {}; this.client = new HttpClient_1.HttpClient(this.url, config); } async login(username, password) { if (username && password) { this.config.username = username; this.config.password = password; } await this.client.login(this.config.username, this.config.password); return this; } async exportTokenToFile(dirPath) { const isDir = await (0, utils_1.checkIsDirectory)(dirPath); if (!isDir) { await (0, utils_1.createDirectory)(dirPath); } // save oauth1 to json if (this.client.oauth1Token) { await (0, utils_1.writeToFile)(node_path_1.default.join(dirPath, 'oauth1_token.json'), JSON.stringify(this.client.oauth1Token)); } if (this.client.oauth2Token) { await (0, utils_1.writeToFile)(node_path_1.default.join(dirPath, 'oauth2_token.json'), JSON.stringify(this.client.oauth2Token)); } } async loadTokenByFile(dirPath) { const isDir = await (0, utils_1.checkIsDirectory)(dirPath); if (!isDir) { throw new Error('loadTokenByFile: Directory not found: ' + dirPath); } let oauth1Data = await node_fs_1.promises.readFile(node_path_1.default.join(dirPath, 'oauth1_token.json'), 'utf-8'); // console.log('loadTokenByFile - oauth1Data:', oauth1Data); const oauth1 = JSON.parse(oauth1Data); // console.log('loadTokenByFile - oauth1:', oauth1); this.client.oauth1Token = oauth1; let oauth2Data = await node_fs_1.promises.readFile(node_path_1.default.join(dirPath, 'oauth2_token.json'), 'utf-8'); // console.log('loadTokenByFile - oauth2Data:', oauth2Data); const oauth2 = JSON.parse(oauth2Data); // console.log('loadTokenByFile - oauth2:', oauth2); this.client.oauth2Token = oauth2; // console.log('loadTokenByFile - oauth2Token:', this); // console.log('loadTokenByFile - oauth2Token:', this.client.oauth2Token); } exportToken() { if (!this.client.oauth1Token || !this.client.oauth2Token) { throw new Error('exportToken: Token not found'); } return { oauth1: this.client.oauth1Token, oauth2: this.client.oauth2Token }; } // from db or localstorage etc loadToken(oauth1, oauth2) { this.client.oauth1Token = oauth1; this.client.oauth2Token = oauth2; } async getUserSettings() { return this.client.get(this.url.USER_SETTINGS); } async getUserProfile() { return this.client.get(this.url.USER_PROFILE); } async getActivities(start, limit, activityType, subActivityType) { return this.client.get(this.url.ACTIVITIES, { params: { start, limit, activityType, subActivityType } }); } async getActivity(activity) { if (!activity.activityId) throw new Error('Missing activityId'); return this.client.get(this.url.ACTIVITY + activity.activityId); } async countActivities() { return this.client.get(this.url.STAT_ACTIVITIES, { params: { aggregation: 'lifetime', startDate: '1970-01-01', endDate: luxon_1.DateTime.now().toFormat('yyyy-MM-dd'), metric: 'duration' } }); } async downloadWellnessData(date, dir) { const dateStr = (0, DateUtils_1.toDateString)(date); const isDir = await (0, utils_1.checkIsDirectory)(dir); if (!isDir) { await (0, utils_1.createDirectory)(dir); } let fileBuffer = await this.client.get(this.url.DOWNLOAD_WELLNESS + dateStr, { responseType: 'arraybuffer' }); await (0, utils_1.writeToFile)(node_path_1.default.join(dir, `${dateStr}.zip`), fileBuffer); } async downloadOriginalActivityData(activity, dir, type = 'zip') { if (!activity.activityId) throw new Error('Missing activityId'); const isDir = await (0, utils_1.checkIsDirectory)(dir); if (!isDir) { await (0, utils_1.createDirectory)(dir); } let fileBuffer; if (type === 'tcx') { fileBuffer = await this.client.get(this.url.DOWNLOAD_TCX + activity.activityId); } else if (type === 'gpx') { fileBuffer = await this.client.get(this.url.DOWNLOAD_GPX + activity.activityId); } else if (type === 'kml') { fileBuffer = await this.client.get(this.url.DOWNLOAD_KML + activity.activityId); } else if (type === 'zip') { fileBuffer = await this.client.get(this.url.DOWNLOAD_ZIP + activity.activityId, { responseType: 'arraybuffer' }); } else { throw new Error('downloadOriginalActivityData - Invalid type: ' + type); } await (0, utils_1.writeToFile)(node_path_1.default.join(dir, `${activity.activityId}.${type}`), fileBuffer); } async uploadActivity(file, format = 'fit') { var _a; const detectedFormat = (_a = (format || node_path_1.default.extname(file))) === null || _a === void 0 ? void 0 : _a.toLowerCase(); if (!lodash_1.default.includes(types_1.UploadFileType, detectedFormat)) { throw new Error('uploadActivity - Invalid format: ' + format); } // const fh = await fs.open(file); const fileBuffer = (0, node_fs_1.createReadStream)(file); // console.log('fileBuffer:', fileBuffer); const form = new form_data_1.default(); form.append('userfile', fileBuffer); const response = await this.client.post(this.url.UPLOAD + '.' + format, form, { headers: { 'Content-Type': form.getHeaders()['content-type'] } }); fileBuffer.close(); return response; } async deleteActivity(activity) { if (!activity.activityId) throw new Error('Missing activityId'); await this.client.delete(this.url.ACTIVITY + activity.activityId); } async getWorkouts(start, limit) { return this.client.get(this.url.WORKOUTS, { params: { start, limit } }); } async getWorkoutDetail(workout) { if (!workout.workoutId) throw new Error('Missing workoutId'); return this.client.get(this.url.WORKOUT(workout.workoutId)); } async addWorkout(workout) { if (!workout) throw new Error('Missing workout'); if (workout instanceof Running_1.default) { if (workout.isValid()) { const data = { ...workout.toJson() }; if (!data.description) { data.description = 'Added by garmin-connect for Node.js'; } return this.client.post(this.url.WORKOUT(), data); } } if (!workout.workoutSegments) throw new Error('Missing workoutSegments, please use WorkoutDetail, not Workout.'); const newWorkout = lodash_1.default.omit(workout, [ 'workoutId', 'ownerId', 'updatedDate', 'createdDate', 'author' ]); if (!newWorkout.description) { newWorkout.description = 'Added by garmin-connect for Node.js'; } // console.log('addWorkout - newWorkout:', newWorkout) return this.client.post(this.url.WORKOUT(), newWorkout); } async addRunningWorkout(name, meters, description) { const running = new Running_1.default(); running.name = name; running.distance = meters; running.description = description; return this.addWorkout(running); } async deleteWorkout(workout) { if (!workout.workoutId) throw new Error('Missing workout'); return this.client.delete(this.url.WORKOUT(workout.workoutId)); } async scheduleWorkout(workout, date = new Date()) { if (!workout.workoutId) throw new Error('Missing workoutId'); const formatedDate = luxon_1.DateTime.fromJSDate(date).toFormat('yyyy-MM-dd'); return this.client.post(`${this.url.SCHEDULE_WORKOUTS}${workout.workoutId}`, { date: formatedDate }); } // Garmin use month 0-11, not real month. async getCalendar(year = new Date().getFullYear(), month = new Date().getMonth()) { return this.client.get(this.url.CALENDAR(year, month)); } async getSteps(date = new Date()) { const dateString = (0, DateUtils_1.toDateString)(date); const days = await this.client.get(`${this.url.DAILY_STEPS}${dateString}/${dateString}`); const dayStats = days.find(({ calendarDate }) => calendarDate === dateString); if (!dayStats) { throw new Error("Can't find daily steps for this date."); } return dayStats.totalSteps; } async getSleepData(date = new Date()) { try { const dateString = (0, DateUtils_1.toDateString)(date); const sleepData = await this.client.get(`${this.url.DAILY_SLEEP}`, { params: { date: dateString } }); if (!sleepData) { throw new Error('Invalid or empty sleep data response.'); } return sleepData; } catch (error) { throw new Error(`Error in getSleepData: ${error.message}`); } } async getSleepDuration(date = new Date()) { try { const sleepData = await this.getSleepData(date); if (!sleepData || !sleepData.dailySleepDTO || sleepData.dailySleepDTO.sleepStartTimestampGMT === undefined || sleepData.dailySleepDTO.sleepEndTimestampGMT === undefined) { throw new Error('Invalid or missing sleep data for the specified date.'); } const sleepStartTimestampGMT = sleepData.dailySleepDTO.sleepStartTimestampGMT; const sleepEndTimestampGMT = sleepData.dailySleepDTO.sleepEndTimestampGMT; const { hours, minutes } = (0, DateUtils_1.calculateTimeDifference)(sleepStartTimestampGMT, sleepEndTimestampGMT); return { hours, minutes }; } catch (error) { throw new Error(`Error in getSleepDuration: ${error.message}`); } } async getDailyWeightData(date = new Date()) { try { const dateString = (0, DateUtils_1.toDateString)(date); const weightData = await this.client.get(`${this.url.DAILY_WEIGHT}/${dateString}`); if (!weightData) { throw new Error('Invalid or empty weight data response.'); } return weightData; } catch (error) { throw new Error(`Error in getDailyWeightData: ${error.message}`); } } async getDailyWeightInPounds(date = new Date()) { const weightData = await this.getDailyWeightData(date); if (weightData.totalAverage && typeof weightData.totalAverage.weight === 'number') { return (0, WeightUtils_1.gramsToPounds)(weightData.totalAverage.weight); } else { throw new Error("Can't find valid daily weight for this date."); } } async getDailyHydration(date = new Date()) { try { const dateString = (0, DateUtils_1.toDateString)(date); const hydrationData = await this.client.get(`${this.url.DAILY_HYDRATION}/${dateString}`); if (!hydrationData || !hydrationData.valueInML) { throw new Error('Invalid or empty hydration data response.'); } return (0, HydrationUtils_1.convertMLToOunces)(hydrationData.valueInML); } catch (error) { throw new Error(`Error in getDailyHydration: ${error.message}`); } } async updateWeight(date = new Date(), lbs, timezone) { try { const weightData = await this.client.post(`${this.url.UPDATE_WEIGHT}`, { dateTimestamp: (0, DateUtils_1.getLocalTimestamp)(date, timezone), gmtTimestamp: date.toISOString().substring(0, 23), unitKey: 'lbs', value: lbs }); return weightData; } catch (error) { throw new Error(`Error in updateWeight: ${error.message}`); } } async updateHydrationLogOunces(date = new Date(), valueInOz) { try { const dateString = (0, DateUtils_1.toDateString)(date); const hydrationData = await this.client.put(`${this.url.HYDRATION_LOG}`, { calendarDate: dateString, valueInML: (0, HydrationUtils_1.convertOuncesToML)(valueInOz), userProfileId: (await this.getUserProfile()).profileId, timestampLocal: date.toISOString().substring(0, 23) }); return hydrationData; } catch (error) { throw new Error(`Error in updateHydrationLogOunces: ${error.message}`); } } async getGolfSummary() { try { const golfSummary = await this.client.get(`${this.url.GOLF_SCORECARD_SUMMARY}`); if (!golfSummary) { throw new Error('Invalid or empty golf summary data response.'); } return golfSummary; } catch (error) { throw new Error(`Error in getGolfSummary: ${error.message}`); } } async getGolfScorecard(scorecardId) { try { const golfScorecard = await this.client.get(`${this.url.GOLF_SCORECARD_DETAIL}`, { params: { 'scorecard-ids': scorecardId } }); if (!golfScorecard) { throw new Error('Invalid or empty golf scorecard data response.'); } return golfScorecard; } catch (error) { throw new Error(`Error in getGolfScorecard: ${error.message}`); } } async getHeartRate(date = new Date()) { try { const dateString = (0, DateUtils_1.toDateString)(date); const heartRate = await this.client.get(`${this.url.DAILY_HEART_RATE}`, { params: { date: dateString } }); return heartRate; } catch (error) { throw new Error(`Error in getHeartRate: ${error.message}`); } } async getCourses() { try { const coursesForUser = await this.client.get(`${this.url.COURSE_OWNER}`); const courses_favorite = await this.client.get(`${this.url.COURSE_FAVORITE}`); const course = [ ...coursesForUser.coursesForUser, ...courses_favorite ]; const uniqCourse = lodash_1.default.uniqBy(course, 'courseId'); return uniqCourse; } catch (error) { throw new Error(`Error in getHeartRate: ${error.message}`); } } async getCourse(course) { try { if (!course.courseId) { throw new Error('Missing courseId'); } const courseDetail = await this.client.get(`${this.url.COURSE(course.courseId)}`); return courseDetail; } catch (error) { throw new Error(`Error in getHeartRate: ${error.message}`); } } async createCourse(course) { try { const createdCourse = await this.client.post(`${this.url.COURSE()}`, lodash_1.default.omit(course, [ 'courseId', // 'description', 'matchedToSegments', 'userProfilePk', 'userGroupPk', 'firstName', 'lastName', 'displayName', 'geoRoutePk', 'sourcePk', 'hasShareableEvent', 'virtualPartnerId', 'includeLaps', 'speedMeterPerSecond', 'createDate', 'updateDate', 'targetCoordinateSystem', 'originalCoordinateSystem', 'consumer', 'elevationSource', 'hasPaceBand', 'hasPowerGuide', 'favorite', 'curatedCoursePk' ])); return createdCourse; } catch (error) { throw new Error(`Error in getHeartRate: ${error.message}`); } } async consenGrant() { try { const result = await this.client.post(`${this.url.CONSENT_GRANT}`, { consentTypeId: 'DI_CONNECT_UPLOAD', consentLocale: 'en-US', consentVersion: '59' }); return result; } catch (error) { throw new Error(`Error in consenGrant: ${error.message}`); } } async get(url, data) { const response = await this.client.get(url, data); return response; } async post(url, data) { const response = await this.client.post(url, data, {}); return response; } async put(url, data) { const response = await this.client.put(url, data, {}); return response; } } exports.default = GarminConnect; //# sourceMappingURL=GarminConnect.js.map