Skip to content

Commit e060d9e

Browse files
👩‍💻 dx(voo): Move scripts to a bun package.
1 parent 25e9a04 commit e060d9e

File tree

7 files changed

+277
-218
lines changed

7 files changed

+277
-218
lines changed

.bin/bun/bun.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.bin/bun/packages/voo/bin/fetch.ts

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
#!/usr/bin/env -S bun run
2+
3+
const puppeteer = require('puppeteer');
4+
const fs = require('fs');
5+
6+
const HOME = process.env['HOME'] ;
7+
const CACHE = HOME + '/.cache/voo' ;
8+
const CACHE_INFO = CACHE + '/info.json' ;
9+
10+
console.log('HOME', HOME);
11+
console.log('CACHE', CACHE);
12+
console.log('CACHE_INFO', CACHE_INFO);
13+
14+
const URL_ROOT = 'https://newmy.voo.be' ;
15+
const URL_CONSUMPTION = `${URL_ROOT}/internet/consumption` ;
16+
17+
const TYPE_UNKNOWN = 'unknown page' ;
18+
const TYPE_LOGIN = 'login page' ;
19+
const TYPE_LOGIN_ERROR = 'login error' ;
20+
const TYPE_CONSUMPTION = 'consumption page' ;
21+
const TYPE_CONSUMPTION_UNLIMITED = 'unlimited consumption page' ;
22+
const TYPE_TIMEOUT = 'timeout error' ;
23+
24+
const TIME_SECOND = 1000 ;
25+
const TIME_MINUTE = TIME_SECOND * 60 ;
26+
const TIME_HOUR = TIME_MINUTE * 60 ;
27+
const TIME_HALF_HOUR = TIME_HOUR / 2 | 0 ;
28+
29+
//currently non polling, single fetch is done, polling is handled by shell script
30+
//const POLL_RATE = TIME_HALF_HOUR ;
31+
//const POLL_RATE = TIME_MINUTE ;
32+
const READ_RATE = TIME_SECOND * 3 ;
33+
const LOGIN_WAIT = TIME_MINUTE ;
34+
const TIMEOUT = TIME_MINUTE ;
35+
36+
let lastlogin = 0 ;
37+
let lastevent = 0 ;
38+
39+
const sleep = ms => {
40+
console.log(`sleeping for ${ms} ms`)
41+
return new Promise(resolve => setTimeout(resolve, ms));
42+
} ;
43+
44+
function main ( browser ) {
45+
46+
try {
47+
fs.mkdirSync(CACHE);
48+
}
49+
catch ( e ) {
50+
if (e && e.code !== 'EEXIST') {
51+
console.error(e);
52+
process.exit(67);
53+
}
54+
}
55+
56+
// parse username and password
57+
const input = fs.readFileSync('/dev/stdin').toString().split('\n');
58+
59+
switch (input.length) {
60+
case 0:
61+
console.log('Could not find username or password');
62+
process.exit(1);
63+
break;
64+
case 1:
65+
console.log('Could not find password');
66+
process.exit(2);
67+
break;
68+
}
69+
70+
const username = input[0];
71+
const password = input[1];
72+
73+
const options = {
74+
executablePath: '/usr/bin/chromium' ,
75+
//headless: false, // debug
76+
//slowMo: 250, // debug
77+
} ;
78+
79+
puppeteer
80+
.launch(options)
81+
.then(async browser => {
82+
await poll(browser, username, password);
83+
await browser.close();
84+
})
85+
.catch( err => {
86+
console.log(err) ;
87+
process.exit(100) ;
88+
}) ;
89+
90+
}
91+
92+
async function poll ( browser , username , password ) {
93+
94+
console.log( 'credentials' , username , password.replace(/./g, '*'));
95+
96+
const page = await browser.newPage();
97+
98+
//page.onError = function (msg, trace) {
99+
//console.log(msg);
100+
//trace.forEach(function(item) {
101+
//console.log(' ', item.file, ':', item.line);
102+
//});
103+
//};
104+
105+
//while ( true ) { // NOT POLLING ATM
106+
//await load(page , username, password);
107+
//await sleep(POLL_RATE);
108+
//}
109+
return await load( page , username , password );
110+
111+
}
112+
113+
async function load ( page , username , password ) {
114+
115+
await page.goto(URL_CONSUMPTION);
116+
117+
while ( true ) {
118+
119+
const _type = await type(page);
120+
console.log(_type);
121+
122+
if (_type === TYPE_TIMEOUT || _type === TYPE_LOGIN_ERROR) return 32;
123+
124+
else if (_type === TYPE_UNKNOWN) await sleep(READ_RATE);
125+
126+
else if (_type === TYPE_LOGIN) {
127+
await login(page, username, password);
128+
await sleep(READ_RATE);
129+
}
130+
131+
else {
132+
const data = await getinfo(_type, page);
133+
write(data);
134+
return 0;
135+
}
136+
137+
}
138+
139+
}
140+
141+
async function type ( page ) {
142+
143+
const now = Date.now();
144+
if (lastevent > 0 && now - lastevent >= TIMEOUT) return TYPE_TIMEOUT;
145+
146+
const value = await page.evaluate((lastlogin, LOGIN_WAIT, TYPE_LOGIN, TYPE_LOGIN_ERROR, TYPE_CONSUMPTION, TYPE_CONSUMPTION_UNLIMITED , TYPE_UNKNOWN) => {
147+
const now = Date.now();
148+
if ( now - lastlogin >= LOGIN_WAIT && document.getElementById('email') !== null ) return TYPE_LOGIN ;
149+
else if ( document.querySelector('.validation-summary-errors') !== null ) return TYPE_LOGIN_ERROR ;
150+
else if ( document.querySelector('.month-consumption-value') !== null ) return TYPE_CONSUMPTION_UNLIMITED ;
151+
else return TYPE_UNKNOWN ;
152+
}, lastlogin, LOGIN_WAIT, TYPE_LOGIN, TYPE_LOGIN_ERROR, TYPE_CONSUMPTION, TYPE_CONSUMPTION_UNLIMITED , TYPE_UNKNOWN);
153+
154+
if ( value !== TYPE_UNKNOWN ) lastevent = Date.now() ;
155+
156+
return value ;
157+
158+
}
159+
160+
async function login (page, username, password) {
161+
162+
lastlogin = Date.now();
163+
164+
console.log( 'login' , username , password.replace(/./g, '*'));
165+
166+
return await page.evaluate( (username, password) => {
167+
document.getElementById('email').value = username;
168+
document.getElementById('incognito').value = password;
169+
document.getElementsByTagName('form')[0].submit();
170+
}, username, password);
171+
172+
}
173+
174+
async function getinfo ( _type , page ) {
175+
176+
const current = await page.evaluate(
177+
() => document.querySelector('.month-consumption-value').innerText
178+
);
179+
180+
const current_float = parseFloat(current.match(/^[0-9]*(,[0-9]*)?/)[0].replace(',', '.')) ;
181+
const current_unit = current.match(/[a-zA-Z]*$/)[0];
182+
183+
const max = 'unlimited' ;
184+
const max_float = null ;
185+
const max_unit = null ;
186+
187+
const data = {
188+
'consumption' : {
189+
'current' : {
190+
'raw' : current ,
191+
'amount' : current_float ,
192+
'unit' : current_unit
193+
} ,
194+
'max' : {
195+
'raw' : max ,
196+
'amount' : max_float ,
197+
'unit' : max_unit
198+
}
199+
}
200+
} ;
201+
202+
return data ;
203+
204+
}
205+
206+
function write ( data ) {
207+
208+
const cache = JSON.stringify(data);
209+
210+
console.log(cache) ;
211+
212+
fs.writeFileSync(CACHE_INFO, cache);
213+
214+
}
215+
216+
main();

.bin/bun/packages/voo/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"private": true,
3+
"name": "__voo",
4+
"version": "0.0.1",
5+
"type": "module",
6+
"dependencies": {
7+
"puppeteer": "^24.16.0"
8+
},
9+
"devDependencies": {
10+
"@types/node": "^24.2.0",
11+
"typescript": "^5.9.2"
12+
}
13+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"$schema": "https://json.schemastore.org/tsconfig",
3+
"compilerOptions": {
4+
"noEmit": true,
5+
"target": "esnext",
6+
"module": "esnext",
7+
"lib": ["esnext"],
8+
"allowJs": true,
9+
"checkJs": true,
10+
"incremental": true,
11+
12+
"strict": true,
13+
"noImplicitAny": false,
14+
"noImplicitThis": false,
15+
"strictNullChecks": true,
16+
17+
"noImplicitReturns": true,
18+
"noFallthroughCasesInSwitch": true,
19+
"noImplicitOverride": false,
20+
"noPropertyAccessFromIndexSignature": false,
21+
"noUncheckedIndexedAccess": true,
22+
"noUnusedLocals": true,
23+
"noUnusedParameters": true,
24+
25+
"allowUnreachableCode": false,
26+
27+
"moduleResolution": "node",
28+
"resolveJsonModule": true,
29+
"esModuleInterop": true,
30+
"preserveSymlinks": true,
31+
"allowSyntheticDefaultImports": true
32+
}
33+
}

0 commit comments

Comments
 (0)