def validate(self, args): if self.schema: validator = Validator(self.schema) if not validator.validate(args): msg = "Invalid card data provided:\n" for field, errors in validator.errors.items(): msg += "- {}:".format(field) if len(errors) == 1: msg += " {}\n".format(errors[0]) elif len(errors) > 1: msg += "\n" for error in errors: msg += " - {}\n".format(error) logger.error(msg) return False return True
def main(): providers_help = "\n".join( [" - {}".format(p_name) for p_name in providers.keys()]) parser = ArgumentParser( formatter_class=RawTextHelpFormatter, description="""Check gift card balances for a variety of providers. Supported providers: {} Requires an Anti-CAPTCHA API key for providers with CAPTCHAs. Get one here: https://anti-captcha.com Configure your key by setting the ANTI_CAPTCHA_KEY environment variable. Your INPUT_CSV should be formatted as follows: - A header row is required - Each column should contain a parameter required by the specified provider Example (for the 'blackhawk' provider): ------------------------------------------------- | card_number | exp_month | exp_year | cvv | |------------------|-----------|----------|-----| | 4111111111111111 | 12 | 24 | 999 | ------------------------------------------------- If you find this tool useful, consider buying a coffee for the author: https://stevenmirabito.com/kudos""".format(providers_help), ) parser.add_argument( "-v", "--version", action="version", version="%(prog)s {}".format(version.__version__), ) parser.add_argument( "provider", metavar="PROVIDER", type=str.lower, help="Name of balance check provider", ) parser.add_argument("input", metavar="INPUT_CSV", type=str, help="Path to input CSV") parser.add_argument( "--output", "-o", metavar="OUTPUT_CSV", type=str, help=("Path to output CSV (optional; default:" "add/overwrite\nbalance columns on input CSV)"), ) args = parser.parse_args() in_filename = path.abspath(args.input) out_filename = in_filename if args.output: # Separate output path specified out_filename = path.abspath(args.output) if args.provider not in providers: logger.fatal("Unknown provider: '{}'".format(args.provider)) sys.exit(1) provider = providers[args.provider] futures = {} results = [] retries = {} with ThreadPoolExecutor(max_workers=config.MAX_WORKERS) as executor: try: with open(in_filename, newline="") as input_csv: reader = csv.DictReader(input_csv) for row in reader: # Add the card details to the result results.append(row) idx = len(results) - 1 # Schedule balance check future = executor.submit(provider.check_balance, **row) futures[future] = idx except (OSError, IOError) as err: logger.fatal("Unable to open input file '{}': {}".format( in_filename, err)) sys.exit(1) except Exception as e: logger.fatal("Unexpected error: {}".format(e)) sys.exit(1) # While there are still tasks queued, jump back in (handles retries) while futures: # Update progress bar as tasks complete for future in tqdm(as_completed(futures), total=len(futures), leave=False): idx = futures.pop(future) try: balance_info = future.result() except Exception as e: # Log the first column value as an ID (usually card number) card_id = next(iter(results[idx].values())) # Attempt to schedule retry if idx in retries: retries[idx] += 1 if retries[idx] > config.RETRY_TIMES: # Out of retries, permanent failure logger.error( "Failed to balance check {} (out of retries). Last error: {}" .format(card_id, e)) else: retries[idx] = 1 logger.warning( "RETRY {}/{}: Failed to balance check {}, retrying. Error: {}" .format(retries[idx], config.RETRY_TIMES, card_id, e)) future = executor.submit(provider.check_balance, **results[idx]) futures[future] = idx else: # Combine original card details with balance information results[idx] = dict(results[idx], **balance_info) try: with open(out_filename, "w", newline="") as output_csv: logger.info("Writing output CSV...") fieldnames = results[0].keys() writer = csv.DictWriter(output_csv, fieldnames=fieldnames) writer.writeheader() for row in results: writer.writerow(row) logger.info("Output written to: {}".format(out_filename)) except (OSError, IOError) as err: logger.fatal("Unable to open output file '{}': {}".format( in_filename, err)) sys.exit(1) except Exception as e: logger.fatal("Unexpected error: {}".format(e)) sys.exit(1)
def main(): providers_help = "\n".join( [" - {}".format(p_name) for p_name in providers.keys()]) parser = ArgumentParser( formatter_class=RawTextHelpFormatter, description=f"""Check gift card balances for a variety of providers. Supported providers: {providers_help} Requires an Anti-CAPTCHA API key for providers with CAPTCHAs. Get one here: https://anti-captcha.com Configure your key by setting the ANTI_CAPTCHA_KEY environment variable. Your INPUT_CSV should be formatted as follows: - A header row is required - Each column should contain a parameter required by the specified provider Example (for the 'blackhawk' provider): ------------------------------------------------- | card_number | exp_month | exp_year | cvv | |------------------|-----------|----------|-----| | 4111111111111111 | 12 | 24 | 999 | ------------------------------------------------- If you find this tool useful, consider buying a coffee for the author: https://stevenmirabito.com/kudos""", ) parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {version.__version__}") parser.add_argument( "provider", metavar="PROVIDER", type=str.lower, help="Name of balance check provider", ) parser.add_argument("input", metavar="INPUT_CSV", type=str, help="Path to input CSV") parser.add_argument( "--output", "-o", metavar="OUTPUT_CSV", type=str, help=("Path to output CSV (optional; default:" "add/overwrite\nbalance columns on input CSV)"), ) args = parser.parse_args() in_filename = path.abspath(args.input) out_filename = in_filename if not args.output else path.abspath( args.output) if args.provider not in providers: logger.fatal(f"Unknown provider: '{args.provider}'") sys.exit(1) provider = providers[args.provider] max_workers = (provider.max_workers if hasattr(provider, "max_workers") else config.MAX_WORKERS) provider_allows_chunks = True if hasattr(provider, "max_simultaneous") else False futures = {} results = [] retries = {} with ThreadPoolExecutor(max_workers=max_workers) as executor: try: with open(in_filename, newline="") as input_csv: reader = csv.DictReader(input_csv) _chunk = [] for i, card_data in enumerate(reader): # Add the card details to the result results.append(card_data) # Some balance checkers accept multiple cards so prepare to send chunks # !! NEEDS WORK, UNFINISHED if provider_allows_chunks: _chunk.append(card_data) if ( i + 1 ) % provider.max_simultaneous: # If end of chunk, send to schedule... # Schedule balance check future = executor.submit(provider.check_balance, _chunk) futures[future] = i _chunk = [] # Clear chunk at end else: future = executor.submit(provider.check_balance, **card_data) futures[future] = i # Done reading input file and scheduling logger.info(f"Read {len(results)} cards from file '{in_filename}'") except (OSError, IOError) as err: logger.fatal(f"Unable to open input file '{in_filename}': {err}") sys.exit(1) except Exception as e: logger.fatal(f"Unexpected error: {e}") sys.exit(1) # While there are still tasks queued, jump back in (handles retries) while futures: # Update progress bar as tasks complete for future in tqdm(as_completed(futures), total=len(futures), leave=False): idx = futures.pop(future) try: balance_info = future.result() except Exception as e: # Log the first column value as an ID (usually card number) card_id = next(iter(results[idx].values())) # Attempt to schedule retry if idx in retries: retries[idx] += 1 if retries[idx] > config.RETRY_TIMES: # Out of retries, permanent failure logger.error( f"Failed to balance check {card_id} (out of retries). Last error: {e}" ) else: executor.submit(logger.error, "error occurred", exc_info=sys.exc_info()) retries[idx] = 1 logger.warning( "RETRY {}/{}: Failed to balance check {}, will retry later. Error: {}" .format(retries[idx], config.RETRY_TIMES, card_id, e)) future = executor.submit(provider.check_balance, **results[idx]) futures[future] = idx else: # Successful balance(s) returned # !! NEEDS WORK, UNFINISHED if provider_allows_chunks: # List of balances from chunk of cards returned for i, balance_info in enumerate(balances_info): results[idx] = dict(results[idx], **balance_info) # If not on last cards balance info... if len(balance_info) - 1 != i: idx += 1 else: # Single balance returned results[idx] = dict(results[idx], **balance_info) try: with open(out_filename, "w", newline="") as output_csv: logger.info(f"Writing CSV output to {out_filename}...") fieldnames = results[0].keys() writer = csv.DictWriter(output_csv, fieldnames=fieldnames) writer.writeheader() for row in results: writer.writerow(row) logger.info(f"Output written to: {out_filename}") except (OSError, IOError) as err: logger.fatal(f"Unable to open output file '{in_filename}': {err}") sys.exit(1) except Exception as e: logger.fatal(f"Unexpected error: {e}") sys.exit(1)