UNPKG

tsme-metering

Version:

A useful lib and CLI to collect water meter data from your TSME group provider account

154 lines 5.9 kB
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