port every console applications to ReplApplication
This commit is contained in:
parent
81706f23ad
commit
37a53b783a
17 changed files with 460 additions and 369 deletions
|
|
@ -1,6 +1,4 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# vim: ft=python et softtabstop=4 cinoptions=4 shiftwidth=4 ts=4 ai
|
|
||||||
|
|
||||||
# Copyright(C) 2009-2010 Romain Bignon, Christophe Benz
|
# Copyright(C) 2009-2010 Romain Bignon, Christophe Benz
|
||||||
#
|
#
|
||||||
|
|
@ -39,6 +37,8 @@ class Boobank(ReplApplication):
|
||||||
|
|
||||||
def do_list(self, line):
|
def do_list(self, line):
|
||||||
"""
|
"""
|
||||||
|
list
|
||||||
|
|
||||||
List every available accounts.
|
List every available accounts.
|
||||||
"""
|
"""
|
||||||
tot_balance = 0.0
|
tot_balance = 0.0
|
||||||
|
|
@ -61,6 +61,8 @@ class Boobank(ReplApplication):
|
||||||
|
|
||||||
def do_history(self, id):
|
def do_history(self, id):
|
||||||
"""
|
"""
|
||||||
|
history ID
|
||||||
|
|
||||||
Display old operations.
|
Display old operations.
|
||||||
"""
|
"""
|
||||||
id, backend_name = self.parse_id(id)
|
id, backend_name = self.parse_id(id)
|
||||||
|
|
@ -76,6 +78,8 @@ class Boobank(ReplApplication):
|
||||||
|
|
||||||
def do_coming(self, id):
|
def do_coming(self, id):
|
||||||
"""
|
"""
|
||||||
|
coming ID
|
||||||
|
|
||||||
Display all future operations.
|
Display all future operations.
|
||||||
"""
|
"""
|
||||||
id, backend_name = self.parse_id(id)
|
id, backend_name = self.parse_id(id)
|
||||||
|
|
@ -88,25 +92,22 @@ class Boobank(ReplApplication):
|
||||||
|
|
||||||
for backend, operation in self.do(do):
|
for backend, operation in self.do(do):
|
||||||
self.format(operation)
|
self.format(operation)
|
||||||
|
|
||||||
def do_transfer(self, arg):
|
def do_transfer(self, line):
|
||||||
"""
|
"""
|
||||||
|
transfer FROM TO AMOUNT
|
||||||
|
|
||||||
Make a transfer beetwen two account
|
Make a transfer beetwen two account
|
||||||
"""
|
"""
|
||||||
id_from , id_to, amount = arg.split()
|
id_from, id_to, amount = self.parseline(line, 3, 3)
|
||||||
|
|
||||||
id_from, backend_name = self.parse_id(id_from)
|
id_from, backend_name = self.parse_id(id_from)
|
||||||
id_to, backend_name = self.parse_id(id_to)
|
id_to, backend_name = self.parse_id(id_to)
|
||||||
names = (backend_name,) if backend_name is not None else None
|
names = (backend_name,) if backend_name is not None else None
|
||||||
self.load_backends(ICapBank, names=names)
|
self.load_backends(ICapBank, names=names)
|
||||||
|
|
||||||
def do(backend):
|
def do(backend):
|
||||||
return backend.transfer(id_from, id_to, amount)
|
return backend.transfer(id_from, id_to, float(amount))
|
||||||
|
|
||||||
for backend, operation in self.do(do):
|
for backend, operation in self.do(do):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from weboob.tools.application.console import ConsoleApplication
|
from weboob.tools.application.repl import ReplApplication
|
||||||
from weboob.capabilities.chat import ICapChat
|
from weboob.capabilities.chat import ICapChat
|
||||||
from weboob.capabilities.contact import ICapContact, Contact
|
from weboob.capabilities.contact import ICapContact, Contact
|
||||||
|
|
||||||
|
|
@ -26,36 +26,42 @@ from weboob.capabilities.contact import ICapContact, Contact
|
||||||
__all__ = ['Chatoob']
|
__all__ = ['Chatoob']
|
||||||
|
|
||||||
|
|
||||||
class Chatoob(ConsoleApplication):
|
class Chatoob(ReplApplication):
|
||||||
APPNAME = 'chatoob'
|
APPNAME = 'chatoob'
|
||||||
VERSION = '0.1'
|
VERSION = '0.1'
|
||||||
COPYRIGHT = 'Copyright(C) 2010 Christophe Benz'
|
COPYRIGHT = 'Copyright(C) 2010 Christophe Benz'
|
||||||
|
|
||||||
def main(self, argv):
|
def load_default_backends(self):
|
||||||
self.load_backends(ICapChat)
|
self.load_backends(ICapChat)
|
||||||
#for backend, result in self.do('start_chat_polling', self.on_new_chat_message):
|
|
||||||
#logging.info(u'Polling chat messages for backend %s' % backend)
|
|
||||||
return self.process_command(*argv[1:])
|
|
||||||
|
|
||||||
def on_new_chat_message(self, message):
|
def on_new_chat_message(self, message):
|
||||||
print 'on_new_chat_message: %s' % message
|
print 'on_new_chat_message: %s' % message
|
||||||
|
|
||||||
@ConsoleApplication.command('exit program')
|
def do_list(self, line):
|
||||||
def command_exit(self):
|
"""
|
||||||
self.weboob.want_stop()
|
list
|
||||||
|
|
||||||
@ConsoleApplication.command('list online contacts')
|
List all contacts.
|
||||||
def command_list(self):
|
"""
|
||||||
for backend, contact in self.do('iter_contacts', status=Contact.STATUS_ONLINE, caps=ICapContact):
|
for backend, contact in self.do('iter_contacts', status=Contact.STATUS_ONLINE, caps=ICapContact):
|
||||||
self.format(contact)
|
self.format(contact)
|
||||||
|
|
||||||
@ConsoleApplication.command('get messages')
|
def do_messages(self, line):
|
||||||
def command_messages(self):
|
"""
|
||||||
|
messages
|
||||||
|
|
||||||
|
Get messages.
|
||||||
|
"""
|
||||||
for backend, message in self.do('iter_chat_messages'):
|
for backend, message in self.do('iter_chat_messages'):
|
||||||
self.format(message)
|
self.format(message)
|
||||||
|
|
||||||
@ConsoleApplication.command('send message to contact')
|
def do_send(self, line):
|
||||||
def command_send(self, _id, message):
|
"""
|
||||||
|
send CONTACT MESSAGE
|
||||||
|
|
||||||
|
Send a message to the specified contact.
|
||||||
|
"""
|
||||||
|
_id, message = self.parseline(line, 2, 2)
|
||||||
for backend, result in self.do('send_chat_message', _id, message):
|
for backend, result in self.do('send_chat_message', _id, message):
|
||||||
if not result:
|
if not result:
|
||||||
logging.error(u'Failed to send message to contact id="%s" on backend "%s"' % (_id, backend.name))
|
logging.error(u'Failed to send message to contact id="%s" on backend "%s"' % (_id, backend.name))
|
||||||
|
|
|
||||||
|
|
@ -19,23 +19,25 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from weboob.capabilities.geolocip import ICapGeolocIp
|
from weboob.capabilities.geolocip import ICapGeolocIp
|
||||||
from weboob.tools.application.console import ConsoleApplication
|
from weboob.tools.application.repl import ReplApplication
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Geolooc']
|
__all__ = ['Geolooc']
|
||||||
|
|
||||||
|
|
||||||
class Geolooc(ConsoleApplication):
|
class Geolooc(ReplApplication):
|
||||||
APPNAME = 'geolooc'
|
APPNAME = 'geolooc'
|
||||||
VERSION = '0.1'
|
VERSION = '0.1'
|
||||||
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
||||||
|
|
||||||
|
def load_default_backends(self):
|
||||||
|
self.load_backends(ICapGeolocIp)
|
||||||
|
|
||||||
def main(self, argv):
|
def main(self, argv):
|
||||||
if len(argv) < 2:
|
if len(argv) < 2:
|
||||||
print >>sys.stderr, 'Syntax: %s ipaddr' % argv[0]
|
print >>sys.stderr, 'Syntax: %s ipaddr' % argv[0]
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
self.load_backends(ICapGeolocIp)
|
|
||||||
for backend, location in self.do('get_location', argv[1]):
|
for backend, location in self.do('get_location', argv[1]):
|
||||||
self.format(location)
|
self.format(location)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,23 +22,25 @@ import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import weboob
|
import weboob
|
||||||
from weboob.tools.application.prompt import PromptApplication
|
from weboob.tools.application.repl import ReplApplication
|
||||||
from weboob.capabilities.dating import ICapDating, OptimizationNotFound
|
from weboob.capabilities.dating import ICapDating, OptimizationNotFound
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['HaveSex']
|
__all__ = ['HaveSex']
|
||||||
|
|
||||||
|
|
||||||
class HaveSex(PromptApplication):
|
class HaveSex(ReplApplication):
|
||||||
APPNAME = 'havesex'
|
APPNAME = 'havesex'
|
||||||
VERSION = '0.1'
|
VERSION = '0.1'
|
||||||
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
||||||
STORAGE_FILENAME = 'dating.storage'
|
STORAGE_FILENAME = 'dating.storage'
|
||||||
CONFIG = {'optimizations': ''}
|
CONFIG = {'optimizations': ''}
|
||||||
|
|
||||||
|
def load_default_backends(self):
|
||||||
|
self.load_backends(ICapDating, storage=self.create_storage(self.STORAGE_FILENAME))
|
||||||
|
|
||||||
def main(self, argv):
|
def main(self, argv):
|
||||||
self.load_config()
|
self.load_config()
|
||||||
self.load_backends(ICapDating, storage=self.create_storage(self.STORAGE_FILENAME))
|
|
||||||
|
|
||||||
self.do('init_optimizations').wait()
|
self.do('init_optimizations').wait()
|
||||||
|
|
||||||
|
|
@ -48,15 +50,14 @@ class HaveSex(PromptApplication):
|
||||||
if optimizations_list:
|
if optimizations_list:
|
||||||
self.optims('Starting', 'start_optimization', optimizations_list)
|
self.optims('Starting', 'start_optimization', optimizations_list)
|
||||||
|
|
||||||
return self.loop()
|
return ReplApplication.main(self, argv)
|
||||||
|
|
||||||
@PromptApplication.command("exit program")
|
def do_profile(self, id):
|
||||||
def command_exit(self):
|
"""
|
||||||
print 'Returning in real-life...'
|
profile ID
|
||||||
self.weboob.want_stop()
|
|
||||||
|
|
||||||
@PromptApplication.command("show a profile")
|
Display a profile
|
||||||
def command_profile(self, id):
|
"""
|
||||||
_id, backend_name = self.parse_id(id)
|
_id, backend_name = self.parse_id(id)
|
||||||
|
|
||||||
def print_node(node, level=1):
|
def print_node(node, level=1):
|
||||||
|
|
@ -116,10 +117,18 @@ class HaveSex(PromptApplication):
|
||||||
if isinstance(error, OptimizationNotFound):
|
if isinstance(error, OptimizationNotFound):
|
||||||
logging.error(u'Optimization "%s" not found' % optim)
|
logging.error(u'Optimization "%s" not found' % optim)
|
||||||
|
|
||||||
@PromptApplication.command("start optimizations")
|
def do_start(self, *optims):
|
||||||
def command_start(self, *optims):
|
"""
|
||||||
|
start OPTIMIZATION [OPTIMIZATION [...]]
|
||||||
|
|
||||||
|
Start optimization services.
|
||||||
|
"""
|
||||||
self.optims('Starting', 'start_optimization', optims)
|
self.optims('Starting', 'start_optimization', optims)
|
||||||
|
|
||||||
@PromptApplication.command("stop optimizations")
|
|
||||||
def command_stop(self, *optims):
|
def command_stop(self, *optims):
|
||||||
|
"""
|
||||||
|
stop OPTIMIZATION [OPTIMIZATION [...]]
|
||||||
|
|
||||||
|
Stop optimization services.
|
||||||
|
"""
|
||||||
self.optims('Stopping', 'stop_optimization', optims)
|
self.optims('Stopping', 'stop_optimization', optims)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import asyncore
|
||||||
from weboob.core.ouiboube import Weboob
|
from weboob.core.ouiboube import Weboob
|
||||||
from weboob.core.scheduler import Scheduler
|
from weboob.core.scheduler import Scheduler
|
||||||
from weboob.capabilities.messages import ICapMessages, ICapMessagesPost, Thread, Message
|
from weboob.capabilities.messages import ICapMessages, ICapMessagesPost, Thread, Message
|
||||||
from weboob.tools.application.console import ConsoleApplication
|
from weboob.tools.application.repl import ReplApplication
|
||||||
from weboob.tools.misc import html2text, get_backtrace, utc2local
|
from weboob.tools.misc import html2text, get_backtrace, utc2local
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ class MonboobScheduler(Scheduler):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Monboob(ConsoleApplication):
|
class Monboob(ReplApplication):
|
||||||
APPNAME = 'monboob'
|
APPNAME = 'monboob'
|
||||||
VERSION = '0.1'
|
VERSION = '0.1'
|
||||||
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
||||||
|
|
@ -91,11 +91,13 @@ class Monboob(ConsoleApplication):
|
||||||
def create_weboob(self):
|
def create_weboob(self):
|
||||||
return Weboob(scheduler=MonboobScheduler(self))
|
return Weboob(scheduler=MonboobScheduler(self))
|
||||||
|
|
||||||
def main(self, argv):
|
def load_default_backends(self):
|
||||||
self.load_config()
|
|
||||||
self.load_backends(ICapMessages, storage=self.create_storage())
|
self.load_backends(ICapMessages, storage=self.create_storage())
|
||||||
|
|
||||||
return self.process_command(*argv[1:])
|
def main(self, argv):
|
||||||
|
self.load_config()
|
||||||
|
|
||||||
|
return self.onecmd(' '.join(argv[1:]))
|
||||||
|
|
||||||
def get_email_address_ident(self, msg, header):
|
def get_email_address_ident(self, msg, header):
|
||||||
s = msg.get(header)
|
s = msg.get(header)
|
||||||
|
|
@ -108,8 +110,12 @@ class Monboob(ConsoleApplication):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return s
|
return s
|
||||||
|
|
||||||
@ConsoleApplication.command("pipe with a mail to post message")
|
def do_post(self):
|
||||||
def command_post(self):
|
"""
|
||||||
|
post
|
||||||
|
|
||||||
|
Pipe with a mail to post message.
|
||||||
|
"""
|
||||||
msg = message_from_file(sys.stdin)
|
msg = message_from_file(sys.stdin)
|
||||||
return self.process_incoming_mail(msg)
|
return self.process_incoming_mail(msg)
|
||||||
|
|
||||||
|
|
@ -185,8 +191,12 @@ class Monboob(ConsoleApplication):
|
||||||
parent=Message(thread, msg_id),
|
parent=Message(thread, msg_id),
|
||||||
content=content))
|
content=content))
|
||||||
|
|
||||||
@ConsoleApplication.command("run daemon")
|
def do_run(self):
|
||||||
def command_run(self):
|
"""
|
||||||
|
run
|
||||||
|
|
||||||
|
Run the fetching daemon.
|
||||||
|
"""
|
||||||
self.weboob.repeat(int(self.config.get('interval')), self.process)
|
self.weboob.repeat(int(self.config.get('interval')), self.process)
|
||||||
self.weboob.loop()
|
self.weboob.loop()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,28 +19,37 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from weboob.capabilities.travel import ICapTravel
|
from weboob.capabilities.travel import ICapTravel
|
||||||
from weboob.tools.application.console import ConsoleApplication
|
from weboob.tools.application.repl import ReplApplication
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Traveloob']
|
__all__ = ['Traveloob']
|
||||||
|
|
||||||
|
|
||||||
class Traveloob(ConsoleApplication):
|
class Traveloob(ReplApplication):
|
||||||
APPNAME = 'traveloob'
|
APPNAME = 'traveloob'
|
||||||
VERSION = '0.1'
|
VERSION = '0.1'
|
||||||
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
||||||
|
|
||||||
def main(self, argv):
|
def load_default_backends(self):
|
||||||
return self.process_command(*argv[1:])
|
|
||||||
|
|
||||||
@ConsoleApplication.command('Search stations')
|
|
||||||
def command_stations(self, pattern):
|
|
||||||
self.load_backends(ICapTravel)
|
self.load_backends(ICapTravel)
|
||||||
|
|
||||||
|
def do_stations(self, pattern):
|
||||||
|
"""
|
||||||
|
search PATTERN
|
||||||
|
|
||||||
|
Search stations.
|
||||||
|
"""
|
||||||
for backend, station in self.do('iter_station_search', pattern):
|
for backend, station in self.do('iter_station_search', pattern):
|
||||||
self.format(station)
|
self.format(station)
|
||||||
|
|
||||||
@ConsoleApplication.command('List all departures for a given station')
|
def do_departures(self, line):
|
||||||
def command_departures(self, station, arrival=None):
|
"""
|
||||||
|
departures STATION [ARRIVAL]
|
||||||
|
|
||||||
|
List all departures for a given station.
|
||||||
|
"""
|
||||||
|
station, arrival = self.parseline(line, 2, 1)
|
||||||
|
|
||||||
station_id, backend_name = self.parse_id(station)
|
station_id, backend_name = self.parse_id(station)
|
||||||
if arrival:
|
if arrival:
|
||||||
arrival_id, backend_name2 = self.parse_id(arrival)
|
arrival_id, backend_name2 = self.parse_id(arrival)
|
||||||
|
|
@ -57,6 +66,5 @@ class Traveloob(ConsoleApplication):
|
||||||
else:
|
else:
|
||||||
backends = None
|
backends = None
|
||||||
|
|
||||||
self.load_backends(ICapTravel, names=backends)
|
for backend, departure in self.do('iter_station_departures', station_id, arrival_id, backends=backends):
|
||||||
for backend, departure in self.do('iter_station_departures', station_id, arrival_id):
|
|
||||||
self.format(departure)
|
self.format(departure)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
# Copyright(C) 2010 Christophe Benz, Romain Bignon
|
# Copyright(C) 2010 Christophe Benz, Romain Bignon
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
|
@ -55,7 +54,7 @@ class Videoob(ReplApplication):
|
||||||
for backend, video in self.do('get_video', _id, backends=backend_names):
|
for backend, video in self.do('get_video', _id, backends=backend_names):
|
||||||
self.format(video)
|
self.format(video)
|
||||||
self.flush()
|
self.flush()
|
||||||
|
|
||||||
def do_nsfw(self, line):
|
def do_nsfw(self, line):
|
||||||
"""
|
"""
|
||||||
nsfw [on | off]
|
nsfw [on | off]
|
||||||
|
|
|
||||||
|
|
@ -22,21 +22,18 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from weboob.tools.application.console import ConsoleApplication
|
from weboob.tools.application.repl import ReplApplication
|
||||||
from weboob.tools.ordereddict import OrderedDict
|
from weboob.tools.ordereddict import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['WeboobCfg']
|
__all__ = ['WeboobCfg']
|
||||||
|
|
||||||
|
|
||||||
class WeboobCfg(ConsoleApplication):
|
class WeboobCfg(ReplApplication):
|
||||||
APPNAME = 'weboob-config'
|
APPNAME = 'weboob-config'
|
||||||
VERSION = '0.1'
|
VERSION = '0.1'
|
||||||
COPYRIGHT = 'Copyright(C) 2010 Christophe Benz, Romain Bignon'
|
COPYRIGHT = 'Copyright(C) 2010 Christophe Benz, Romain Bignon'
|
||||||
|
|
||||||
def main(self, argv):
|
|
||||||
return self.process_command(*argv[1:])
|
|
||||||
|
|
||||||
def caps_included(self, modcaps, caps):
|
def caps_included(self, modcaps, caps):
|
||||||
modcaps = [x.__name__ for x in modcaps]
|
modcaps = [x.__name__ for x in modcaps]
|
||||||
for cap in caps:
|
for cap in caps:
|
||||||
|
|
@ -44,8 +41,18 @@ class WeboobCfg(ConsoleApplication):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ConsoleApplication.command('Add a configured backend')
|
def do_add(self, line):
|
||||||
def command_add(self, name, *options):
|
"""
|
||||||
|
add NAME [OPTIONS ...]
|
||||||
|
|
||||||
|
Add a configured backend.
|
||||||
|
"""
|
||||||
|
name, options = self.parseline(line, 2, 1)
|
||||||
|
if options:
|
||||||
|
options = options.split(' ')
|
||||||
|
else:
|
||||||
|
options = ()
|
||||||
|
|
||||||
self.weboob.modules_loader.load_all()
|
self.weboob.modules_loader.load_all()
|
||||||
if name not in [_name for _name, backend in self.weboob.modules_loader.loaded.iteritems()]:
|
if name not in [_name for _name, backend in self.weboob.modules_loader.loaded.iteritems()]:
|
||||||
logging.error(u'Backend "%s" does not exist.' % name)
|
logging.error(u'Backend "%s" does not exist.' % name)
|
||||||
|
|
@ -101,8 +108,12 @@ class WeboobCfg(ConsoleApplication):
|
||||||
except ConfigParser.DuplicateSectionError:
|
except ConfigParser.DuplicateSectionError:
|
||||||
print 'Instance "%s" already exists for backend "%s".' % (new_name, name)
|
print 'Instance "%s" already exists for backend "%s".' % (new_name, name)
|
||||||
|
|
||||||
@ConsoleApplication.command('Show configured backends')
|
def do_listconfigured(self):
|
||||||
def command_listconfigured(self):
|
"""
|
||||||
|
list
|
||||||
|
|
||||||
|
Show configured backends.
|
||||||
|
"""
|
||||||
self.set_default_formatter('table')
|
self.set_default_formatter('table')
|
||||||
for instance_name, name, params in sorted(self.weboob.backends_config.iter_backends()):
|
for instance_name, name, params in sorted(self.weboob.backends_config.iter_backends()):
|
||||||
backend = self.weboob.modules_loader.get_or_load_module(name)
|
backend = self.weboob.modules_loader.get_or_load_module(name)
|
||||||
|
|
@ -112,20 +123,33 @@ class WeboobCfg(ConsoleApplication):
|
||||||
])
|
])
|
||||||
self.format(row)
|
self.format(row)
|
||||||
|
|
||||||
@ConsoleApplication.command('Remove a configured backend')
|
def do_remove(self, instance_name):
|
||||||
def command_remove(self, instance_name):
|
"""
|
||||||
|
remove NAME
|
||||||
|
|
||||||
|
Remove a configured backend.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
self.weboob.backends_config.remove_backend(instance_name)
|
self.weboob.backends_config.remove_backend(instance_name)
|
||||||
except ConfigParser.NoSectionError:
|
except ConfigParser.NoSectionError:
|
||||||
logging.error('Backend instance "%s" does not exist' % instance_name)
|
logging.error('Backend instance "%s" does not exist' % instance_name)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
@ConsoleApplication.command('Edit configuration file')
|
def do_edit(self):
|
||||||
def command_edit(self):
|
"""
|
||||||
|
edit
|
||||||
|
|
||||||
|
Edit configuration file.
|
||||||
|
"""
|
||||||
subprocess.call([os.environ.get('EDITOR', 'vi'), self.weboob.backends_config.confpath])
|
subprocess.call([os.environ.get('EDITOR', 'vi'), self.weboob.backends_config.confpath])
|
||||||
|
|
||||||
@ConsoleApplication.command('Show available backends')
|
def do_backends(self, line):
|
||||||
def command_backends(self, *caps):
|
"""
|
||||||
|
backends [CAPS ...]
|
||||||
|
|
||||||
|
Show available backends
|
||||||
|
"""
|
||||||
|
caps = line.split(' ')
|
||||||
self.set_default_formatter('table')
|
self.set_default_formatter('table')
|
||||||
self.weboob.modules_loader.load_all()
|
self.weboob.modules_loader.load_all()
|
||||||
for name, backend in sorted(self.weboob.modules_loader.loaded.iteritems()):
|
for name, backend in sorted(self.weboob.modules_loader.loaded.iteritems()):
|
||||||
|
|
@ -137,8 +161,12 @@ class WeboobCfg(ConsoleApplication):
|
||||||
])
|
])
|
||||||
self.format(row)
|
self.format(row)
|
||||||
|
|
||||||
@ConsoleApplication.command('Display information about a backend')
|
def do_info(self, name):
|
||||||
def command_info(self, name):
|
"""
|
||||||
|
info NAME
|
||||||
|
|
||||||
|
Display information about a backend.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
backend = self.weboob.modules_loader.get_or_load_module(name)
|
backend = self.weboob.modules_loader.get_or_load_module(name)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
@ -166,8 +194,12 @@ class WeboobCfg(ConsoleApplication):
|
||||||
print '| | %s: %s' % (key, value)
|
print '| | %s: %s' % (key, value)
|
||||||
print "'-----------------'"
|
print "'-----------------'"
|
||||||
|
|
||||||
@ConsoleApplication.command('Show applications')
|
def do_applications(self, line):
|
||||||
def command_applications(self, *caps):
|
"""
|
||||||
|
applications
|
||||||
|
|
||||||
|
Show applications.
|
||||||
|
"""
|
||||||
applications = set()
|
applications = set()
|
||||||
import weboob.applications
|
import weboob.applications
|
||||||
for path in weboob.applications.__path__:
|
for path in weboob.applications.__path__:
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,20 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from weboob.tools.application.console import ConsoleApplication
|
from weboob.tools.application.repl import ReplApplication
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['WeboobCli']
|
__all__ = ['WeboobCli']
|
||||||
|
|
||||||
|
|
||||||
class WeboobCli(ConsoleApplication):
|
class WeboobCli(ReplApplication):
|
||||||
APPNAME = 'weboob-cli'
|
APPNAME = 'weboob-cli'
|
||||||
VERSION = '0.1'
|
VERSION = '0.1'
|
||||||
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
||||||
|
|
||||||
|
def load_default_backends(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def main(self, argv):
|
def main(self, argv):
|
||||||
if len(argv) < 3:
|
if len(argv) < 3:
|
||||||
print >>sys.stderr, "Syntax: %s capability command [args ..]" % argv[0]
|
print >>sys.stderr, "Syntax: %s capability command [args ..]" % argv[0]
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,23 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from weboob.tools.application.console import ConsoleApplication
|
from weboob.tools.application.repl import ReplApplication
|
||||||
|
|
||||||
|
|
||||||
class WeboobDebug(ConsoleApplication):
|
class WeboobDebug(ReplApplication):
|
||||||
APPNAME = 'weboobdebug'
|
APPNAME = 'weboobdebug'
|
||||||
VERSION = '0.1'
|
VERSION = '0.1'
|
||||||
COPYRIGHT = 'Copyright(C) 2010 Christophe Benz'
|
COPYRIGHT = 'Copyright(C) 2010 Christophe Benz'
|
||||||
|
|
||||||
def main(self, argv):
|
def load_default_backends(self):
|
||||||
return self.process_command(*argv[1:])
|
pass
|
||||||
|
|
||||||
@ConsoleApplication.command('Debug backend')
|
def do_shell(self, backend_name):
|
||||||
def command_shell(self, backend_name):
|
"""
|
||||||
|
shell BACKEND
|
||||||
|
|
||||||
|
Debug a backend.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
backend = self.weboob.load_backends(names=[backend_name])[backend_name]
|
backend = self.weboob.load_backends(names=[backend_name])[backend_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
||||||
|
|
@ -20,23 +20,26 @@ from __future__ import with_statement
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from weboob.capabilities.torrent import ICapTorrent
|
from weboob.capabilities.torrent import ICapTorrent
|
||||||
from weboob.tools.application.console import ConsoleApplication
|
from weboob.tools.application.repl import ReplApplication
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Weboorrents']
|
__all__ = ['Weboorrents']
|
||||||
|
|
||||||
|
|
||||||
class Weboorrents(ConsoleApplication):
|
class Weboorrents(ReplApplication):
|
||||||
APPNAME = 'weboorrents'
|
APPNAME = 'weboorrents'
|
||||||
VERSION = '0.1'
|
VERSION = '0.1'
|
||||||
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
||||||
|
|
||||||
def main(self, argv):
|
def load_default_backends(self):
|
||||||
self.load_backends(ICapTorrent)
|
self.load_backends(ICapTorrent)
|
||||||
return self.process_command(*argv[1:])
|
|
||||||
|
|
||||||
@ConsoleApplication.command('Get information about a torrent')
|
def do_info(self, id):
|
||||||
def command_info(self, id):
|
"""
|
||||||
|
info ID
|
||||||
|
|
||||||
|
Get information about a torrent.
|
||||||
|
"""
|
||||||
_id, backend_name = self.parse_id(id)
|
_id, backend_name = self.parse_id(id)
|
||||||
|
|
||||||
found = 0
|
found = 0
|
||||||
|
|
@ -48,8 +51,16 @@ class Weboorrents(ConsoleApplication):
|
||||||
if not found:
|
if not found:
|
||||||
print >>sys.stderr, 'Torrent "%s" not found' % id
|
print >>sys.stderr, 'Torrent "%s" not found' % id
|
||||||
|
|
||||||
@ConsoleApplication.command('Get the torrent file')
|
def do_getfile(self, line):
|
||||||
def command_getfile(self, id, dest):
|
"""
|
||||||
|
getfile ID FILENAME
|
||||||
|
|
||||||
|
Get the .torrent file.
|
||||||
|
FILENAME is where to write the file. If FILENAME is '-',
|
||||||
|
the file is written to stdout.
|
||||||
|
"""
|
||||||
|
id, dest = self.parseline(line, 2, 2)
|
||||||
|
|
||||||
_id, backend_name = self.parse_id(id)
|
_id, backend_name = self.parse_id(id)
|
||||||
|
|
||||||
for backend, buf in self.do('get_torrent_file', _id, backends=backend_name):
|
for backend, buf in self.do('get_torrent_file', _id, backends=backend_name):
|
||||||
|
|
@ -63,8 +74,14 @@ class Weboorrents(ConsoleApplication):
|
||||||
|
|
||||||
print >>sys.stderr, 'Torrent "%s" not found' % id
|
print >>sys.stderr, 'Torrent "%s" not found' % id
|
||||||
|
|
||||||
@ConsoleApplication.command('Search torrents')
|
def do_search(self, pattern):
|
||||||
def command_search(self, pattern=None):
|
"""
|
||||||
|
search [PATTERN]
|
||||||
|
|
||||||
|
Search torrents.
|
||||||
|
"""
|
||||||
|
if not pattern:
|
||||||
|
pattern = None
|
||||||
self.set_formatter_header(u'Search pattern: %s' % pattern if pattern else u'Latest torrents')
|
self.set_formatter_header(u'Search pattern: %s' % pattern if pattern else u'Latest torrents')
|
||||||
for backend, torrent in self.do('iter_torrents', pattern=pattern):
|
for backend, torrent in self.do('iter_torrents', pattern=pattern):
|
||||||
self.format(torrent)
|
self.format(torrent)
|
||||||
|
|
|
||||||
|
|
@ -16,51 +16,61 @@
|
||||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import sys
|
||||||
|
|
||||||
from weboob.core import CallErrors
|
from weboob.core import CallErrors
|
||||||
from weboob.capabilities.weather import ICapWeather, CityNotFound
|
from weboob.capabilities.weather import ICapWeather, CityNotFound
|
||||||
from weboob.tools.application.console import ConsoleApplication
|
from weboob.tools.application.repl import ReplApplication
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['WetBoobs']
|
__all__ = ['WetBoobs']
|
||||||
|
|
||||||
|
|
||||||
class WetBoobs(ConsoleApplication):
|
class WetBoobs(ReplApplication):
|
||||||
APPNAME = 'wetboobs'
|
APPNAME = 'wetboobs'
|
||||||
VERSION = '0.1'
|
VERSION = '0.1'
|
||||||
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
|
||||||
|
|
||||||
def main(self, argv):
|
def load_default_backends(self):
|
||||||
self.load_backends(ICapWeather)
|
self.load_backends(ICapWeather)
|
||||||
|
|
||||||
return self.process_command(*argv[1:])
|
def do_search(self, pattern):
|
||||||
|
"""
|
||||||
|
search PATTERN
|
||||||
|
|
||||||
@ConsoleApplication.command('search cities')
|
Search cities.
|
||||||
def command_search(self, pattern):
|
"""
|
||||||
for backend, city in self.do('iter_city_search', pattern):
|
for backend, city in self.do('iter_city_search', pattern):
|
||||||
self.format(city)
|
self.format(city)
|
||||||
|
|
||||||
@ConsoleApplication.command('get current weather')
|
def do_current(self, city):
|
||||||
def command_current(self, city):
|
"""
|
||||||
|
current CITY
|
||||||
|
|
||||||
|
Get current weather.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
for backend, current in self.do('get_current', city):
|
for backend, current in self.do('get_current', city):
|
||||||
self.format(current)
|
self.format(current)
|
||||||
except CallErrors, e:
|
except CallErrors, e:
|
||||||
for error in e:
|
for error in e:
|
||||||
if isinstance(error, CityNotFound):
|
if isinstance(error, CityNotFound):
|
||||||
logging.error('City "%s" not found' % city)
|
print >>sys.stderr, 'City "%s" not found' % city
|
||||||
else:
|
else:
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
@ConsoleApplication.command('get forecasts')
|
def do_forecasts(self, city):
|
||||||
def command_forecasts(self, city):
|
"""
|
||||||
|
forecasts CITY
|
||||||
|
|
||||||
|
Get forecasts.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
for backend, forecast in self.do('iter_forecast', city):
|
for backend, forecast in self.do('iter_forecast', city):
|
||||||
self.format(forecast)
|
self.format(forecast)
|
||||||
except CallErrors, e:
|
except CallErrors, e:
|
||||||
for error in e:
|
for error in e:
|
||||||
if isinstance(error, CityNotFound):
|
if isinstance(error, CityNotFound):
|
||||||
logging.error('City "%s" not found' % city)
|
print >>sys.stderr, 'City "%s" not found' % city
|
||||||
else:
|
else:
|
||||||
raise error
|
raise error
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# backend.py
|
# backend.py
|
||||||
#
|
#
|
||||||
# Copyright 2010 nicolas <nicolas@jombi.fr>
|
# Copyright 2010 nicolas <nicolas@jombi.fr>
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, version 3 of the License.
|
# the Free Software Foundation, version 3 of the License.
|
||||||
|
|
@ -23,44 +23,38 @@ from weboob.tools.backend import BaseBackend
|
||||||
from .browser import BPbrowser
|
from .browser import BPbrowser
|
||||||
|
|
||||||
class BPBackend(BaseBackend, ICapBank):
|
class BPBackend(BaseBackend, ICapBank):
|
||||||
NAME = 'bp'
|
NAME = 'bp'
|
||||||
MAINTAINER = 'Nicolas Duhamel'
|
MAINTAINER = 'Nicolas Duhamel'
|
||||||
EMAIL = 'nicolas@jombi.fr'
|
EMAIL = 'nicolas@jombi.fr'
|
||||||
VERSION = '0.1'
|
VERSION = '0.1'
|
||||||
LICENSE = 'GPLv3'
|
LICENSE = 'GPLv3'
|
||||||
DESCRIPTION = 'La banque postale, banque française'
|
DESCRIPTION = 'La banque postale, banque française'
|
||||||
CONFIG = {'login': BaseBackend.ConfigField(description='Account ID'),
|
CONFIG = {'login': BaseBackend.ConfigField(description='Account ID'),
|
||||||
'password': BaseBackend.ConfigField(description='Password of account', is_masked=True)
|
'password': BaseBackend.ConfigField(description='Password of account', is_masked=True)
|
||||||
}
|
}
|
||||||
BROWSER = BPbrowser
|
BROWSER = BPbrowser
|
||||||
|
|
||||||
def create_default_browser(self):
|
|
||||||
return self.create_browser(self.config['login'], self.config['password'])
|
|
||||||
|
|
||||||
def iter_accounts(self):
|
def create_default_browser(self):
|
||||||
for account in self.browser.get_accounts_list():
|
return self.create_browser(self.config['login'], self.config['password'])
|
||||||
yield account
|
|
||||||
|
|
||||||
def get_account(self, _id):
|
|
||||||
account = self.browser.get_account(_id)
|
|
||||||
if account:
|
|
||||||
return account
|
|
||||||
else:
|
|
||||||
raise AccountNotFound()
|
|
||||||
|
|
||||||
def iter_history(self, account):
|
|
||||||
for history in self.browser.get_history(account):
|
|
||||||
yield history
|
|
||||||
|
|
||||||
def transfer(self, id_from, id_to, amount):
|
|
||||||
from_account = self.get_account(id_from)
|
|
||||||
to_account = self.get_account(id_to)
|
|
||||||
|
|
||||||
#TODO: retourner le numero du virement
|
|
||||||
self.browser.make_transfer(from_account, to_account, amount)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def iter_accounts(self):
|
||||||
|
for account in self.browser.get_accounts_list():
|
||||||
|
yield account
|
||||||
|
|
||||||
|
def get_account(self, _id):
|
||||||
|
account = self.browser.get_account(_id)
|
||||||
|
if account:
|
||||||
|
return account
|
||||||
|
else:
|
||||||
|
raise AccountNotFound()
|
||||||
|
|
||||||
|
def iter_history(self, account):
|
||||||
|
for history in self.browser.get_history(account):
|
||||||
|
yield history
|
||||||
|
|
||||||
|
def transfer(self, id_from, id_to, amount):
|
||||||
|
from_account = self.get_account(id_from)
|
||||||
|
to_account = self.get_account(id_to)
|
||||||
|
|
||||||
|
#TODO: retourner le numero du virement
|
||||||
|
self.browser.make_transfer(from_account, to_account, amount)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# browser.py
|
# browser.py
|
||||||
#
|
#
|
||||||
# Copyright 2010 nicolas <nicolas@NicolasDesktop>
|
# Copyright 2010 nicolas <nicolas@NicolasDesktop>
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, version 3 of the License.
|
# the Free Software Foundation, version 3 of the License.
|
||||||
|
|
@ -22,7 +22,6 @@ import mechanize
|
||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from weboob.tools.browser import BrowserIncorrectPassword
|
|
||||||
from weboob.tools.parsers import get_parser
|
from weboob.tools.parsers import get_parser
|
||||||
from weboob.capabilities.bank import Account
|
from weboob.capabilities.bank import Account
|
||||||
from weboob.capabilities.bank import Operation
|
from weboob.capabilities.bank import Operation
|
||||||
|
|
@ -30,187 +29,172 @@ from weboob.capabilities.bank import Operation
|
||||||
def remove_html_tags(data):
|
def remove_html_tags(data):
|
||||||
p = re.compile(r'<.*?>')
|
p = re.compile(r'<.*?>')
|
||||||
return p.sub(' ', data)
|
return p.sub(' ', data)
|
||||||
|
|
||||||
def remove_extra_spaces(data):
|
def remove_extra_spaces(data):
|
||||||
p = re.compile(r'\s+')
|
p = re.compile(r'\s+')
|
||||||
return p.sub(' ', data)
|
return p.sub(' ', data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
LOCAL_HASH = ['a02574d7bf67677d2a86b7bfc5e864fe', 'eb85e1cc45dd6bdb3cab65c002d7ac8a',
|
LOCAL_HASH = ['a02574d7bf67677d2a86b7bfc5e864fe', 'eb85e1cc45dd6bdb3cab65c002d7ac8a',
|
||||||
'596e6fbd54d5b111fe5df8a4948e80a4', '9cdc989a4310554e7f5484d0d27a86ce', '0183943de6c0e331f3b9fc49c704ac6d',
|
'596e6fbd54d5b111fe5df8a4948e80a4', '9cdc989a4310554e7f5484d0d27a86ce', '0183943de6c0e331f3b9fc49c704ac6d',
|
||||||
'291b9987225193ab1347301b241e2187', '163279f1a46082408613d12394e4042a', 'b0a9c740c4cada01eb691b4acda4daea',
|
'291b9987225193ab1347301b241e2187', '163279f1a46082408613d12394e4042a', 'b0a9c740c4cada01eb691b4acda4daea',
|
||||||
'3c4307ee92a1f3b571a3c542eafcb330', 'c5b854ae314b61ba42948909e0b2eae7']
|
'3c4307ee92a1f3b571a3c542eafcb330', 'c5b854ae314b61ba42948909e0b2eae7']
|
||||||
ENCODING = 'utf-8'
|
ENCODING = 'utf-8'
|
||||||
|
|
||||||
class BPbrowser(object):
|
class BPbrowser(object):
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, login, pwd):
|
|
||||||
|
|
||||||
self.is_logged = False
|
|
||||||
|
|
||||||
self.login_id = login
|
|
||||||
self.pwd = pwd
|
|
||||||
|
|
||||||
self.parser = get_parser()()
|
|
||||||
|
|
||||||
self.Browser = mechanize.Browser()
|
|
||||||
self.Browser.set_handle_robots(False)
|
|
||||||
|
|
||||||
self.Account_List = []
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def login(self):
|
|
||||||
|
|
||||||
def md5(file):
|
|
||||||
f = open(file,'rb')
|
|
||||||
md5 = hashlib.md5()
|
|
||||||
md5.update(f.read())
|
|
||||||
return md5.hexdigest()
|
|
||||||
|
|
||||||
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/wsost/OstBrokerWeb/loginform?TAM_OP=login&ERROR_CODE=0x00000000&URL=%2Fvoscomptes%2FcanalXHTML%2Fidentif.ea%3Forigin%3Dparticuliers")
|
|
||||||
|
|
||||||
process = lambda i: md5(self.Browser.retrieve(("https://voscomptesenligne.labanquepostale.fr/wsost/OstBrokerWeb/loginform?imgid=%d&0.25122230781963073" % i ))[0])
|
|
||||||
Keypad = [ process(i) for i in range(10)]
|
|
||||||
correspondance = [ Keypad.index(i) for i in LOCAL_HASH]
|
|
||||||
|
|
||||||
Newpassword = "".join([str(correspondance[int(c)]) for c in self.pwd])
|
|
||||||
|
|
||||||
|
|
||||||
self.Browser.select_form(name="formAccesCompte")
|
|
||||||
self.Browser.find_control("password").readonly = False
|
|
||||||
self.Browser["password"] = Newpassword
|
|
||||||
self.Browser["username"] = self.login_id
|
|
||||||
|
|
||||||
self.Browser.submit()
|
|
||||||
self.is_logged = True
|
|
||||||
|
|
||||||
def get_accounts_list(self):
|
|
||||||
|
|
||||||
if self.Account_List:
|
|
||||||
return self.Account_List
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if not self.is_logged:
|
|
||||||
self.login()
|
|
||||||
|
|
||||||
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/authentification/liste_contrat_atos.ea")
|
|
||||||
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/releve/liste_comptes.jsp")
|
|
||||||
|
|
||||||
document = self.parser.parse(self.Browser.response(), ENCODING)
|
|
||||||
|
|
||||||
|
|
||||||
#Parse CCP
|
|
||||||
compte_table = document.xpath("//table[@id='comptes']", smart_strings=False)[0]
|
|
||||||
compte_ligne = compte_table.xpath("./tbody/tr")
|
|
||||||
|
|
||||||
for compte in compte_ligne:
|
|
||||||
account = Account()
|
|
||||||
tp = compte.xpath("./td/a")[0]
|
|
||||||
account.label = tp.text
|
|
||||||
account.link_id = tp.get("href")
|
|
||||||
account.id = compte.xpath("./td")[1].text
|
|
||||||
account.balance = ''.join( compte.xpath("./td/span")[0].text.replace('.','').replace(',','.').split() )
|
|
||||||
self.Account_List.append(account)
|
|
||||||
|
|
||||||
#Parse epargne
|
|
||||||
epargne_table = document.xpath("//table[@id='comptesEpargne']", smart_strings=False)[0]
|
|
||||||
epargne_ligne = epargne_table.xpath("./tbody/tr")
|
|
||||||
|
|
||||||
for epargne in epargne_ligne:
|
|
||||||
account = Account()
|
|
||||||
tp = epargne.xpath("./td/a")[0]
|
|
||||||
account.label = tp.text
|
|
||||||
account.link_id = tp.get("href")
|
|
||||||
account.id = epargne.xpath("./td")[1].text
|
|
||||||
account.balance = ''.join( epargne.xpath("./td/span")[0].text.replace('.','').replace(',','.').split() )
|
|
||||||
self.Account_List.append(account)
|
|
||||||
|
|
||||||
return self.Account_List
|
|
||||||
|
|
||||||
|
|
||||||
def get_account(self, id):
|
|
||||||
if self.Account_List:
|
|
||||||
for account in self.Account_List:
|
|
||||||
if account.id == id:
|
|
||||||
return account
|
|
||||||
return None
|
|
||||||
|
|
||||||
self.get_accounts_list()
|
|
||||||
|
|
||||||
for account in self.Account_List:
|
|
||||||
if account.id == id:
|
|
||||||
return account
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_history(self, account):
|
|
||||||
|
|
||||||
self.Browser.open(account.link_id)
|
|
||||||
rep = self.Browser.follow_link(url_regex="releve", tag="a")
|
|
||||||
document = self.parser.parse(rep, ENCODING)
|
|
||||||
|
|
||||||
mvt_table = document.xpath("//table[@id='mouvements']", smart_strings=False)[0]
|
|
||||||
mvt_ligne = mvt_table.xpath("./tbody/tr")
|
|
||||||
|
|
||||||
operations = []
|
|
||||||
|
|
||||||
for mvt in mvt_ligne:
|
|
||||||
operation = Operation(len(operations))
|
|
||||||
operation.date = mvt.xpath("./td")[0].text
|
|
||||||
tp = mvt.xpath("./td")[1]
|
|
||||||
operation.label = remove_extra_spaces(remove_html_tags(self.parser.tostring(tp)))
|
|
||||||
|
|
||||||
|
|
||||||
r = re.compile(r'\d+')
|
|
||||||
tp = mvt.xpath("./td/span")
|
|
||||||
amount = None
|
|
||||||
for t in tp:
|
|
||||||
if r.search(t.text):
|
|
||||||
amount = t.text
|
|
||||||
amount = ''.join( amount.replace('.', '').replace(',', '.').split() )
|
|
||||||
if amount[0] == "-":
|
|
||||||
operation.amount = -float(amount[1:])
|
|
||||||
else:
|
|
||||||
operation.amount = float(amount)
|
|
||||||
|
|
||||||
operations.append(operation)
|
|
||||||
return operations
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def make_transfer(self, from_account, to_account, amount):
|
|
||||||
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/f_virementSafran.jsp?n=11")
|
|
||||||
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/virementsafran/aiguillage/saisieComptes.ea")
|
|
||||||
|
|
||||||
self.Browser.select_form(name="AiguillageForm")
|
|
||||||
self.Browser["idxCompteEmetteur"] = [from_account.id]
|
|
||||||
self.Browser["idxCompteReceveur"] = [to_account.id]
|
|
||||||
self.Browser.submit()
|
|
||||||
|
|
||||||
self.Browser.select_form(name="VirementNationalForm")
|
|
||||||
self.Browser["montant"] = str(amount)
|
|
||||||
self.Browser.submit()
|
|
||||||
|
|
||||||
#Confirmation
|
|
||||||
# TODO: verifier que tout c'est bien passe
|
|
||||||
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/virementsafran/virementnational/4-virementNational.ea")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, login, pwd):
|
||||||
|
|
||||||
|
self.is_logged = False
|
||||||
|
|
||||||
|
self.login_id = login
|
||||||
|
self.pwd = pwd
|
||||||
|
|
||||||
|
self.parser = get_parser()()
|
||||||
|
|
||||||
|
self.Browser = mechanize.Browser()
|
||||||
|
self.Browser.set_handle_robots(False)
|
||||||
|
|
||||||
|
self.Account_List = []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
|
||||||
|
def md5(file):
|
||||||
|
f = open(file,'rb')
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
md5.update(f.read())
|
||||||
|
return md5.hexdigest()
|
||||||
|
|
||||||
|
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/wsost/OstBrokerWeb/loginform?TAM_OP=login&ERROR_CODE=0x00000000&URL=%2Fvoscomptes%2FcanalXHTML%2Fidentif.ea%3Forigin%3Dparticuliers")
|
||||||
|
|
||||||
|
process = lambda i: md5(self.Browser.retrieve(("https://voscomptesenligne.labanquepostale.fr/wsost/OstBrokerWeb/loginform?imgid=%d&0.25122230781963073" % i ))[0])
|
||||||
|
Keypad = [ process(i) for i in range(10)]
|
||||||
|
correspondance = [ Keypad.index(i) for i in LOCAL_HASH]
|
||||||
|
|
||||||
|
Newpassword = "".join([str(correspondance[int(c)]) for c in self.pwd])
|
||||||
|
|
||||||
|
|
||||||
|
self.Browser.select_form(name="formAccesCompte")
|
||||||
|
self.Browser.find_control("password").readonly = False
|
||||||
|
self.Browser["password"] = Newpassword
|
||||||
|
self.Browser["username"] = self.login_id
|
||||||
|
|
||||||
|
self.Browser.submit()
|
||||||
|
self.is_logged = True
|
||||||
|
|
||||||
|
def get_accounts_list(self):
|
||||||
|
|
||||||
|
if self.Account_List:
|
||||||
|
return self.Account_List
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if not self.is_logged:
|
||||||
|
self.login()
|
||||||
|
|
||||||
|
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/authentification/liste_contrat_atos.ea")
|
||||||
|
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/releve/liste_comptes.jsp")
|
||||||
|
|
||||||
|
document = self.parser.parse(self.Browser.response(), ENCODING)
|
||||||
|
|
||||||
|
|
||||||
|
#Parse CCP
|
||||||
|
compte_table = document.xpath("//table[@id='comptes']", smart_strings=False)[0]
|
||||||
|
compte_ligne = compte_table.xpath("./tbody/tr")
|
||||||
|
|
||||||
|
for compte in compte_ligne:
|
||||||
|
account = Account()
|
||||||
|
tp = compte.xpath("./td/a")[0]
|
||||||
|
account.label = tp.text
|
||||||
|
account.link_id = tp.get("href")
|
||||||
|
account.id = compte.xpath("./td")[1].text
|
||||||
|
account.balance = ''.join( compte.xpath("./td/span")[0].text.replace('.','').replace(',','.').split() )
|
||||||
|
self.Account_List.append(account)
|
||||||
|
|
||||||
|
#Parse epargne
|
||||||
|
epargne_table = document.xpath("//table[@id='comptesEpargne']", smart_strings=False)[0]
|
||||||
|
epargne_ligne = epargne_table.xpath("./tbody/tr")
|
||||||
|
|
||||||
|
for epargne in epargne_ligne:
|
||||||
|
account = Account()
|
||||||
|
tp = epargne.xpath("./td/a")[0]
|
||||||
|
account.label = tp.text
|
||||||
|
account.link_id = tp.get("href")
|
||||||
|
account.id = epargne.xpath("./td")[1].text
|
||||||
|
account.balance = ''.join( epargne.xpath("./td/span")[0].text.replace('.','').replace(',','.').split() )
|
||||||
|
self.Account_List.append(account)
|
||||||
|
|
||||||
|
return self.Account_List
|
||||||
|
|
||||||
|
|
||||||
|
def get_account(self, id):
|
||||||
|
if self.Account_List:
|
||||||
|
for account in self.Account_List:
|
||||||
|
if account.id == id:
|
||||||
|
return account
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.get_accounts_list()
|
||||||
|
|
||||||
|
for account in self.Account_List:
|
||||||
|
if account.id == id:
|
||||||
|
return account
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_history(self, account):
|
||||||
|
|
||||||
|
self.Browser.open(account.link_id)
|
||||||
|
rep = self.Browser.follow_link(url_regex="releve", tag="a")
|
||||||
|
document = self.parser.parse(rep, ENCODING)
|
||||||
|
|
||||||
|
mvt_table = document.xpath("//table[@id='mouvements']", smart_strings=False)[0]
|
||||||
|
mvt_ligne = mvt_table.xpath("./tbody/tr")
|
||||||
|
|
||||||
|
operations = []
|
||||||
|
|
||||||
|
for mvt in mvt_ligne:
|
||||||
|
operation = Operation(len(operations))
|
||||||
|
operation.date = mvt.xpath("./td")[0].text
|
||||||
|
tp = mvt.xpath("./td")[1]
|
||||||
|
operation.label = remove_extra_spaces(remove_html_tags(self.parser.tostring(tp)))
|
||||||
|
|
||||||
|
|
||||||
|
r = re.compile(r'\d+')
|
||||||
|
tp = mvt.xpath("./td/span")
|
||||||
|
amount = None
|
||||||
|
for t in tp:
|
||||||
|
if r.search(t.text):
|
||||||
|
amount = t.text
|
||||||
|
amount = ''.join( amount.replace('.', '').replace(',', '.').split() )
|
||||||
|
if amount[0] == "-":
|
||||||
|
operation.amount = -float(amount[1:])
|
||||||
|
else:
|
||||||
|
operation.amount = float(amount)
|
||||||
|
|
||||||
|
operations.append(operation)
|
||||||
|
return operations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def make_transfer(self, from_account, to_account, amount):
|
||||||
|
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/f_virementSafran.jsp?n=11")
|
||||||
|
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/virementsafran/aiguillage/saisieComptes.ea")
|
||||||
|
|
||||||
|
self.Browser.select_form(name="AiguillageForm")
|
||||||
|
self.Browser["idxCompteEmetteur"] = [from_account.id]
|
||||||
|
self.Browser["idxCompteReceveur"] = [to_account.id]
|
||||||
|
self.Browser.submit()
|
||||||
|
|
||||||
|
self.Browser.select_form(name="VirementNationalForm")
|
||||||
|
self.Browser["montant"] = str(amount)
|
||||||
|
self.Browser.submit()
|
||||||
|
|
||||||
|
#Confirmation
|
||||||
|
# TODO: verifier que tout c'est bien passe
|
||||||
|
self.Browser.open("https://voscomptesenligne.labanquepostale.fr/voscomptes/canalXHTML/virementsafran/virementnational/4-virementNational.ea")
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ class BaseApplication(object):
|
||||||
# Default storage tree
|
# Default storage tree
|
||||||
STORAGE = {}
|
STORAGE = {}
|
||||||
# Synopsis
|
# Synopsis
|
||||||
SYNOPSIS = 'Usage: %prog [-h] [-dqv] [-b backends] ...'
|
SYNOPSIS = 'Usage: %prog [-h] [-dqv] [-b backends] ...'
|
||||||
SYNOPSIS += ' %prog [--help] [--version]'
|
SYNOPSIS += ' %prog [--help] [--version]'
|
||||||
# Description
|
# Description
|
||||||
DESCRIPTION = None
|
DESCRIPTION = None
|
||||||
|
|
@ -128,6 +128,10 @@ class BaseApplication(object):
|
||||||
self._parser = option_parser
|
self._parser = option_parser
|
||||||
if self.DESCRIPTION:
|
if self.DESCRIPTION:
|
||||||
self._parser.description = self.DESCRIPTION
|
self._parser.description = self.DESCRIPTION
|
||||||
|
app_options = OptionGroup(option_parser, '%s Options' % self.APPNAME.capitalize())
|
||||||
|
self.add_application_options(app_options)
|
||||||
|
if len(app_options.option_list) > 0:
|
||||||
|
self._parser.add_option_group(app_options)
|
||||||
self._parser.add_option('-b', '--backends', help='what backend(s) to enable (comma separated)')
|
self._parser.add_option('-b', '--backends', help='what backend(s) to enable (comma separated)')
|
||||||
logging_options = OptionGroup(self._parser, 'Logging Options')
|
logging_options = OptionGroup(self._parser, 'Logging Options')
|
||||||
logging_options.add_option('-d', '--debug', action='store_true', help='display debug messages')
|
logging_options.add_option('-d', '--debug', action='store_true', help='display debug messages')
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,6 @@ class ConsoleApplication(BaseApplication):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.enabled_backends = set()
|
self.enabled_backends = set()
|
||||||
option_parser = OptionParser(self.SYNOPSIS, version=self._get_optparse_version())
|
option_parser = OptionParser(self.SYNOPSIS, version=self._get_optparse_version())
|
||||||
app_options = OptionGroup(option_parser, '%s Options' % self.APPNAME.capitalize())
|
|
||||||
self.add_application_options(app_options)
|
|
||||||
option_parser.add_option_group(app_options)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
BaseApplication.__init__(self, option_parser=option_parser)
|
BaseApplication.__init__(self, option_parser=option_parser)
|
||||||
|
|
@ -85,10 +82,6 @@ class ConsoleApplication(BaseApplication):
|
||||||
formatting_options.add_option('--no-keys', dest='no_keys', action='store_true', help='do not display item keys')
|
formatting_options.add_option('--no-keys', dest='no_keys', action='store_true', help='do not display item keys')
|
||||||
self._parser.add_option_group(formatting_options)
|
self._parser.add_option_group(formatting_options)
|
||||||
|
|
||||||
def add_application_options(self, group):
|
|
||||||
# XXX why is it in ConsoleApplication and not BaseApplication? -romain
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _handle_options(self):
|
def _handle_options(self):
|
||||||
if self.options.formatter:
|
if self.options.formatter:
|
||||||
formatter_name = self.options.formatter
|
formatter_name = self.options.formatter
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright(C) 2010 Christophe Benz
|
# Copyright(C) 2010 Christophe Benz, Romain Bignon
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -19,16 +19,12 @@
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
import cmd
|
from cmd import Cmd
|
||||||
from cStringIO import StringIO
|
|
||||||
from functools import partial
|
|
||||||
import getpass
|
import getpass
|
||||||
from inspect import getargspec
|
|
||||||
import logging
|
import logging
|
||||||
from optparse import OptionGroup, OptionParser
|
from optparse import OptionGroup, OptionParser
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from weboob.capabilities.base import FieldNotFound
|
from weboob.capabilities.base import FieldNotFound
|
||||||
|
|
@ -41,10 +37,13 @@ from .formatters.load import formatters as available_formatters, load_formatter
|
||||||
from .results import ResultsCondition, ResultsConditionException
|
from .results import ResultsCondition, ResultsConditionException
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ReplApplication']
|
__all__ = ['ReplApplication', 'NotEnoughArguments']
|
||||||
|
|
||||||
|
|
||||||
class ReplApplication(cmd.Cmd, BaseApplication):
|
class NotEnoughArguments(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ReplApplication(Cmd, BaseApplication):
|
||||||
"""
|
"""
|
||||||
Base application class for CLI applications.
|
Base application class for CLI applications.
|
||||||
"""
|
"""
|
||||||
|
|
@ -53,7 +52,7 @@ class ReplApplication(cmd.Cmd, BaseApplication):
|
||||||
SYNOPSIS += ' %prog [--help] [--version]'
|
SYNOPSIS += ' %prog [--help] [--version]'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
cmd.Cmd.__init__(self)
|
Cmd.__init__(self)
|
||||||
self.prompt = '%s> ' % self.APPNAME
|
self.prompt = '%s> ' % self.APPNAME
|
||||||
self.intro = '\n'.join(('Welcome to %s v%s' % (self.APPNAME, self.VERSION),
|
self.intro = '\n'.join(('Welcome to %s v%s' % (self.APPNAME, self.VERSION),
|
||||||
'',
|
'',
|
||||||
|
|
@ -69,9 +68,6 @@ class ReplApplication(cmd.Cmd, BaseApplication):
|
||||||
self.hidden_commands = set(['EOF'])
|
self.hidden_commands = set(['EOF'])
|
||||||
|
|
||||||
option_parser = OptionParser(self.SYNOPSIS, version=self._get_optparse_version())
|
option_parser = OptionParser(self.SYNOPSIS, version=self._get_optparse_version())
|
||||||
app_options = OptionGroup(option_parser, '%s Options' % self.APPNAME.capitalize())
|
|
||||||
self.add_application_options(app_options)
|
|
||||||
option_parser.add_option_group(app_options)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
BaseApplication.__init__(self, option_parser=option_parser)
|
BaseApplication.__init__(self, option_parser=option_parser)
|
||||||
|
|
@ -85,7 +81,7 @@ class ReplApplication(cmd.Cmd, BaseApplication):
|
||||||
self._parser.description = ''
|
self._parser.description = ''
|
||||||
|
|
||||||
app_cmds, weboob_cmds, undoc_cmds = self.get_commands_doc()
|
app_cmds, weboob_cmds, undoc_cmds = self.get_commands_doc()
|
||||||
help_str = '%s Commands:\n%s\n\n' % (self.APPNAME, '\n'.join(' %s' % cmd for cmd in sorted(app_cmds + undoc_cmds)))
|
help_str = '%s Commands:\n%s\n\n' % (self.APPNAME.capitalize(), '\n'.join(' %s' % cmd for cmd in sorted(app_cmds + undoc_cmds)))
|
||||||
help_str +='Weboob Commands:\n%s\n' % '\n'.join(' %s' % cmd for cmd in weboob_cmds)
|
help_str +='Weboob Commands:\n%s\n' % '\n'.join(' %s' % cmd for cmd in weboob_cmds)
|
||||||
self._parser.description += help_str
|
self._parser.description += help_str
|
||||||
|
|
||||||
|
|
@ -140,6 +136,29 @@ class ReplApplication(cmd.Cmd, BaseApplication):
|
||||||
except BackendNotFound, e:
|
except BackendNotFound, e:
|
||||||
logging.error(e)
|
logging.error(e)
|
||||||
|
|
||||||
|
def parseargs(self, line, nb, req_n=None):
|
||||||
|
args = line.strip().split(' ', nb - 1)
|
||||||
|
if req_n is not None and len(args) < req_n:
|
||||||
|
raise NotEnoughArguments('Command needs %d arguments' % req_n)
|
||||||
|
|
||||||
|
if len(args) < nb:
|
||||||
|
args += tuple([None for i in xrange(nb - len(args))])
|
||||||
|
return args
|
||||||
|
|
||||||
|
def onecmd(self, _cmd):
|
||||||
|
"""
|
||||||
|
This REPL method is overrided to catch some particular exceptions.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return super(ReplApplication, self).onecmd(_cmd)
|
||||||
|
except CallErrors, e:
|
||||||
|
if len(e.errors) == 1 and isinstance(e.errors[0][1], FieldNotFound):
|
||||||
|
print >>sys.stderr, e.errors[0][1]
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
except NotEnoughArguments, e:
|
||||||
|
print >>sys.stderr, 'Error: no enough arguments.'
|
||||||
|
|
||||||
def main(self, argv):
|
def main(self, argv):
|
||||||
cmd_args = argv[1:]
|
cmd_args = argv[1:]
|
||||||
if cmd_args:
|
if cmd_args:
|
||||||
|
|
@ -164,15 +183,11 @@ class ReplApplication(cmd.Cmd, BaseApplication):
|
||||||
if fields == '$direct':
|
if fields == '$direct':
|
||||||
fields = None
|
fields = None
|
||||||
elif fields == '$full':
|
elif fields == '$full':
|
||||||
fields = [k for k, v in iter_fields(obj)]
|
# XXX IT ABSOLUTLY DOESN'T WORK, OBJ ISN'T EXISTANT.
|
||||||
try:
|
# PLEASE REVIEW THIS CODE.
|
||||||
for values in self.weboob.do(self._complete, self.options.count, fields, function, *args, **kwargs):
|
#fields = [k for k, v in iter_fields(obj)]
|
||||||
yield values
|
fields = None
|
||||||
except CallErrors, e:
|
return self.weboob.do(self._complete, self.options.count, fields, function, *args, **kwargs)
|
||||||
if len(e.errors) == 1 and isinstance(e.errors[0][1], FieldNotFound):
|
|
||||||
logging.error(e.errors[0][1])
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
# options related methods
|
# options related methods
|
||||||
|
|
||||||
|
|
@ -284,7 +299,7 @@ class ReplApplication(cmd.Cmd, BaseApplication):
|
||||||
print 'Unknown command: "%s"' % line
|
print 'Unknown command: "%s"' % line
|
||||||
|
|
||||||
def completenames(self, text, *ignored):
|
def completenames(self, text, *ignored):
|
||||||
return ['%s ' % name for name in cmd.Cmd.completenames(self, text, *ignored) if name not in self.hidden_commands]
|
return ['%s ' % name for name in Cmd.completenames(self, text, *ignored) if name not in self.hidden_commands]
|
||||||
|
|
||||||
def completion_helper(self, text, choices):
|
def completion_helper(self, text, choices):
|
||||||
if text:
|
if text:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue