Changeset View
Changeset View
Standalone View
Standalone View
plugins/import/skrooge_import_backend/skrooge-sabb.py
1 | #!/usr/bin/env python3 | 1 | #!/usr/bin/env python3 | ||
---|---|---|---|---|---|
2 | # -*- coding: utf-8 -*- | 2 | # -*- coding: utf-8 -*- | ||
3 | #************************************************************************** | 3 | #************************************************************************** | ||
4 | #* Copyright (C) 2017 by Bernhard Scheirle bernhard@scheirle.de | 4 | #* Copyright (C) 2017 by Bernhard Scheirle bernhard@scheirle.de | ||
5 | #* This program is free software: you can redistribute it and/or modify | 5 | #* This program is free software: you can redistribute it and/or modify | ||
6 | #* it under the terms of the GNU General Public License as published by | 6 | #* it under the terms of the GNU General Public License as published by | ||
7 | #* the Free Software Foundation, either version 3 of the License, or | 7 | #* the Free Software Foundation, either version 3 of the License, or | ||
8 | #* (at your option) any later version. | 8 | #* (at your option) any later version. | ||
9 | #* | 9 | #* | ||
10 | #* This program is distributed in the hope that it will be useful, | 10 | #* This program is distributed in the hope that it will be useful, | ||
11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of | 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | #* GNU General Public License for more details. | 13 | #* GNU General Public License for more details. | ||
14 | #* | 14 | #* | ||
15 | #* You should have received a copy of the GNU General Public License | 15 | #* You should have received a copy of the GNU General Public License | ||
16 | #* along with this program. If not, see <http://www.gnu.org/licenses/> | 16 | #* along with this program. If not, see <http://www.gnu.org/licenses/> | ||
17 | #************************************************************************** | 17 | #************************************************************************** | ||
18 | 18 | | |||
19 | """ | 19 | """ | ||
20 | Skrooge AqBanking Bridge (SABB) | 20 | Skrooge AqBanking Bridge (SABB) | ||
21 | ------------------------------- | 21 | ------------------------------- | ||
22 | 22 | | |||
23 | Authors: | 23 | Authors: | ||
24 | * Bernhard Scheirle <bernhard@scheirle.de> | 24 | * Bernhard Scheirle <bernhard@scheirle.de> | ||
25 | """ | 25 | | ||
26 | Changelog: | ||||
27 | | ||||
28 | 1.1.0 - 2018.05.21 | ||||
29 | * Added command line parameter --terminal-emulator | ||||
30 | | ||||
31 | 1.0.0 - 2017.07.29 | ||||
32 | * Initial release | ||||
33 | | ||||
34 | """ | ||||
26 | 35 | | |||
27 | import argparse | 36 | import argparse | ||
28 | import contextlib | 37 | import contextlib | ||
29 | import csv | 38 | import csv | ||
30 | import datetime | 39 | import datetime | ||
31 | import io | 40 | import io | ||
32 | import os | 41 | import os | ||
33 | import re | 42 | import re | ||
34 | import shutil | 43 | import shutil | ||
35 | import subprocess | 44 | import subprocess | ||
36 | import sys | 45 | import sys | ||
37 | import tempfile | 46 | import tempfile | ||
38 | from distutils.version import LooseVersion | 47 | from distutils.version import LooseVersion | ||
39 | 48 | | |||
40 | __VERSION__ = "1.0.0" | 49 | __VERSION__ = "1.1.0" | ||
41 | 50 | | |||
42 | class Account(object): | 51 | class Account(object): | ||
43 | def __init__(self): | 52 | def __init__(self): | ||
44 | self.bank_number = "" | 53 | self.bank_number = "" | ||
45 | self.account_number = "" | 54 | self.account_number = "" | ||
46 | self.iban = "" | 55 | self.iban = "" | ||
47 | 56 | | |||
48 | def toString(self): | 57 | def toString(self): | ||
▲ Show 20 Lines • Show All 91 Lines • ▼ Show 20 Line(s) | |||||
140 | 149 | | |||
141 | class SABB(object): | 150 | class SABB(object): | ||
142 | # Tools | 151 | # Tools | ||
143 | AqBanking = 'aqbanking-cli' | 152 | AqBanking = 'aqbanking-cli' | ||
144 | AqHBCI = 'aqhbci-tool4' | 153 | AqHBCI = 'aqhbci-tool4' | ||
145 | 154 | | |||
146 | ReturnValue_NormalExit = 0 | 155 | ReturnValue_NormalExit = 0 | ||
147 | ReturnValue_InvalidVersion = 1 | 156 | ReturnValue_InvalidVersion = 1 | ||
148 | 157 | | |||
149 | def __init__(self): | 158 | def __init__(self): | ||
150 | self.accounts = Accounts() | 159 | self.accounts = Accounts() | ||
151 | 160 | | |||
152 | def build_command(self, executable, args): | 161 | def build_command(self, executable, args): | ||
153 | com = [executable] | 162 | com = [executable] | ||
154 | if executable is self.AqBanking: | 163 | if executable is self.AqBanking: | ||
155 | com.append('--charset=utf-8') | 164 | com.append('--charset=utf-8') | ||
156 | com.extend(args) | 165 | com.extend(args) | ||
▲ Show 20 Lines • Show All 116 Lines • ▼ Show 20 Line(s) | 281 | for i in range(1, 12): | |||
273 | comment = comment + record['purpose' + str(i)] + " " | 282 | comment = comment + record['purpose' + str(i)] + " " | ||
274 | row['comment'] = comment.strip() | 283 | row['comment'] = comment.strip() | ||
275 | row['payee'] = (record['remoteName'] + " " + record['remoteName1']).strip() | 284 | row['payee'] = (record['remoteName'] + " " + record['remoteName1']).strip() | ||
276 | row['amount'] = record['value_value'] | 285 | row['amount'] = record['value_value'] | ||
277 | row['unit'] = record['value_currency'] | 286 | row['unit'] = record['value_currency'] | ||
278 | writer.writerow(row) | 287 | writer.writerow(row) | ||
279 | return output.getvalue() | 288 | return output.getvalue() | ||
280 | 289 | | |||
281 | def download(self, output_folder_path, balance, openTerminal): | 290 | def download(self, output_folder_path, balance, terminal_emulator): | ||
282 | if not self.check_version(): | 291 | if not self.check_version(): | ||
283 | return self.ReturnValue_InvalidVersion | 292 | return self.ReturnValue_InvalidVersion | ||
284 | with TemporaryContextFile() as context_file_path: | 293 | with TemporaryContextFile() as context_file_path: | ||
285 | args = ['request', | 294 | args = ['request', | ||
286 | '--ignoreUnsupported', | 295 | '--ignoreUnsupported', | ||
287 | '--transactions', | 296 | '--transactions', | ||
288 | '-c', | 297 | '-c', | ||
289 | context_file_path | 298 | context_file_path | ||
290 | ] | 299 | ] | ||
291 | if balance: | 300 | if balance: | ||
292 | args.append('--balance') | 301 | args.append('--balance') | ||
293 | 302 | | |||
294 | command = self.build_command(self.AqBanking, args) | 303 | command = str.split(terminal_emulator) | ||
295 | if openTerminal: | 304 | command.extend(self.build_command(self.AqBanking, args)) | ||
296 | shell_command = ['x-terminal-emulator', '-e'] | 305 | subprocess.run(command) | ||
297 | shell_command.extend(command) | | |||
298 | command = shell_command | | |||
299 | process = subprocess.Popen(command) | | |||
300 | process.wait() | | |||
301 | | ||||
302 | 306 | | |||
303 | output_folder_path = os.path.abspath(output_folder_path) | 307 | output_folder_path = os.path.abspath(output_folder_path) | ||
304 | if not os.path.exists(output_folder_path): | 308 | if not os.path.exists(output_folder_path): | ||
305 | os.makedirs(output_folder_path) | 309 | os.makedirs(output_folder_path) | ||
306 | files = {} | 310 | files = {} | ||
307 | for account in self.accounts.get_accounts(): | 311 | for account in self.accounts.get_accounts(): | ||
308 | process_result = subprocess.run( | 312 | process_result = subprocess.run( | ||
309 | self.build_command(self.AqBanking, | 313 | self.build_command(self.AqBanking, | ||
Show All 32 Lines | 339 | def main(): | |||
342 | # Command: listaccounts | 346 | # Command: listaccounts | ||
343 | parser_listaccounts = subparsers.add_parser('listaccounts', help='Returns a list of accounts that can be queried with AqBanking.') | 347 | parser_listaccounts = subparsers.add_parser('listaccounts', help='Returns a list of accounts that can be queried with AqBanking.') | ||
344 | 348 | | |||
345 | # Command: bulkdownload | 349 | # Command: bulkdownload | ||
346 | parser_download = subparsers.add_parser('bulkdownload', help='Downloads all transactions into the given output folder') | 350 | parser_download = subparsers.add_parser('bulkdownload', help='Downloads all transactions into the given output folder') | ||
347 | parser_download.add_argument('--output_folder', required=True, help='The folder to store the csv files.') | 351 | parser_download.add_argument('--output_folder', required=True, help='The folder to store the csv files.') | ||
348 | parser_download.add_argument('--balance', required=False, action='store_true', | 352 | parser_download.add_argument('--balance', required=False, action='store_true', | ||
349 | help='Additionally also download the current balance of all accounts and stores it in a "balance.csv" file in the output folder.') | 353 | help='Additionally also download the current balance of all accounts and stores it in a "balance.csv" file in the output folder.') | ||
354 | parser_download.add_argument('--terminal-emulator', required=False, | ||||
355 | default="x-terminal-emulator -e", | ||||
356 | help='The terminal emulator command string that gets used to run the aqbanking user-interactive session. ' | ||||
357 | 'Use an empty value »""« to not start a new terminal, but reuse the terminal running this command. ' | ||||
358 | 'Example: "xterm -e". ' | ||||
359 | '(Default: "x-terminal-emulator -e")' | ||||
360 | ) | ||||
350 | 361 | | |||
351 | args = parser.parse_args() | 362 | args = parser.parse_args() | ||
352 | 363 | | |||
353 | if (args.command == "listaccounts"): | 364 | if (args.command == "listaccounts"): | ||
354 | return SABB().get_accounts() | 365 | return SABB().get_accounts() | ||
355 | elif (args.command == "bulkdownload"): | 366 | elif (args.command == "bulkdownload"): | ||
356 | return SABB().download(args.output_folder, args.balance, True) | 367 | return SABB().download(args.output_folder, args.balance, args.terminal_emulator) | ||
357 | else: | 368 | else: | ||
358 | parser.print_help() | 369 | parser.print_help() | ||
359 | return 1 | 370 | return 1 | ||
360 | return 0 | 371 | return 0 | ||
361 | 372 | | |||
362 | 373 | | |||
363 | if __name__ == "__main__": | 374 | if __name__ == "__main__": | ||
364 | sys.exit(main()) | 375 | sys.exit(main()) |