diff --git a/nlbcli/__main__.py b/nlbcli/__main__.py index ed850b6..f0f09ff 100644 --- a/nlbcli/__main__.py +++ b/nlbcli/__main__.py @@ -4,6 +4,7 @@ import sys import os import re +from . import formatter from datetime import date, timedelta # local files @@ -41,15 +42,8 @@ def main(): url = 'https://www.nlbklik.com.mk/Retail/Account' _, soup = nlb_post(url, data) table_cells = soup.select('.dps-content') - print('Account Id:', parsed_args.account_id) - print('Account owner:', table_cells[0].text) - print('Status:', table_cells[1].text) - print('Current balance:', table_cells[3].text) - print('Available balance:', table_cells[5].text) - print('Allowed overdraft:', table_cells[7].text) - print('Reserved funds:', table_cells[9].text) - print('Last change:', table_cells[13].text) - print('Last interest:', table_cells[15].text) + # Print the data in the desired output format + formatter.OutputResult(parsed_args, table_cells, "accounts") elif parsed_args.accounts_subparser_name == 'transactions': # direction explanation: @@ -95,9 +89,7 @@ def main(): # we print all cells except the "Details" link/button # todo: parse the 0th column to extract the unique transaction id - for tr in soup.select('tbody > tr'): - tds = tr.select('td')[1:] - print('\t'.join(td.text.strip() for td in tds)) + formatter.OutputResult(parsed_args, soup.select('tbody > tr'), "transactions") elif parsed_args.accounts_subparser_name == 'reservations': url = 'https://www.nlbklik.com.mk/Retail/ReservationList' diff --git a/nlbcli/args_parser.py b/nlbcli/args_parser.py index c0d3940..4855ca8 100644 --- a/nlbcli/args_parser.py +++ b/nlbcli/args_parser.py @@ -7,6 +7,8 @@ month_ago = today - timedelta(days=30) parser = argparse.ArgumentParser() +# FIXME: this would need to be enabled so that every command has the --format argument, instead of just nlbcli accounts balance +#parser.add_argument('--format', nargs="?", choices=['tab','csv','json'], required=False, default='tab') main_subparsers = parser.add_subparsers(dest='subparser_name') login_parser = main_subparsers.add_parser( 'login', help='Log in and save your credentials.') @@ -30,10 +32,12 @@ transactions_parser.add_argument( '--type', choices=['in', 'out'], required=False) transactions_parser.add_argument('--name', required=False) +transactions_parser.add_argument('--format', nargs="?", choices=['tab','csv','json'], required=False, default='tab') # ACCOUNTS [id] balance balance_parser = accounts_subparsers.add_parser( 'balance', help="Show the balance on the specified account") +balance_parser.add_argument('--format', nargs="?", choices=['tab','csv','json'], required=False, default='tab') # ACCOUNtS [id] reservations reservations_parser = accounts_subparsers.add_parser( diff --git a/nlbcli/formatter.py b/nlbcli/formatter.py new file mode 100644 index 0000000..ca8e2a8 --- /dev/null +++ b/nlbcli/formatter.py @@ -0,0 +1,77 @@ +import re +import csv +import json +import sys + + +def OutputResult(parsed_args, table_cells, input_type): + output_format = parsed_args.format + if output_format == 'tab': + if input_type == "accounts": + print('Account Id:', parsed_args.account_id) + print('Account owner:', table_cells[0].text) + print('Status:', table_cells[1].text) + print('Current balance:', table_cells[3].text) + print('Available balance:', table_cells[5].text) + print('Allowed overdraft:', table_cells[7].text) + print('Reserved funds:', table_cells[9].text) + print('Last change:', table_cells[13].text) + print('Last interest:', table_cells[15].text) + elif input_type == "transactions": + for tr in table_cells: + tds = tr.select('td')[1:] + print('\t'.join(td.text.strip() for td in tds)) + elif output_format == 'json': + if input_type == "accounts": + json_output = json.dumps( + { + 'account-id': parsed_args.account_id, + 'account-owner': table_cells[0].text, + 'account-status': table_cells[1].text, + 'current-balance': table_cells[3].text, + 'available-balance': table_cells[5].text, + 'allowed-overdraft': table_cells[7].text, + 'reserved-funds': table_cells[9].text, + 'last-change': table_cells[13].text, + 'last-interest': table_cells[15].text + },indent=4 + ) + elif input_type == "transactions": + txns = [] + for tr in table_cells: + tds = tr.select('td')[1:] + dict_txn = {} + dict_txn['date'] = tds[0].text.strip() + dict_txn['recipient_name'] = tds[1].text.strip() + dict_txn['txn_description'] = tds[2].text.strip() + dict_txn['status'] = tds[3].text.strip() + dict_txn['amount'] = tds[4].text.strip() + dict_txn['txn_fee'] = tds[5].text.strip() + dict_txn['balance'] = tds[6].text.strip() + txns.append(dict_txn) + json_output = json.dumps(txns,indent=4) + print(json_output) + elif output_format == 'csv': + if input_type == "accounts": + field_names = ['account-id', + 'account-owner', + 'account-status', + 'current-balance', + 'available-balance', + 'allowed-overdraft', + 'reserved-funds', + 'last-change', + 'last-interest'] + csv_writer = csv.DictWriter(sys.stdout, field_names) + csv_writer.writeheader() + csv_writer.writerow({ + 'account-id': parsed_args.account_id, + 'account-owner': table_cells[0].text, + 'account-status': table_cells[1].text, + 'current-balance': table_cells[3].text, + 'available-balance': table_cells[5].text, + 'allowed-overdraft': table_cells[7].text, + 'reserved-funds': table_cells[9].text, + 'last-change': table_cells[13].text, + 'last-interest': table_cells[15].text + }) diff --git a/nlbcli/nlbklik-chain.pem b/nlbcli/nlbklik-chain.pem index a06c8a1..50b038e 100644 --- a/nlbcli/nlbklik-chain.pem +++ b/nlbcli/nlbklik-chain.pem @@ -1,49 +1,69 @@ -----BEGIN CERTIFICATE----- -MIIEiTCCA3GgAwIBAgIQAlqK7xlvfg1sIQSyGuZwKzANBgkqhkiG9w0BAQsFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0xNzExMDYxMjIzNTJaFw0yNzExMDYxMjIzNTJaMFwxCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xGzAZBgNVBAMTElRoYXd0ZSBSU0EgQ0EgMjAxODCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAMoIXuVTipccHkMvtoqnVumLhEOorJ16VYJ6FEuGty+P -Up8cyrEgW2+6It2mnC142ukGCE6+E6bry7s+uQUMPkrh8DIfE071BsVHc4k+gKOL -8QEkm6OZZpJraK0NLbTNcqL0+ThaZaa0jFPBCBqE+P0u8xF1btxqMSmsDYfMk2B4 -3yW6JlmRxoNSNabKnLgoGs7XHO4Uv3ZcZas4HnnpfMxJIyaiUlBm0Flh/6D+mkwM -n/nojt4Ji7gVwaQITCacewbb/Yp0W1h+zWOkkS9F8Ho8lAuKfLIFqWeTn2jllWNg -2FiVX+BV75OnETt85pLYZkTgq72nj82khXhBJFTn2AMCAwEAAaOCAUAwggE8MB0G -A1UdDgQWBBSjyF5lVOUweMEF6gcKalnMuf7eWjAfBgNVHSMEGDAWgBQD3lA1VtFM -u2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUH -AwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwNAYIKwYBBQUHAQEEKDAm -MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQgYDVR0fBDsw -OTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFs -Um9vdENBLmNybDA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0 -cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzANBgkqhkiG9w0BAQsFAAOCAQEARE2F -5d0cgozhZNWokCLfdhhl6mXSOyU3SoPamYcWfLH1CzMwD8a1+pFvwHIQfvlwXFH8 -MrjB3C+jVobNbVWRrgqS3Jsa0ltRH/Ffs6ZTgP4WJYm1SNpUbgR7LWUD2F+PTvKB -M/gf9eSyqP4OiJslYaa38NU1aVAxZI15o+4xX4RZMqKXIIBTG2V+oPBjQ1oPmHGA -C/yWt2eThvb8/re7OpSpUdJyfGf97XeM4PiJAl6+4HQXhjwN7ZPZKrQv9Ay33Mgm -YLVQA+x9HONZXx9vvy8pl9bu+NVYWKGxzGxBK0CBozmVUCeXQPJKPTZleYuNM18p -U1P8Xh1CDguM+ZEoew== +MIIGzjCCBbagAwIBAgIQDUG2N7MQ1k8yapHLr+yiyDANBgkqhkiG9w0BAQsFADBC +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRwwGgYDVQQDExNU +aGF3dGUgRVYgUlNBIENBIEcyMB4XDTIzMDQwNjAwMDAwMFoXDTI0MDQwNTIzNTk1 +OVowgZ8xEzARBgsrBgEEAYI3PAIBAxMCTUsxHTAbBgNVBA8MFFByaXZhdGUgT3Jn +YW5pemF0aW9uMRAwDgYDVQQFEwc0NjY0NTMxMQswCQYDVQQGEwJNSzEPMA0GA1UE +BxMGU2tvcGplMRwwGgYDVQQKExNOTEIgQmFua2EgQUQgU2tvcGplMRswGQYDVQQD +ExJ3d3cubmxia2xpay5jb20ubWswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCcFO0Sqxp4su64m6CMRrNzrEt5ui+5mbpi3TjXTJ34V+iDk+xnhFOosQ+P +FNM0doK2+LDEBdVwTpTf7vS3ftEFi8ZedeEd1I8g3z+edTz3bRurjKA+83I59bmS +BbiTHU4TqPKUb7H4iNV0MDrXWDJPhaF82djZVFzOFHrkwoVMBD/UomFw9kwIOHt9 +ytf4/k80TpH2PGveCXBoPlin+4WAf8sMwZTEG94Il3Xc4XCxhnIFjJFHVVrIuMKE +piuyNskgk4iZABwdZikDVc7YKZdYwsJl2LIeWNez24z5Y+6SDZbsVgeB3v1bfKAA +cLDkzqDi4MSVUiTtYqBcnqNAq4T/AgMBAAGjggNgMIIDXDAfBgNVHSMEGDAWgBRs +LuRhtMO5vfDKrabBaHq41MwdoDAdBgNVHQ4EFgQUBwTNMc+POWYqXj+XEE9krVCQ ++ZYwLQYDVR0RBCYwJIISd3d3Lm5sYmtsaWsuY29tLm1rgg5ubGJrbGlrLmNvbS5t +azAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MHEGA1UdHwRqMGgwMqAwoC6GLGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9UaGF3 +dGVFVlJTQUNBRzIuY3JsMDKgMKAuhixodHRwOi8vY3JsNC5kaWdpY2VydC5jb20v +VGhhd3RlRVZSU0FDQUcyLmNybDBKBgNVHSAEQzBBMAsGCWCGSAGG/WwCATAyBgVn +gQwBATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMw +cQYIKwYBBQUHAQEEZTBjMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy +dC5jb20wOwYIKwYBBQUHMAKGL2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9U +aGF3dGVFVlJTQUNBRzIuY3J0MAkGA1UdEwQCMAAwggF9BgorBgEEAdZ5AgQCBIIB +bQSCAWkBZwB2AO7N0GTV2xrOxVy3nbTNE6Iyh0Z8vOzew1FIWUZxH7WbAAABh1Wy +26QAAAQDAEcwRQIgJkzMXrDb+paUpqqt3cTNO/Kj2GtDq8Ajs3nIHGZkXqsCIQDN +F19KiqiQF/lhi1UnJQVDUbBjucL1ZB842m3UuExwowB1AHPZnokbTJZ4oCB9R53m +ssYc0FFecRkqjGuAEHrBd3K1AAABh1Wy3A0AAAQDAEYwRAIgCcDm4RkYp/b79nMO +YCwjlyHDaJKypf0356fZA/NIjaUCIBXkIrFypBVdR4tA6GVy1ebJMMHOsfAYR0at +vjJLS+WPAHYASLDja9qmRzQP5WoC+p0w6xxSActW3SyB2bu/qznYhHMAAAGHVbLb +0wAABAMARzBFAiEAscugz4Z6j0mOBGmUruu+F4l2BwOUugU7HrGTTj84MNICIFCp +npiEy1CEZAyCAUXvUzX1yXaPExizQZW3sZuakro2MA0GCSqGSIb3DQEBCwUAA4IB +AQBRPkQKRTIstnu8FgPm4DuHYMfdIvTguo6b7g29pdVCLTye1AVMeNqCFAzNERF5 +W8BDdeJBZRsgghJsiFpjuZlIXx5R8XT1i3F9oZWEFp567LUG5JQAPsEbAaM/0eEF +SPbQEjUPnW7qpT8vvvow9YcN1qTuc19xjhv+veCgSmny5zCk+HtdQxnYfOdrpvhd +qvnPykMiHyMyIqyfKDDgJHlJIaJxVCVHvbuTwS3l0vDozugIMkq8Gen5VJRBkbXb +S0qPqhs2Pmdu2qdrUH9xxvR8JWz/iQh73rBPV98CEeyuvBG7JOAJuBq0mc7AP0bb +hnXLjgr9rirDyUzvqK+/DYeE -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MIIFOjCCBCKgAwIBAgIQB8LG0yxvDgqrrA3Q+fzVszANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- \ No newline at end of file +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0yMDA3MDIxMjQzMDJaFw0zMDA3MDIxMjQzMDJaMEIxCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHDAaBgNVBAMTE1RoYXd0ZSBFViBSU0Eg +Q0EgRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ6jsIHs3bmIoe +7DvnuSvGX375jpWKv25Gf7uz8GZQT3DFVHeRS0NKn5WosqMVNYJKKEImR1Gb2dxl +90/GDypngguJeBVxdTtgGWPpGnuKPXfc6Qy8yKS1SRQsEs2q2EHl0tEU+/vyNro0 +DDZcox947My8htb1dLx0Q/y9KjEuagQ5AXeLidtiaiAyKnThZRslD8EK/EcHRvkP +AVMSfGyCVmho3VLBP7LAlLA/RyrAX282OfK8lPZqsASNsOQhmsZPT3IuQhXz7RXc +nCpBM+GfZoFSP6+uO5j8TZMkqLTAuVAsSyGY8zNbzYupA7QmPcIfAwqG1oD9dGah +2GgKoeTnAgMBAAGjggILMIICBzAdBgNVHQ4EFgQUbC7kYbTDub3wyq2mwWh6uNTM +HaAwHwYDVR0jBBgwFoAUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDgYDVR0PAQH/BAQD +AgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAG +AQH/AgEAMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3Au +ZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmwzLmRpZ2lj +ZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOGMWh0dHA6Ly9j +cmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwgc4GA1Ud +IASBxjCBwzCBwAYEVR0gADCBtzAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGln +aWNlcnQuY29tL0NQUzCBigYIKwYBBQUHAgIwfgx8QW55IHVzZSBvZiB0aGlzIENl +cnRpZmljYXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFJlbHlpbmcg +UGFydHkgQWdyZWVtZW50IGxvY2F0ZWQgYXQgaHR0cHM6Ly93d3cuZGlnaWNlcnQu +Y29tL3JwYS11YTANBgkqhkiG9w0BAQsFAAOCAQEADf6H4Rxu128yUZjIR/vkWEv0 +PnYKWUQkGXwbqaioq/zjVC/+LrIwBKwEH2aBQyBO/uvMwF+4PSThZKinw51Pfp8w +PcaBMeuQR1PEIYjQN3+NjRpMIFb2CUtbnMsHybKqKSW3rsTT2r2EIBtDrZ3EfeEg +5bfneV6PKvGqcQRZq5BBNTH11tedhcLJB+7F7GRVz8cKCTz0INpQk8gthjBND1+7 +CR+ZHTWPqdXGRqiFJ09NTVmohj4dgL3VyOIArpmPbigODbp80fxzoQJepkHFef6q +mniWnIl7HApTrTIV/UN22gbCcL5dosGv69wnOVQomQsalcOkujetA5BruTiB9A== +-----END CERTIFICATE----- diff --git a/nlbcli/session.py b/nlbcli/session.py index f810213..3cf89bc 100644 --- a/nlbcli/session.py +++ b/nlbcli/session.py @@ -49,7 +49,7 @@ def _login(username, password): "X-Requested-With": "XMLHttpRequest"} url = 'https://www.nlbklik.com.mk/Account/LoginUserNamePassword' login_res = new_session.post( - url, data=data, verify=constants.VERIFY_SSL, allow_redirects=False) + url, data=data, allow_redirects=False) login_json_body = login_res.json() # Response always contains 'ErrorMessage' key and has status code 200, unfortunately. @@ -120,7 +120,7 @@ def nlb_post(url, data): Returns a tuple: the response and the parsed html body.""" def req_fn(sess): return sess.post( - url, data=data, verify=constants.VERIFY_SSL, allow_redirects=False) + url, data=data, allow_redirects=False) response = _fetch_with_autorenewal(req_fn) soup = bs.BeautifulSoup(response.text, 'html.parser') return (response, soup) @@ -132,8 +132,7 @@ def nlb_get(url): Returns a tuple: the response and the parsed html body. """ def req_fn(sess): - return sess.get(url, verify=constants.VERIFY_SSL, - allow_redirects=False) + return sess.get(url, allow_redirects=False) response = _fetch_with_autorenewal(req_fn) soup = bs.BeautifulSoup(response.text, 'html.parser') return (response, soup)