@accounter/modern-poalim-scraper
Version:
Modern scraper for Israeli banks (Hapoalim, Isracard, Max)
122 lines • 5.34 kB
JavaScript
import { addMonths, format, min, subYears } from 'date-fns';
import { getAccountSchema } from '../scrapers/types/discount/get-account.js';
import { zodLastTransactionsSchema } from '../scrapers/types/discount/get-last-transactions.js';
import { waitUntilElementFound } from '../utils/browser-util.js';
import { fetchGetWithinPage } from '../utils/fetch.js';
import { sleep } from '../utils/sleep.js';
const BASE_URL = 'https://start.telebank.co.il';
const LOGIN_URL = `${BASE_URL}/login/#/LOGIN_PAGE_SME`; // `/LOGIN_PAGE` instead if logging in to a personal account
export async function discount(page, credentials, options = {}) {
console.debug('Starting discount');
await login(credentials, page);
return {
getMonthTransactions: async (month) => {
console.log('getMonthTransactions');
const txData = await fetchTransactions(page, month);
console.log(`Got ${txData.transactions.length} transactions for ${format(month, 'yyyy-MM')}`);
console.log(txData);
return txData;
},
getTransactions: async () => {
const startDate = options.startDate || subYears(new Date(), 2);
const endDate = new Date();
const txs = [];
let currentDate = startDate;
while (currentDate <= endDate) {
const txData = await fetchTransactions(page, currentDate);
console.log(txData);
txs.push(txData);
currentDate = addMonths(currentDate, 1);
}
return txs;
},
};
}
async function login(credentials, page) {
console.debug('Logging in to Discount');
await page.goto(LOGIN_URL);
// Wait for and click the login button
await waitUntilElementFound(page, '#tzId', true);
console.debug('Filling in login form');
// Fill login form
await page.type('#tzId', credentials.ID);
await page.type('#tzPassword', credentials.password);
if (credentials.code) {
await page.type('#aidnum', credentials.code);
}
// TODO: see if we can remove this
await sleep(3000);
console.debug('Clicking login button');
// Wait for submit button and click
await waitUntilElementFound(page, '.sendBtn');
await page.click('.sendBtn');
// Handle post-login scenarios
try {
// Wait for either navigation or error
await Promise.race([
page.waitForNavigation({ waitUntil: 'networkidle0' }),
waitUntilElementFound(page, '#general-error', true),
]);
// Check for password change requirement
const passwordChangeForm = await page.$('form[name="changePasswordForm"]');
if (passwordChangeForm) {
throw new Error('Password change required');
}
// Wait for dashboard to load
await page.waitForFunction(() => window.location.href.includes('MY_ACCOUNT_HOMEPAGE'));
}
catch (e) {
if (e instanceof Error && e.message === 'Password change required') {
throw e;
}
// Check if login failed
const errorElement = await page.$('#general-error');
if (errorElement) {
throw new Error('Login failed', { cause: e });
}
// Check if we successfully reached the dashboard
const currentUrl = page.url();
if (!currentUrl.includes('MY_ACCOUNT_HOMEPAGE')) {
throw new Error('Login failed', { cause: e });
}
}
}
async function fetchTransactions(page, month) {
const apiSiteUrl = `${BASE_URL}/Titan/gatewayAPI`;
const accountDataUrl = `${apiSiteUrl}/userAccounts/bsUserAccountsData?FetchAccountsNickName=true&FirstTimeEntry=false`;
const accountInfo = await fetchGetWithinPage(page, accountDataUrl);
const parsedAccountInfo = getAccountSchema.safeParse(accountInfo);
if (!parsedAccountInfo.success) {
console.error('failed to parse response', parsedAccountInfo.error, accountInfo);
throw new Error('Failed to parse response', parsedAccountInfo.error);
}
const accountNumber = parsedAccountInfo.data.UserAccountsData.DefaultAccountNumber;
const startDate = month;
const startDateStr = format(startDate, 'yyyyMMdd');
const endDate = addMonths(month, 1);
const endMoment = min([endDate, new Date()]);
const endDateStr = format(endMoment, 'yyyyMMdd');
const txnsUrl = `${apiSiteUrl}/lastTransactions/${accountNumber}/Date?IsCategoryDescCode=True&IsTransactionDetails=True&IsEventNames=True&IsFutureTransactionFlag=True&FromDate=${startDateStr}&ToDate=${endDateStr}`;
const txnsResult = await fetchGetWithinPage(page, txnsUrl);
const { success, data, error } = zodLastTransactionsSchema.safeParse(txnsResult);
if (!success) {
console.error('failed to parse response', error, txnsResult);
throw new Error('Failed to parse response', error);
}
const accountData = {
success: true,
accountNumber,
balance: data.CurrentAccountLastTransactions.CurrentAccountInfo.AccountBalance,
transactions: data.CurrentAccountLastTransactions.OperationEntry,
};
return accountData;
}
export class DiscountCredentials {
ID = '';
password = '';
code;
}
export class DiscountOptions {
startDate;
}
//# sourceMappingURL=discount.js.map