def bcall_errors_handler(self, errors): """ Handler for the CallErrors exception. """ self.ind.set_status(appindicator.IndicatorStatus.ATTENTION) for backend, error, backtrace in errors.errors: notify = True if isinstance(error, BrowserIncorrectPassword): msg = 'invalid login/password.' elif isinstance(error, BrowserSSLError): msg = '/!\ SERVER CERTIFICATE IS INVALID /!\\' elif isinstance(error, BrowserForbidden): msg = unicode(error) or 'Forbidden' elif isinstance(error, BrowserUnavailable): msg = unicode(error) if not msg: msg = 'website is unavailable.' elif isinstance(error, NotImplementedError): notify = False elif isinstance(error, UserError): msg = unicode(error) elif isinstance(error, MoreResultsAvailable): notify = False else: msg = unicode(error) if notify: Notify.Notification.new('<b>Error Boobank: %s</b>' % backend.name, msg, 'notification-message-im').show()
def __init__(self, video, parent=None): super(Video, self).__init__(parent) self.ui = Ui_Video() self.ui.setupUi(self) self.video = video self.setWindowTitle("Video - %s" % video.title) self.ui.urlEdit.setText(video.url) self.ui.titleLabel.setText(video.title) self.ui.durationLabel.setText(unicode(video.duration)) self.ui.authorLabel.setText(unicode(video.author)) self.ui.dateLabel.setText(unicode(video.date)) if video.rating_max: self.ui.ratingLabel.setText('%s / %s' % (video.rating, video.rating_max)) else: self.ui.ratingLabel.setText('%s' % video.rating) self.mediaPlayer = QMediaPlayer() self.mediaPlayer.durationChanged.connect(self._setMax) self.mediaPlayer.seekableChanged.connect(self.ui.seekSlider.setEnabled) self.mediaPlayer.positionChanged.connect(self._slide) self.ui.seekSlider.valueChanged.connect(self.mediaPlayer.setPosition) mc = QMediaContent(QUrl(video.url)) self.mediaPlayer.setMedia(mc) self.ui.videoPlayer.setMediaObject(self.mediaPlayer) self.mediaPlayer.play()
def get_account_status(self): return ( StatusField(u'myname', u'My name', unicode(self.browser.get_my_name())), StatusField(u'score', u'Score', unicode(self.browser.score())), StatusField(u'avcharms', u'Available charms', unicode(self.browser.nb_available_charms())), StatusField(u'newvisits', u'New visits', unicode(self.browser.nb_new_visites())), )
def filter(self, el): try: el = el[0] except IndexError: return self.default_or_raise(XPathNotFound('Unable to find element %s' % self.selector)) if el.tag == 'input': # checkboxes or radios if el.attrib.get('type') in ('radio', 'checkbox'): return 'checked' in el.attrib # regular text input elif el.attrib.get('type', '') in ('', 'text', 'email', 'search', 'tel', 'url'): try: return unicode(el.attrib['value']) except KeyError: return self.default_or_raise(AttributeNotFound('Element %s does not have attribute value' % el)) # TODO handle html5 number, datetime, etc. else: raise UnrecognizedElement('Element %s is recognized' % el) elif el.tag == 'textarea': return unicode(el.text) elif el.tag == 'select': options = el.xpath('.//option[@selected]') # default is the first one if len(options) == 0: options = el.xpath('.//option[1]') return u'\n'.join([unicode(o.text) for o in options]) else: raise UnrecognizedElement('Element %s is recognized' % el)
def unique_id(self, seen=None, account_id=None): """ Get an unique ID for the transaction based on date, amount and raw. :param seen: if given, the method uses this dictionary as a cache to prevent several transactions with the same values to have the same unique ID. :type seen: :class:`dict` :param account_id: if given, add the account ID in data used to create the unique ID. Can be useful if you want your ID to be unique across several accounts. :type account_id: :class:`str` :returns: an unique ID encoded in 8 length hexadecimal string (for example ``'a64e1bc9'``) :rtype: :class:`str` """ crc = crc32(unicode(self.date).encode('utf-8')) crc = crc32(unicode(self.amount).encode('utf-8'), crc) if not empty(self.raw): label = self.raw else: label = self.label crc = crc32(re.sub('[ ]+', ' ', label).encode("utf-8"), crc) if account_id is not None: crc = crc32(unicode(account_id).encode('utf-8'), crc) if seen is not None: while crc in seen: crc = crc32(b"*", crc) seen.add(crc) return "%08x" % (crc & 0xffffffff)
def create_transfer(self, account, recipient, transfer): transfer = Transfer() transfer.currency = FrenchTransaction.Currency('.//tr[td[contains(text(), "Montant")]]/td[not(@class)] | \ .//tr[th[contains(text(), "Montant")]]/td[not(@class)]')(self.doc) transfer.amount = CleanDecimal('.//tr[td[contains(text(), "Montant")]]/td[not(@class)] | \ .//tr[th[contains(text(), "Montant")]]/td[not(@class)]', replace_dots=True)(self.doc) transfer.account_iban = account.iban if recipient.category == u'Externe': for word in Upper(CleanText(u'.//tr[th[contains(text(), "Compte à créditer")]]/td[not(@class)]'))(self.doc).split(): if is_iban_valid(word): transfer.recipient_iban = word break else: raise TransferError('Unable to find IBAN (original was %s)' % recipient.iban) else: transfer.recipient_iban = recipient.iban transfer.account_id = unicode(account.id) transfer.recipient_id = unicode(recipient.id) transfer.exec_date = Date(CleanText('.//tr[th[contains(text(), "En date du")]]/td[not(@class)]'), dayfirst=True)(self.doc) transfer.label = (CleanText(u'.//tr[td[contains(text(), "Motif de l\'opération")]]/td[not(@class)]')(self.doc) or CleanText(u'.//tr[td[contains(text(), "Libellé")]]/td[not(@class)]')(self.doc) or CleanText(u'.//tr[th[contains(text(), "Libellé")]]/td[not(@class)]')(self.doc)) transfer.account_label = account.label transfer.recipient_label = recipient.label transfer._account = account transfer._recipient = recipient transfer.account_balance = account.balance return transfer
def edit_issue(self, issue, edit=True): backend = self.weboob.get_backend(issue.backend) content = self.issue2text(issue, backend) while True: if self.stdin.isatty(): content = self.acquire_input(content, {'vim': "-c 'set ft=mail'"}) m = message_from_string(content.encode('utf-8')) else: m = message_from_file(self.stdin) try: email_to = self.text2issue(issue, m) except ValueError as e: if not self.stdin.isatty(): raise input("%s -- Press Enter to continue..." % unicode(e).encode("utf-8")) continue try: issue = backend.post_issue(issue) print('Issue %s %s' % (self.formatter.colored(issue.fullid, 'red', 'bold'), 'updated' if edit else 'created')) if edit: self.format(issue) elif email_to: self.send_notification(email_to, issue) return 0 except IssueError as e: if not self.stdin.isatty(): raise input("%s -- Press Enter to continue..." % unicode(e).encode("utf-8"))
def iter_persons(self, pattern): params = [('partner', self.PARTNER_KEY), ('q', pattern), ('format', 'json'), ('filter', 'person')] jres = self.__do_request('search', params) if jres is None: return if 'person' not in jres['feed']: return for p in jres['feed']['person']: thumbnail_url = NotAvailable if 'picture' in p: thumbnail_url = unicode(p['picture']['href']) person = Person(p['code'], unicode(p['name'])) desc = u'' if 'birthDate' in p: desc += '(%s), ' % p['birthDate'] if 'activity' in p: for a in p['activity']: desc += '%s, ' % a['$'] person.real_name = NotLoaded person.birth_place = NotLoaded person.birth_date = NotLoaded person.death_date = NotLoaded person.gender = NotLoaded person.nationality = NotLoaded person.short_biography = NotLoaded person.short_description = desc.strip(', ') person.roles = NotLoaded person.thumbnail_url = thumbnail_url yield person
def parse(self): self.url = '%s#%s' % (self.preurl, self.div.attrib['id']) self.title = unicode(self.div.find('h2').xpath('.//a[has-class("title")]')[0].text) try: a = self.div.find('p').xpath('.//a[@rel="author"]')[0] except IndexError: self.author = 'Anonyme' self.username = None else: self.author = unicode(a.text) self.username = unicode(a.attrib['href'].split('/')[2]) self.date = datetime.strptime(self.div.find('p').xpath('.//time')[0].attrib['datetime'].split('+')[0], '%Y-%m-%dT%H:%M:%S') self.date = local2utc(self.date) content = self.div.find('div') try: signature = content.xpath('.//p[has-class("signature")]')[0] except IndexError: # No signature. pass else: content.remove(signature) self.signature = lxml.html.tostring(signature).decode('utf-8') self.body = lxml.html.tostring(content).decode('utf-8') self.score = int(self.div.find('p').xpath('.//span[has-class("score")]')[0].text) forms = self.div.find('footer').xpath('.//form[has-class("button_to")]') if len(forms) > 0: self.relevance_url = forms[0].attrib['action'].rstrip('for').rstrip('against') self.relevance_token = forms[0].xpath('.//input[@name="authenticity_token"]')[0].attrib['value']
def get_accounts_list(self): for table in self.doc.xpath('//div[@class="comptestabl"]/table'): try: account_type = self.ACCOUNT_TYPES[table.get('summary').lower()] if not account_type: account_type = self.ACCOUNT_TYPES[table.xpath('./caption/text()')[0].strip().lower()] except (IndexError,KeyError): account_type = Account.TYPE_UNKNOWN for tr in table.xpath('./tbody/tr'): cols = tr.findall('td') link = cols[0].find('a') if link is None: continue a = Account() a.type = account_type a.id = unicode(re.search('([A-Z\d]{4}[A-Z\d\*]{3}[A-Z\d]{4})', link.attrib['title']).group(1)) a.label = unicode(link.attrib['title'].replace('%s ' % a.id, '')) tmp_balance = CleanText(None).filter(cols[1]) a.currency = a.get_currency(tmp_balance) if not a.currency: a.currency = u'EUR' a.balance = Decimal(Transaction.clean_amount(tmp_balance)) a._has_cards = False a.url = urljoin(self.url, link.attrib['href']) yield a
def __init__(self, browser, url, tree): super(Article, self).__init__(browser) self.url = url self.id = url2id(self.url) if tree is None: return header = tree.find('header') self.title = u' — '.join([a.text for a in header.find('h1').xpath('.//a')]) try: a = header.xpath('.//a[@rel="author"]')[0] except IndexError: self.author = 'Anonyme' self.username = None else: self.author = unicode(a.text) self.username = unicode(a.attrib['href'].split('/')[2]) self.body = lxml.html.tostring(tree.xpath('.//div[has-class("content")]')[0]).decode('utf-8') try: self.date = datetime.strptime(header.xpath('.//time')[0].attrib['datetime'].split('+')[0], '%Y-%m-%dT%H:%M:%S') self.date = local2utc(self.date) except IndexError: pass for form in tree.find('footer').xpath('//form[has-class("button_to")]'): if form.attrib['action'].endswith('/for'): self.relevance_url = form.attrib['action'].rstrip('for').rstrip('against') self.relevance_token = form.xpath('.//input[@name="authenticity_token"]')[0].attrib['value'] self.score = int(tree.xpath('.//div[has-class("figures")]//figure[has-class("score")]')[0].text)
def get_arte_cinema_categories(self, cat=[]): menu = self.videos_list.go(site=SITE.CINEMA.get('id'), lang=self.lang.get('site'), cat='').get_arte_cinema_menu() menuSplit = map(lambda x: x.split("/")[2:], menu) result = {} for record in menuSplit: here = result for item in record[:-1]: if item not in here: here[item] = {} here = here[item] if "end" not in here: here["end"] = [] here["end"].append(record[-1]) cat = cat if not cat else cat[1:] if not cat and "end" in result: del result["end"] for el in cat: result = result.get(el) if "end" in result.keys(): return self.page.iter_arte_cinema_categories(cat='/'.join(cat)) else: categories = [] for item in result.keys(): if item == "programs": continue categories.append(Collection([SITE.CINEMA.get('id'), unicode(item)], unicode(item))) return categories
def iter_investment(self): for tr in self.doc.xpath("//table/tbody/tr[starts-with(@class, 'net2g_asv_tableau_ligne_')]"): cells = tr.findall('td') inv = self.create_investment(cells) inv.label = unicode(cells[self.COL_LABEL].xpath('a/span')[0].text.strip()) inv.description = unicode(cells[self.COL_LABEL].xpath('a//div/b[last()]')[0].tail) yield inv
def iter_investment(self): not_rounded_valuations = self.get_not_rounded_valuations() doc = self.browser.open('/brs/fisc/fisca10a.html').page.doc num_page = None try: num_page = int(CleanText('.')(doc.xpath(u'.//tr[contains(td[1], "Relevé des plus ou moins values latentes")]/td[2]')[0]).split('/')[1]) except IndexError: pass docs = [doc] if num_page: for n in range(2, num_page + 1): docs.append(self.browser.open('%s%s' % ('/brs/fisc/fisca10a.html?action=12&numPage=', str(n))).page.doc) for doc in docs: # There are two different tables possible depending on the market account type. is_detailed = bool(doc.xpath(u'//span[contains(text(), "Années d\'acquisition")]')) tr_xpath = '//tr[@height and td[@colspan="6"]]' if is_detailed else '//tr[count(td)>5]' for tr in doc.xpath(tr_xpath): cells = tr.findall('td') inv = Investment() title_split = cells[self.COL_LABEL].xpath('.//span')[0].attrib['title'].split(' - ') inv.label = unicode(title_split[0]) for code in title_split[1:]: if is_isin_valid(code): inv.code = unicode(code) inv.code_type = Investment.CODE_TYPE_ISIN break else: inv.code = NotAvailable inv.code_type = NotAvailable if is_detailed: inv.quantity = MyDecimal('.')(tr.xpath('./following-sibling::tr/td[2]')[0]) inv.unitprice = MyDecimal('.', replace_dots=True)(tr.xpath('./following-sibling::tr/td[3]')[1]) inv.unitvalue = MyDecimal('.', replace_dots=True)(tr.xpath('./following-sibling::tr/td[3]')[0]) try: # try to get not rounded value inv.valuation = not_rounded_valuations[inv.label] except KeyError: # ok.. take it from the page inv.valuation = MyDecimal('.')(tr.xpath('./following-sibling::tr/td[4]')[0]) inv.diff = MyDecimal('.')(tr.xpath('./following-sibling::tr/td[5]')[0]) or \ MyDecimal('.')(tr.xpath('./following-sibling::tr/td[6]')[0]) else: inv.quantity = MyDecimal('.')(cells[self.COL_QUANTITY]) inv.diff = MyDecimal('.')(cells[self.COL_DIFF]) inv.unitprice = MyDecimal('.')(cells[self.COL_UNITPRICE].xpath('.//tr[1]/td[2]')[0]) inv.unitvalue = MyDecimal('.')(cells[self.COL_VALUATION].xpath('.//tr[1]/td[2]')[0]) inv.valuation = MyDecimal('.')(cells[self.COL_VALUATION].xpath('.//tr[2]/td[2]')[0]) yield inv
def load_state(self, state): super(LCLBrowser, self).load_state(state) # lxml _ElementStringResult were put in the state, convert them to plain strs # TODO to remove at some point if self.contracts: self.contracts = [unicode(s) for s in self.contracts] if self.current_contract: self.current_contract = unicode(self.current_contract)
def get_emissions(self, basename): params = [('partner', self.PARTNER_KEY), ('format', 'json'), ('filter', 'acshow'), ] result = self.__do_request('termlist', params) if result is None: return for emission in result['feed']['term']: yield Collection([basename, unicode(emission['nameShort'])], unicode(emission['$']))
def iter_transactions(self): for li in self.doc.xpath('//section[@class="transactions"]//div/li'): date = li.xpath('p[@data-type="date"]//text()')[0].strip() label = li.xpath('p[@data-type="description"]//text()')[0].strip() amount = li.xpath('p[@data-type="amount"]//text()')[0].strip() t = Transaction() t.date = datetime.strptime(date, '%m/%d/%Y') t.rdate = datetime.strptime(date, '%m/%d/%Y') t.type = Transaction.TYPE_UNKNOWN t.raw = unicode(label) t.label = unicode(label) t.amount = -AmTr.decimal_amount(amount) yield t
def parse_video(self, _video, category): video = BaseVideo(u'%s#%s' % (_video['code'], category)) video.title = unicode(_video['title']) video._video_code = unicode(_video['code']) video.ext = u'mp4' if 'runtime' in _video: video.duration = timedelta(seconds=int(_video['runtime'])) if 'description' in _video: video.description = unicode(_video['description']) renditions = sorted(_video['rendition'], key=lambda x: 'bandwidth' in x and x['bandwidth']['code'], reverse=True) video.url = unicode(max(renditions, key=lambda x: 'bandwidth' in x)['href']) return video
def obj_url(self): links = XPath('//div[@id="download_links"]/div[@class="paragraph"]/div[has-class("share")]/a[@target="_blank"]/@href')(self) for link in links: ext = str(link).split('.')[-1] self.logger.debug("Link:%s Ext:%s", link, ext) if ext in ['mp4', 'webm']: return self.page.browser.BASEURL + unicode(link)
def iter_movies(self, pattern): res = self.open('http://www.imdb.com/xml/find?json=1&nr=1&tt=on', params={'q': pattern}) jres = res.json() htmlparser = HTMLParser() for cat in ['title_popular', 'title_exact', 'title_approx']: if cat in jres: for m in jres[cat]: tdesc = unicode(m['title_description']) if '<a' in tdesc and '>' in tdesc: short_description = u'%s %s' % (tdesc.split('<')[ 0].strip(', '), tdesc.split('>')[1].split('<')[0]) else: short_description = tdesc.strip(', ') movie = Movie(m['id'], htmlparser.unescape(m['title'])) movie.other_titles = NotLoaded movie.release_date = NotLoaded movie.duration = NotLoaded movie.short_description = htmlparser.unescape(short_description) movie.pitch = NotLoaded movie.country = NotLoaded movie.note = NotLoaded movie.roles = NotLoaded movie.all_release_dates = NotLoaded movie.thumbnail_url = NotLoaded yield movie
def retrieve_index(self, browser, repo_path): """ Retrieve the index file of this repository. It can use network if this is a remote repository. :param repo_path: path to save the downloaded index file. :type repo_path: str """ if self.local: # Repository is local, open the file. filename = os.path.join(self.localurl2path(), self.INDEX) try: fp = open(filename, 'r') except IOError as e: # This local repository doesn't contain a built modules.list index. self.name = Repositories.url2filename(self.url) self.build_index(self.localurl2path(), filename) fp = open(filename, 'r') else: # This is a remote repository, download file try: fp = StringIO(browser.open(posixpath.join(self.url, self.INDEX)).text) except BrowserHTTPError as e: raise RepositoryUnavailable(unicode(e)) self.parse_index(fp) if self.local: # Always rebuild index of a local repository. self.build_index(self.localurl2path(), filename) # Save the repository index in ~/.weboob/repositories/ self.save(repo_path, private=True)
def load_backends(self, *args, **kwargs): while True: last_exc = None try: return Application.load_backends(self, *args, **kwargs) except VersionsMismatchError as e: msg = 'Versions of modules mismatch with version of weboob.' except ConfigError as e: msg = unicode(e) last_exc = e res = QMessageBox.question(None, 'Configuration error', u'%s\n\nDo you want to update repositories?' % msg, QMessageBox.Yes|QMessageBox.No) if res == QMessageBox.No: raise last_exc # Do not import it globally, it causes circular imports from .backendcfg import ProgressDialog pd = ProgressDialog('Update of repositories', "Cancel", 0, 100) pd.setWindowModality(Qt.WindowModal) try: self.weboob.update(pd) except ModuleInstallError as err: QMessageBox.critical(None, self.tr('Update error'), self.tr('Unable to update repositories: %s' % err), QMessageBox.Ok) pd.setValue(100) QMessageBox.information(None, self.tr('Update of repositories'), self.tr('Repositories updated!'), QMessageBox.Ok)
def do_cd(self, line): """ cd [PATH] Follow a path. ".." is a special case and goes up one directory. "" is a special case and goes home. """ if not len(line.strip()): self.working_path.home() elif line.strip() == '..': self.working_path.up() else: self.working_path.cd1(line) collections = [] try: for res in self.do('get_collection', objs=self.COLLECTION_OBJECTS, split_path=self.working_path.get(), caps=CapCollection): if res: collections.append(res) except CallErrors as errors: self.bcall_errors_handler(errors, CollectionNotFound) if len(collections): # update the path from the collection if possible if len(collections) == 1: self.working_path.split_path = collections[0].split_path else: print(u"Path: %s not found" % unicode(self.working_path), file=self.stderr) self.working_path.restore() return 1 self._change_prompt()
def retrieve_keyring(self, browser, keyring_path, progress): # ignore local if self.local: return keyring = Keyring(keyring_path) # prevent previously signed repos from going unsigned if not self.signed and keyring.exists(): raise RepositoryUnavailable('Previously signed repository can not go unsigned') if not self.signed: return if not keyring.exists() or self.key_update > keyring.version: # This is a remote repository, download file try: keyring_data = browser.open(posixpath.join(self.url, self.KEYRING)).content sig_data = browser.open(posixpath.join(self.url, self.KEYRING + '.sig')).content except BrowserHTTPError as e: raise RepositoryUnavailable(unicode(e)) if keyring.exists(): if not keyring.is_valid(keyring_data, sig_data): raise InvalidSignature('the keyring itself') progress.progress(0.0, 'The keyring was updated (and validated by the previous one).') elif not progress.prompt('The repository %s isn\'t trusted yet.\nFingerprint of keyring is %s\nAre you sure you want to continue?' % (self.url, hashlib.sha1(keyring_data).hexdigest())): raise RepositoryUnavailable('Repository not trusted') keyring.save(keyring_data, self.key_update) progress.progress(0.0, str(keyring))
def update(self, progress=PrintProgress()): """ Update repositories and install new packages versions. :param progress: observer object. :type progress: :class:`IProgress` """ self.update_repositories(progress) to_update = [] for name, info in self.get_all_modules_info().items(): if not info.is_local() and info.is_installed(): if self.versions.get(name) != info.version: to_update.append(info) if len(to_update) == 0: progress.progress(1.0, 'All modules are up-to-date.') return class InstallProgress(PrintProgress): def __init__(self, n): self.n = n def progress(self, percent, message): progress.progress(float(self.n)/len(to_update) + 1.0/len(to_update)*percent, message) for n, info in enumerate(to_update): inst_progress = InstallProgress(n) try: self.install(info, inst_progress) except ModuleInstallError as e: inst_progress.progress(1.0, unicode(e))
def __init__(self, value): super(_QtValueStr, self).__init__() self._value = value if value.default: self.setText(unicode(value.default)) if value.masked: self.setEchoMode(self.Password)
def __init__(self, movie, backend, parent=None): super(Movie, self).__init__(parent) self.parent = parent self.ui = Ui_Movie() self.ui.setupUi(self) langs = sorted(LANGUAGE_CONV.keys()) for lang in langs: self.ui.langCombo.addItem(lang) self.ui.castingButton.clicked.connect(self.casting) self.ui.torrentButton.clicked.connect(self.searchTorrent) self.ui.subtitleButton.clicked.connect(self.searchSubtitle) self.ui.personsInCommonButton.clicked.connect(self.personsInCommon) self.movie = movie self.backend = backend self.ui.titleLabel.setText(movie.original_title) self.ui.durationLabel.setText(unicode(movie.duration)) self.gotThumbnail() self.putReleases() self.ui.idEdit.setText(u'%s@%s' % (movie.id, backend.name)) if not empty(movie.other_titles): self.ui.otherTitlesPlain.setPlainText('\n'.join(movie.other_titles)) else: self.ui.otherTitlesPlain.parent().hide() if not empty(movie.genres): genres = u'' for g in movie.genres: genres += '%s, ' % g genres = genres[:-2] self.ui.genresLabel.setText(genres) else: self.ui.genresLabel.parent().hide() if not empty(movie.release_date): self.ui.releaseDateLabel.setText(movie.release_date.strftime('%Y-%m-%d')) else: self.ui.releaseDateLabel.parent().hide() if not empty(movie.duration): self.ui.durationLabel.setText('%s min' % movie.duration) else: self.ui.durationLabel.parent().hide() if not empty(movie.pitch): self.ui.pitchPlain.setPlainText('%s' % movie.pitch) else: self.ui.pitchPlain.parent().hide() if not empty(movie.country): self.ui.countryLabel.setText('%s' % movie.country) else: self.ui.countryLabel.parent().hide() if not empty(movie.note): self.ui.noteLabel.setText('%s' % movie.note) else: self.ui.noteLabel.parent().hide() for role in movie.roles.keys(): self.ui.castingCombo.addItem('%s' % role) self.ui.verticalLayout.setAlignment(Qt.AlignTop) self.ui.verticalLayout_2.setAlignment(Qt.AlignTop)
def get_loan_list(self): accounts = OrderedDict() # Old website for tr in self.doc.xpath('//table[@cellpadding="1"]/tr[not(@class) and td[a]]'): tds = tr.findall('td') account = Account() account.id = CleanText('./a')(tds[2]).split('-')[0].strip() account.label = CleanText('./a')(tds[2]).split('-')[-1].strip() account.type = Account.TYPE_LOAN account.balance = -CleanDecimal('./a', replace_dots=True)(tds[4]) account.currency = account.get_currency(CleanText('./a')(tds[4])) accounts[account.id] = account if len(accounts) == 0: # New website for table in self.doc.xpath('//div[@class="panel"]'): title = table.getprevious() if title is None: continue account_type = self.ACCOUNT_TYPES.get(CleanText('.')(title), Account.TYPE_UNKNOWN) for tr in table.xpath('./table/tbody/tr[contains(@id,"MM_SYNTHESE_CREDITS") and contains(@id,"IdTrGlobal")]'): tds = tr.findall('td') if len(tds) == 0 : continue for i in tds[0].xpath('.//a/strong'): label = i.text.strip() break if len(tds) == 3 and Decimal(FrenchTransaction.clean_amount(CleanText('.')(tds[-2]))) and any(cls in Attr('.', 'id')(tr) for cls in ['dgImmo', 'dgConso']) == False: # in case of Consumer credit or revolving credit, we substract avalaible amount with max amout # to get what was spend balance = Decimal(FrenchTransaction.clean_amount(CleanText('.')(tds[-2]))) - Decimal(FrenchTransaction.clean_amount(CleanText('.')(tds[-1]))) else: balance = Decimal(FrenchTransaction.clean_amount(CleanText('.')(tds[-1]))) account = Loan() account.id = label.split(' ')[-1] account.label = unicode(label) account.type = account_type account.balance = -abs(balance) account.currency = account.get_currency(CleanText('.')(tds[-1])) account._card_links = [] if "immobiliers" in CleanText('.')(title): xp = './/div[contains(@id, "IdDivDetail")]/table/tbody/tr[contains(@id, "%s")]/td' account.maturity_date = Date(CleanText(xp % 'IdDerniereEcheance'), dayfirst=True, default=NotAvailable)(tr) account.total_amount = CleanDecimal(CleanText(xp % 'IdCapitalEmprunte'), replace_dots=True, default=NotAvailable)(tr) account.subscription_date = Date(CleanText(xp % 'IdDateOuverture'), dayfirst=True, default=NotAvailable)(tr) account.next_payment_date = Date(CleanText(xp % 'IdDateProchaineEcheance'), dayfirst=True, default=NotAvailable)(tr) account.rate = CleanDecimal(CleanText(xp % 'IdTaux'), replace_dots=True, default=NotAvailable)(tr) account.next_payment_amount = CleanDecimal(CleanText(xp % 'IdMontantEcheance'), replace_dots=True, default=NotAvailable)(tr) elif "renouvelables" in CleanText('.')(title): self.go_loans_conso(tr) d = self.browser.loans_conso() if d: account.total_amount = d['contrat']['creditMaxAutorise'] account.available_amount = d['situationCredit']['disponible'] account.next_payment_amount = d['situationCredit']['mensualiteEnCours'] accounts[account.id] = account return accounts.values()
def __init__(self, _id, title=NotLoaded, language=NotLoaded, contents=NotLoaded, public=NotLoaded, url=None): super(BasePaste, self).__init__(unicode(_id), url) self.title = title self.language = language self.contents = contents self.public = public
def dump(self, **kwargs): kwargs.pop('since_seconds', None) target = os.path.splitext(self.path)[0] + '.sql' with tempfile.NamedTemporaryFile(dir=os.path.dirname(self.path), delete=False) as f: for line in self.storage.iterdump(): f.write(unicode(line).encode('utf-8')) f.write(b'\n') replace(f.name, target)
def get_account_status(self): return (StatusField(u'myname', u'My name', unicode(self.browser.my_name)), StatusField(u'credits', u'Credits', unicode(self.browser.credits)), )
def send_email(self, backend_name, mail): domain = self.config.get('domain') recipient = self.config.get('recipient') parent_message = mail.parent references = [] while parent_message: references.append(u'<%s.%s@%s>' % (backend_name, mail.parent.full_id, domain)) parent_message = parent_message.parent subject = mail.title sender = u'"%s" <%s@%s>' % (mail.sender.replace('"', '""') if mail.sender else '', backend_name, domain) # assume that .date is an UTC datetime date = formatdate(time.mktime(utc2local(mail.date).timetuple()), localtime=True) msg_id = u'<%s.%s@%s>' % (backend_name, mail.full_id, domain) if self.config.get('html') and mail.flags & mail.IS_HTML: body = mail.content content_type = 'html' else: if mail.flags & mail.IS_HTML: body = html2text(mail.content) else: body = mail.content content_type = 'plain' if body is None: body = '' if mail.signature: if self.config.get('html') and mail.flags & mail.IS_HTML: body += u'<p>-- <br />%s</p>' % mail.signature else: body += u'\n\n-- \n' if mail.flags & mail.IS_HTML: body += html2text(mail.signature) else: body += mail.signature # Header class is smart enough to try US-ASCII, then the charset we # provide, then fall back to UTF-8. header_charset = 'ISO-8859-1' # We must choose the body charset manually for body_charset in 'US-ASCII', 'ISO-8859-1', 'UTF-8': try: body.encode(body_charset) except UnicodeError: pass else: break # Split real name (which is optional) and email address parts sender_name, sender_addr = parseaddr(sender) recipient_name, recipient_addr = parseaddr(recipient) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(unicode(sender_name), header_charset)) recipient_name = str(Header(unicode(recipient_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode('ascii') recipient_addr = recipient_addr.encode('ascii') # Create the message ('plain' stands for Content-Type: text/plain) msg = MIMEText(body.encode(body_charset), content_type, body_charset) msg['From'] = formataddr((sender_name, sender_addr)) msg['To'] = formataddr((recipient_name, recipient_addr)) msg['Subject'] = Header(unicode(subject), header_charset) msg['Message-Id'] = msg_id msg['Date'] = date if references: msg['In-Reply-To'] = references[0] msg['References'] = u" ".join(reversed(references)) self.logger.info('Send mail from <%s> to <%s>' % (sender, recipient)) if len(self.config.get('pipe')) > 0: p = subprocess.Popen(self.config.get('pipe'), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) p.stdin.write(msg.as_string()) p.stdin.close() if p.wait() != 0: self.logger.error('Unable to deliver mail: %s' % p.stdout.read().strip()) return False else: # Send the message via SMTP to localhost:25 try: smtp = SMTP(self.config.get('smtp')) smtp.sendmail(sender, recipient, msg.as_string()) except Exception as e: self.logger.error('Unable to deliver mail: %s' % e) return False else: smtp.quit() return True
def rebuild_rib(rib): rib = clean(rib) assert len(rib) >= 21 key = find_rib_checksum(rib[:5], rib[5:10], rib[10:21]) return unicode(rib[:21] + ('%02d' % key))
def __init__(self, movie, backend, parent=None): super(Movie, self).__init__(parent) self.parent = parent self.ui = Ui_Movie() self.ui.setupUi(self) langs = sorted(LANGUAGE_CONV.keys()) for lang in langs: self.ui.langCombo.addItem(lang) self.ui.castingButton.clicked.connect(self.casting) self.ui.torrentButton.clicked.connect(self.searchTorrent) self.ui.subtitleButton.clicked.connect(self.searchSubtitle) self.ui.personsInCommonButton.clicked.connect(self.personsInCommon) self.movie = movie self.backend = backend self.ui.titleLabel.setText(movie.original_title) self.ui.durationLabel.setText(unicode(movie.duration)) self.gotThumbnail() self.putReleases() self.ui.idEdit.setText(u'%s@%s' % (movie.id, backend.name)) if not empty(movie.other_titles): self.ui.otherTitlesPlain.setPlainText('\n'.join( movie.other_titles)) else: self.ui.otherTitlesPlain.parent().hide() if not empty(movie.genres): genres = u'' for g in movie.genres: genres += '%s, ' % g genres = genres[:-2] self.ui.genresLabel.setText(genres) else: self.ui.genresLabel.parent().hide() if not empty(movie.release_date): self.ui.releaseDateLabel.setText( movie.release_date.strftime('%Y-%m-%d')) else: self.ui.releaseDateLabel.parent().hide() if not empty(movie.duration): self.ui.durationLabel.setText('%s min' % movie.duration) else: self.ui.durationLabel.parent().hide() if not empty(movie.pitch): self.ui.pitchPlain.setPlainText('%s' % movie.pitch) else: self.ui.pitchPlain.parent().hide() if not empty(movie.country): self.ui.countryLabel.setText('%s' % movie.country) else: self.ui.countryLabel.parent().hide() if not empty(movie.note): self.ui.noteLabel.setText('%s' % movie.note) else: self.ui.noteLabel.parent().hide() for role in movie.roles.keys(): self.ui.castingCombo.addItem('%s' % role) self.ui.verticalLayout.setAlignment(Qt.AlignTop) self.ui.verticalLayout_2.setAlignment(Qt.AlignTop)
def __init__(self, backend_name, exception): super(WebNip.LoadError, self).__init__(unicode(exception)) self.backend_name = backend_name
def __init__(self, id, name, value, url=None): super(Status, self).__init__(id, url) self.name = unicode(name) self.value = value
def run(self): now = datetime.datetime.now().strftime("%Y-%m-%d") if self.boomoney.options.force: from_date = self.date_min else: from_date = self.last_date if from_date >= now: self.boomoney.print( Style.BRIGHT + "%s (%s): Last import date is %s, no need to import again..." % (self.account, self.label, self.last_date) + Style.RESET_ALL) return boobank = self.boomoney.createBoobank(self.account) if boobank is None: with numMutex: self.boomoney.importIndex = self.boomoney.importIndex + 1 return boobank.stderr = StringIO() boobank.stdout = boobank.stderr id, backend = self.account.split("@") module_name, foo = boobank.weboob.backends_config.get_backend(backend) moduleHandler = "%s.bat" % os.path.join( os.path.dirname(self.boomoney.getMoneyFile()), module_name) self.boomoney.logger.info("Starting history of %s (%s)..." % (self.account, self.label)) MAX_RETRIES = 3 count = 0 found = False content = '' boobank.error = False while count <= MAX_RETRIES and not (found and not boobank.error): boobank.options.outfile = StringIO() boobank.error = False # executing history command boobank.onecmd("history " + self.account + " " + from_date) if count > 0: self.boomoney.logger.info( "Retrying %s (%s)... %i/%i" % (self.account, self.label, count, MAX_RETRIES)) found = re.match(r'^OFXHEADER:100', boobank.options.outfile.getvalue()) if found and not boobank.error: content = boobank.options.outfile.getvalue() boobank.options.outfile.close() count = count + 1 if content == '': # error occurred with numMutex: self.boomoney.importIndex = self.boomoney.importIndex + 1 index = self.boomoney.importIndex self.boomoney.logger.error( "(%i/%i) %s (%s): %saborting after %i retries.%s" % (index, len(self.boomoney.threads), self.account, self.label, Fore.RED + Style.BRIGHT, MAX_RETRIES, Style.RESET_ALL)) return # postprocessing of the ofx content to match MSMoney expectations content = re.sub(r'<BALAMT>Not loaded', r'<BALAMT></BALAMT>', content) input = StringIO(content) output = StringIO() field = {} fields = ' ' for line in input: if re.match(r'^OFXHEADER:100', line): inTransaction = False if re.match(r'^<STMTTRN>', line): inTransaction = True if not inTransaction: output.write(line) if re.match(r'^</STMTTRN>', line): # MSMoney expects CHECKNUM instead of NAME for CHECK transactions if "TRNTYPE" in field and field["TRNTYPE"] == "CHECK": if "NAME" in field and unicode(field["NAME"]).isnumeric(): field["CHECKNUM"] = field["NAME"] del field["NAME"] fields = fields.replace(' NAME ', ' CHECKNUM ') # go through specific backend process if any IGNORE = False NEW = None origfields = fields origfield = field.copy() if os.path.exists(moduleHandler): self.boomoney.logger.info("Calling backend handler %s..." % moduleHandler) # apply the transformations, in the form # field_NAME=... # field_MEMO=... # field=... cmd = 'cmd /C ' for f in field: value = field[f] cmd = cmd + 'set field_%s=%s& ' % (f, value) cmd = cmd + '"' + moduleHandler + '"' result = subprocess.check_output( cmd.encode(sys.stdout.encoding)) for line in re.split(r'[\r\n]+', result): if not line == "": f, value = line.split("=", 1) if f == "IGNORE": IGNORE = True elif f == "NEW": NEW = value elif f.startswith('field_'): f = re.sub(r'^field_', '', f) if value == "": if f in field: del field[f] fields = re.sub(" " + f + " ", " ", fields) else: field[f] = value if f not in fields.strip().split(" "): # MSMoney does not like when CHECKNUM is after MEMO if f == "CHECKNUM": fields = fields.replace( "MEMO", "CHECKNUM MEMO") else: fields = fields + f + " " if not IGNORE: # dump transaction self.dumpTransaction(output, fields, field) if NEW is not None: for n in NEW.strip().split(" "): fields = origfields field = origfield.copy() field["FITID"] = origfield["FITID"] + "_" + n for line in re.split(r'[\r\n]+', result): if not line == "": f, value = line.split("=", 1) if f.startswith(n + '_field_'): f = re.sub(r'^.*_field_', '', f) field[f] = value if f not in fields.strip().split(" "): fields = fields + f + " " # dump secondary transaction self.dumpTransaction(output, fields, field) inTransaction = False if inTransaction: if re.match(r'^<STMTTRN>', line): field = {} fields = ' ' else: t = line.split(">", 1) v = re.split(r'[\r\n]', t[1]) field[t[0][1:]] = v[0] fields = fields + t[0][1:] + ' ' ofxcontent = output.getvalue() stderrcontent = boobank.stderr.getvalue() input.close() output.close() boobank.stderr.close() if self.boomoney.options.display: self.boomoney.print(Style.BRIGHT + ofxcontent + Style.RESET_ALL) nbTransactions = ofxcontent.count('<STMTTRN>') # create ofx file fname = re.sub(r'[^\w@\. ]', '_', self.account + " " + self.label) ofxfile = os.path.join(self.boomoney.getDownloadsPath(), fname + ".ofx") with open(ofxfile, "w") as ofx_file: ofx_file.write( re.sub(r'\r\n', r'\n', ofxcontent.encode(sys.stdout.encoding))) with numMutex: self.boomoney.write(stderrcontent) self.boomoney.importIndex = self.boomoney.importIndex + 1 index = self.boomoney.importIndex if not (self.boomoney.options.noimport or nbTransactions == 0): self.boomoney.backupIfNeeded() with printMutex: if self.boomoney.options.noimport or nbTransactions == 0: if nbTransactions == 0: print(Style.BRIGHT + '(%i/%i) %s (%s) (no transaction).' % (index, len(self.boomoney.threads), self.account, self.label) + Style.RESET_ALL) else: print(Fore.GREEN + Style.BRIGHT + '(%i/%i) %s (%s) (%i transaction(s)).' % (index, len(self.boomoney.threads), self.account, self.label, nbTransactions) + Style.RESET_ALL) else: # import into money print( Fore.GREEN + Style.BRIGHT + '(%i/%i) Importing "%s" into MSMoney (%i transaction(s))...' % (index, len(self.boomoney.threads), ofxfile, nbTransactions) + Style.RESET_ALL) if not self.boomoney.options.noimport: if nbTransactions > 0: subprocess.check_call('"%s" %s' % (os.path.join( self.boomoney.getMoneyPath(), "mnyimprt.exe"), ofxfile)) self.last_date = now
def fetch(self, which, modulename=None, login=None): """ Wrapper to fetch data from the Weboob connector. This wrapper fetches the required data from Weboob and returns it. It handles the translation between Weboob exceptions and Kresus error codes stored in the JSON response. :param which: The type of data to fetch. Can be either ``accounts`` or ``operations``. :param modulename: The name of the module from which data should be fetched. Optional, if not provided all available backends are used. :param login: The login to further filter on the available backends. Optional, if not provided all matching backends are used. :returns: A dict of the fetched data, in a ``values`` keys. Errors are described under ``error_code``, ``error_short`` and ``error_content`` keys. """ results = {} try: results['values'] = [] backends = self.get_backends(modulename, login) if which == 'accounts': fetch_function = self.get_accounts elif which == 'operations': fetch_function = self.get_operations else: raise Exception('Invalid fetch command.') for backend in backends: with backend: # Acquire lock on backend results['values'].extend(fetch_function(backend)) except NoAccountsException: results['error_code'] = NO_ACCOUNTS except ModuleLoadError: results['error_code'] = UNKNOWN_MODULE except BrowserPasswordExpired: results['error_code'] = EXPIRED_PASSWORD except ActionNeeded as exc: # This `except` clause is not in alphabetic order and cannot be, # because BrowserPasswordExpired (above) inherits from it in # Weboob 1.4. results['error_code'] = ACTION_NEEDED results['error_content'] = unicode(exc) except BrowserIncorrectPassword: # This `except` clause is not in alphabetic order and cannot be, # because BrowserPasswordExpired (above) inherits from it in # Weboob 1.3. results['error_code'] = INVALID_PASSWORD except Module.ConfigError as exc: results['error_code'] = INVALID_PARAMETERS results['error_content'] = unicode(exc) except ConnectionError as exc: results['error_code'] = CONNECTION_ERROR results['error_content'] = unicode(exc) except Exception as exc: fail(GENERIC_EXCEPTION, 'Unknown error: %s.' % unicode(exc), traceback.format_exc()) return results
def filter(self, values): values = [unicode(v) for v in values if v] if not values and self.default is not _NO_DEFAULT: return self.default return self.pattern.join(values)
def read_text(self, pos): t = self._tok.tok(pos) # TODO: handle PDF encodings properly. return (pos+1, unicode(t.value(), errors='ignore')) \ if t.is_text() else (pos, None)
def main(): """ Guess what? It's the main function! """ parser = argparse.ArgumentParser( description='Process CLI arguments for Kresus') parser.add_argument('command', choices=['test', 'version', 'operations', 'accounts'], help='The command to be executed by the script') parser.add_argument('--module', help="The weboob module name.") parser.add_argument('--login', help="The login for the access.") parser.add_argument('--password', help="The password for the access.") parser.add_argument('--field', nargs=2, action='append', help="Custom fields. Can be set several times.", metavar=('NAME', 'VALUE')) parser.add_argument('--debug', action='store_true', help="If set, the debug mode is activated.") parser.add_argument( '--update', action='store_true', help=("If set, the repositories will be updated prior to command " "accounts or operations.")) # Parse command from standard input. options = parser.parse_args() # Handle logging is_prod = os.environ.get('NODE_ENV', 'production') == 'production' if options.debug: init_logging(logging.DEBUG, is_prod) else: init_logging(logging.WARNING, is_prod) kresus_dir = os.environ.get('KRESUS_DIR', None) if kresus_dir is None: fail(INTERNAL_ERROR, "KRESUS_DIR must be set to use the weboob cli tool.", traceback.format_exc()) sources_list_content = None if ('WEBOOB_SOURCES_LIST' in os.environ and os.path.isfile(os.environ['WEBOOB_SOURCES_LIST'])): # Read the new content from the sources.list provided as env # variable. with io.open(os.environ['WEBOOB_SOURCES_LIST'], encoding="utf-8") as fh: sources_list_content = fh.read().splitlines() # Build a Weboob connector. try: weboob_connector = Connector( weboob_data_path=os.path.join(kresus_dir, 'weboob-data'), fakemodules_path=os.path.join(kresus_dir, 'fakemodules'), sources_list_content=sources_list_content, is_prod=is_prod, ) except ConnectionError as exc: fail(CONNECTION_ERROR, 'The connection seems down: %s' % unicode(exc), traceback.format_exc()) except Exception as exc: fail(WEBOOB_NOT_INSTALLED, ('Is weboob installed? Unknown exception raised: %s.' % unicode(exc)), traceback.format_exc()) # Handle the command and output the expected result on standard output, as # JSON encoded string. command = options.command if command == 'version': # Return Weboob version. obj = {'values': weboob_connector.version()} print(json.dumps(obj)) sys.exit() if options.update: # Update Weboob modules. try: weboob_connector.update() except ConnectionError as exc: fail(CONNECTION_ERROR, 'Exception when updating weboob: %s.' % unicode(exc), traceback.format_exc()) except Exception as exc: fail(GENERIC_EXCEPTION, 'Exception when updating weboob: %s.' % unicode(exc), traceback.format_exc()) if command == 'test': # Do nothing, just check we arrived so far. print(json.dumps({})) sys.exit() if command in ['accounts', 'operations']: if not options.module: fail_unset_field('Module') if not options.login: fail_unset_field('Login') if not options.password: fail_unset_field('Password', error_type=NO_PASSWORD) # Format parameters for the Weboob connector. bank_module = options.module params = { 'login': options.login, 'password': options.password, } if options.field is not None: for name, value in options.field: if not name: fail_unset_field('Name of custom field') if not value: fail_unset_field('Value of custom field') params[name] = value # Create a Weboob backend, fetch data and delete the module. try: weboob_connector.create_backend(bank_module, params) except Module.ConfigError as exc: fail(INVALID_PARAMETERS, "Unable to load module %s." % bank_module, traceback.format_exc()) except ModuleLoadError as exc: fail(UNKNOWN_MODULE, "Unable to load module %s." % bank_module, traceback.format_exc()) content = weboob_connector.fetch(command) weboob_connector.delete_backend(bank_module, login=params['login']) # Output the fetched data as JSON. print(json.dumps(content, cls=WeboobEncoder)) sys.exit()
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 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 = account_details_iframe.page.doc.xpath('//span[contains(@id,"NumeroContrat")]/text()')[0] account._url = iframe_url account.type = account.TYPE_LIFE_INSURANCE account.balance = MyDecimal().filter(account_details_iframe.page.doc.xpath('//span[contains(@id,"MontantEpargne")]/text()')[0]) account._acctype = "bank" 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: 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'] try: account.balance = Decimal(FrenchTransaction.clean_amount(self.parse_number(u''.join([txt.strip() for txt in box.cssselect("td.montant")[0].itertext()])))) except InvalidOperation: #The account doesn't have a amount pass for l in table.attrib['class'].split(' '): if 'tableaux-comptes-' in l: account_type_str = l[len('tableaux-comptes-'):].lower() break else: account_type_str = '' for pattern, type in self.ACCOUNT_TYPES.items(): if pattern in account_type_str or pattern in account.label.lower(): account.type = type break else: account.type = Account.TYPE_UNKNOWN types = [('Valorisation', Account.TYPE_MARKET), ('Visa', Account.TYPE_CARD), ] for sub, t in types: if sub in account.label: account.type = t break account._url = self.doc.xpath('//form[contains(@action, "panorama")]/@action')[0] account._acctype = "bank" 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 __str__(self): return unicode(self).encode('utf-8')
def get_movie(self, id): res = self.open( 'http://www.omdbapi.com/?apikey=b7c56eb5&i=%s&plot=full' % id) if res is not None: jres = res.json() else: return None htmlparser = HTMLParser() title = NotAvailable duration = NotAvailable release_date = NotAvailable pitch = NotAvailable country = NotAvailable note = NotAvailable short_description = NotAvailable thumbnail_url = NotAvailable other_titles = [] genres = [] roles = {} if 'Title' not in jres: return title = htmlparser.unescape(unicode(jres['Title'].strip())) if 'Poster' in jres: thumbnail_url = unicode(jres['Poster']) if 'Director' in jres: short_description = unicode(jres['Director']) if 'Genre' in jres: for g in jres['Genre'].split(', '): genres.append(g) if 'Runtime' in jres: m = re.search('(\d+?) min', jres['Runtime']) if m: duration = int(m.group(1)) if 'Released' in jres: released_string = str(jres['Released']) if released_string == 'N/A': release_date = NotAvailable else: months = { 'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12', } for st in months: released_string = released_string.replace(st, months[st]) release_date = datetime.strptime(released_string, '%d %m %Y') if 'Country' in jres: country = u'' for c in jres['Country'].split(', '): country += '%s, ' % c country = country[:-2] if 'Plot' in jres: pitch = unicode(jres['Plot']) if 'imdbRating' in jres and 'imdbVotes' in jres: note = u'%s/10 (%s votes)' % (jres['imdbRating'], jres['imdbVotes']) for r in ['Actors', 'Director', 'Writer']: if '%s' % r in jres.keys(): roles['%s' % r] = [('N/A', e) for e in jres['%s' % r].split(', ')] movie = Movie(id, title) movie.other_titles = other_titles movie.release_date = release_date movie.duration = duration movie.genres = genres movie.pitch = pitch movie.country = country movie.note = note movie.roles = roles movie.short_description = short_description movie.all_release_dates = NotLoaded movie.thumbnail_url = thumbnail_url return movie
def iter_recipes(self, pattern): return self.browser.iter_recipes( strip_accents(unicode(pattern)).encode('utf-8'))
def decode_row(self, row, encoding): if encoding: return [unicode(cell, encoding) for cell in row] else: return row
def __init__(self, id, name, url=None): super(Version, self).__init__(id, url) self.name = unicode(name)
def tostring(self, element): if not isinstance(element, basestring): return unicode(element) return element
def __init__(self, id, name, url=None): super(Project, self).__init__(id, url) self.name = unicode(name)
sys.path.append(os.environ['WEBOOB_DIR']) try: from weboob.capabilities.base import empty from weboob.core import Weboob from weboob.exceptions import (ActionNeeded, BrowserIncorrectPassword, BrowserPasswordExpired, NoAccountsException, ModuleInstallError, ModuleLoadError) from weboob.tools.backend import Module from weboob.tools.compat import unicode from weboob.tools.log import createColoredFormatter from weboob.tools.json import WeboobEncoder except ImportError as exc: fail(WEBOOB_NOT_INSTALLED, ('Is weboob correctly installed? Unknown exception raised: %s.' % unicode(exc)), traceback.format_exc()) def init_logging(level, is_prod): """ Initialize loggers. :param level: Minimal severity to log. :param is_prod: whether we're running in production or not. """ root_logger = logging.getLogger() root_logger.setLevel(level) handler = logging.StreamHandler(sys.stderr) fmt = ('%(asctime)s:%(levelname)s:%(name)s:%(filename)s:'
def searchSubtitle(self): tosearch = unicode(self.movie.original_title) lang = self.ui.langCombo.currentText() desc = 'Search subtitles for "%s" (lang:%s)' % (tosearch, lang) self.parent.doAction(desc, self.parent.searchSubtitleAction, [lang, tosearch])
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 rebuild_iban(iban): return unicode(iban[:2] + ('%02d' % find_iban_checksum(iban)) + iban[4:])
def get_iban(self): m = re.search(self.iban_regexp, self.parsed_text.decode('utf-8')) if m: return unicode(m.group(1)) return None
def process_incoming_mail(self, msg): to = self.get_email_address_ident(msg, 'To') sender = msg.get('From') reply_to = self.get_email_address_ident(msg, 'In-Reply-To') title = msg.get('Subject') if title: new_title = u'' for part in decode_header(title): if part[1]: new_title += unicode(part[0], part[1]) else: new_title += unicode(part[0]) title = new_title content = u'' for part in msg.walk(): if part.get_content_type() == 'text/plain': s = part.get_payload(decode=True) charsets = part.get_charsets() + msg.get_charsets() for charset in charsets: try: if charset is not None: content += unicode(s, charset) else: content += unicode(s) except UnicodeError as e: self.logger.warning('Unicode error: %s' % e) continue except Exception as e: self.logger.exception(e) continue else: break if len(content) == 0: print('Unable to send an empty message', file=self.stderr) return 1 # remove signature content = content.split(u'\n-- \n')[0] parent_id = None if reply_to is None: # This is a new message if '.' in to: backend_name, thread_id = to.split('.', 1) else: backend_name = to thread_id = None else: # This is a reply try: backend_name, id = reply_to.split('.', 1) thread_id, parent_id = id.rsplit('.', 1) except ValueError: print( 'In-Reply-To header might be in form <backend.thread_id.message_id>', file=self.stderr) return 1 # Default use the To header field to know the backend to use. if to and backend_name != to: backend_name = to try: backend = self.weboob.backend_instances[backend_name] except KeyError: print('Backend %s not found' % backend_name, file=self.stderr) return 1 if not backend.has_caps(CapMessagesPost): print('The backend %s does not implement CapMessagesPost' % backend_name, file=self.stderr) return 1 thread = Thread(thread_id) message = Message( thread, 0, title=title, sender=sender, receivers=[to], parent=Message(thread, parent_id) if parent_id else None, content=content) try: backend.post_message(message) except Exception as e: content = u'Unable to send message to %s:\n' % thread_id content += u'\n\t%s\n' % to_unicode(e) if logging.root.level <= logging.DEBUG: content += u'\n%s\n' % to_unicode(get_backtrace(e)) self.send_email( backend.name, Message( thread, 0, title='Unable to send message', sender='Monboob', parent=Message(thread, parent_id) if parent_id else None, content=content))
def get_iban(self): try: return unicode(self.doc.xpath('.//td[@width="315"]/font')[0].text.replace(' ', '').strip()) except AttributeError: return NotAvailable
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() == 'Prêt 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'] account.number = account.id 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() account.ownership = loan_details.get_loan_ownership() # 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 = '%s %s' % ( table.xpath('./caption')[0].text.strip(), 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 = table.xpath('./caption')[0].text.strip() # Adding 'Valorisation' to the account label in order to differentiate it # from the card and checking account associate to the './caption' if 'Valorisation' not in account.label and len( box.xpath( './td[contains(text(), "Valorisation")]')): account.label = '%s Valorisation Titres' % CleanText( './caption')(table) 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 = list(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.number = account.id 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 account.id = args['paramNumCompte'] + args.get( 'paramNumContrat', '') if 'Visa' in account.label: account.number = Regexp( CleanText('./td[contains(@class,"libelle")]', replace=[(' ', ''), ('x', 'X')]), r'(X{12}\d{4})')(box) account.id += Regexp( CleanText('./td[contains(@class,"libelle")]'), r'(\d+)')(box) if 'Valorisation' in account.label or 'Liquidités' in account.label: account.id += args[next( k for k in args.keys() if '_idcl' in k)].split('Jsp')[-1] account.number = account.id # 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 == '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" account._owner = CleanText('./td[has-class("libelle")]')( box) # 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 iter_recipes(self, pattern): # the search form does that so the url is clean of special chars # we go directly on search results by the url so we strip it too return self.browser.iter_recipes(strip_accents(unicode(pattern)))
def bcall_error_handler(self, backend, error, backtrace): """ Handler for an exception inside the CallErrors exception. This method can be overrided to support more exceptions types. """ if isinstance(error, BrowserQuestion): for field in error.fields: v = self.ask(field) if v: backend.config[field.id].set(v) if isinstance(error, CaptchaQuestion): print(u'Warning(%s): Captcha has been found on login page' % backend.name, file=self.stderr) elif isinstance(error, BrowserIncorrectPassword): msg = unicode(error) if not msg: msg = 'invalid login/password.' print('Error(%s): %s' % (backend.name, msg), file=self.stderr) if self.ask('Do you want to reconfigure this backend?', default=True): self.unload_backends(names=[backend.name]) self.edit_backend(backend.name) self.load_backends(names=[backend.name]) elif isinstance(error, BrowserSSLError): print(u'FATAL(%s): ' % backend.name + self.BOLD + '/!\ SERVER CERTIFICATE IS INVALID /!\\' + self.NC, file=self.stderr) elif isinstance(error, BrowserHTTPSDowngrade): print(u'FATAL(%s): ' % backend.name + 'Downgrade from HTTPS to HTTP') elif isinstance(error, BrowserForbidden): msg = unicode(error) print(u'Error(%s): %s' % (backend.name, msg or 'Forbidden'), file=self.stderr) elif isinstance(error, BrowserUnavailable): msg = unicode(error) print(u'Error(%s): %s' % (backend.name, msg or 'Website is unavailable.'), file=self.stderr) elif isinstance(error, ActionNeeded): msg = unicode(error) print(u'Error(%s): Action needed on website: %s' % (backend.name, msg), file=self.stderr) elif isinstance(error, NotImplementedError): print( u'Error(%s): this feature is not supported yet by this backend.' % backend.name, file=self.stderr) print( u' %s To help the maintainer of this backend implement this feature,' % (' ' * len(backend.name)), file=self.stderr) print(u' %s please contact us on the project mailing list' % (' ' * len(backend.name)), file=self.stderr) elif isinstance(error, TransferInvalidAmount): print(u'Error(%s): %s' % (backend.name, to_unicode(error) or 'The transfer amount is invalid'), file=self.stderr) elif isinstance(error, TransferInvalidLabel): print(u'Error(%s): %s' % (backend.name, to_unicode(error) or 'The transfer label is invalid'), file=self.stderr) elif isinstance(error, TransferInvalidEmitter): print(u'Error(%s): %s' % (backend.name, to_unicode(error) or 'The transfer emitter is invalid'), file=self.stderr) elif isinstance(error, TransferInvalidRecipient): print(u'Error(%s): %s' % (backend.name, to_unicode(error) or 'The transfer recipient is invalid'), file=self.stderr) elif isinstance(error, TransferInvalidDate): print(u'Error(%s): %s' % (backend.name, to_unicode(error) or 'The transfer execution date is invalid'), file=self.stderr) elif isinstance(error, UserError): print(u'Error(%s): %s' % (backend.name, to_unicode(error)), file=self.stderr) elif isinstance(error, MoreResultsAvailable): print(u'Hint: There are more results for backend %s' % (backend.name), file=self.stderr) elif isinstance(error, NoAccountsException): print(u'Error(%s): %s' % (backend.name, to_unicode(error) or 'No account on this backend'), file=self.stderr) else: print(u'Bug(%s): %s' % (backend.name, to_unicode(error)), file=self.stderr) minfo = self.weboob.repositories.get_module_info(backend.NAME) if minfo and not minfo.is_local(): if self.options.auto_update: self.weboob.repositories.update_repositories( ConsoleProgress(self)) # minfo of the new available module minfo = self.weboob.repositories.get_module_info( backend.NAME) if minfo and minfo.version > self.weboob.repositories.versions.get(minfo.name) and \ self.ask('A new version of %s is available. Do you want to install it?' % minfo.name, default=True) and \ self.install_module(minfo): print( 'New version of module %s has been installed. Retry to call the command.' % minfo.name) return else: print( '(If --auto-update is passed on the command-line, new versions of the module will be checked automatically)' ) if logging.root.level <= logging.DEBUG: print(backtrace, file=self.stderr) else: return True
def parse_transaction(self, transaction, account): trans = [] # Add secondary transactions on label condition. for t in transaction['secondaryTransactions']: if t['transactionDescription'][ 'description'] == u'Virement à partir de': trans.extend(self.parse_transaction(t, account)) if 'transactionStatus' in transaction and transaction[ 'transactionStatus'] in [ u'Créé', u'Annulé', u'Suspendu', u'Mis à jour', u'Actif', u'Payé', u'En attente', u'Rejeté', u'Expiré', u'Created', u'Brouillon', u'Paid', u'Pending', u'Canceled', u'Suspended' ]: return [] for pattern in [u'Commande à', u'Offre de remboursement', u'Bill to']: if 'description' not in transaction[ 'transactionDescription'] or transaction[ 'transactionDescription']['description'].startswith( pattern): return [] t = FrenchTransaction(transaction['transactionId']) # Those are not really transactions. if 'grossAmount' not in transaction or not 'currency' in transaction['grossAmount'] \ or transaction['transactionDescription']['description'].startswith("Conversion de devise"): return [] original_currency = unicode(transaction['grossAmount']['currency']) if not original_currency == account.currency: if original_currency in self.browser.account_currencies: return [] cc = [tr['grossAmount']['amountUnformatted'] for tr in transaction['secondaryTransactions'] \ if account.currency == tr['grossAmount']['currency'] \ and (int(tr['grossAmount']['amountUnformatted']) < 0) == (int(transaction['grossAmount']['amountUnformatted']) < 0) \ and tr['transactionDescription']['description'].startswith('Conversion de devise')] if not cc: return [] assert len(cc) == 1 t.original_amount = Decimal( str(transaction['netAmount']['amountUnformatted'])) t.original_currency = original_currency t.amount = Decimal(str(cc[0])) else: t.amount = Decimal( str(transaction['netAmount']['amountUnformatted'])) date = parse_french_date(transaction['transactionTime']) raw = "%s %s" % (transaction['transactionDescription']['description'], transaction['transactionDescription']['name']) if raw == "Transfert de Compte bancaire": t.type = FrenchTransaction.TYPE_TRANSFER if raw == u'Annulation des frais de PayPal': return [] # Dougs told us that commission should always be netAmount minus grossAmount grossAmount = Decimal( str(transaction['grossAmount']['amountUnformatted'])) t.commission = Decimal( str(transaction['feeAmount']['amountUnformatted'])) if t.commission: if original_currency == account.currency: assert abs(t.amount - grossAmount) == abs(t.commission) t.commission = t.amount - grossAmount else: t.commission = (t.commission * t.amount / t.original_amount).quantize( Decimal('.01'), rounding=ROUND_DOWN) t.parse(date=date, raw=raw) trans.append(t) return trans