first commit
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
+119
@@ -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": []
|
||||
}
|
||||
|
||||
*/
|
||||
Reference in New Issue
Block a user