diff --git a/.gitignore b/.gitignore index 303c988..72e6fc7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ assets/ +reddit/__pycache__/ +utils/__pycache__/ .env reddit-bot-351418-5560ebc49cac.json +video_creation/__pycache__/ +.setup-done-before __pycache__ .idea/ .DS_Store diff --git a/README.md b/README.md index 23770a8..df52b59 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,17 @@ These videos on TikTok, YouTube and Instagram get MILLIONS of views across all p ## Installation 👩‍💻 1. Clone this repository -2. Rename `.env.template` to `.env` and replace all values with the appropriate fields. To get Reddit keys (**required**), visit [the Reddit Apps page.](https://www.reddit.com/prefs/apps) TL;DR set up an app that is a "script". Copy your keys into the `.env` file, along with whether your account uses two-factor authentication. -3. Run `pip3 install -r requirements.txt` -4. Run `playwright install` and `playwright install-deps`. -5. Run `python3 main.py` -6. Enjoy 😎 + +2. Run `pip3 install -r requirements.txt` +3. Run `playwright install` and `playwright install-deps`. +4. + 4a **Automatic Install**: Run `python3 main.py` and type 'yes' to activate the setup assistant. + + 4b **Manual Install**: Rename `.env.template` to `.env` and replace all values with the appropriate fields. To get Reddit keys (**required**), visit [the Reddit Apps page.](https://www.reddit.com/prefs/apps) TL;DR set up an app that is a "script". Copy your keys into the `.env` file, along with whether your account uses two-factor authentication. + +5. Run `python3 main.py` (unless you chose automatic install, then the installer will automatically run main.py) +7. Enjoy 😎 + If you want to see more detailed guide, please refer to the official [documentation](https://luka-hietala.gitbook.io/documentation-for-the-reddit-bot/). \*The Documentation is still being developed and worked on, please be patient as we change / add new knowledge! diff --git a/main.py b/main.py index 1b66e8c..61ccbbb 100644 --- a/main.py +++ b/main.py @@ -1,43 +1,112 @@ +# Main from utils.console import print_markdown +from utils.console import print_step +from utils.console import print_substep +from rich.console import Console +import time from reddit.subreddit import get_subreddit_threads from video_creation.background import download_background, chop_background_video from video_creation.voices import save_text_to_mp3 from video_creation.screenshot_downloader import download_screenshots_of_reddit_posts from video_creation.final_video import make_final_video +from utils.loader import Loader +from dotenv import load_dotenv + +console = Console() from dotenv import load_dotenv import os, time, shutil -REQUIRED_VALUES = ["REDDIT_CLIENT_ID","REDDIT_CLIENT_SECRET","REDDIT_USERNAME","REDDIT_PASSWORD", "OPACITY"] +configured = True +REQUIRED_VALUES = [ + "REDDIT_CLIENT_ID", + "REDDIT_CLIENT_SECRET", + "REDDIT_USERNAME", + "REDDIT_PASSWORD", + "OPACITY", +] + 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." ) -time.sleep(3) +""" + +Load .env file if exists. If it doesnt exist, print a warning and launch the setup wizard. +If there is a .env file, check if the required variables are set. If not, print a warning and launch the setup wizard. + +""" + +client_id = os.getenv("REDDIT_CLIENT_ID") +client_secret = os.getenv("REDDIT_CLIENT_SECRET") +username = os.getenv("REDDIT_USERNAME") +password = os.getenv("REDDIT_PASSWORD") +reddit2fa = os.getenv("REDDIT_2FA") load_dotenv() -configured = True +console.log("[bold green]Checking environment variables...") +time.sleep(1) + if not os.path.exists(".env"): - shutil.copy(".env.template", ".env") configured = False + console.log("[red] Your .env file is invalid, or was never created. Standby.") for val in REQUIRED_VALUES: + print(os.getenv(val)) if val not in os.environ or not os.getenv(val): - print(f"Please set the variable \"{val}\" in your .env file.") + console.log(f'[bold red]Missing Variable: "{val}"') configured = False + console.log( + "[red]Looks like you need to set your Reddit credentials in the .env file. Please follow the instructions in the README.md file to set them up." + ) + time.sleep(0.5) + console.log( + "[red]We can also launch the easy setup wizard. type yes to launch it, or no to quit the program." + ) + setup_ask = input("Launch setup wizard? > ") + if setup_ask == "yes": + console.log("[bold green]Here goes nothing! Launching setup wizard...") + time.sleep(0.5) + os.system("python3 setup.py") + else: + if setup_ask == "no": + console.print("[red]Exiting...") + time.sleep(0.5) + exit() + else: + console.print("[red]I don't understand that. Exiting...") + time.sleep(0.5) + exit() + exit() try: float(os.getenv("OPACITY")) except: - print(f"Please ensure that OPACITY is set between 0 and 1 in your .env file") + console.log( + f"[red]Please ensure that OPACITY is set between 0 and 1 in your .env file" + ) configured = False + exit() +console.log("[bold green]Enviroment Variables are set! Continuing...") + + +length, number_of_comments = save_text_to_mp3(reddit_object) +download_screenshots_of_reddit_posts( + reddit_object, number_of_comments, os.getenv("THEME") +) +download_background() +chop_background_video(length) +final_video = make_final_video(number_of_comments) + if configured: reddit_object = get_subreddit_threads() length, number_of_comments = save_text_to_mp3(reddit_object) - download_screenshots_of_reddit_posts(reddit_object, number_of_comments, os.getenv("THEME", "light")) + download_screenshots_of_reddit_posts( + reddit_object, number_of_comments, os.getenv("THEME", "light") + ) download_background() chop_background_video(length) final_video = make_final_video(number_of_comments) diff --git a/reddit/subreddit.py b/reddit/subreddit.py index a518d24..24150a0 100644 --- a/reddit/subreddit.py +++ b/reddit/subreddit.py @@ -1,5 +1,8 @@ +from rich.console import Console from utils.console import print_markdown, print_step, print_substep from dotenv import load_dotenv + +console = Console() import os, random, praw, re @@ -23,7 +26,6 @@ def get_subreddit_threads(): passkey = os.getenv("REDDIT_PASSWORD") content = {} - reddit = praw.Reddit( client_id=os.getenv("REDDIT_CLIENT_ID"), client_secret=os.getenv("REDDIT_CLIENT_SECRET"), @@ -31,6 +33,7 @@ def get_subreddit_threads(): username=os.getenv("REDDIT_USERNAME"), password=passkey, ) + # If the user specifies that he doesnt want a random thread, or if he doesn't insert the "RANDOM_THREAD" variable at all, ask the thread link if not os.getenv("RANDOM_THREAD") or os.getenv("RANDOM_THREAD") == "no": print_substep("Insert the full thread link:", style="bold green") @@ -45,11 +48,7 @@ def get_subreddit_threads(): # ! Prompt the user to enter a subreddit try: subreddit = reddit.subreddit( - re.sub( - r"r\/", - "", - input("What subreddit would you like to pull from? "), - ) + re.sub(r"r\/", "",input("What subreddit would you like to pull from? ")) ) except ValueError: subreddit = reddit.subreddit("askreddit") @@ -59,6 +58,7 @@ def get_subreddit_threads(): submission = list(threads)[random.randrange(0, 25)] print_substep(f"Video will be: {submission.title} :thumbsup:") + console.log("Getting video comments...") try: content["thread_url"] = submission.url content["thread_title"] = submission.title diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..feaa344 --- /dev/null +++ b/setup.py @@ -0,0 +1,122 @@ +""" + +Setup Script for RedditVideoMakerBot + +""" + +# Imports +import os +import time +from utils.console import print_markdown +from utils.console import print_step +from utils.console import print_substep +from rich.console import Console +from utils.loader import Loader +from os.path import exists +console = Console() + +setup_done = exists(".setup-done-before") + +if setup_done == True: + console.log("[red]Setup was already completed! Please make sure you have to run this script again. If you have to, please delete the file .setup-done-before") + exit() + +# These lines ensure the user: +# - knows they are in setup mode +# - knows that they are about to erase any other setup files/data. + +print_step("Setup Assistant") + +print_markdown( + "### You're in the setup wizard. Ensure you're supposed to be here, then type yes to continue. If you're not sure, type no to quit." +) + +# This Input is used to ensure the user is sure they want to continue. +ensureSetupIsRequired = input("Are you sure you want to continue? > ").casefold() +if ensureSetupIsRequired != "yes": + console.print("[red]Exiting...") + time.sleep(0.5) + exit() +else: + # Again, let them know they are about to erase all other setup data. + console.print("[bold red] This will overwrite your current settings. Are you sure you want to continue? [bold green]yes/no") + overwriteSettings = input("Are you sure you want to continue? > ").casefold() + if overwriteSettings != "yes": + console.print("[red]Abort mission! Exiting...") + time.sleep(0.5) + exit() + else: + # Once they confirm, move on with the script. + console.print("[bold green]Alright! Let's get started!") + time.sleep(1) + +console.log("Ensure you have the following ready to enter:") +console.log("[bold green]Reddit Client ID") +console.log("[bold green]Reddit Client Secret") +console.log("[bold green]Reddit Username") +console.log("[bold green]Reddit Password") +console.log("[bold green]Reddit 2FA (yes or no)") +console.log("[bold green]Opacity (range of 0-1, decimals are OK)") +console.log("[bold green]Subreddit (without r/ or /r/)") +console.log("[bold green]Theme (light or dark)") +time.sleep(0.5) +console.print("[green]If you don't have these, please follow the instructions in the README.md file to set them up.") +console.print("[green]If you do have these, type yes to continue. If you dont, go ahead and grab those quickly and come back.") +confirmUserHasCredentials = input("Are you sure you have the credentials? > ").casefold() +if confirmUserHasCredentials != "yes": + console.print("[red]I don't understand that.") + console.print("[red]Exiting...") + exit() +else: + console.print("[bold green]Alright! Let's get started!") + time.sleep(1) + +""" + +Begin the setup process. + +""" + +console.log("Enter your credentials now.") +cliID = input("Client ID > ") +cliSec = input("Client Secret > ") +user = input("Username > ") +passw = input("Password > ") +twofactor = input("2fa Enabled? (yes/no) > ") +opacity = input("Opacity? (range of 0-1) > ") +subreddit = input("Subreddit (without r/) > ") +theme = input("Theme? (light or dark) > ") +console.log("Attempting to save your credentials...") +loader = Loader("Saving Credentials...", "Done!").start() + # you can also put a while loop here, e.g. while VideoIsBeingMade == True: ... +time.sleep(0.5) +console.log("Removing old .env file...") +os.remove(".env") +time.sleep(0.5) +console.log("Creating new .env file...") +with open('.env', 'a') as f: + f.write(f'REDDIT_CLIENT_ID="{cliID}"\n') + time.sleep(0.5) + f.write(f'REDDIT_CLIENT_SECRET="{cliSec}"\n') + time.sleep(0.5) + f.write(f'REDDIT_USERNAME="{user}"\n') + time.sleep(0.5) + f.write(f'REDDIT_PASSWORD="{passw}"\n') + time.sleep(0.5) + f.write(f'REDDIT_2FA="{twofactor}"\n') + time.sleep(0.5) + f.write(f'THEME="{theme}"\n') + time.sleep(0.5) + f.write(f'SUBREDDIT="{subreddit}"\n') + time.sleep(0.5) + f.write(f'OPACITY="{opacity}"\n') + +with open('.setup-done-before', 'a') as f: + f.write("This file blocks the setup assistant from running again. Delete this file to run setup again.") + +loader.stop() + +console.log("[bold green]Setup Complete! Returning...") + +# Post-Setup: send message and try to run main.py again. +os.system("python3 main.py") diff --git a/utils/loader.py b/utils/loader.py new file mode 100644 index 0000000..58fd662 --- /dev/null +++ b/utils/loader.py @@ -0,0 +1,53 @@ +""" + +Okay, have to admit. This code is from StackOverflow. It's so efficient, that it's probably the best way to do it. +Although, it is edited to use less threads. + +""" +from itertools import cycle +from shutil import get_terminal_size +from threading import Thread +from time import sleep + + +class Loader: + def __init__(self, desc="Loading...", end="Done!", timeout=0.1): + """ + A loader-like context manager + + Args: + desc (str, optional): The loader's description. Defaults to "Loading...". + end (str, optional): Final print. Defaults to "Done!". + timeout (float, optional): Sleep time between prints. Defaults to 0.1. + """ + self.desc = desc + self.end = end + self.timeout = timeout + + self._thread = Thread(target=self._animate, daemon=True) + self.steps = ["⢿", "⣻", "⣽", "⣾", "⣷", "⣯", "⣟", "⡿"] + self.done = False + + def start(self): + self._thread.start() + return self + + def _animate(self): + for c in cycle(self.steps): + if self.done: + break + print(f"\r{self.desc} {c}", flush=True, end="") + sleep(self.timeout) + + def __enter__(self): + self.start() + + def stop(self): + self.done = True + cols = get_terminal_size((80, 20)).columns + print("\r" + " " * cols, end="", flush=True) + print(f"\r{self.end}", flush=True) + + def __exit__(self, exc_type, exc_value, tb): + # handle exceptions with those variables ^ + self.stop() \ No newline at end of file