def _parse_account(self, tr): account = Account() account.id = tr.xpath('.//td[@class="libelleCompte"]/input')[0].attrib['id'][len('libelleCompte'):] if len(str(account.id)) == 23: account.id = str(account.id)[5:21] a = tr.xpath('.//td[@class="libelleCompte"]/a')[0] m = re.match(r'javascript:goToStatements\(\'(\d+)\'', a.get('onclick', '')) if m: account._link_id = m.group(1) else: # Can't get history for this account. account._link_id = None # To prevent multiple-IDs for CIF (for example), add an arbitrary char in ID. account.id += 'C' account.label = u''+a.text.strip() tds = tr.findall('td') account.balance = self._parse_amount(tds[3].find('a')) if tds[4].find('a') is not None: account.coming = self._parse_amount(tds[4].find('a')) else: account.coming = NotAvailable return account
def iter_accounts(self): if not self.islogged: self.login() data = self.browser.open( "https://www.cmb.fr/domiweb/prive/particulier/releve/0-releve.act" ).content parser = etree.HTMLParser() tree = etree.parse(StringIO(data), parser) table = tree.xpath('/html/body/table') if len(table) == 0: title = tree.xpath('/html/head/title')[0].text if title == u"Utilisateur non identifié": self.login() data = self.browser.open( "https://www.cmb.fr/domiweb/prive/particulier/releve/0-releve.act" ).content parser = etree.HTMLParser() tree = etree.parse(StringIO(data), parser) table = tree.xpath('/html/body/table') if len(table) == 0: raise ParseError() else: raise ParseError() for tr in tree.xpath('/html/body//table[contains(@class, "Tb")]/tr'): if tr.get('class', None) not in ('LnTit', 'LnTot', 'LnMnTiers', None): account = Account() td = tr.xpath('td') a = td[1].xpath('a') account.label = unicode(a[0].text).strip() href = a[0].get('href') m = match(r"javascript:releve\((.*),'(.*)','(.*)'\)", href) if not m: continue account.id = unicode(m.group(1) + m.group(2) + m.group(3)) account._cmbvaleur = m.group(1) account._cmbvaleur2 = m.group(2) account._cmbtype = m.group(3) balance = u''.join([txt.strip() for txt in td[2].itertext()]) balance = balance.replace(',', '.').replace(u"\xa0", '') account.balance = Decimal(balance) span = td[4].xpath('a/span') if len(span): coming = span[0].text.replace(' ', '').replace(',', '.') coming = coming.replace(u"\xa0", '') account.coming = Decimal(coming) else: account.coming = NotAvailable yield account
def get_list(self): a = Account() a.id = '0' a.label = u'Compte miams' a.balance = Decimal(self.parser.tocleanstring(self.document.xpath('//div[@class="compteur"]//strong')[0])) a.currency = u'MIAM' try: a.coming = Decimal(Transaction.clean_amount(self.document.xpath('//table[@id="solde_acquisition_lignes"]//th[@class="col_montant"]')[0].text)) except BrokenPageError: a.coming = Decimal('0') yield a
def iter_accounts(self): if not self.islogged: self.login() data = self.browser.open("https://www.cmb.fr/domiweb/prive/particulier/releve/0-releve.act").content parser = etree.HTMLParser() tree = etree.parse(StringIO(data), parser) table = tree.xpath('/html/body/table') if len(table) == 0: title = tree.xpath('/html/head/title')[0].text if title == u"Utilisateur non identifié": self.login() data = self.browser.open("https://www.cmb.fr/domiweb/prive/particulier/releve/0-releve.act").content parser = etree.HTMLParser() tree = etree.parse(StringIO(data), parser) table = tree.xpath('/html/body/table') if len(table) == 0: raise ParseError() else: raise ParseError() for tr in tree.xpath('/html/body//table[contains(@class, "Tb")]/tr'): if tr.get('class', None) not in ('LnTit', 'LnTot', 'LnMnTiers', None): account = Account() td = tr.xpath('td') a = td[1].xpath('a') account.label = unicode(a[0].text).strip() href = a[0].get('href') m = match(r"javascript:releve\((.*),'(.*)','(.*)'\)", href) if not m: continue account.id = unicode(m.group(1) + m.group(2) + m.group(3)) account._cmbvaleur = m.group(1) account._cmbvaleur2 = m.group(2) account._cmbtype = m.group(3) balance = u''.join([txt.strip() for txt in td[2].itertext()]) balance = balance.replace(',', '.').replace(u"\xa0", '') account.balance = Decimal(balance) span = td[4].xpath('a/span') if len(span): coming = span[0].text.replace(' ', '').replace(',', '.') coming = coming.replace(u"\xa0", '') account.coming = Decimal(coming) else: account.coming = NotAvailable yield account
def get_list(self): for box in self.document.getroot().cssselect('div.roundedBox div.contentBox'): a = Account() a.id = self.parser.tocleanstring(box.xpath('.//tr[@id="summaryImageHeaderRow"]//div[@class="summaryTitles"]')[0]) a.label = self.parser.tocleanstring(box.xpath('.//span[@class="cardTitle"]')[0]) a.balance = Decimal('0.0') coming = self.parser.tocleanstring(self.parser.select(box, 'td#colOSBalance div.summaryValues', 1)) if coming in (u'Indisponible', ''): a.coming = NotAvailable else: a.coming = - abs(Decimal(Transaction.clean_amount(coming))) a.currency = a.get_currency(coming) a._link = self.parser.select(box, 'div.summaryTitles a.summaryLink', 1).attrib['href'] yield a
def get_list(self, pro=True): accounts = [] for tr in self.document.xpath('//tr[@class="comptes"]'): cols = tr.findall('td') if len(cols) < 5: continue account = Account() account.id = self.parser.tocleanstring(cols[self.COL_ID]).replace( " ", "") account.label = self.parser.tocleanstring(cols[self.COL_LABEL]) account.balance = Decimal( self.parser.tocleanstring(cols[self.COL_BALANCE])) try: account.coming = Decimal( self.parser.tocleanstring(cols[self.COL_COMING])) except InvalidOperation: if self.parser.tocleanstring(cols[self.COL_COMING]) != '-': self.logger.warning('Unable to parse coming value', exc_info=True) account.coming = NotAvailable account._link_id = None account._stp = None a = cols[self.COL_LABEL].find('a') if a is not None: url = urlparse(a.attrib['href']) p = dict(parse_qsl(url.query)) account._link_id = p.get('ch4', None) account._stp = p.get('stp', None) accounts.append(account) # If there are also personnal accounts linked, display the page and iter on them. if pro and len( self.document.xpath( '//div[@class="onglets"]//a[contains(@href, "afficherComptesPrives")]' )) > 0: self.browser.select_form(name='myForm') self.browser.set_all_readonly(False) self.browser['udcAction'] = '/afficherComptesPrives' self.browser.submit() for a in self.browser.page.get_list(False): accounts.append(a) return accounts
def get_list(self): div = self.document.xpath('//div[@id="descriptifdroite"]')[0] account = Account() account.id = re.search( u'(\d+)', div.xpath('.//div[@class="credithauttexte"]')[0].text).group(1) account.label = u'Carte PASS' account.balance = Decimal('0') for tr in div.xpath('.//table/tr'): tds = tr.findall('td') if len(tds) < 3: continue label = u''.join([txt.strip() for txt in tds[1].itertext()]) value = u''.join([txt.strip() for txt in tds[2].itertext()]) if 'encours depuis le dernier' in label.lower(): coming = u'-' + value account.coming = Decimal( FrenchTransaction.clean_amount(coming)) account.currency = account.get_currency(coming) elif u'arrêté de compte' in label.lower(): m = re.search(u'(\d+)/(\d+)/(\d+)', label) if m: account._outstanding_date = datetime.date( *reversed(map(int, m.groups()))) break yield account
def get_list(self): accounts = [] previous_account = None noaccounts = self.get_from_js('_js_noMvts =', ';') if noaccounts is not None: assert 'avez aucun compte' in noaccounts return [] txt = self.get_from_js('_data = new Array(', ');', is_list=True) if txt is None: raise BrowserUnavailable('Unable to find accounts list in scripts') data = json.loads('[%s]' % txt.replace("'", '"')) for line in data: a = Account() a.id = line[self.COL_ID].replace(' ', '') if re.match(r'Classement=(.*?):::Banque=(.*?):::Agence=(.*?):::SScompte=(.*?):::Serie=(.*)', a.id): a.id = str(CleanDecimal().filter(a.id)) a._acc_nb = a.id.split('_')[0] if len(a.id.split('_')) > 1 else None a.label = MyStrip(line[self.COL_LABEL], xpath='.//div[@class="libelleCompteTDB"]') # This account can be multiple life insurance accounts if a.label == 'ASSURANCE VIE-BON CAPI-SCPI-DIVERS *': continue a.balance = Decimal(FrenchTransaction.clean_amount(line[self.COL_BALANCE])) a.currency = a.get_currency(line[self.COL_BALANCE]) a.type = self.get_account_type(a.label) # The parent account must be created right before if a.type == Account.TYPE_CARD: # duplicate if find_object(accounts, id=a.id): self.logger.warning('Ignoring duplicate card %r', a.id) continue a.parent = previous_account if line[self.COL_HISTORY] == 'true': a._inv = False a._link = self.get_history_link() a._args = self.make__args_dict(line) else: a._inv = True a._args = {'_ipc_eventValue': line[self.COL_ID], '_ipc_fireEvent': line[self.COL_FIRE_EVENT], } a._link = self.doc.xpath('//form[@name="changePageForm"]')[0].attrib['action'] if a.type is Account.TYPE_CARD: a.coming = a.balance a.balance = Decimal('0.0') accounts.append(a) previous_account = a return accounts
def get_list(self, accounts_ids): l = [] # Read the json data json_data = self.browser.readurl('/banque/PA_Autonomy-war/ProxyIAService?cleOutil=IA_SMC_UDC&service=getlstcpt&dashboard=true&refreshSession=true&cre=udc&poka=true') json_infos = json.loads(json_data) for famille in json_infos['smc']['data']['familleCompte']: id_famille = famille['idFamilleCompte'] for compte in famille['compte']: account = Account() account.label = u''+compte['libellePersoProduit'] account.currency = account.get_currency(compte['devise']) account.balance = Decimal(compte['soldeDispo']) account.coming = Decimal(compte['soldeAVenir']) account.type = self.ACCOUNT_TYPES.get(id_famille, Account.TYPE_UNKNOWN) account.id = 0 account._link_id = 'KEY'+compte['key'] # IBAN aren't in JSON # Fast method, get it from transfer page. for i,a in accounts_ids.items(): if a.label == account.label: account.id = i # But it's doesn't work with LOAN and MARKET, so use slow method : Get it from transaction page. if account.id == 0: account.id = self.browser.get_IBAN_from_account(account) l.append(account) if len(l) == 0: print 'no accounts' # oops, no accounts? check if we have not exhausted the allowed use # of this password for img in self.document.getroot().cssselect('img[align="middle"]'): if img.attrib.get('alt', '') == 'Changez votre code secret': raise BrowserPasswordExpired('Your password has expired') return l
def get_list(self): # TODO: no idea abount how proxy account are displayed for a in self.document.xpath('//a[@class="mainclic"]'): account = Account() account.currency = Currency.CUR_EUR account.id = unicode(a.find('span[@class="account-number"]').text) account._id = account.id account.label = unicode(a.find('span[@class="title"]').text) balance = a.find('span[@class="solde"]/label').text account.balance = Decimal(FrenchTransaction.clean_amount(balance)) account.coming = NotAvailable if "Courant" in account.label: account.id = "CC-" + account.id account.type = Account.TYPE_CHECKING elif "Livret A" in account.label: account.id = "LA-" + account.id account.type = Account.TYPE_SAVINGS elif "Orange" in account.label: account.id = "LEO-" + account.id account.type = Account.TYPE_SAVINGS elif "Durable" in account.label: account.id = "LDD-" + account.id account.type = Account.TYPE_SAVINGS elif "Titres" in account.label: account.id = "TITRE-" + account.id account.type = Account.TYPE_MARKET elif "PEA" in account.label: account.id = "PEA-" + account.id account.type = Account.TYPE_MARKET jid = self.document.find('//input[@name="javax.faces.ViewState"]') account._jid = jid.attrib['value'] yield account
def get_list(self): for tr in self.document.xpath('//table[@class="datas"]//tr'): if tr.attrib.get('class', '') == 'entete': continue cols = tr.findall('td') a = Account() a.label = unicode(cols[self.COL_ID].xpath('.//span[@class="left-underline"]')[0].text.strip()) a.type = self.get_account_type(a.label) balance = self.parser.tocleanstring(cols[self.COL_BALANCE]) a.balance = Decimal(FrenchTransaction.clean_amount(balance)) a.currency = a.get_currency(balance) a._link, a._args = self.params_from_js(cols[self.COL_ID].find('a').attrib['href']) a._acc_nb = cols[self.COL_ID].xpath('.//span[@class="right-underline"]')[0].text.replace(' ', '').strip() if a._args: a.id = '%s%s%s' % (a._acc_nb, a._args['IndiceCompte'], a._args['Indiceclassement']) else: a.id = a._acc_nb # This account can be multiple life insurance accounts if any(a.label.startswith(lab) for lab in ['ASS.VIE-BONS CAPI-SCPI-DIVERS', 'BONS CAPI-SCPI-DIVERS']) or \ u'Aucun d\\351tail correspondant pour ce compte' in tr.xpath('.//a/@href')[0]: continue if a.type is Account.TYPE_CARD: a.coming = a.balance a.balance = Decimal('0.0') a._inv = False yield a
def get_list(self, accounts_ids): l = [] # Read the json data json_data = self.browser.readurl('/banque/PA_Autonomy-war/ProxyIAService?cleOutil=IA_SMC_UDC&service=getlstcpt&dashboard=true&refreshSession=true&cre=udc&poka=true') json_infos = json.loads(json_data) for famille in json_infos['smc']['data']['familleCompte']: id_famille = famille['idFamilleCompte'] for compte in famille['compte']: account = Account() account.label = u''+compte['libellePersoProduit'] account.currency = account.get_currency(compte['devise']) account.balance = Decimal(str(compte['soldeDispo'])) account.coming = Decimal(str(compte['soldeAVenir'])) account.type = self.ACCOUNT_TYPES.get(id_famille, Account.TYPE_UNKNOWN) account.id = 0 account._link_id = 'KEY'+compte['key'] # IBAN aren't in JSON # Fast method, get it from transfer page. for i,a in accounts_ids.items(): if a.label == account.label: account.id = i # But it's doesn't work with LOAN and MARKET, so use slow method : Get it from transaction page. if account.id == 0: account.id = self.browser.get_IBAN_from_account(account) l.append(account) if len(l) == 0: print 'no accounts' # oops, no accounts? check if we have not exhausted the allowed use # of this password for img in self.document.getroot().cssselect('img[align="middle"]'): if img.attrib.get('alt', '') == 'Changez votre code secret': raise BrowserPasswordExpired('Your password has expired') return l
def get_list(self): for tr in self.document.getiterator('tr'): tds = tr.findall('td') if len(tds) != 3 or tds[0].find('a') is None or tds[0].find('a').attrib.get('class', '') != 'flecheM': continue account = Account() account.id = tds[1].text.strip() a = tds[0].findall('a')[-1] account.label = a.text.strip() account._link_id = a.attrib['href'] m = re.search('(\w+)_IdPrestation', account._link_id) if not m or m.group(1) != 'CPT': account._link_id = None if m: account.id += '.%s' % m.group(1) tag = tds[2].find('font') if tag is None: tag = tds[2] account.balance = Decimal(tag.text.replace('.','').replace(',','.').replace(' ', '').strip(u' \t\u20ac\xa0€\n\r')) account.coming = NotAvailable yield account
def get_list(self): for trCompte in self.document.xpath('//table[@id="compte"]/tbody/tr'): tds = trCompte.findall('td') account = Account() account.id = tds[self.CPT_ROW_ID].text.strip() account.label = unicode(tds[self.CPT_ROW_NAME].text.strip()) account_type_str = "".join([ td.text for td in tds[self.CPT_ROW_NATURE].xpath('.//td[@class="txt"]') ]).strip() account.type = self.ACCOUNT_TYPES.get(account_type_str, Account.TYPE_UNKNOWN) account.balance = Decimal( FrenchTransaction.clean_amount( self.parser.tocleanstring(tds[self.CPT_ROW_BALANCE]))) account.coming = Decimal( FrenchTransaction.clean_amount( self.parser.tocleanstring(tds[self.CPT_ROW_ENCOURS]))) account.currency = account.get_currency( tds[self.CPT_ROW_BALANCE].find("a").text) yield account return
def get_list(self): div = self.document.xpath('//div[@id="descriptifdroite"]')[0] account = Account() account.id = re.search(u'(\d+)', div.xpath('.//div[@class="credithauttexte"]')[0].text).group(1) account.label = u'Carte PASS' account.balance = Decimal('0') for tr in div.xpath('.//table/tr'): tds = tr.findall('td') if len(tds) < 3: continue label = u''.join([txt.strip() for txt in tds[1].itertext()]) value = u''.join([txt.strip() for txt in tds[2].itertext()]) if 'encours depuis le dernier' in label.lower(): coming = u'-' + value account.coming = Decimal(FrenchTransaction.clean_amount(coming)) account.currency = account.get_currency(coming) elif u'arrêté de compte' in label.lower(): m = re.search(u'(\d+)/(\d+)/(\d+)', label) if m: account._outstanding_date = datetime.date(*reversed(map(int, m.groups()))) break yield account
def get_list(self): accounts = OrderedDict() for tr in self.document.getiterator('tr'): first_td = tr.getchildren()[0] if (first_td.attrib.get('class', '') == 'i g' or first_td.attrib.get('class', '') == 'p g') \ and first_td.find('a') is not None: a = first_td.find('a') link = a.get('href', '') if link.startswith('POR_SyntheseLst'): continue url = urlparse(link) p = parse_qs(url.query) if not 'rib' in p: continue for i in (2,1): balance = FrenchTransaction.clean_amount(tr.getchildren()[i].text) currency = Account.get_currency(tr.getchildren()[i].text) if len(balance) > 0: break balance = Decimal(balance) id = p['rib'][0] if id in accounts: account = accounts[id] if not account.coming: account.coming = Decimal('0.0') account.coming += balance account._card_links.append(link) continue account = Account() account.id = id account.label = unicode(a.text).strip().lstrip(' 0123456789').title() account._link_id = link account._card_links = [] # Find accounting amount page = self.browser.get_document(self.browser.openurl(link)) coming = self.find_amount(page, u"Opérations à venir") accounting = self.find_amount(page, u"Solde comptable") if accounting is not None and accounting + (coming or Decimal('0')) != balance: self.logger.warning('%s + %s != %s' % (accounting, coming, balance)) if accounting is not None: balance = accounting if coming is not None: account.coming = coming account.balance = balance account.currency = currency accounts[account.id] = account return accounts.itervalues()
def get_list(self, pro=True): accounts = [] for tr in self.document.xpath('//tr[@class="comptes"]'): cols = tr.findall('td') if len(cols) < 5: continue account = Account() account.id = self.parser.tocleanstring(cols[self.COL_ID]).replace(" ", "") account.label = self.parser.tocleanstring(cols[self.COL_LABEL]) account.balance = Decimal(self.parser.tocleanstring(cols[self.COL_BALANCE])) try: account.coming = Decimal(self.parser.tocleanstring(cols[self.COL_COMING])) except InvalidOperation: if self.parser.tocleanstring(cols[self.COL_COMING]) != '-': self.logger.warning('Unable to parse coming value', exc_info=True) account.coming = NotAvailable account._link_id = None account._stp = None a = cols[self.COL_LABEL].find('a') if a is not None: url = urlparse(a.attrib['href']) p = dict(parse_qsl(url.query)) account._link_id = p.get('ch4', None) account._stp = p.get('stp', None) for input_tag in tr.xpath('.//input[starts-with(@id, "urlRib")]'): m = re.search('ch4=(\w+)', input_tag.get('value', '')) if m: account.iban = unicode(m.group(1)) accounts.append(account) # If there are also personnal accounts linked, display the page and iter on them. if pro and len(self.document.xpath('//div[@class="onglets"]//a[contains(@href, "afficherComptesPrives")]')) > 0: self.browser.select_form(name='myForm') self.browser.set_all_readonly(False) self.browser['udcAction'] = '/afficherComptesPrives' self.browser.submit() for a in self.browser.page.get_list(False): accounts.append(a) return accounts
def get_list(self): l = [] ids = set() for a in self.document.getiterator('a'): link = a.attrib.get('href') if link is None: continue if link.startswith("/outil/UWLM/ListeMouvements"): account = Account() account._link_id = link + "&mode=45" account._coming_links = [] parameters = link.split("?").pop().split("&") for parameter in parameters: list = parameter.split("=") value = list.pop() name = list.pop() if name == "agence": account.id = value elif name == "compte": account.id += value elif name == "nature": # TODO parse this string to get the right Account.TYPE_* to # store in account.type. account._type = value if account.id in ids: continue ids.add(account.id) div = a.getparent().getprevious() if not div.text.strip(): div = div.find('div') account.label = u'' + div.text.strip() balance = FrenchTransaction.clean_amount(a.text) if '-' in balance: balance = '-' + balance.replace('-', '') account.balance = Decimal(balance) account.currency = account.get_currency(a.text) self.logger.debug('%s Type: %s' % (account.label, account._type)) l.append(account) if link.startswith('/outil/UWCB/UWCBEncours'): if len(l) == 0: self.logger.warning( 'There is a card account but not any check account') continue account = l[-1] coming = FrenchTransaction.clean_amount(a.text) if '-' in coming: coming = '-' + coming.replace('-', '') if not account.coming: account.coming = Decimal('0') account.coming += Decimal(coming) account._coming_links.append(link) return l
def get_list(self): l = [] ids = set() for a in self.document.getiterator('a'): link=a.attrib.get('href') if link is None: continue if link.startswith("/outil/UWLM/ListeMouvements"): account = Account() #by default the website propose the last 7 days or last 45 days but we can force to have the last 55days account._link_id=link+"&mode=55" account._coming_links = [] parameters=link.split("?").pop().split("&") for parameter in parameters: list=parameter.split("=") value=list.pop() name=list.pop() if name=="agence": account.id=value elif name=="compte": account.id+=value elif name=="nature": # TODO parse this string to get the right Account.TYPE_* to # store in account.type. account._type=value if account.id in ids: continue ids.add(account.id) div = a.getparent().getprevious() if not div.text.strip(): div = div.find('div') account.label=u''+div.text.strip() balance = FrenchTransaction.clean_amount(a.text) if '-' in balance: balance='-'+balance.replace('-', '') account.balance=Decimal(balance) account.currency = account.get_currency(a.text) self.logger.debug('%s Type: %s' % (account.label, account._type)) l.append(account) if link.startswith('/outil/UWCB/UWCBEncours'): if len(l) == 0: self.logger.warning('There is a card account but not any check account') continue account = l[-1] coming = FrenchTransaction.clean_amount(a.text) if '-' in coming: coming = '-'+coming.replace('-', '') if not account.coming: account.coming = Decimal('0') account.coming += Decimal(coming) account._coming_links.append(link) return l
def get_single_card(self, parent_id): div, = self.doc.xpath('//div[@class="infosynthese"]') ret = Account() ret.type = Account.TYPE_CARD ret.coming = CleanDecimal(Regexp(CleanText('.'), r'En cours prélevé au \d+/\d+/\d+ : ([\d\s,-]+) euros'), replace_dots=True)(div) ret.number = Regexp(CleanText('.'), 'sur votre carte n°([\d*]+)')(div) ret.id = '%s.%s' % (parent_id, ret.number) ret.currency = 'EUR' ret.label = 'CARTE %s' % ret.number ret.url = self.url return ret
def get_list(self): l = [] for tr in self.document.getiterator('tr'): if tr.attrib.get('class', '') == 'comptes': account = Account() for td in tr.getiterator('td'): if td.attrib.get('headers', '').startswith('Numero_'): id = td.text account.id = ''.join(id.split(' ')).strip() elif td.attrib.get('headers', '').startswith('Libelle_'): a = td.findall('a') label = unicode(a[0].text) account.label = label.strip() m = self.LINKID_REGEXP.match(a[0].attrib.get('href', '')) if m: account.link_id = m.group(1) elif td.attrib.get('headers', '').startswith('Solde'): a = td.findall('a') balance = a[0].text balance = balance.replace('.','').replace(',','.') account.balance = float(balance) elif td.attrib.get('headers', '').startswith('Avenir'): a = td.findall('a') # Some accounts don't have a "coming" if len(a): coming = a[0].text coming = coming.replace('.','').replace(',','.') account.coming = float(coming) else: account.coming = NotAvailable l.append(account) if len(l) == 0: # oops, no accounts? check if we have not exhausted the allowed use # of this password for div in self.document.getroot().cssselect('div.Style_texte_gras'): if div.text.strip() == 'Vous avez atteint la date de fin de vie de votre code secret.': raise PasswordExpired(div.text.strip()) return l
def iter_cards(self, account): for el in account._cards: if el['carteDebitDiffere']: card = Account() card.id = card.number = el['numeroCompteFormate'].replace(' ', '') card.label = el['labelToDisplay'] card.balance = Decimal('0') card.coming = Decimal(str(el['montantProchaineEcheance'])) card.type = Account.TYPE_CARD card.currency = account.currency card._internal_id = el['idTechnique'] card._prestation_id = el['id'] yield card
def get_list(self): for tr in self.document.getiterator('tr'): if 'LGNTableRow' not in tr.attrib.get('class', '').split(): continue account = Account() for td in tr.getiterator('td'): if td.attrib.get('headers', '') == 'TypeCompte': a = td.find('a') if a is None: break account.label = self.parser.tocleanstring(a) for pattern, actype in self.TYPES.iteritems(): if account.label.startswith(pattern): account.type = actype account._link_id = a.get('href', '') elif td.attrib.get('headers', '') == 'NumeroCompte': account.id = self.parser.tocleanstring(td).replace( u'\xa0', '') elif td.attrib.get('headers', '') == 'Libelle': text = self.parser.tocleanstring(td) if text != '': account.label = text elif td.attrib.get('headers', '') == 'Solde': div = td.xpath('./div[@class="Solde"]') if len(div) > 0: balance = self.parser.tocleanstring(div[0]) if len(balance) > 0 and balance not in ('ANNULEE', 'OPPOSITION'): try: account.balance = Decimal( FrenchTransaction.clean_amount(balance)) except InvalidOperation: raise BrokenPageError( 'Unable to parse balance %r' % balance) account.currency = account.get_currency(balance) else: account.balance = NotAvailable if not account.label or empty(account.balance): continue if 'CARTE_' in account._link_id: account.type = account.TYPE_CARD account.coming = account.balance account.balance = Decimal('0') yield account
def get_list(self): l = [] for a in self.document.getiterator('a'): link=a.attrib.get('href') if link is None: continue if link.startswith("/outil/UWLM/ListeMouvements"): account = Account() account._link_id=link+"&mode=45" account._coming_links = [] parameters=link.split("?").pop().split("&") for parameter in parameters: list=parameter.split("=") value=list.pop() name=list.pop() if name=="agence": account.id=value elif name=="compte": account.id+=value elif name=="nature": # TODO parse this string to get the right Account.TYPE_* to # store in account.type. account._type=value div = a.getparent().getprevious() if not div.text.strip(): div = div.find('div') account.label=u''+div.text.strip() balance=a.text.replace(u"\u00A0",'').replace(' ','').replace('.','').replace('+','').replace(',','.').strip() if '-' in balance: balance='-'+balance.replace('-', '') account.balance=Decimal(balance) self.logger.debug('%s Type: %s' % (account.label, account._type)) l.append(account) if link.startswith('/outil/UWCB/UWCBEncours'): if len(l) == 0: self.logger.warning('There is a card account but not any check account') continue account = l[-1] coming = a.text.replace(u"\u00A0",'').replace(' ','').replace('.','').replace('+','').replace(',','.').strip() if '-' in coming: coming = '-'+coming.replace('-', '') if not account.coming: account.coming = Decimal('0') account.coming += Decimal(coming) account._coming_links.append(link) return l
def get_list(self): for box in self.document.getroot().cssselect( 'div.roundedBox div.contentBox'): a = Account() a.id = self.parser.tocleanstring( box.xpath( './/tr[@id="summaryImageHeaderRow"]//div[@class="summaryTitles"]' )[0]) a.label = self.parser.tocleanstring( box.xpath('.//span[@class="cardTitle"]')[0]) a.balance = Decimal('0.0') coming = self.parser.tocleanstring( self.parser.select(box, 'td#colOSBalance div.summaryValues', 1)) if coming in (u'Indisponible', ''): a.coming = NotAvailable else: a.coming = -abs(Decimal(Transaction.clean_amount(coming))) a.currency = a.get_currency(coming) a._link = self.parser.select(box, 'div.summaryTitles a.summaryLink', 1).attrib['href'] yield a
def iter_cards(self, account): for el in account._cards: if el['carteDebitDiffere']: card = Account() card.id = card.number = el['numeroCompteFormate'].replace( ' ', '') card.label = el['labelToDisplay'] card.balance = Decimal('0') card.coming = Decimal(str(el['montantProchaineEcheance'])) card.type = Account.TYPE_CARD card.currency = account.currency card._internal_id = el['idTechnique'] card._prestation_id = el['id'] card.owner_type = AccountOwnerType.PRIVATE yield card
def get_single_card(self, parent_id): div, = self.doc.xpath('//div[@class="infosynthese"]') ret = Account() ret.type = Account.TYPE_CARD ret.coming = CleanDecimal(Regexp( CleanText('.'), r'En cours prélevé au \d+/\d+/\d+ : ([\d\s,-]+) euros'), replace_dots=True)(div) ret.number = Regexp(CleanText('.'), 'sur votre carte n°([\d*]+)')(div) ret.id = '%s.%s' % (parent_id, ret.number) ret.currency = 'EUR' ret.label = 'CARTE %s' % ret.number ret.url = self.url return ret
def get_list(self): no_accounts_message = self.doc.xpath( u'//span/b[contains(text(),"Votre abonnement est clôturé. Veuillez contacter votre conseiller.")]/text()' ) if no_accounts_message: raise ActionNeeded(no_accounts_message[0]) for tr in self.doc.xpath('//table[has-class("datas")]//tr'): if tr.attrib.get('class', '') == 'entete': continue cols = tr.findall('td') a = Account() a.label = unicode(cols[self.COL_ID].xpath( './/span[@class="left-underline"] | .//span[@class="left"]/a') [0].text.strip()) a.type = self.get_account_type(a.label) balance = CleanText('.')(cols[self.COL_BALANCE]) if balance == '': continue a.balance = CleanDecimal(replace_dots=True).filter(balance) a.currency = a.get_currency(balance) if cols[self.COL_ID].find('a'): a._link, a._args = self.params_from_js( cols[self.COL_ID].find('a').attrib['href']) a._acc_nb = cols[self.COL_ID].xpath( './/span[@class="right-underline"] | .//span[@class="right"]' )[0].text.replace(' ', '').strip() if hasattr(a, '_args') and a._args: a.id = '%s%s%s' % (a._acc_nb, a._args['IndiceCompte'], a._args['Indiceclassement']) else: a.id = a._acc_nb # This account can be multiple life insurance accounts if any(a.label.startswith(lab) for lab in ['ASS.VIE-BONS CAPI-SCPI-DIVERS', 'BONS CAPI-SCPI-DIVERS']) or \ u'Aucun d\\351tail correspondant pour ce compte' in tr.xpath('.//a/@href')[0]: continue if a.type is Account.TYPE_CARD: a.coming = a.balance a.balance = Decimal('0.0') a._inv = False yield a
def get_list(self): for tr in self.document.getiterator('tr'): if not 'LGNTableRow' in tr.attrib.get('class', '').split(): continue account = Account() for td in tr.getiterator('td'): if td.attrib.get('headers', '') == 'TypeCompte': a = td.find('a') if a is None: break account.label = self.parser.tocleanstring(a) for pattern, actype in self.TYPES.iteritems(): if account.label.startswith(pattern): account.type = actype account._link_id = a.get('href', '') elif td.attrib.get('headers', '') == 'NumeroCompte': account.id = self.parser.tocleanstring(td).replace(u'\xa0', '') elif td.attrib.get('headers', '') == 'Libelle': text = self.parser.tocleanstring(td) if text != '': account.label = text elif td.attrib.get('headers', '') == 'Solde': div = td.xpath('./div[@class="Solde"]') if len(div) > 0: balance = self.parser.tocleanstring(div[0]) if len(balance) > 0 and balance not in ('ANNULEE', 'OPPOSITION'): try: account.balance = Decimal(FrenchTransaction.clean_amount(balance)) except InvalidOperation: raise BrokenPageError('Unable to parse balance %r' % balance) account.currency = account.get_currency(balance) else: account.balance = NotAvailable if not account.label or empty(account.balance): continue if 'CARTE_' in account._link_id: account.type = account.TYPE_CARD account.coming = account.balance account.balance = Decimal('0') yield account
def get_list(self): accounts = [] txt = self.get_from_js('_data = new Array(', ');', is_list=True) if txt is None: raise BrokenPageError('Unable to find accounts list in scripts') data = json.loads('[%s]' % txt.replace("'", '"')) for line in data: a = Account() a.id = line[self.COL_ID].replace(' ', '') a._acc_nb = a.id.split('_')[0] if len(a.id.split('_')) > 1 else None fp = StringIO(unicode(line[self.COL_LABEL]).encode(self.browser.ENCODING)) a.label = self.parser.tocleanstring(self.parser.parse(fp, self.browser.ENCODING).xpath('//div[@class="libelleCompteTDB"]')[0]) # This account can be multiple life insurance accounts if a.label == 'ASSURANCE VIE-BON CAPI-SCPI-DIVERS *': continue a.balance = Decimal(FrenchTransaction.clean_amount(line[self.COL_BALANCE])) a.currency = a.get_currency(line[self.COL_BALANCE]) a.type = self.get_account_type(a.label) if line[self.COL_HISTORY] == 'true': a._inv = False a._link = self.get_history_link() a._args = {'_eventId': 'clicDetailCompte', '_ipc_eventValue': '', '_ipc_fireEvent': '', 'deviseAffichee': 'DEVISE', 'execution': self.get_execution(), 'idCompteClique': line[self.COL_ID], } else: a._inv = True a._args = {'_ipc_eventValue': line[self.COL_ID], '_ipc_fireEvent': line[self.COL_FIRE_EVENT], } a._link = self.document.xpath('//form[@name="changePageForm"]')[0].attrib['action'] if a.type is Account.TYPE_CARD: a.coming = a.balance a.balance = Decimal('0.0') accounts.append(a) return accounts
def get_list(self): for trCompte in self.document.xpath('//table[@id="compte"]/tbody/tr'): tds = trCompte.findall('td') account = Account() account.id = tds[self.CPT_ROW_ID].text.strip() account.label = tds[self.CPT_ROW_NAME].text.strip() account_type_str = "".join([td.text for td in tds[self.CPT_ROW_NATURE].xpath('.//td[@class="txt"]')]).strip() account.type = self.ACCOUNT_TYPES.get(account_type_str, Account.TYPE_UNKNOWN) account.balance = Decimal(FrenchTransaction.clean_amount(tds[self.CPT_ROW_BALANCE].find("a").text)) account.coming = Decimal(FrenchTransaction.clean_amount( tds[self.CPT_ROW_ENCOURS].find("a").text)) yield account return
def get_list(self): ids = set() for td in self.document.xpath('.//td[@nowrap="nowrap"]'): account = Account() link = td.xpath('.//a')[0] account._index = int(re.search('\d', link.attrib['href']).group(0)) if not account._index in ids: ids.add(account._index) account.id = unicode(link.text.strip()) account.label = account.id urltofind = './/a[@href="' + link.attrib['href'] + '"]' linkbis = self.document.xpath(urltofind).pop() if linkbis.text == link.text: linkbis = self.document.xpath(urltofind)[1] account.balance = Decimal(linkbis.text.replace('.', '').\ replace(' ', '').replace(',', '.')) account.coming = NotAvailable yield account
def get_list(self): for table in self.has_accounts(): tds = table.xpath('./tbody/tr')[0].findall('td') if len(tds) < 3: if tds[0].text_content() == u'Pr\xeat Personnel': account = Account() args = self.js2args(table.xpath('.//a')[0].attrib['onclick']) account._args = args account.label = CleanText().filter(tds[0].xpath('./ancestor::table[has-class("tableaux-pret-personnel")]/caption')) account.id = account.label.split()[-1] + args['paramNumContrat'] loan_details = self.browser.open("/webapp/axabanque/jsp/panorama.faces",data=args) # Need to go back on home page after open self.browser.bank_accounts.open() account.balance = -CleanDecimal().filter(loan_details.page.doc.xpath('//*[@id="table-detail"]/tbody/tr/td[7]/text()')) account.currency = Currency().filter(loan_details.page.doc.xpath('//*[@id="table-detail"]/tbody/tr/td[7]/text()')) account.type = Account.TYPE_LOAN account._acctype = "bank" account._hasinv = False account._is_debit_card = False yield account continue boxes = table.xpath('./tbody//tr[not(.//strong[contains(text(), "Total")])]') foot = table.xpath('./tfoot//tr') for box in boxes: account = Account() account._url = None if len(box.xpath('.//a')) != 0 and 'onclick' in box.xpath('.//a')[0].attrib: args = self.js2args(box.xpath('.//a')[0].attrib['onclick']) account.label = u'{0} {1}'.format(unicode(table.xpath('./caption')[0].text.strip()), unicode(box.xpath('.//a')[0].text.strip())) elif len(foot[0].xpath('.//a')) != 0 and 'onclick' in foot[0].xpath('.//a')[0].attrib: args = self.js2args(foot[0].xpath('.//a')[0].attrib['onclick']) account.label = unicode(table.xpath('./caption')[0].text.strip()) else: continue self.logger.debug('Args: %r' % args) if 'paramNumCompte' not in args: #The displaying of life insurances is very different from the other if args.get('idPanorama:_idcl').split(":")[1] == 'tableaux-direct-solution-vie': account_details = self.browser.open("#", data=args) scripts = account_details.page.doc.xpath('//script[@type="text/javascript"]/text()') script = filter(lambda x: "src" in x, scripts)[0] iframe_url = re.search("src:(.*),", script).group()[6:-2] account_details_iframe = self.browser.open(iframe_url, data=args) account.id = CleanText('//span[contains(@id,"NumeroContrat")]/text()')(account_details_iframe.page.doc) account._url = iframe_url account.type = account.TYPE_LIFE_INSURANCE account.balance = MyDecimal('//span[contains(@id,"MontantEpargne")]/text()')(account_details_iframe.page.doc) account._acctype = "bank" account._is_debit_card = False else: try: label = unicode(table.xpath('./caption')[0].text.strip()) except Exception: label = 'Unable to determine' self.logger.warning('Unable to get account ID for %r' % label) continue if account.type != account.TYPE_LIFE_INSURANCE: # get accounts type account_type_str = '' for l in table.attrib['class'].split(' '): if 'tableaux-comptes-' in l: account_type_str = l[len('tableaux-comptes-'):].lower() break account.type = Account.TYPE_UNKNOWN for pattern, type in self.ACCOUNT_TYPES.items(): if pattern in account_type_str or pattern in account.label.lower(): account.type = type break # get accounts id try: account.id = args['paramNumCompte'] + args['paramNumContrat'] if 'Visa' in account.label: card_id = re.search('(\d+)', box.xpath('./td[2]')[0].text.strip()) if card_id: account.id += card_id.group(1) if u'Valorisation' in account.label or u'Liquidités' in account.label: account.id += args[next(k for k in args.keys() if "_idcl" in k)].split('Jsp')[-1] except KeyError: account.id = args['paramNumCompte'] # get accounts balance try: balance_value = CleanText('.//td[has-class("montant")]')(box) # skip debit card # some cards don't have information in balance tab, skip them if balance_value == u'Débit immédiat' or balance_value == '': account._is_debit_card = True else: account._is_debit_card = False account.balance = Decimal(FrenchTransaction.clean_amount(self.parse_number(balance_value))) if account.type == Account.TYPE_CARD: account.coming = account.balance account.balance = Decimal(0) except InvalidOperation: #The account doesn't have a amount pass account._url = self.doc.xpath('//form[contains(@action, "panorama")]/@action')[0] account._acctype = "bank" # get accounts currency currency_title = table.xpath('./thead//th[@class="montant"]')[0].text.strip() m = re.match('Montant \((\w+)\)', currency_title) if not m: self.logger.warning('Unable to parse currency %r' % currency_title) else: account.currency = account.get_currency(m.group(1)) account._args = args account._hasinv = True if "Valorisation" in account.label else False yield account
def get_list(self): no_accounts_message = self.doc.xpath(u'//span/b[contains(text(),"Votre abonnement est clôturé. Veuillez contacter votre conseiller.")]/text()') if no_accounts_message: raise ActionNeeded(no_accounts_message[0]) previous_checking_account = None # Several deposit accounts ('Compte à terme') have the same id and the same label # So a number is added to distinguish them previous_deposit_account = None deposit_count = 1 for tr in self.doc.xpath('//table[has-class("datas")]//tr'): if tr.attrib.get('class', '') == 'entete': owner = CleanText('.')(tr.findall('td')[0]) continue cols = tr.findall('td') a = Account() a.label = unicode(cols[self.COL_ID].xpath('.//span[@class="left-underline"] | .//span[@class="left"]/a')[0].text.strip()) a.type = self.get_account_type(a.label) a.ownership = self.get_account_ownership(owner) balance = CleanText('.')(cols[self.COL_BALANCE]) if balance == '': continue a.balance = CleanDecimal(replace_dots=True).filter(balance) a.currency = a.get_currency(balance) if cols[self.COL_ID].find('a'): a._link, a._args = self.params_from_js(cols[self.COL_ID].find('a').attrib['href']) # There may be a href with 'javascript:NoDetail();' # The _link and _args should be None else: a._link, a._args = None, None a._acc_nb = cols[self.COL_ID].xpath('.//span[@class="right-underline"] | .//span[@class="right"]')[0].text.replace(' ', '').strip() a.id = a._acc_nb # If available we add 'IndiceCompte' and 'IndiceClassement' to the id due to the lack of information # on the website. This method is not enough because on some connections, if there are multiple account with the # same id and the same label, but with different currencies, we will add an index at the end of the id relative to the # order the accounts appear on the website. This will cause the accounts to be shifted when the user will add a new account # with same label/id, if this happens the new account will appear first on the website and it will take the index of '1' # previously used by the first account. the already gathered transactions of the previously first account will appear on # the new first account, the already gathered transactions of the previously second account will appear on the new # second account (the previous one), etc. if hasattr(a, '_args') and a._args: if a._args['IndiceCompte'].isdigit(): a.id = '%s%s' % (a.id, a._args['IndiceCompte']) if a._args['Indiceclassement'].isdigit(): a.id = '%s%s' % (a.id, a._args['Indiceclassement']) # This account can be multiple life insurance accounts if (any(a.label.startswith(lab) for lab in ['ASS.VIE-BONS CAPI-SCPI-DIVERS', 'BONS CAPI-SCPI-DIVERS']) or (u'Aucun d\\351tail correspondant pour ce compte' in tr.xpath('.//a/@href')[0]) and 'COMPTE A TERME' not in tr.xpath('.//span[contains(@class, "left")]/text()')[0]): continue if a.type is Account.TYPE_CARD: a.coming = a.balance a.balance = Decimal('0.0') # Take the predecessiong checking account as parent if previous_checking_account: a.parent = previous_checking_account else: self.logger.warning('The card account %s has no parent account' % a.id) a._inv = True if a.type == Account.TYPE_CHECKING: previous_checking_account = a if previous_deposit_account and previous_deposit_account.id == a.id: a.id = a.id + '_%s' % deposit_count deposit_count += 1 previous_deposit_account = a if a.type == Account.TYPE_DEPOSIT: previous_deposit_account = a yield a
def get_list(self): accounts = [] for block in self.document.xpath('//div[@class="pave"]/div'): head_type = block.xpath( './div/span[@class="accGroupLabel"]')[0].text.strip() account_type = self.ACCOUNT_TYPES.get(head_type, Account.TYPE_UNKNOWN) for tr in block.cssselect('ul li.tbord_account'): id = tr.attrib.get('id', '') if id.find('contratId') != 0: self.logger.warning('Unable to parse contract ID: %r' % id) continue id = id[id.find('contratId') + len('contratId'):] link = tr.cssselect('span.accountLabel a')[0] balance = Decimal( FrenchTransaction.clean_amount( tr.cssselect('span.accountTotal')[0].text)) if id.endswith('CRT'): account = accounts[-1] account._card_links.append(link.attrib['href']) if not account.coming: account.coming = Decimal('0.0') account.coming += balance continue account = Account() account.id = id account.label = unicode(link.text.strip()) account.type = account_type account.balance = balance account.currency = account.get_currency( tr.cssselect('span.accountDev')[0].text) account._link = link.attrib['href'] account._card_links = [] accounts.append(account) if len(accounts) == 0: # Sometimes, accounts are only in javascript... for script in self.document.xpath('//script'): text = script.text if text is None: continue if 'remotePerso' not in text: continue account = None card_account = None attribs = {} account_type = Account.TYPE_UNKNOWN for line in text.split('\n'): line = line.strip() m = re.match("data.libelle = '(.*)';", line) if m: account_type = self.ACCOUNT_TYPES.get( m.group(1), Account.TYPE_UNKNOWN) elif line == 'var remotePerso = new Object;': account = Account() elif account is not None: m = re.match("remotePerso.(\w+) = '?(.*?)'?;", line) if m: attribs[m.group(1)] = m.group(2) elif line.startswith('listProduitsGroup'): account.id = attribs['refContrat'] account.label = attribs['libelle'] account.type = account_type account.balance = Decimal( FrenchTransaction.clean_amount( attribs['soldeDateOpeValeurFormatted'])) account.currency = account.get_currency( attribs['codeDevise']) account._link = 'tbord.do?id=%s&%s' % ( attribs['id'], self.browser.SESSION_PARAM) account._card_links = [] if account.id.endswith('CRT'): if not len(accounts): card_account = account else: a = accounts[-1] a._card_links.append(account._link) if not a.coming: a.coming = Decimal('0.0') a.coming += account.balance else: if 'COURANT' in account.label: account.type = account.TYPE_CHECKING elif account.id.endswith('TTR'): account.type = account.TYPE_MARKET elif re.match('^\d+C$', account.id): account.type = account.TYPE_LIFE_INSURANCE elif re.match('^\d+PRT$', account.id): account.type = account.TYPE_LOAN elif not account.type: account.type = account.TYPE_SAVINGS if card_account: account._card_links.append( card_account._link) if not account.coming: account.coming = Decimal('0.0') account.coming += card_account.balance card_account = None accounts.append(account) account = None return accounts
def iter_accounts(self): if not self.cookie: self.login() def do_http(): if 'no_check' in self.config and self.config['no_check'].get() == "y": conn = HellHTTPS("www.cmb.fr") else: conn = HellHTTPS("www.cmb.fr", ca_file=self.AUTH_CERT, callBack=self.sslCallBack) conn.connect() headers = self.headers headers['Cookie'] = self.cookie conn.request("GET", '/domiweb/prive/particulier/releve/0-releve.act', {}, headers) response = conn.getresponse() data = response.read() conn.close() return data data = do_http() parser = etree.HTMLParser() tree = etree.parse(StringIO(data), parser) table = tree.xpath('/html/body/table') if len(table) == 0: title = tree.xpath('/html/head/title')[0].text if title == u"Utilisateur non identifié": self.login() data = do_http() parser = etree.HTMLParser() tree = etree.parse(StringIO(data), parser) table = tree.xpath('/html/body/table') if len(table) == 0: raise BrokenPageError() else: raise BrokenPageError() for tr in tree.xpath('/html/body//table[contains(@class, "Tb")]/tr'): if tr.get('class', None) not in ('LnTit', 'LnTot', 'LnMnTiers', None): account = Account() td = tr.xpath('td') a = td[1].xpath('a') account.label = unicode(a[0].text).strip() href = a[0].get('href') m = match(r"javascript:releve\((.*),'(.*)','(.*)'\)", href) if not m: continue account.id = unicode(m.group(1) + m.group(2) + m.group(3)) account._cmbvaleur = m.group(1) account._cmbvaleur2 = m.group(2) account._cmbtype = m.group(3) balance = u''.join([txt.strip() for txt in td[2].itertext()]) balance = balance.replace(',', '.').replace(u"\xa0", '') account.balance = Decimal(balance) span = td[4].xpath('a/span') if len(span): coming = span[0].text.replace(' ', '').replace(',', '.') coming = coming.replace(u"\xa0", '') account.coming = Decimal(coming) else: account.coming = NotAvailable yield account
def get_list(self): accounts = [] for cpt in self.doc.xpath('//div[contains(@class, " compte") and not(contains(@class, "compte_selected"))]'): # ignore auto assurance accounts if 'aut' in cpt.get('class'): continue account = Account() account._history_link = Link('./ul/li/a[contains(@id, "consulter_solde") ' 'or contains(@id, "historique") ' 'or contains(@id, "contrat") ' 'or contains(@id, "assurance_vie_operations")]')(cpt) # this is to test if access to the accounts info is blocked for different reasons page = self.browser.open(account._history_link).page if isinstance(page, LoanPage): account = Loan() account._history_link = Link('./ul/li/a[contains(@id, "consulter_solde") ' 'or contains(@id, "historique") ' 'or contains(@id, "contrat") ' 'or contains(@id, "assurance_vie_operations")]')(cpt) if isinstance(page, LoanPage): account.id = CleanText('(//p[@id="c_montantEmprunte"]//span[@class="valStatic"]//strong)[1]')(cpt) account.label = CleanText('(//p[@id="c_montantEmprunte"]//span[@class="valStatic"]//strong)[1]')(cpt) account.type = Account.TYPE_LOAN account_history_page = page account.total_amount = account_history_page.get_total_amount() account.next_payment_amount = account_history_page.get_next_payment_amount() account.next_payment_date = account_history_page.get_next_payment_date() account.account_label = account_history_page.get_account_label() account.subscription_date = account_history_page.get_subscription_date() account.maturity_date = account_history_page.get_maturity_date() if len(accounts) == 0: global_error_message = page.doc.xpath('//div[@id="as_renouvellementMIFID.do_"]/div[contains(text(), "Bonjour")] ' '| //div[@id="as_afficherMessageBloquantMigration.do_"]//div[@class="content_message"] ' '| //p[contains(text(), "Et si vous faisiez de Fortuneo votre banque principale")] ' '| //div[@id="as_renouvellementMotDePasse.do_"]//p[contains(text(), "votre mot de passe")]' '| //div[@id="as_afficherSecuriteForteOTPIdentification.do_"]//span[contains(text(), "Pour valider ")]') if global_error_message: raise ActionNeeded(CleanText('.')(global_error_message[0])) local_error_message = page.doc.xpath('//div[@id="error"]/p[@class="erreur_texte1"]') if local_error_message: raise BrowserUnavailable(CleanText('.')(local_error_message[0])) number = RawText('./a[contains(@class, "numero_compte")]')(cpt).replace(u'N° ', '') account.id = CleanText(None).filter(number).replace(u'N°', '') account._card_links = [] card_link = Link('./ul/li/a[contains(text(), "Carte bancaire")]', default='')(cpt) if len(card_link) > 0: account._card_links.append(card_link) account.label = CleanText('./a[contains(@class, "numero_compte")]/@title')(cpt) for pattern, type in self.ACCOUNT_TYPES.items(): if pattern in account._history_link: account.type = type break investment_page = None if account.type in {Account.TYPE_PEA, Account.TYPE_MARKET, Account.TYPE_LIFE_INSURANCE}: account._investment_link = Link('./ul/li/a[contains(@id, "portefeuille")]')(cpt) investment_page = self.browser.open(account._investment_link).page balance = investment_page.get_balance(account.type) if account.type in {Account.TYPE_PEA, Account.TYPE_MARKET}: self.browser.investments[account.id] = list(self.browser.open(account._investment_link).page.get_investments(account)) else: balance = page.get_balance() if account.type is not Account.TYPE_LOAN: account.coming = page.get_coming() if account.type in {Account.TYPE_PEA, Account.TYPE_MARKET}: account.currency = investment_page.get_currency() else: account.currency = account.get_currency(balance) account.balance = CleanDecimal(None, replace_dots=True).filter(balance) if account.type in (Account.TYPE_CHECKING, Account.TYPE_SAVINGS): # Need a token sent by SMS to customers account.iban = NotAvailable if (account.label, account.id, account.balance) not in [(a.label, a.id, a.balance) for a in accounts]: accounts.append(account) return accounts
def get_list(self, pro=True): accounts = [] for tr in self.document.xpath('//tr[@class="comptes"]'): cols = tr.findall('td') if len(cols) < 5: continue account = Account() account.id = self.parser.tocleanstring(cols[self.COL_ID]).replace( " ", "") account.label = self.parser.tocleanstring(cols[self.COL_LABEL]) account.balance = Decimal( self.parser.tocleanstring(cols[self.COL_BALANCE])) try: account.coming = Decimal( self.parser.tocleanstring(cols[self.COL_COMING])) except InvalidOperation: if self.parser.tocleanstring(cols[self.COL_COMING]) != '-': self.logger.warning('Unable to parse coming value', exc_info=True) account.coming = NotAvailable account._link_id = None account._stp = None a = cols[self.COL_LABEL].find('a') if a is not None: url = urlparse(a.attrib['href']) p = dict(parse_qsl(url.query)) account._link_id = p.get('ch4', None) account._stp = p.get('stp', None) for input_tag in tr.xpath('.//input[starts-with(@id, "urlRib")]'): m = re.search('ch4=(\w+)', input_tag.get('value', '')) if m: account.iban = unicode(m.group(1)) break else: select = tr.xpath('.//select//@onchange')[0] m = re.search("\(this,'(\w+)", select) if m: iban = unicode(m.group(1)) if iban.startswith('FR') and len(iban) == 27: account.iban = unicode(m.group(1)) accounts.append(account) # If there are also personnal accounts linked, display the page and iter on them. if pro and len( self.document.xpath( '//div[@class="onglets"]//a[contains(@href, "afficherComptesPrives")]' )) > 0: self.browser.select_form(name='myForm') self.browser.set_all_readonly(False) self.browser['udcAction'] = '/afficherComptesPrives' self.browser.submit() for a in self.browser.page.get_list(False): accounts.append(a) return accounts
def iter_accounts(self): if not self.cookie: self.login() def do_http(): if 'no_check' in self.config and self.config['no_check'].get( ) == "y": conn = HellHTTPS("www.cmb.fr") else: conn = HellHTTPS("www.cmb.fr", ca_file=self.AUTH_CERT, callBack=self.sslCallBack) conn.connect() headers = self.headers headers['Cookie'] = self.cookie conn.request("GET", '/domiweb/prive/particulier/releve/0-releve.act', {}, headers) response = conn.getresponse() data = response.read() conn.close() return data data = do_http() parser = etree.HTMLParser() tree = etree.parse(StringIO(data), parser) table = tree.xpath('/html/body/table') if len(table) == 0: title = tree.xpath('/html/head/title')[0].text if title == u"Utilisateur non identifié": self.login() data = do_http() parser = etree.HTMLParser() tree = etree.parse(StringIO(data), parser) table = tree.xpath('/html/body/table') if len(table) == 0: raise BrokenPageError() else: raise BrokenPageError() for tr in table[1].getiterator('tr'): if tr.get('class') != 'LnTit' and tr.get('class') != 'LnTot': account = Account() td = tr.xpath('td') a = td[1].xpath('a') account.label = unicode(a[0].text).strip() href = a[0].get('href') m = match(r"javascript:releve\((.*),'(.*)','(.*)'\)", href) if not m: continue account.id = unicode(m.group(1) + m.group(2) + m.group(3)) account._cmbvaleur = m.group(1) account._cmbvaleur2 = m.group(2) account._cmbtype = m.group(3) balance = td[2].text balance = balance.replace(',', '.').replace(u"\xa0", '') account.balance = Decimal(balance) span = td[4].xpath('a/span') if len(span): coming = span[0].text.replace(' ', '').replace(',', '.') coming = coming.replace(u"\xa0", '') account.coming = Decimal(coming) else: account.coming = NotAvailable yield account
def get_list(self): accounts = [] for cpt in self.doc.xpath('//div[contains(@class, " compte") and not(contains(@class, "compte_selected"))]'): # ignore auto assurance accounts if 'aut' in cpt.get('class'): continue account = Account() account._history_link = Link('./ul/li/a[contains(@id, "consulter_solde") ' 'or contains(@id, "historique") ' 'or contains(@id, "contrat") ' 'or contains(@id, "assurance_vie_operations")]')(cpt) # this is to test if access to the accounts info is blocked for different reasons page = self.browser.open(account._history_link).page if isinstance(page, LoanPage): account = Loan() account._history_link = Link('./ul/li/a[contains(@id, "consulter_solde") ' 'or contains(@id, "historique") ' 'or contains(@id, "contrat") ' 'or contains(@id, "assurance_vie_operations")]')(cpt) if isinstance(page, LoanPage): account.id = CleanText('(//p[@id="c_montantEmprunte"]//span[@class="valStatic"]//strong)[1]')(cpt) account.label = CleanText('(//p[@id="c_montantEmprunte"]//span[@class="valStatic"]//strong)[1]')(cpt) account.type = Account.TYPE_LOAN account_history_page = page account.total_amount = account_history_page.get_total_amount() account.next_payment_amount = account_history_page.get_next_payment_amount() account.next_payment_date = account_history_page.get_next_payment_date() account.account_label = account_history_page.get_account_label() account.subscription_date = account_history_page.get_subscription_date() account.maturity_date = account_history_page.get_maturity_date() account.ownership = account_history_page.get_owner() if len(accounts) == 0: global_error_message = page.doc.xpath('//div[@id="as_renouvellementMIFID.do_"]/div[contains(text(), "Bonjour")] ' '| //div[@id="as_afficherMessageBloquantMigration.do_"]//div[@class="content_message"] ' '| //p[contains(text(), "Et si vous faisiez de Fortuneo votre banque principale")] ' '| //div[@id="as_renouvellementMotDePasse.do_"]//p[contains(text(), "votre mot de passe")]' '| //div[@id="as_afficherSecuriteForteOTPIdentification.do_"]//span[contains(text(), "Pour valider ")]') if global_error_message: if "Et si vous faisiez de Fortuneo votre banque principale" in CleanText(global_error_message)(self): self.browser.location('/ReloadContext', data={'action': 4}) return raise ActionNeeded(CleanText('.')(global_error_message[0])) local_error_message = page.doc.xpath('//div[@id="error"]/p[@class="erreur_texte1"]') if local_error_message: raise BrowserUnavailable(CleanText('.')(local_error_message[0])) account.id = account.number = CleanText('./a[contains(@class, "numero_compte")]/div')(cpt).replace(u'N° ', '') account._ca = CleanText('./a[contains(@class, "numero_compte")]/@rel')(cpt) account._card_links = [] card_link = Link('./ul/li/a[contains(text(), "Carte bancaire")]', default='')(cpt) if len(card_link) > 0: account._card_links.append(card_link) account.label = CleanText('./a[contains(@class, "numero_compte")]/@title')(cpt) for pattern, type in self.ACCOUNT_TYPES.items(): if pattern in account._history_link: account.type = type break investment_page = None if account.type in (Account.TYPE_PEA, Account.TYPE_MARKET, Account.TYPE_LIFE_INSURANCE): account._investment_link = Link('./ul/li/a[contains(@id, "portefeuille")]')(cpt) investment_page = self.browser.location(account._investment_link).page balance = investment_page.get_balance(account.type) if account.type in (Account.TYPE_PEA, Account.TYPE_MARKET): self.browser.investments[account.id] = list(self.browser.open(account._investment_link).page.get_investments(account)) else: balance = page.get_balance() if account.type is not Account.TYPE_LOAN: account.coming = page.get_coming() if account.type in (Account.TYPE_PEA, Account.TYPE_MARKET): account.currency = investment_page.get_currency() elif balance: account.currency = account.get_currency(balance) account.balance = CleanDecimal.French().filter(balance) if account.type in (Account.TYPE_CHECKING, Account.TYPE_SAVINGS): # Need a token sent by SMS to customers account.iban = NotAvailable if account.type is not Account.TYPE_LOAN: regexp = re.search(r'(m\. |mme\. )(.+)', CleanText('//span[has-class("mon_espace_nom")]')(self.doc), re.IGNORECASE) if regexp and len(regexp.groups()) == 2: gender = regexp.group(1).replace('.', '').rstrip() name = regexp.group(2) label = account.label if re.search(r'(m|mr|me|mme|mlle|mle|ml)\.? (.*)\bou (m|mr|me|mme|mlle|mle|ml)\b(.*)', label, re.IGNORECASE): account.ownership = AccountOwnership.CO_OWNER elif re.search(r'{} {}'.format(gender, name), label, re.IGNORECASE): account.ownership = AccountOwnership.OWNER else: account.ownership = AccountOwnership.ATTORNEY if (account.label, account.id, account.balance) not in [(a.label, a.id, a.balance) for a in accounts]: accounts.append(account) return accounts
def get_list(self): no_accounts_message = self.doc.xpath(u'//span/b[contains(text(),"Votre abonnement est clôturé. Veuillez contacter votre conseiller.")]/text()') if no_accounts_message: raise ActionNeeded(no_accounts_message[0]) previous_checking_account = None # Several deposit accounts ('Compte à terme') have the same id and the same label # So a number is added to distinguish them previous_deposit_account = None deposit_count = 1 for tr in self.doc.xpath('//table[has-class("datas")]//tr'): if tr.attrib.get('class', '') == 'entete': continue cols = tr.findall('td') a = Account() a.label = unicode(cols[self.COL_ID].xpath('.//span[@class="left-underline"] | .//span[@class="left"]/a')[0].text.strip()) a.type = self.get_account_type(a.label) balance = CleanText('.')(cols[self.COL_BALANCE]) if balance == '': continue a.balance = CleanDecimal(replace_dots=True).filter(balance) a.currency = a.get_currency(balance) if cols[self.COL_ID].find('a'): a._link, a._args = self.params_from_js(cols[self.COL_ID].find('a').attrib['href']) # There may be a href with 'javascript:NoDetail();' # The _link and _args should be None else: a._link, a._args = None, None a._acc_nb = cols[self.COL_ID].xpath('.//span[@class="right-underline"] | .//span[@class="right"]')[0].text.replace(' ', '').strip() if hasattr(a, '_args') and a._args: a.id = '%s%s%s' % (a._acc_nb, a._args['IndiceCompte'], a._args['Indiceclassement']) else: a.id = a._acc_nb # This account can be multiple life insurance accounts if (any(a.label.startswith(lab) for lab in ['ASS.VIE-BONS CAPI-SCPI-DIVERS', 'BONS CAPI-SCPI-DIVERS']) or (u'Aucun d\\351tail correspondant pour ce compte' in tr.xpath('.//a/@href')[0]) and 'COMPTE A TERME' not in tr.xpath('.//span[contains(@class, "left")]/text()')[0]): continue if a.type is Account.TYPE_CARD: a.coming = a.balance a.balance = Decimal('0.0') # Take the predecessiong checking account as parent if previous_checking_account: a.parent = previous_checking_account else: self.logger.warning('The card account %s has no parent account' % a.id) a._inv = False if a.type == Account.TYPE_CHECKING: previous_checking_account = a if previous_deposit_account and previous_deposit_account.id == a.id: a.id = a.id + '_%s' % deposit_count deposit_count += 1 previous_deposit_account = a if a.type == Account.TYPE_DEPOSIT: previous_deposit_account = a yield a
def get_list(self): no_accounts_message = self.doc.xpath(u'//span/b[contains(text(),"Votre abonnement est clôturé. Veuillez contacter votre conseiller.")]/text()') if no_accounts_message: raise ActionNeeded(no_accounts_message[0]) previous_checking_account = None # Several deposit accounts ('Compte à terme') have the same id and the same label # So a number is added to distinguish them previous_deposit_account = None deposit_count = 1 for tr in self.doc.xpath('//table[has-class("datas")]//tr'): if tr.attrib.get('class', '') == 'entete': continue cols = tr.findall('td') a = Account() a.label = unicode(cols[self.COL_ID].xpath('.//span[@class="left-underline"] | .//span[@class="left"]/a')[0].text.strip()) a.type = self.get_account_type(a.label) balance = CleanText('.')(cols[self.COL_BALANCE]) if balance == '': continue a.balance = CleanDecimal(replace_dots=True).filter(balance) a.currency = a.get_currency(balance) if cols[self.COL_ID].find('a'): a._link, a._args = self.params_from_js(cols[self.COL_ID].find('a').attrib['href']) # There may be a href with 'javascript:NoDetail();' # The _link and _args should be None else: a._link, a._args = None, None a._acc_nb = cols[self.COL_ID].xpath('.//span[@class="right-underline"] | .//span[@class="right"]')[0].text.replace(' ', '').strip() a.id = a._acc_nb if hasattr(a, '_args') and a._args: if a._args['IndiceCompte'].isdigit(): a.id = '%s%s' % (a.id, a._args['IndiceCompte']) if a._args['Indiceclassement'].isdigit(): a.id = '%s%s' % (a.id, a._args['Indiceclassement']) # This account can be multiple life insurance accounts if (any(a.label.startswith(lab) for lab in ['ASS.VIE-BONS CAPI-SCPI-DIVERS', 'BONS CAPI-SCPI-DIVERS']) or (u'Aucun d\\351tail correspondant pour ce compte' in tr.xpath('.//a/@href')[0]) and 'COMPTE A TERME' not in tr.xpath('.//span[contains(@class, "left")]/text()')[0]): continue if a.type is Account.TYPE_CARD: a.coming = a.balance a.balance = Decimal('0.0') # Take the predecessiong checking account as parent if previous_checking_account: a.parent = previous_checking_account else: self.logger.warning('The card account %s has no parent account' % a.id) a._inv = True if a.type == Account.TYPE_CHECKING: previous_checking_account = a if previous_deposit_account and previous_deposit_account.id == a.id: a.id = a.id + '_%s' % deposit_count deposit_count += 1 previous_deposit_account = a if a.type == Account.TYPE_DEPOSIT: previous_deposit_account = a yield a
def get_list(self): for table in self.has_accounts(): tds = table.xpath('./tbody/tr')[0].findall('td') if len(tds) < 3: if tds[0].text_content() == u'Pr\xeat Personnel': account = Account() args = self.js2args(table.xpath('.//a')[0].attrib['onclick']) account._args = args account.label = CleanText().filter(tds[0].xpath('./ancestor::table[has-class("tableaux-pret-personnel")]/caption')) account.id = account.label.split()[-1] + args['paramNumContrat'] loan_details = self.browser.open('/webapp/axabanque/jsp/panorama.faces', data=args).page # Need to go back on home page after open self.browser.bank_accounts.open() account.balance = loan_details.get_loan_balance() account.currency = loan_details.get_loan_currency() # Skip loans without any balance (already fully reimbursed) if empty(account.balance): continue account.type = Account.TYPE_LOAN account._acctype = "bank" account._hasinv = False account._is_debit_card = False yield account continue boxes = table.xpath('./tbody//tr[not(.//strong[contains(text(), "Total")])]') foot = table.xpath('./tfoot//tr') for box in boxes: account = Account() account._url = None if len(box.xpath('.//a')) != 0 and 'onclick' in box.xpath('.//a')[0].attrib: args = self.js2args(box.xpath('.//a')[0].attrib['onclick']) account.label = u'{0} {1}'.format(unicode(table.xpath('./caption')[0].text.strip()), unicode(box.xpath('.//a')[0].text.strip())) elif len(foot[0].xpath('.//a')) != 0 and 'onclick' in foot[0].xpath('.//a')[0].attrib: args = self.js2args(foot[0].xpath('.//a')[0].attrib['onclick']) account.label = unicode(table.xpath('./caption')[0].text.strip()) else: continue self.logger.debug('Args: %r' % args) if 'paramNumCompte' not in args: #The displaying of life insurances is very different from the other if args.get('idPanorama:_idcl').split(":")[1] == 'tableaux-direct-solution-vie': account_details = self.browser.open("#", data=args) scripts = account_details.page.doc.xpath('//script[@type="text/javascript"]/text()') script = filter(lambda x: "src" in x, scripts)[0] iframe_url = re.search("src:(.*),", script).group()[6:-2] account_details_iframe = self.browser.open(iframe_url, data=args) account.id = CleanText('//span[contains(@id,"NumeroContrat")]/text()')(account_details_iframe.page.doc) account._url = iframe_url account.type = account.TYPE_LIFE_INSURANCE account.balance = MyDecimal('//span[contains(@id,"MontantEpargne")]/text()')(account_details_iframe.page.doc) account._acctype = "bank" account._is_debit_card = False else: try: label = unicode(table.xpath('./caption')[0].text.strip()) except Exception: label = 'Unable to determine' self.logger.warning('Unable to get account ID for %r' % label) continue if account.type != account.TYPE_LIFE_INSURANCE: # get accounts type account_type_str = '' for l in table.attrib['class'].split(' '): if 'tableaux-comptes-' in l: account_type_str = l[len('tableaux-comptes-'):].lower() break account.type = Account.TYPE_UNKNOWN for pattern, type in self.ACCOUNT_TYPES.items(): if pattern in account_type_str or pattern in account.label.lower(): account.type = type break # get accounts id try: account.id = args['paramNumCompte'] + args['paramNumContrat'] if 'Visa' in account.label: card_id = re.search('(\d+)', box.xpath('./td[2]')[0].text.strip()) if card_id: account.id += card_id.group(1) if u'Valorisation' in account.label or u'Liquidités' in account.label: account.id += args[next(k for k in args.keys() if "_idcl" in k)].split('Jsp')[-1] except KeyError: account.id = args['paramNumCompte'] # get accounts balance try: balance_value = CleanText('.//td[has-class("montant")]')(box) # skip debit card # some cards don't have information in balance tab, skip them if balance_value == u'Débit immédiat' or balance_value == '': account._is_debit_card = True else: account._is_debit_card = False account.balance = Decimal(FrenchTransaction.clean_amount(self.parse_number(balance_value))) if account.type == Account.TYPE_CARD: account.coming = account.balance account.balance = Decimal(0) except InvalidOperation: #The account doesn't have a amount pass account._url = self.doc.xpath('//form[contains(@action, "panorama")]/@action')[0] account._acctype = "bank" # get accounts currency currency_title = table.xpath('./thead//th[@class="montant"]')[0].text.strip() m = re.match('Montant \((\w+)\)', currency_title) if not m: self.logger.warning('Unable to parse currency %r' % currency_title) else: account.currency = account.get_currency(m.group(1)) account._args = args account._hasinv = True if "Valorisation" in account.label else False yield account
def get_list(self): accounts = [] for block in self.document.xpath('//div[@class="pave"]/div'): head_type = block.xpath('./div/span[@class="accGroupLabel"]')[0].text.strip() account_type = self.ACCOUNT_TYPES.get(head_type, Account.TYPE_UNKNOWN) for tr in block.cssselect('ul li.tbord_account'): id = tr.attrib.get('id', '') if id.find('contratId') != 0: self.logger.warning('Unable to parse contract ID: %r' % id) continue id = id[id.find('contratId')+len('contratId'):] link = tr.cssselect('span.accountLabel a')[0] balance = Decimal(FrenchTransaction.clean_amount(tr.cssselect('span.accountTotal')[0].text)) if id.endswith('CRT'): account = accounts[-1] account._card_links.append(link.attrib['href']) if not account.coming: account.coming = Decimal('0.0') account.coming += balance continue account = Account() account.id = id account.label = unicode(link.text.strip()) account.type = account_type account.balance = balance account.currency = account.get_currency(tr.cssselect('span.accountDev')[0].text) account._link = link.attrib['href'] account._card_links = [] accounts.append(account) if len(accounts) == 0: # Sometimes, accounts are only in javascript... for script in self.document.xpath('//script'): text = script.text if text is None: continue if 'remotePerso' not in text: continue account = None card_account = None attribs = {} account_type = Account.TYPE_UNKNOWN for line in text.split('\n'): line = line.strip() m = re.match("data.libelle = '(.*)';", line) if m: account_type = self.ACCOUNT_TYPES.get(m.group(1), Account.TYPE_UNKNOWN) elif line == 'var remotePerso = new Object;': account = Account() elif account is not None: m = re.match("remotePerso.(\w+) = '?(.*?)'?;", line) if m: attribs[m.group(1)] = m.group(2) elif line.startswith('listProduitsGroup'): account.id = attribs['refContrat'] account.label = attribs['libelle'] account.type = account_type account.balance = Decimal(FrenchTransaction.clean_amount(attribs['soldeDateOpeValeurFormatted'])) account.currency = account.get_currency(attribs['codeDevise']) account._link = 'tbord.do?id=%s&%s' % (attribs['id'], self.browser.SESSION_PARAM) account._card_links = [] if account.id.endswith('CRT'): if not len(accounts): card_account = account else: a = accounts[-1] a._card_links.append(account._link) if not a.coming: a.coming = Decimal('0.0') a.coming += account.balance else: if 'COURANT' in account.label: account.type = account.TYPE_CHECKING elif account.id.endswith('TTR'): account.type = account.TYPE_MARKET elif re.match('^\d+C$', account.id): account.type = account.TYPE_LIFE_INSURANCE elif re.match('^\d+PRT$', account.id): account.type = account.TYPE_LOAN elif not account.type: account.type = account.TYPE_SAVINGS if card_account: account._card_links.append(card_account._link) if not account.coming: account.coming = Decimal('0.0') account.coming += card_account.balance card_account = None accounts.append(account) account = None return accounts
def get_list(self): accounts = [] for cpt in self.doc.xpath( '//div[contains(@class, " compte") and not(contains(@class, "compte_selected"))]' ): # ignore auto assurance accounts if 'aut' in cpt.get('class'): continue account = Account() account._history_link = Link( './ul/li/a[contains(@id, "consulter_solde") ' 'or contains(@id, "historique") ' 'or contains(@id, "contrat") ' 'or contains(@id, "assurance_vie_operations")]')(cpt) # this is to test if access to the accounts info is blocked for different reasons page = self.browser.open(account._history_link).page if isinstance(page, LoanPage): account = Loan() account._history_link = Link( './ul/li/a[contains(@id, "consulter_solde") ' 'or contains(@id, "historique") ' 'or contains(@id, "contrat") ' 'or contains(@id, "assurance_vie_operations")]')(cpt) if isinstance(page, LoanPage): account.id = CleanText( '(//p[@id="c_montantEmprunte"]//span[@class="valStatic"]//strong)[1]' )(cpt) account.label = CleanText( '(//p[@id="c_montantEmprunte"]//span[@class="valStatic"]//strong)[1]' )(cpt) account.type = Account.TYPE_LOAN account_history_page = self.browser.open( account._history_link).page account.total_amount = account_history_page.get_total_amount() account.next_payment_amount = account_history_page.get_next_payment_amount( ) account.next_payment_date = account_history_page.get_next_payment_date( ) account.account_label = account_history_page.get_account_label( ) account.subscription_date = account_history_page.get_subscription_date( ) account.maturity_date = account_history_page.get_maturity_date( ) if len(accounts) == 0: global_error_message = page.doc.xpath( '//div[@id="as_renouvellementMIFID.do_"]/div[contains(text(), "Bonjour")] ' '| //div[@id="as_afficherMessageBloquantMigration.do_"]//div[@class="content_message"] ' '| //p[contains(text(), "Et si vous faisiez de Fortuneo votre banque principale")] ' '| //div[@id="as_renouvellementMotDePasse.do_"]//p[contains(text(), "votre mot de passe")]' '| //div[@id="as_afficherSecuriteForteOTPIdentification.do_"]//span[contains(text(), "Pour valider ")]' ) if global_error_message: raise ActionNeeded(CleanText('.')(global_error_message[0])) local_error_message = page.doc.xpath( '//div[@id="error"]/p[@class="erreur_texte1"]') if local_error_message: raise BrowserUnavailable( CleanText('.')(local_error_message[0])) number = RawText('./a[contains(@class, "numero_compte")]')( cpt).replace(u'N° ', '') account.id = CleanText(None).filter(number).replace(u'N°', '') account._card_links = [] card_link = Link('./ul/li/a[contains(text(), "Carte bancaire")]', default='')(cpt) if len(card_link) > 0: account._card_links.append(card_link) account.label = CleanText( './a[contains(@class, "numero_compte")]/@title')(cpt) for pattern, type in self.ACCOUNT_TYPES.items(): if pattern in account._history_link: account.type = type break if account.type in { Account.TYPE_PEA, Account.TYPE_MARKET, Account.TYPE_LIFE_INSURANCE }: account._investment_link = Link( './ul/li/a[contains(@id, "portefeuille")]')(cpt) balance = self.browser.open( account._investment_link).page.get_balance(account.type) if account.type in {Account.TYPE_PEA, Account.TYPE_MARKET}: self.browser.investments[account.id] = list( self.browser.open( account._investment_link).page.get_investments( account)) else: balance = self.browser.open( account._history_link).page.get_balance() if account.type is not Account.TYPE_LOAN: account.coming = self.browser.open( account._history_link).page.get_coming() if account.type in {Account.TYPE_PEA, Account.TYPE_MARKET}: account.currency = self.browser.open( account._investment_link).page.get_currency() else: account.currency = account.get_currency(balance) account.balance = CleanDecimal(None, replace_dots=True).filter(balance) if account.type in (Account.TYPE_CHECKING, Account.TYPE_SAVINGS): # Need a token sent by SMS to customers account.iban = NotAvailable if (account.label, account.id, account.balance) not in [ (a.label, a.id, a.balance) for a in accounts ]: accounts.append(account) return accounts
def get_list(self): accounts = OrderedDict() for tr in self.document.getiterator('tr'): first_td = tr.getchildren()[0] if (first_td.attrib.get('class', '') == 'i g' or first_td.attrib.get('class', '') == 'p g') \ and first_td.find('a') is not None: a = first_td.find('a') link = a.get('href', '') if link.startswith('POR_SyntheseLst'): continue url = urlparse(link) p = parse_qs(url.query) if not 'rib' in p: continue for i in (2, 1): balance = FrenchTransaction.clean_amount( tr.getchildren()[i].text) currency = Account.get_currency(tr.getchildren()[i].text) if len(balance) > 0: break balance = Decimal(balance) id = p['rib'][0] if id in accounts: account = accounts[id] if not account.coming: account.coming = Decimal('0.0') account.coming += balance account._card_links.append(link) continue account = Account() account.id = id account.label = unicode( a.text).strip().lstrip(' 0123456789').title() account._link_id = link account._card_links = [] # Find accounting amount page = self.browser.get_document(self.browser.openurl(link)) coming = self.find_amount(page, u"Opérations à venir") accounting = self.find_amount(page, u"Solde comptable") if accounting is not None and accounting + ( coming or Decimal('0')) != balance: self.logger.warning('%s + %s != %s' % (accounting, coming, balance)) if accounting is not None: balance = accounting if coming is not None: account.coming = coming account.balance = balance account.currency = currency accounts[account.id] = account return accounts.itervalues()