|
| 1 | +#!python |
| 2 | +"""Have I been Pwned account database check. |
| 3 | +
|
| 4 | +Usage: pwned-account [--help] [options] [ACCOUNT] |
| 5 | +
|
| 6 | +Arguments: |
| 7 | + ACCOUNT The account to check, if empty prompt for account. |
| 8 | +
|
| 9 | +Options: |
| 10 | + -d --debug Debug only (displays informations and quits). |
| 11 | + -h --help Displays the help. |
| 12 | + -v --verbose Displays more informations. |
| 13 | +""" |
| 14 | + |
| 15 | +import sys |
| 16 | +import hashlib |
| 17 | +import json |
| 18 | +import re |
| 19 | + |
| 20 | +import requests |
| 21 | +import docopt as docpt |
| 22 | + |
| 23 | +from docopt import docopt |
| 24 | + |
| 25 | +# options |
| 26 | +options = None # type: dict |
| 27 | +debug = False # type: bool |
| 28 | +verbose = False # type: bool |
| 29 | + |
| 30 | +# arguments |
| 31 | +account = "" # type: str |
| 32 | + |
| 33 | + |
| 34 | +def main(): |
| 35 | + """Main method.""" |
| 36 | + |
| 37 | + # options |
| 38 | + global debug, verbose |
| 39 | + debug, verbose = options['--debug'], options['--verbose'] # type: bool, bool |
| 40 | + |
| 41 | + if debug: |
| 42 | + verbose = True |
| 43 | + |
| 44 | + # arguments |
| 45 | + global account |
| 46 | + account = options['ACCOUNT'] # type: str |
| 47 | + |
| 48 | + if not account: |
| 49 | + account = input('Account: ') |
| 50 | + print() |
| 51 | + |
| 52 | + if not account: |
| 53 | + raise RuntimeWarning('Empty account.') |
| 54 | + |
| 55 | + # API URL |
| 56 | + url = 'https://haveibeenpwned.com/api/v2/breachedaccount/%s' % account |
| 57 | + |
| 58 | + # verbose |
| 59 | + if verbose: |
| 60 | + print('API URL %s' % url) |
| 61 | + print() |
| 62 | + |
| 63 | + # fetch matching hashes |
| 64 | + r = requests.get(url) |
| 65 | + |
| 66 | + pwns = json.loads(r.content) |
| 67 | + |
| 68 | + if debug: |
| 69 | + print(json.dumps(pwns, indent=4)) |
| 70 | + |
| 71 | + # output |
| 72 | + if not pwns: |
| 73 | + print('Account not pwned.') |
| 74 | + else: |
| 75 | + print('Pwned in %d breach%s:' % (len(pwns), 'es' if len(pwns) > 1 else '')) |
| 76 | + idx = 0 |
| 77 | + for pwned in pwns: # type: dict |
| 78 | + idx = idx + 1 |
| 79 | + label = pwned.get('Title', None) |
| 80 | + |
| 81 | + if verbose: |
| 82 | + desc = pwned.get('Description', '') |
| 83 | + desc = re.sub(r'<a.*>(.*)</a>', r'\g<1>', desc) |
| 84 | + |
| 85 | + label = '%s: %s' % (label, desc) |
| 86 | + label = '%s\n [%s]' % (label, ', '.join(pwned.get('DataClasses', []))) |
| 87 | + |
| 88 | + print('%5d. %s' % (idx, label)) |
| 89 | + |
| 90 | + if verbose: |
| 91 | + print() |
| 92 | + print() |
| 93 | + |
| 94 | + |
| 95 | +def cli(): |
| 96 | + """Command-line interface""" |
| 97 | + global options |
| 98 | + options = docopt(__doc__) |
| 99 | + try: |
| 100 | + main() |
| 101 | + except RuntimeWarning as w: |
| 102 | + print(" Warning: %s" % w, file=sys.stderr) |
| 103 | + sys.exit(1) |
| 104 | + except RuntimeError as w: |
| 105 | + print("%s" % w, file=sys.stderr) |
| 106 | + print(docpt.printable_usage(__doc__)) |
| 107 | + sys.exit(1) |
| 108 | + |
| 109 | + |
| 110 | +# main entry point |
| 111 | +if __name__ == '__main__': |
| 112 | + cli() |
0 commit comments