tsme-metering
Version:
A useful lib and CLI to collect water meter data from your TSME group provider account
154 lines • 5.9 kB
JavaScript
import axios from "axios";
import { CookieJar } from "tough-cookie";
import { wrapper } from "axios-cookiejar-support";
import * as cheerio from "cheerio";
import { TZDate } from "@date-fns/tz";
import { format, subDays, endOfDay, isAfter, startOfMonth } from "date-fns";
import config from '../config.js';
export default class BaseProviderClient {
email;
password;
options;
_isLoggedIn = false;
axios;
jar;
constructor(options, email = config.TSME_EMAIL, password = config.TSME_PASSWORD) {
this.options = options;
if (email === undefined || password === undefined) {
throw new Error('Both email and password should be defined');
}
this.email = email;
this.password = password;
this.jar = new CookieJar();
this.axios = wrapper(axios.create({
baseURL: this.options.baseUrl,
jar: this.jar,
withCredentials: true,
}));
}
isLoggedIn() {
return this._isLoggedIn;
}
checkApiResponse(response) {
if (response.status > 200) {
return false;
}
const responseData = response.data;
if (responseData.message !== "OK") {
return false;
}
return true;
}
extractCsrf(loginPage) {
const $ = cheerio.load(loginPage.data);
const scriptContent = $("script")
.toArray()
.map((el) => $(el).html())
.find((html) => html?.includes("window.tsme_data = JSON.parse"));
if (!scriptContent) {
throw new Error("❌ Could not find script with tsme_data");
}
const match = scriptContent.match(/JSON\.parse\("(.+?)"\)/);
if (!match || !match[1]) {
throw new Error("❌ Could not extract JSON string from tsme_data");
}
const jsonStringEscaped = match[1];
const decoded = JSON.parse(`"${jsonStringEscaped}"`);
const tsmeData = JSON.parse(decoded);
const csrfToken = tsmeData?.csrfToken;
if (!csrfToken) {
throw new Error("❌ CSRF token not found in tsme_data");
}
return csrfToken;
}
async login() {
console.warn("🔐 Logging in...");
const loginPage = await this.axios.get(this.options.loginEndpoint);
if (loginPage.status > 200) {
throw new Error(`❌ Login preparation failed: HTTP ${loginPage.status}`);
}
const csrfToken = this.extractCsrf(loginPage);
console.warn("🔑 CSRF Token: %s", csrfToken);
const loginResponse = await this.axios.post(this.options.loginEndpoint, {
"tsme_user_login[_username]": this.email,
"tsme_user_login[_password]": this.password,
"tsme_user_login[_target_path]": this.options.dashboardEndpoint,
_csrf_token: csrfToken,
}, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
if (loginResponse.status > 200) {
throw new Error(`❌ Login failed: HTTP ${loginResponse.status}`);
}
const $ = cheerio.load(loginResponse.data);
const canonical = $('link[rel="canonical"]')
.toArray()
.map((el) => $(el).attr("href"))
.find((href) => href?.includes(this.options.dashboardEndpoint));
if (!canonical) {
throw new Error(`❌ Login failed: incorrect credentials`);
}
console.warn("✅ Logged in");
this._isLoggedIn = true;
return true;
}
async getMetersIds() {
if (!this.isLoggedIn()) {
await this.login();
}
console.warn(`🔢 Getting all meters IDS of ${this.email}...`);
const metersList = await this.axios.get(this.options.metersListEndpoint);
if (!this.checkApiResponse(metersList)) {
throw new Error(`❌ Meters listing failed: HTTP ${metersList.status}`);
}
const metersListData = metersList.data;
const metersIds = [];
if (metersListData.content.nbMeters < 1) {
return metersIds;
}
metersListData.content.clientCompteursPro.forEach((customer) => {
if (customer.nombreCompteurTr === 0)
return;
const trMeters = customer.compteursPro.filter((meter) => meter.codeEquipement === "TR");
trMeters.forEach((meter) => {
metersIds.push(meter.idPDS);
});
});
console.warn(`✅ Meters IDS extracted (${metersIds.length})`);
return metersIds;
}
async getMetering(meterId, from, to) {
if (!this.isLoggedIn()) {
await this.login();
}
console.warn(`📊 Getting meter data of ${meterId}...`);
const maxTo = endOfDay(subDays(TZDate.tz("Europe/Paris"), 1));
if (!to || isAfter(to, maxTo)) {
to = new TZDate(maxTo, "Europe/Paris");
}
if (!from || isAfter(from, to)) {
from = new TZDate(startOfMonth(maxTo), "Europe/Paris");
}
const metering = await this.axios.get(this.options.meteringEndpoint, {
params: {
id_PDS: meterId,
mode: "daily",
start_date: format(from, "yyyy-MM-dd"),
end_date: format(to, "yyyy-MM-dd"),
},
});
if (!this.checkApiResponse(metering)) {
throw new Error(`❌ Metering extraction failed: HTTP ${metering.status}`);
}
const meteringData = metering.data;
const measures = meteringData.content.measures.map((data) => ({
...data,
date: new TZDate(data.date, "Europe/Paris"),
}));
console.warn(`✅ Meter data extracted (${measures.length})`);
return measures;
}
}
//# sourceMappingURL=base.js.map