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);
}