DEV Community

Cover image for Automazione Iliad con Node.js e puppeteer.
Mattia Zanella
Mattia Zanella

Posted on

Automazione Iliad con Node.js e puppeteer.

TL;DR

Quello che andremo a creare in questo articolo è più o meno questo:

Ho scritto più o meno perché il video è puramente esemplificativo ed esula le seguenti ragioni:

  • Sarà un cron-job, quindi non una cosa da lanciare manualmente ogni volta.
  • Solo se l'utilizzo corrente è maggiore a una X% (ora impostata a 70) allora si riceve la mail.

Intro

Una delle cose che non ho mai digerito della mia giovinezza è stata quella di aver speso delle cospique decine di euro per acquistare un Raspberry Pi 3 e averlo usato principalmente per raccogliere la polvere.

Non tutti i mali vengono per nuocere, dicono, e in questo periodo di pandemia spesso mi sono ritrovato a lavorare da remoto facendo affidamento solo ed esclusivamente al tethering del mio telefono.
Ed è qui che nasce l'esigenza di tenere monitorati i consumi effettivi del mio piano Ilaid.

Piccolo disclaimer: la mia è la 1° offerta uscita di Iliad e beneficio di un tetto massimo di 30GB al mese. Se sei anche tu un utente Iliad probabilmente avrai quella più recente da 50.

Come puoi vedere dall'immagine in copertina, Iliad offre un servizio analogo ma solo quando arrivi al 100% di quei 30 o 50GB. Servizio utile come...lascio a te il sillogismo migliore.

Requisiti per lo script

Percisazione prima di iniziare:

Il mio target device sarà un raspberry ma può essere tranquillamente il tuo pc.

Let's rock 🤘

Da terminale:

mkdir iliad-script && cd $_
yarn init -y

// Dependencies required:
yarn add -D dotenv node-cron nodemailer puppeteer
Enter fullscreen mode Exit fullscreen mode

Una volta installate le dipendenze, il 1° step da fare è quello di creare un file .env all'interno del repo poc'anzi creato.
E' un file essenziale e deve avere le seguenti variabili:

# .env
PUPPETEER_PRODUCT=chrome

NODE_ENV=development

ILIAD_LOGIN_PAGE=https://www.iliad.it/account/
ILIAD_USER=YOUR_AMAZING_USERNAME
ILIAD_PASS=YOUR_AMAZING_PASS

# ATTENZIONE: Mail DA dove si inviano le notifiche 👇 (vedi requisiti)
# Nel mio caso è NETSONS
NODEMAILER_HOST=srv-hp4.netsons.net
NODEMAILER_MAIL=YOUR_AMAZING_EMAIL
NODEMAILER_PASS=YOUR_AMAZING_EMAIL_PASS

# ATTENZIONE: Mail dove si riceve le notifiche 👇
EMAIL_RECIPIENT=my_amazing_email@icloud.com

PERCENTACE_GUARD_DEVELOPMENT=5
PERCENTACE_GUARD_PRODUCTION=70
Enter fullscreen mode Exit fullscreen mode

Il 2° step è quello di aggiungere gli script di yarn al package.json

  "scripts" :{
    "start": "node index.js",
    "dev": "node index.js"
  },
Enter fullscreen mode Exit fullscreen mode

A questo punto possiamo iniziare a scrivere il pezzo forte dello script e creiamo un file index.js

require("dotenv").config()

const puppeteer = require("puppeteer")
const mailManager = require('./mailManager.js')

const iliadScraper = async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  try {
    await page.goto(process.env.ILIAD_LOGIN_PAGE)

    await page.type("input[type=text]", process.env.ILIAD_USER)
    await page.type("input[type=password]", process.env.ILIAD_PASS)
    await page.click("button[type=submit]")

    await page.waitForNavigation({ waitUntil: "networkidle0" })

    const fatherSelector = ".conso-infos.conso-local"
    await page.waitForSelector(fatherSelector)

    const gridChildValues = await page.$$(
      `${fatherSelector} .conso__content .conso__text`
    )

    const chain = gridChildValues.map((child) => {
      return page.evaluate((element) => element.textContent, child)
    })
    const res = await Promise.all(chain)
    console.log(res)
  } catch (error) {
    console.error(`ERROR::${JSON.stringify(error, null, 2)}`)
    browser.close()
  }
}

iliadScraper()
Enter fullscreen mode Exit fullscreen mode

Trovo verboso dover spiegare ogni metodo di puppeteer quando è già presente una documentazione abbastanza esaustiva.
In sonstanza le operazioni sono:

  • Avvio di chrome in headless-mode (default è true).
  • Indirizzo chrome alla pagina di login di Iliad (definito in .env file).
  • Inserimento di user e pass del profilo Iliad (definito in .env file).
  • Click del bottone di login.
  • Attesa della dashboard.

A questo punto mi soffermerei sul log della costante res:

[
  '\n' +
    '                     Chiamate: 1m 34s\n' +
    '                     Consumi voce: 0.00€\n' +
    '                  ',
  '1 SMS\n                     SMS extra: 0.00€\n                  ',
  '\n' +
    '                     64,81mb / 30GB\n' +
    '                     Consumi Dati: 0.00€\n' +
    '                  ',
  '\n' +
    '                     0 MMS\n' +
    '                     Consumi MMS: 0.00€\n' +
    '                  '
]
Enter fullscreen mode Exit fullscreen mode

L'array è la seguente rappresentazione dei box evidenziati:

Boxes

A questo punto mi sono creato una funzione di cleaning in un file apposito utils/clean.js:

/**
 * @description Since ILIAD's DOM has no ID or specific class for the consume
 * We have to parse all the grid 2x2.
 * @param {Array} arrayResponses
 * @returns {Array}
 */
