From 4f430378ed993ce9d5eec1ad8a7dbec71f3bb6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=AE?= Date: Sun, 25 Feb 2024 16:25:55 +0200 Subject: [PATCH 1/6] Add `console_scripts` --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index b9be62a..cdad2da 100755 --- a/setup.py +++ b/setup.py @@ -42,4 +42,9 @@ "Programming Language :: Python :: 3.11", "Topic :: Software Development :: Libraries :: Python Modules", ], + entry_points={ + 'console_scripts': [ + 'pyotp = pyotp.__main__:main' + ] + }, ) From bdcfb3fd01ff242467de0bee709943d063cd1d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=AE?= Date: Sun, 25 Feb 2024 16:27:18 +0200 Subject: [PATCH 2/6] Add `__version__` --- src/pyotp/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyotp/__init__.py b/src/pyotp/__init__.py index a695790..f093ea8 100644 --- a/src/pyotp/__init__.py +++ b/src/pyotp/__init__.py @@ -9,6 +9,8 @@ from .otp import OTP as OTP from .totp import TOTP as TOTP +__version__ = "2.9.0" + def random_base32(length: int = 32, chars: Sequence[str] = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")) -> str: # Note: the otpauth scheme DOES NOT use base32 padding for secret lengths not divisible by 8. From aad1951f33f027d48b61a94794224c4daade4492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=AE?= Date: Sun, 25 Feb 2024 16:29:44 +0200 Subject: [PATCH 3/6] Add `ArgumentParser` and functioning --- src/pyotp/__main__.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/pyotp/__main__.py diff --git a/src/pyotp/__main__.py b/src/pyotp/__main__.py new file mode 100644 index 0000000..4c28b5c --- /dev/null +++ b/src/pyotp/__main__.py @@ -0,0 +1,36 @@ +from pyotp import __version__, TOTP, HOTP, random_base32, random_hex +import argparse + +parser = argparse.ArgumentParser() + + +def parse_terminal_arguments(): + parser.add_argument('secret', nargs='?', help=f'Base32 secret') + parser.add_argument('--hotp', action='store', metavar='NUMBER', type=int, help='Specify HOTP position\\number ') + parser.add_argument('--hex', action='store_true', help='returns a 40-character hex-encoded secret') + parser.add_argument('-v', '--version', action='version', version=f'PyOTP v.{__version__}') + return vars(parser.parse_args()) + + +def main(): # TODO: pass args to set_global_server_repo() set_github_token('') + args = parse_terminal_arguments() + + if not args['secret']: + if not args['hex']: + print(random_base32()) + else: + print(random_hex()) + exit() + + try: + if not args['hotp']: + print(TOTP(args['secret']).now()) + else: + print(HOTP(args['secret']).at(args['hotp'])) + except: + print(f'Invalid secret: {args["secret"]}') + + + +if __name__ == "__main__": + main() From 0b7dd06719c54503f9c05d12b2c5a6648b80b1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=AE?= Date: Sun, 25 Feb 2024 16:31:20 +0200 Subject: [PATCH 4/6] Fix typos --- src/pyotp/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyotp/__main__.py b/src/pyotp/__main__.py index 4c28b5c..d548176 100644 --- a/src/pyotp/__main__.py +++ b/src/pyotp/__main__.py @@ -5,8 +5,8 @@ def parse_terminal_arguments(): - parser.add_argument('secret', nargs='?', help=f'Base32 secret') - parser.add_argument('--hotp', action='store', metavar='NUMBER', type=int, help='Specify HOTP position\\number ') + parser.add_argument('secret', nargs='?', help=f'base32 secret') + parser.add_argument('--hotp', action='store', metavar='NUMBER', type=int, help='specify HOTP position\\number ') parser.add_argument('--hex', action='store_true', help='returns a 40-character hex-encoded secret') parser.add_argument('-v', '--version', action='version', version=f'PyOTP v.{__version__}') return vars(parser.parse_args()) From b6615923b1f7cdd484780a6ebeaa46ec95bd6303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=AE?= Date: Mon, 26 Feb 2024 11:09:34 +0200 Subject: [PATCH 5/6] Add extended `console_scripts` --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cdad2da..a2aabc2 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,10 @@ ], entry_points={ 'console_scripts': [ - 'pyotp = pyotp.__main__:main' + 'pyotp = pyotp.__main__:generate', + 'pyotph = pyotp.__main__:generate_hex', + 'pytotp = pyotp.__main__:otp', + 'pyhotp = pyotp.__main__:hotp', ] }, ) From 80f70c181e740297bad512ed960903eeacb225b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=AE?= Date: Mon, 26 Feb 2024 11:12:18 +0200 Subject: [PATCH 6/6] Use stdin instead of arguments --- src/pyotp/__main__.py | 59 ++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/src/pyotp/__main__.py b/src/pyotp/__main__.py index d548176..e4e55a6 100644 --- a/src/pyotp/__main__.py +++ b/src/pyotp/__main__.py @@ -1,36 +1,53 @@ from pyotp import __version__, TOTP, HOTP, random_base32, random_hex -import argparse +from sys import argv -parser = argparse.ArgumentParser() +def argument_parser(): + if len(argv) > 1: + if argv[1] in ('-v', '--version'): + print(f'PyOTP v.{__version__}') + else: + print(f'''PyOTP v.{__version__} usage:\ + \n pyotp - Generate a 32-character base32 secret\ + \n pyotph - Generate a hex-encoded base32 secret\ + \n pytotp - Return TOTP digits from base32 secret\ + \n pyhotp - Return HOTP digits from base32 secret\ + ''') + exit() + -def parse_terminal_arguments(): - parser.add_argument('secret', nargs='?', help=f'base32 secret') - parser.add_argument('--hotp', action='store', metavar='NUMBER', type=int, help='specify HOTP position\\number ') - parser.add_argument('--hex', action='store_true', help='returns a 40-character hex-encoded secret') - parser.add_argument('-v', '--version', action='version', version=f'PyOTP v.{__version__}') - return vars(parser.parse_args()) +def generate(hex=False): + argument_parser() + if not hex: + print(random_base32()) + else: + print(random_hex()) -def main(): # TODO: pass args to set_global_server_repo() set_github_token('') - args = parse_terminal_arguments() +def generate_hex(): + generate(hex=True) - if not args['secret']: - if not args['hex']: - print(random_base32()) - else: - print(random_hex()) - exit() +def otp(is_totp=True): + argument_parser() try: - if not args['hotp']: - print(TOTP(args['secret']).now()) + if is_totp: + print("Enter base32 secret: ", end='') + secret = input() + print(TOTP(secret).now()) else: - print(HOTP(args['secret']).at(args['hotp'])) + print("Enter base32 secret: ", end='') + secret = input() + print("Enter number: ", end='') + number = int(input()) + print(HOTP(secret).at(number)) except: - print(f'Invalid secret: {args["secret"]}') + print(f'Invalid secret: {secret}') + +def hotp(): + otp(is_totp=False) if __name__ == "__main__": - main() + generate()