Gisle Aune
3 months ago
5 changed files with 265 additions and 56 deletions
-
28index.mjs
-
174lib/client.mjs
-
27package-lock.json
-
1package.json
-
77stufftxt.mjs
@ -1,56 +1,168 @@ |
|||||
|
import WebSocket from "ws"; |
||||
|
|
||||
export default class Trimlog { |
export default class Trimlog { |
||||
constructor({username, password}) { |
constructor({username, password}) { |
||||
this.token = null; |
|
||||
this.tokenExpiry = new Date(0); |
|
||||
this.username = username; |
this.username = username; |
||||
this.password = password; |
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}` |
|
||||
|
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, |
||||
|
}})); |
||||
|
|
||||
|
const interval = setInterval(() => { |
||||
|
if (this.socket !== ws) { |
||||
|
clearInterval(interval); |
||||
|
} |
||||
|
|
||||
|
ws.send(JSON.stringify({ping: { |
||||
|
reference: "ping:" + new Date().toISOString(), |
||||
|
register: { |
||||
|
clientId: this.username, |
||||
|
clientSecret: this.password, |
||||
}, |
}, |
||||
body: body ? JSON.stringify(body) : void(0), |
|
||||
|
}})) |
||||
|
}, 60000) |
||||
|
|
||||
|
this.socket = ws; |
||||
}) |
}) |
||||
|
|
||||
if (!res.ok) { |
|
||||
throw new Error(res.statusText); |
|
||||
|
ws.on("message", (data) => { |
||||
|
data = JSON.parse(data); |
||||
|
if (data.signal) { |
||||
|
return |
||||
} |
} |
||||
|
|
||||
return (await res.json()).data; |
|
||||
|
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); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
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.inbox.push(data); |
||||
|
}) |
||||
|
|
||||
|
ws.on("error", (err) => { |
||||
|
if (!answered) { |
||||
|
answered = true; |
||||
|
return reject(err); |
||||
|
} |
||||
}) |
}) |
||||
const data = await res.json(); |
|
||||
|
|
||||
this.token = data.access_token; |
|
||||
this.tokenExpiry = new Date(Date.now() + (data.expires_in * 1000 * 0.95)); |
|
||||
|
ws.on("close", () => { |
||||
|
if (this.socket === ws) { |
||||
|
this.socket = null; |
||||
|
|
||||
|
console.log("Socket closed") |
||||
|
this.connect().then(() => { |
||||
|
console.log("Socket reconnected") |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
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)); |
||||
|
|
||||
|
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") |
||||
|
}) |
||||
|
}) |
||||
} |
} |
||||
} |
} |
@ -1,6 +1,7 @@ |
|||||
{ |
{ |
||||
"dependencies": { |
"dependencies": { |
||||
"garmin-connect": "^1.6.1", |
"garmin-connect": "^1.6.1", |
||||
|
"ws": "^8.18.0", |
||||
"xml2js": "^0.6.2" |
"xml2js": "^0.6.2" |
||||
} |
} |
||||
} |
} |
@ -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(); |
Write
Preview
Loading…
Cancel
Save
Reference in new issue