const clean = (arrayResponses) =>
  arrayResponses.map((response) =>
    response
      .split("\n")
      .map((s) => s.trim())
      .filter(Boolean)
  );

module.exports = clean;
Enter fullscreen mode Exit fullscreen mode

Che successivamente importo in index.js e lo uso per avere una risposta più esaustiva:


...
// Custom utils.
const clean = require('./utils/clean.js')
...
    const res = await Promise.all(chain)
    const cleanedRes = clean(res)
...
Enter fullscreen mode Exit fullscreen mode

A questo punto cleanedRes diventa nel seguente modo:

[
  [ 'Chiamate: 1m 34s', 'Consumi voce: 0.00€' ],
  [ '1 SMS', 'SMS extra: 0.00€' ],
  [ '64,81mb / 30GB', 'Consumi Dati: 0.00€' ],
  [ '0 MMS', 'Consumi MMS: 0.00€' ]
]
Enter fullscreen mode Exit fullscreen mode

Sappiamo che il figlio desiderto per calcolare il consumo corrente è il 3°, quindi:

const extract = require('./utils/extract.js')
...
    // We know that consume is the third child.
    const splittedConsumeValues = cleanedRes[2][0].split("/").map(val => val.trim())
    const consume = {
      current: extract(splittedConsumeValues[0]),
      max: extract(splittedConsumeValues[1])
    }
    console.log(consume)
...
Enter fullscreen mode Exit fullscreen mode

È l'occasione per creare una 2° utility utils/extract.js

  /**
   * @description Given a plain text from DOM, extracting the number.
   * @param {String} str Like 2,64GB or plain 20GB.
   * Attention! can be also something like...200mb, 402.34mb
   * @returns {Number} Pay attention to comma vs dot.
   */
  const extractValue = str => {
    const unit = str.match(/[a-z]+/ig).join()
    const digits = str.match(/\d+/ig).join('.')

    return unit === 'mb'
      ? (parseFloat(digits) / 1000)
      : parseFloat(digits)
  }

  module.exports = extractValue
Enter fullscreen mode Exit fullscreen mode

Che permette di estrarre consumo corrente e il tetto massimo dell'offerta (inseriti poi nell'oggetto consume).
L'oggetto consume ora è:

{ current: 0.06481, max: 30 }
Enter fullscreen mode Exit fullscreen mode

Calcoliamo ora il consumo effettivo in %

...
    const currentProgress = Math.round(consume.current / consume.max * 100)
...
Enter fullscreen mode Exit fullscreen mode

A questo punto abbiamo 0 come consumo in %.
Da qui in poi aggiungo una 3° utility per prevenire l'invio della mail se siamo sotto a una determinata % (definito in .env file).

// utils/mustKillFlow.js

  /**
   * @description This is a utility guard to kill the MAIL flow in specific cases.
   * Pay attentio to PERCENTACE_GUARD, it's a default value fixed to 70.
   * By doing so we're sending email-alert ONLY if the user is over 70% of his GIGA.
   * @param {Number | any} percentage SHOULD be a number of current consume.
   * @returns {Boolean}
   */
  const mustKillFlow = percentage => {
    const alertLimit = process.env.NODE_ENV === 'development'
      ? process.env.PERCENTACE_GUARD_DEVELOPMENT
      : process.env.PERCENTACE_GUARD_PRODUCTION

    return !percentage || isNaN(percentage) || (percentage <= alertLimit)
  }

  module.exports = mustKillFlow
Enter fullscreen mode Exit fullscreen mode

e in index.js avrò dunque:

const mustKillFlow = require('./utils/mustKillFlow.js')
...
    const currentProgress = Math.round(consume.current / consume.max * 100)
    if ( mustKillFlow(currentProgress) ) return browser.close()

    const mailOptions = {
      from: process.env.NODEMAILER_MAIL,
      to: process.env.EMAIL_RECIPIENT,
      subject: 'Report from iliad cron-job ⏰.',
      html: `<p>📵 Your're at more or less ${currentProgress}% of your GIGA. 📵</p>`
    }

    mailManager.sendEmail(mailOptions).catch(console.error)
    browser.close()
} catch(error) {
...
Enter fullscreen mode Exit fullscreen mode

Rimane solo da definire il file:

// mailManager.js, è in root.
const nodemailer = require("nodemailer");

module.exports = {
    async sendEmail(mailOptions) {
        // @see https://ethereal.email/
        const transporter = nodemailer.createTransport({
            host: process.env.NODEMAILER_HOST,
            port: 465,
            secure: true, // true for 465, false for other ports
            auth: {
                user: process.env.NODEMAILER_MAIL,
                pass: process.env.NODEMAILER_PASS
            }
        });

        const sent = await transporter.sendMail(mailOptions);

        console.log("Message sent: %s", sent.messageId);
        // Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321@example.com>
    }    
}
Enter fullscreen mode Exit fullscreen mode

Bene, per finalità di utilizzo cambiamo l'ultima parte di index.js da

...
iliadScraper()
...
Enter fullscreen mode Exit fullscreen mode

a

...
// It will run at 1 A.M and 1 P.M
const timing = process.env.NODE_ENV === 'development' ? '* * * * *' : '00 01,13 * * *'
cron.schedule(timing, iliadScraper)
Enter fullscreen mode Exit fullscreen mode

E nel file .env cambiamo la variabile

NODE_ENV=production
Enter fullscreen mode Exit fullscreen mode

Perfetto, il tutorial è concluso.
Fintanto che terrete in vita il processo, il cron continuerà a girare 2 volte al giorno e verificherà che la vostra soglia non superi il limite impostato.

Top comments (0)