Browse Source

use new stuff

meyn 0.2.0
Gisle Aune 4 months ago
parent
commit
80ce539241
  1. 30
      index.mjs
  2. 186
      lib/client.mjs
  3. 27
      package-lock.json
  4. 1
      package.json
  5. 77
      stufftxt.mjs

30
index.mjs

@ -1,6 +1,7 @@
import gc from 'garmin-connect';
import Trimlog from './lib/client.mjs';
import { generateTrimlogInput, today } from './lib/tcx.mjs';
import fs from "fs/promises";
import { today } from './lib/tcx.mjs';
async function main() {
const garmin = new gc.GarminConnect({
@ -12,14 +13,14 @@ async function main() {
password: process.env.TRIMLOG_PASSWORD,
})
await trimlog.refreshToken();
await trimlog.submit({});
await garmin.login();
console.log("CONNECTED!");
const blackList = {};
setInterval(async() => {
const looseActivities = await trimlog.getLooseActivities();
const recentWorkouts = await trimlog.getWorkouts();
const activities = await garmin.getActivities();
const todayDate = today();
@ -40,25 +41,16 @@ async function main() {
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;
}
console.log("Going ahead with", act.activityId, act.activityType.typeKey)
await garmin.downloadOriginalActivityData(act, "/tmp/", "tcx")
const input = await generateTrimlogInput(`/tmp/${act.activityId}.tcx`, act.activityName, act.activityType?.typeKey)
input.tags.push({key: "gc:ActivityID", value: id})
await trimlog.postWorkout(input);
const input = (await fs.readFile(`/tmp/${act.activityId}.tcx`)).toString("base64")
await trimlog.submit({addUpload: {
name: `${act.activityId}.tcx`,
base64: input,
}});
blackList[id] = true;
}

186
lib/client.mjs

@ -1,56 +1,168 @@
import WebSocket from "ws";
export default class Trimlog {
constructor({username, password}) {
this.token = null;
this.tokenExpiry = new Date(0);
this.username = username;
this.password = password;
this.socket = null;
this.inbox = [];
}
async getLooseActivities() {
return this.fetch("GET", "activities");
send(key, data) {
this.socket.send(JSON.stringify({
[key]: data,
register: {
clientId: this.username,
clientSecret: this.password,
},
}));
}
async getWorkouts() {
return this.fetch("GET", `workouts?from=${new Date(Date.now() - 86400000 * 3).toISOString().slice(0, 10)}`);
}
waitFor(key) {
return new Promise((resolve, reject) => {
const ws = this.socket;
setInterval(() => {
if (ws !== this.socket) {
reject("disconnected");
}
async postWorkout(data) {
return this.fetch("POST", "workouts", data);
for (const [i, data] of this.inbox.entries()) {
if (!!data[key]) {
this.inbox.splice(i, 1);
return data[key];
}
}
}, 100)
})
}
async fetch(method, path, body) {
if (new Date() > this.tokenExpiry) {
await this.refreshToken();
}
connect() {
return new Promise((resolve, reject) => {
if (this.socket != null) {
this.socket.close();
}
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),
})
let answered = false;
const ws = new WebSocket("wss://i.stifred.dev/");
ws.on("open", () => {
ws.send(JSON.stringify({register: {
clientId: this.username,
clientSecret: this.password,
}}));
if (!res.ok) {
throw new Error(res.statusText);
}
const interval = setInterval(() => {
if (this.socket !== ws) {
clearInterval(interval);
}
return (await res.json()).data;
}
ws.send(JSON.stringify({ping: {
reference: "ping:" + new Date().toISOString(),
register: {
clientId: this.username,
clientSecret: this.password,
},
}}))
}, 60000)
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",
this.socket = ws;
})
ws.on("message", (data) => {
data = JSON.parse(data);
if (data.signal) {
return
}
console.log(JSON.stringify(data, 0, 4));
if (!answered) {
if (data.connection?.active) {
answered = true;
return resolve();
} else if (data.error != null) {
return reject(data.error.message);
}
}
this.inbox.push(data);
})
ws.on("error", (err) => {
if (!answered) {
answered = true;
return reject(err);
}
})
ws.on("close", () => {
if (this.socket === ws) {
this.socket = null;
console.log("Socket closed")
this.connect().then(() => {
console.log("Socket reconnected")
})
}
})
})
const data = await res.json();
}
submit(message) {
return new Promise((resolve, reject) => {
const reference = Date.now().toString(36) + Math.random().toString(36).replace(".", "");
let answered = false;
let sent = false;
const ws = new WebSocket("wss://i.stifred.dev/");
ws.on("open", () => {
ws.send(JSON.stringify({register: {
clientId: this.username,
clientSecret: this.password,
}}));
})
ws.on("message", (data) => {
data = JSON.parse(data);
console.log(JSON.stringify(data, 0, 4));
this.token = data.access_token;
this.tokenExpiry = new Date(Date.now() + (data.expires_in * 1000 * 0.95));
if (data.signal?.reference === reference) {
if (!answered) {
answered = true;
resolve();
ws.close();
}
}
if (!answered && !sent) {
if (data.connection?.active) {
ws.send(JSON.stringify({
...message,
ping: { reference },
register: {
clientId: this.username,
clientSecret: this.password,
}
}));
sent = true;
}
}
this.inbox.push(data);
})
ws.on("error", (err) => {
if (!answered) {
answered = true;
return reject(err);
}
})
ws.on("close", () => {
console.log("Socket closed")
})
})
}
}

27
package-lock.json

@ -6,6 +6,7 @@
"": {
"dependencies": {
"garmin-connect": "^1.6.1",
"ws": "^8.18.0",
"xml2js": "^0.6.2"
}
},
@ -303,6 +304,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xml2js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
@ -535,6 +556,12 @@
"object-inspect": "^1.9.0"
}
},
"ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"requires": {}
},
"xml2js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",

1
package.json

@ -1,6 +1,7 @@
{
"dependencies": {
"garmin-connect": "^1.6.1",
"ws": "^8.18.0",
"xml2js": "^0.6.2"
}
}

77
stufftxt.mjs

@ -0,0 +1,77 @@
import Trimlog from './lib/client.mjs';
async function readStdin() {
return new Promise((resolve) => {
const allLines = [];
let lingeringLine = "";
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(chunk) {
const lines = chunk.split("\n");
lines[0] = lingeringLine + lines[0];
lingeringLine = lines.pop();
allLines.push(...lines);
});
process.stdin.on('end', function() {
allLines.push(lingeringLine);
resolve(allLines);
});
})
}
function parseLine(line) {
const split = line.split(" ");
const split2 = split[0].split(/[:,.]/g);
let seconds = parseInt(split2[0], 10) * 60;
if (split2.length > 1) {
seconds += parseInt(split2[1], 10);
}
return ({
seconds,
calories: parseInt(split[1]),
});
}
async function main() {
const [id] = process.argv.slice(2);
if (!id) {
return;
}
const stuff = (await readStdin())
.filter(l => l)
.map(parseLine)
.map(l => ({...l, roundId: id}))
console.log(stuff);
const trimlog = new Trimlog({
username: process.env.TRIMLOG_USERNAME,
password: process.env.TRIMLOG_PASSWORD,
})
await trimlog.connect();
console.log("CONNECTED!")
trimlog.send("addRoundMeasurementBatch", {
measurements: stuff,
overwrite: true,
});
process.on('SIGINT', function() {
console.log("INTERRUPTED");
process.exit(0);
});
process.on('SIGTERM', function() {
console.log("TERMINATED");
process.exit(0);
});
}
main();
Loading…
Cancel
Save