UNPKG

n26-puppeteer-scraper

Version:

n26 scraper: a tool to download account informations and transactions archive

134 lines (111 loc) 4.7 kB
const megaScraper = require('mega-scraper') const pRetry = require('p-retry') const sendTelegramMessage = require('./send-telegram-message') const debug = require('debug') const log = debug('n26:login') module.exports = n26login async function n26login (credentials = {}, { browser, page, headless = false, loadMembership = true } = {}) { if (!credentials) throw new WrongCredentials('missing credentials') if (!credentials.username) throw new WrongCredentials('missing username') if (!credentials.password) throw new WrongCredentials('missing password') browser = browser || await megaScraper.browser.createBrowser({ headless }) log('prepared browser') page = page || await browser.newPage() log('prepared page') const loggedIn = await isLoggedIn(page) log({ loggedIn }) if (loggedIn) { log('navigating to https://app.n26.com/account') await page.goto('https://app.n26.com/account') } else { await sendTelegramMessage('prepare for confirming n26 login').catch(err => false && log(err.message)) await login(page, credentials) } let membership if (loadMembership) { membership = await getMembershipInfo(page, credentials) } return { page, membership } } async function isLoggedIn (page) { log('navigating to https://app.n26.com/account') await page.goto('https://app.n26.com/account') const passwordFieldFound = await page.$('input[type=password]') return !passwordFieldFound } async function getMembershipInfo (page, credentials) { let membership log('navigating to https://app.n26.com/account') await page.goto('https://app.n26.com/account') await pRetry(async () => { await new Promise(resolve => setTimeout(resolve, 1000)) const selector = '#membership' log('looking for membership div', selector) const membershipDiv = await page.$(selector) if (membershipDiv) { const text = await page.evaluate(() => document.querySelector('#membership').innerText) || '' const lines = text.split('\n').filter(Boolean).filter(s => typeof s === 'string').map(s => s.trim()) log('found membership', lines.length) const name = lines[lines.indexOf('Membership') + 1] const iban = lines[lines.indexOf('IBAN:') + 1] const bic = lines[lines.indexOf('BIC:') + 1] const accountType = lines.find(l => /account type/gi.test(l)).replace(/account type: /gi, '').trim() const atmWithdrawalsLine = lines.find(l => /ATM withdrawals/gi.test(l)) || '' const atmWithdrawals = atmWithdrawalsLine.replace(/ATM withdrawals/gi, '').trim() const markupLine = lines.find(l => /markup for all withdrawals/gi.test(l)) || '' const markup = markupLine.replace(/markup for all withdrawals/gi, '').trim() const worldwide = !!lines.find(l => /Worldwide/gi.test(l)) || '' membership = { name, iban, bic, accountType, atmWithdrawals, markup, worldwide } } else { log('membership div not found') throw new Error('membership div not found') } }, { retries: 5 }).catch(err => { false && log(err.message) }) return membership } async function login (page, credentials) { await page.goto('https://app.n26.com/login') log('navigated to page') await page.type('input[type=email]', credentials.username) await page.type('input[type=password]', credentials.password) log('filled credentials page', credentials.username, '*'.repeat(credentials.password.length)) await page.click('button[type=submit]') log('submitted form') await pRetry(async () => { await new Promise(resolve => setTimeout(resolve, 1000)) const continueSelector = '#verify-login-dialog form button' const continueButton = await page.$(continueSelector) if (continueButton) { log('found continue button') continueButton.click && await continueButton.click() log('clicked continue button') } else { throw new Error('continue link not found') } }, { retries: 10 }) log('confirm on n26 in 30s') return new Promise((resolve, reject) => { const start = Date.now() const handle = setInterval(async () => { try { const accountsElement = await page.$('[href="/account"]') log('checking n26 login status..') if (accountsElement) { clearInterval(handle) resolve(true) } if (start < Date.now() - 1000 * 60 * 1) { clearInterval(handle) reject(new Error('timeout n26 confirmation')) } } catch (err) { // log(err.message) } }, 1000) }) } function WrongCredentials (message) { this.name = 'Wrong Credentials Error' this.message = message } WrongCredentials.prototype = Error.prototype