From 629b74c5d476051a171bc07566aa712a6aa4ed05 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 21 Jun 2022 16:26:06 +0300 Subject: [PATCH] completely revampted .env.template syntax, shiny new checker, better formatting and more --- .env.template | 30 ++++- .github/workflows/codeql-analysis.yml | 97 +++++++-------- main.py | 13 +- utils/checker.py | 169 +++++++++++++++++++++----- utils/console.py | 5 +- 5 files changed, 220 insertions(+), 94 deletions(-) diff --git a/.env.template b/.env.template index 6a4923c..ed85764 100644 --- a/.env.template +++ b/.env.template @@ -1,24 +1,40 @@ REDDIT_CLIENT_ID="" #EXPLANATION the ID of your Reddit app of SCRIPT type +#RANGE 12:30 +#MATCH_REGEX [-a-zA-Z0-9._~+/]+=*$ +#OOB_ERROR The ID should be over 12 and under 30 characters, double check your input. REDDIT_CLIENT_SECRET="" #EXPLANATION the SECRET of your Reddit app of SCRIPT type +#RANGE 20:40 +#MATCH_REGEX [-a-zA-Z0-9._~+/]+=*$ +#OOB_ERROR The secret should be over 20 and under 40 characters, double check your input. REDDIT_USERNAME="" #EXPLANATION the username of your reddit account +#RANGE 3:20 +#MATCH_REGEX [_0-9a-zA-Z]+$ +#OOB_ERROR A username HAS to be between 3 and 20 characters REDDIT_PASSWORD="" #EXPLANATION the password of your reddit account +#RANGE 8:None +#OOB_ERROR Password too short #OPTIONAL RANDOM_THREAD="no" -#EXPLANATION If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: "no" +# If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: "no" REDDIT_2FA="" #MATCH_REGEX ^(yes|no) #EXPLANATION Whether you have Reddit 2FA enabled, Valid options are "yes" and "no" SUBREDDIT="AskReddit" +#EXPLANATION what subreddit to pull posts from, the name of the sub, not the URL +#RANGE 3:20 +#MATCH_REGEX [_0-9a-zA-Z]+$ +#OOB_ERROR A subreddit name HAS to be between 3 and 20 characters + ALLOW_NSFW="False" #EXPLANATION Whether to allow NSFW content, True or False #MATCH_REGEX ^(True|False)$ @@ -29,16 +45,22 @@ POST_ID="" THEME="LIGHT" #EXPLANATION sets the Reddit theme, either LIGHT or DARK +#MATCH_REGEX ^(dark|light|DARK|LIGHT)$ TIMES_TO_RUN="" #EXPLANATION used if you want to run multiple times. set to an int e.g. 4 or 29 and leave blank for 1 MAX_COMMENT_LENGTH="500" #EXPLANATION max number of characters a comment can have. default is 500 +#RANGE 0:10000 +#MATCH_TYPE int +#OOB_ERROR the max comment length should be between 0 and 10000 -#OPTIONAL OPACITY="1" -#EXPLANATION sets the opacity of the comments, Range is 0 -> 1 recommended around 0.8-0.9 +#EXPLANATION Sets the opacity of the comments when overlayed over the background +#RANGE 0:1 +#MATCH_TYPE float +#OOB_ERROR The opacity HAS to be between 0 and 1 # see different voice options: todo: add docs VOICE="Matthew" @@ -49,4 +71,4 @@ TTsChoice="polly" #OPTIONAL STORYMODE="False" -#EXPLANATION IN-PROGRESS - not yet implemented +# IN-PROGRESS - not yet implemented diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 835b4fb..238dad4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,3 +1,4 @@ + # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # @@ -12,61 +13,61 @@ name: "CodeQL" on: - push: - branches: [ "master" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "master" ] - schedule: - - cron: '16 14 * * 3' + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '16 14 * * 3' jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. - steps: - - name: Checkout repository - uses: actions/checkout@v3 + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/main.py b/main.py index 75863d9..9998f44 100755 --- a/main.py +++ b/main.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -import time from subprocess import Popen from dotenv import load_dotenv @@ -26,23 +25,16 @@ print( ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ """ ) -load_dotenv() # Modified by JasonLovesDoggo print_markdown( "### Thanks for using this tool! [Feel free to contribute to this project on GitHub!](https://lewismenelaws.com) If you have any questions, feel free to reach out to me on Twitter or submit a GitHub issue. You can find solutions to many common problems in the [Documentation](https://luka-hietala.gitbook.io/documentation-for-the-reddit-bot/)" ) -client_id = getenv("REDDIT_CLIENT_ID") -client_secret = getenv("REDDIT_CLIENT_SECRET") -username = getenv("REDDIT_USERNAME") -password = getenv("REDDIT_PASSWORD") -reddit2fa = getenv("REDDIT_2FA") - - def main(): if check_env() is not True: exit() + load_dotenv() cleanup() def get_obj(): @@ -58,8 +50,7 @@ def main(): def run_many(times): - for x in range(times): - x = x + 1 + for x in range(1, times + 1): print_step( f'on the {x}{("st" if x == 1 else ("nd" if x == 2 else ("rd" if x == 3 else "th")))} iteration of {times}' ) # correct 1st 2nd 3rd 4th 5th.... diff --git a/utils/checker.py b/utils/checker.py index 75617b1..5735bda 100755 --- a/utils/checker.py +++ b/utils/checker.py @@ -1,75 +1,184 @@ #!/usr/bin/env python import os from rich.console import Console +from rich.table import Table +from rich import box import re import dotenv from utils.console import handle_input console = Console() -success = True - def check_env() -> bool: if not os.path.exists(".env.template"): console.print("[red]Couldn't find .env.template. Unable to check variables.") - return False + return True + if not os.path.exists(".env"): + console.print("[red]Couldn't find the .env file, creating one now.") + with open(".env", "x") as file: + file.write("") with open(".env.template", "r") as template: # req_envs = [env.split("=")[0] for env in template.readlines() if "=" in env] matching = {} explanations = {} + bounds = {} + types = {} + oob_errors = {} req_envs = [] var_optional = False for line in template.readlines(): - if "=" in line and var_optional is not True: + if line.startswith("#") is not True and "=" in line and var_optional is not True: req_envs.append(line.split("=")[0]) elif "#OPTIONAL" in line: var_optional = True elif line.startswith("#MATCH_REGEX "): matching[req_envs[-1]] = line.removeprefix("#MATCH_REGEX ")[:-1] var_optional = False + elif line.startswith("#OOB_ERROR "): + oob_errors[req_envs[-1]] = line.removeprefix("#OOB_ERROR ")[:-1] + var_optional = False + elif line.startswith("#RANGE "): + bounds[req_envs[-1]] = tuple( + map( + lambda x: float(x) if x != "None" else None, + line.removeprefix("#RANGE ")[:-1].split(":"), + ) + ) + var_optional = False + elif line.startswith("#MATCH_TYPE "): + types[req_envs[-1]] = eval(line.removeprefix("#MATCH_TYPE ")[:-1].split()[0]) + var_optional = False elif line.startswith("#EXPLANATION "): explanations[req_envs[-1]] = line.removeprefix("#EXPLANATION ")[:-1] var_optional = False else: var_optional = False - missing = [] - incorrect = [] + missing = set() + incorrect = set() dotenv.load_dotenv() for env in req_envs: value = os.getenv(env) if value is None: - missing.append(env) + missing.add(env) continue if env in matching.keys(): - env, re.match(matching[env], value) is None and incorrect.append(env) - if len(missing): - for i in range(len(missing)): + re.match(matching[env], value) is None and incorrect.add(env) + if env in bounds.keys() and env not in types.keys(): + len(value) >= bounds[env][0] or ( + len(bounds[env]) > 1 and bounds[env][1] >= len(value) + ) or incorrect.add(env) + continue + if env in types.keys(): try: - missing[i] = missing[i] + ": " + explanations[missing[i]] - except KeyError: - pass - console.print( - f"[red]{'These variables are'*(len(missing) > 1) or 'This variable is'} non-optional and missing: \n\n" - + "\n\n".join(missing) + temp = types[env](value) + if env in bounds.keys(): + (bounds[env][0] <= temp or incorrect.add(env)) and len(bounds[env]) > 1 and ( + bounds[env][1] >= temp or incorrect.add(env) + ) + except ValueError: + incorrect.add(env) + + if len(missing): + table = Table( + title="Missing variables", + highlight=True, + show_lines=True, + box=box.ROUNDED, + border_style="#414868", + header_style="#C0CAF5 bold", + title_justify="left", + title_style="#C0CAF5 bold", ) + table.add_column("Variable", justify="left", style="#7AA2F7 bold", no_wrap=True) + table.add_column("Explanation", justify="left", style="#BB9AF7", no_wrap=False) + table.add_column("Type", justify="center", style="#F7768E", no_wrap=True) + table.add_column("Min", justify="right", style="#F7768E", no_wrap=True) + table.add_column("Max", justify="left", style="#F7768E", no_wrap=True) + for env in missing: + table.add_row( + env, + explanations[env] if env in explanations.keys() else "No explanation given", + str(types[env].__name__) if env in types.keys() else "str", + str(bounds[env][0]) if env in bounds.keys() and bounds[env][1] is not None else "", + str(bounds[env][1]) + if env in bounds.keys() and len(bounds[env]) > 1 and bounds[env][1] is not None + else "", + ) + console.print(table) success = False if len(incorrect): - console.print( - f"[red]{'These variables are'*(len(incorrect) > 1) or 'This variable is'} set incorrectly: " - + "\n".join(incorrect) + table = Table( + title="Incorrect variables", + highlight=True, + show_lines=True, + box=box.ROUNDED, + border_style="#414868", + header_style="#C0CAF5 bold", + title_justify="left", + title_style="#C0CAF5 bold", ) - success = False - # if success is True: - # return True - # console.print("[green]Do you want to enter the missing variables by hand(y/n)") - # if not input().casefold().startswith("y"): - # console.print("[red]Aborting: Unresolved missing variables") - # return success - # with open(".env", "a") as env_file: - # for env in missing: - # pass - return success + table.add_column("Variable", justify="left", style="#7AA2F7 bold", no_wrap=True) + table.add_column("Current value", justify="left", style="#F7768E", no_wrap=False) + table.add_column("Explanation", justify="left", style="#BB9AF7", no_wrap=False) + table.add_column("Type", justify="center", style="#F7768E", no_wrap=True) + table.add_column("Min", justify="right", style="#F7768E", no_wrap=True) + table.add_column("Max", justify="left", style="#F7768E", no_wrap=True) + for env in incorrect: + table.add_row( + env, + os.getenv(env), + explanations[env] if env in explanations.keys() else "No explanation given", + str(types[env].__name__) if env in types.keys() else "str", + str(bounds[env][0]) if env in bounds.keys() else "None", + str(bounds[env][1]) if env in bounds.keys() and len(bounds[env]) > 1 else "None", + ) + missing.add(env) + console.print(table) + if success is True: + return True + console.print( + "[green]Do you want to automatically overwrite incorrect variables and add the missing variables? (y/n)" + ) + if not input().casefold().startswith("y"): + console.print("[red]Aborting: Unresolved missing variables") + return False + if len(incorrect): + with open(".env", "r+") as env_file: + lines = [] + for line in env_file.readlines(): + line.split("=")[0].strip() not in incorrect and lines.append(line) + env_file.seek(0) + env_file.write("\n".join(lines)) + env_file.truncate() + console.print("[green]Successfully removed incorrectly set variables from .env") + with open(".env", "a") as env_file: + for env in missing: + env_file.write( + env + + "=" + + ('"' if env not in types.keys() else "") + + str( + handle_input( + "[#F7768E bold]" + env + "[#C0CAF5 bold]=", + types[env] if env in types.keys() else False, + matching[env] if env in matching.keys() else ".*", + explanations[env] + if env in explanations.keys() + else "Incorrect input. Try again.", + bounds[env][0] if env in bounds.keys() else None, + bounds[env][1] if env in bounds.keys() and len(bounds[env]) > 1 else None, + oob_errors[env] if env in oob_errors.keys() else "Input too long/short.", + extra_info="[#C0CAF5 bold]âŽļ " + + ( + explanations[env] if env in explanations.keys() else "No info available" + ), + ) + ) + + ('"' if env not in types.keys() else "") + + "\n" + ) + return True if __name__ == "__main__": diff --git a/utils/console.py b/utils/console.py index 83736ac..905b2aa 100644 --- a/utils/console.py +++ b/utils/console.py @@ -36,10 +36,13 @@ def handle_input( nmin=None, nmax=None, oob_error="", + extra_info="", ): match = re.compile(match + "$") + console.print(extra_info, no_wrap=True) while True: - user_input = input(message + "\n> ").strip() + console.print(message, end="") + user_input = input("").strip() if re.match(match, user_input) is not None: if check_type is not False: try: