import {chromium} from 'playwright'; import {Temporal} from '@js-temporal/polyfill'; import {createWriteStream, writeFileSync, readFileSync} from 'fs'; import dotenv from 'dotenv'; dotenv.config(); const endDate = Temporal.Now.plainDateISO().subtract({days: 1}); let startDate = endDate.subtract({years: 1}).with({ day: 1 }); try { startDate = Temporal.PlainDate.from(readFileSync('lastDate', 'utf8')).add({days: 1}); } catch(e) {} if(Temporal.Duration.compare(startDate.until(endDate), Temporal.Duration.from({days: 0})) === 1) { const browser = await chromium.launch({headless: false}); const context = await browser.newContext(); const page = await context.newPage(); await page.route('**/*', route => { const f = route.request().resourceType(); if(f === 'document' || f === 'script' || f === 'xhr') { route.continue(); } else { route.abort(); } }); await page.goto('https://selfserve.publicmobile.ca'); await page.fill('#FullContent_ContentBottom_LoginControl_UserName', process.env.EMAIL); await page.fill('#FullContent_ContentBottom_LoginControl_Password', process.env.PASSPHRASE); await page.click('#FullContent_ContentBottom_LoginControl_LoginButton'); await page.goto('https://selfserve.publicmobile.ca/Overview/plan-and-Add-ons/call-history/'); await page.check('#UseDateRangeRadioButton'); await page.fill('#startdate', startDate.toString()); await page.fill('#enddate', endDate.toString()); await Promise.all([ page.waitForNavigation(), page.click('#FullContent_DashboardContent_ViewCallHistoryButton') ]); await Promise.all([ page.waitForResponse(r => r.url() === 'https://selfserve.publicmobile.ca/Overview/plan-and-Add-ons/call-history/'), page.click('#FullContent_DashboardContent_gvCallHistory > tbody > tr.gvCallHistoryHeader > th.gridViewTDHideShowHeader > a') ]); let shit = []; const h1 = (await page.$eval('.gvCallHistoryHeader', e => e.innerText.split('\t'))).join() + '\n'; while(true) { shit = shit.concat(await page.$eval('#FullContent_DashboardContent_gvCallHistory > tbody', x => { x = x.children; let out = []; for(let i = 1; i < x.length - 1; ++i) { let t = x[i].innerText.split('\t'); for(let j = 0; j < t.length; ++j) { t[j] = t[j].trim(); } out.push(t); } return out; })); if(await page.getAttribute('#FullContent_DashboardContent_gvCallHistory_gvPagerTemplate_pagerNextPage', 'href') === null) { break; } else { await Promise.all([ page.waitForResponse(r => r.url() === 'https://selfserve.publicmobile.ca/Overview/plan-and-Add-ons/call-history/'), page.click('#FullContent_DashboardContent_gvCallHistory_gvPagerTemplate_pagerNextPage') ]); } } await page.goto('https://selfserve.publicmobile.ca/Overview/payment/Payment-History/'); await page.check('#UseDateRangeRadioButton'); await page.fill('#startdate', startDate.toString()); await page.fill('#enddate', endDate.toString()); await Promise.all([ page.waitForNavigation(), page.click('#FullContent_DashboardContent_ViewTransactionHistoryButton') ]); let shit2 = []; writeFileSync('payment.csv', (await page.$eval('.gvTransactionHistoryHeader1', e => e.innerText.split('\t'))).join() + '\n', {flag: 'wx'}); while(true) { shit2 = shit2.concat(await page.$eval('#FullContent_DashboardContent_gvTransactionHistory > tbody', x => { x = x.children; let out = []; for(let i = 1; i < x.length - 1; ++i) { let t = x[i].innerText.split('\t'); for(let j = 0; j < t.length; ++j) { t[j] = t[j].trim(); } out.push(t); } return out; })); if(await page.getAttribute('#FullContent_DashboardContent_gvTransactionHistory_gvPagerTemplate_pagerNextPage', 'href') === null) { break; } else { await Promise.all([ page.waitForResponse(r => r.url() === 'https://selfserve.publicmobile.ca/Overview/payment/Payment-History/'), page.click('#FullContent_DashboardContent_gvTransactionHistory_gvPagerTemplate_pagerNextPage') ]); } } await browser.close(); let usageStreams = new Map(); for(let i = shit.length - 1; i >= 0; --i) { shit[i][0] = shit[i][0].replace(/^(\d{2})\/(\d{2})\/(\d{4})/, "$3-$2-$1"); let ym = shit[i][0].substring(0, 7); if(shit[i][8] === 'MB') { ym = shit[i][8] + '-' + ym; } if(!usageStreams.has(ym)) { writeFileSync(`usage-${ym}.csv`, h1, {flag: 'wx'}); usageStreams.set(ym, [createWriteStream(`usage-${ym}.csv`, {flags: 'a'}), []]); } usageStreams.get(ym)[1].push(shit[i]); } usageStreams.forEach(v => { function datehour(s) { let fuck = s.split(/\s+/); return [fuck[0], Number(fuck[1].split(':')[0])]; } function dumb(temp, idx) { if(temp[1] === 12) { v[1][idx][0] = v[1][idx][0].replace('12:', '00:'); } else { console.error('what the fuck', temp, idx, v[1][idx]); } } function gay(i) { switch(drops.length) { case 0: break; case 2: let idx = drops[0] - 1; let original = datehour(v[1][idx][0]); let temp = original; dumb(temp, idx); while(--idx >= 0 && original[0] === (temp = datehour(v[1][idx][0]))[0]) { dumb(temp, idx); } case 1: for(let j = drops[drops.length - 1]; j < i; ++j) { v[1][j][0] = v[1][j][0].replace(/(\d{1,2}):/, (match, p1) => Number(p1) + 12 + ':'); } break; default: console.error('wtf', v[1][i], i, drops); } drops = []; } let dt = datehour(v[1][0][0]); let drops = []; for(let i = 1; i < v[1].length; ++i) { let dtt = datehour(v[1][i][0]); if(dtt[0] === dt[0]) {//same day if(dtt[1] < dt[1]) {//new hour is less than (for example, 12 noon -> 1pm) drops.push(i); } } else {//different day gay(i); } dt = dtt; } gay(v[1].length); for(let i = 0; i < v[1].length; ++i) { v[1][i][0] = v[1][i][0].replace(/ (\d):/, " 0$1:"); v[0].write(v[1][i].join() + '\n'); } v[0].end(); }); let months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']; const stream2 = createWriteStream("payment.csv", {flags: 'a'}); for(let i = shit2.length - 1; i >= 0; --i) { let date = shit2[i][0].split(/\s+/); date[1].replace(',', ''); for(let j = 0; j < months.length; ++j) { if(months[j].startsWith(date[0].toLowerCase())) { date[0] = (j + 1 + '').padStart(2, '0'); break; } } shit2[i][0] = `${date[2]}-${date[0]}-${date[1].padStart(2, '0')}`; stream2.write(shit2[i].join() + '\n'); } stream2.end(); writeFileSync('lastDate', endDate.toString()); } else { console.log('nothing to do'); process.exit(0); }