diff --git a/Dockerfile b/Dockerfile index 0e5cbd9..cfc4917 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +# For production, pin to a digest: FROM python:3.10-slim-bookworm@sha256:... FROM python:3.10-slim-bookworm ENV PYTHONDONTWRITEBYTECODE=1 \ @@ -14,6 +15,7 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* COPY requirements.txt ./ +# Pin pip for reproducible builds: --upgrade pip==24.0 RUN pip install --upgrade pip \ && pip install -r requirements.txt \ && pip install pytest diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index 8e4e2a5..71008d5 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -58,8 +58,8 @@ class TTSEngine: self, ): # adds periods to the end of paragraphs (where people often forget to put them) so tts doesn't blend sentences for comment in self.reddit_object["comments"]: - # remove links - regex_urls = r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*" + # remove links — atomic URL pattern (no backtracking) + regex_urls = r"https?://\S+|www\.\S+\.\S+" comment["comment_body"] = re.sub(regex_urls, " ", comment["comment_body"]) comment["comment_body"] = comment["comment_body"].replace("\n", ". ") comment["comment_body"] = re.sub(r"\bAI\b", "A.I", comment["comment_body"]) diff --git a/main.py b/main.py index 042099a..2adb0e5 100755 --- a/main.py +++ b/main.py @@ -19,7 +19,7 @@ from video_creation.background import ( download_background_video, get_background_config, ) -from video_creation.final_video import make_final_video, name_normalize +from video_creation.final_video import get_output_path, make_final_video from video_creation.voices import save_text_to_mp3 from video_creation.youtube_uploader import upload_to_youtube @@ -80,22 +80,9 @@ def main(POST_ID=None) -> None: # -- YouTube upload (if enabled in config) --------------------------- youtube_config = settings.config.get("youtube", {}) if youtube_config.get("enabled", False): - # Compute the video path using the same logic as final_video.py - title_raw = reddit_object.get("thread_title", "video") - filename = f"{name_normalize(title_raw)[:251]}" - platform = settings.config["settings"].get("platform", "reddit") - if platform == "reddit": - subreddit = ( - settings.config.get("reddit", {}) - .get("thread", {}) - .get("subreddit", "unknown") - ) - else: - subreddit = reddit_object.get("thread_category", platform) - video_path = f"results/{subreddit}/{filename}.mp4" - + video_path = get_output_path(reddit_object) youtube_url = upload_to_youtube( - video_path, title_raw, settings.config + video_path, reddit_object.get("thread_title", "video"), settings.config ) if youtube_url: print_substep(f"YouTube URL: {youtube_url}", "bold green") diff --git a/platforms/threads/auth.py b/platforms/threads/auth.py index 2b9658c..d7636a8 100644 --- a/platforms/threads/auth.py +++ b/platforms/threads/auth.py @@ -40,7 +40,9 @@ def login_to_threads(page: Page, _context: BrowserContext) -> None: page.locator('input[autocomplete="current-password"]').fill(password) page.get_by_role("button", name="Log in", exact=True).first.click() - page.wait_for_timeout(6000) + # Wait for navigation to feed (success) or stay on login (failure) + page.wait_for_url("https://www.threads.net/", timeout=15000) + page.wait_for_load_state("networkidle") cookies = _context.cookies() Path(THREADS_COOKIE_FILE).parent.mkdir(parents=True, exist_ok=True) diff --git a/utils/.config.template.toml b/utils/.config.template.toml index 042efde..3a8b96e 100644 --- a/utils/.config.template.toml +++ b/utils/.config.template.toml @@ -4,7 +4,8 @@ client_secret = { optional = false, nmin = 20, nmax = 40, explanation = "The SEC username = { optional = false, nmin = 3, nmax = 20, explanation = "The username of your reddit account", example = "JasonLovesDoggo", regex = "^[-_0-9a-zA-Z]+$", oob_error = "A username HAS to be between 3 and 20 characters" } password = { optional = false, nmin = 8, explanation = "The password of your reddit account", example = "fFAGRNJru1FTz70BzhT3Zg", oob_error = "Password too short" } 2fa = { optional = true, type = "bool", options = [true, false, ], default = false, explanation = "Whether you have Reddit 2FA enabled, Valid options are True and False", example = true } -2fa_secret = { optional = true, default = "", explanation = "TOTP shared secret (base32). If provided, 2FA codes are generated automatically instead of prompting interactively.", example = "JBSWY3DPEHPK3PXP" } +# WARNING: Treat this secret like a password — it allows anyone to generate valid 2FA codes. +2fa_secret = { optional = true, default = "", explanation = "TOTP shared secret (base32). If provided, 2FA codes are generated automatically instead of prompting interactively. SECURITY: store this with the same care as a password.", example = "JBSWY3DPEHPK3PXP" } [reddit.thread] diff --git a/utils/version.py b/utils/version.py index 0818c87..5478b6b 100644 --- a/utils/version.py +++ b/utils/version.py @@ -2,20 +2,33 @@ import requests from utils.console import print_step +# Set to the correct GitHub "owner/repo" for this fork, or leave empty to skip check. +_UPSTREAM_REPO = "" + def checkversion(__VERSION__: str): - response = requests.get( - "https://api.github.com/repos/elebumm/RedditVideoMakerBot/releases/latest" - ) - latestversion = response.json()["tag_name"] + if not _UPSTREAM_REPO: + return + try: + response = requests.get( + f"https://api.github.com/repos/{_UPSTREAM_REPO}/releases/latest", + timeout=10, + ) + response.raise_for_status() + latestversion = response.json()["tag_name"] + except (requests.RequestException, KeyError, ValueError): + return # Network or API error — skip version check silently + if __VERSION__ == latestversion: print_step(f"You are using the newest version ({__VERSION__}) of the bot") - return True elif __VERSION__ < latestversion: print_step( - f"You are using an older version ({__VERSION__}) of the bot. Download the newest version ({latestversion}) from https://github.com/elebumm/RedditVideoMakerBot/releases/latest" + f"You are using an older version ({__VERSION__}) of the bot. " + f"Download the newest version ({latestversion}) from " + f"https://github.com/{_UPSTREAM_REPO}/releases/latest" ) else: print_step( - f"Welcome to the test version ({__VERSION__}) of the bot. Thanks for testing and feel free to report any bugs you find." + f"Welcome to the test version ({__VERSION__}) of the bot. " + f"Thanks for testing and feel free to report any bugs you find." ) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index c255825..e86cd17 100644 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -27,6 +27,22 @@ from utils.videos import save_data console = Console() +def get_output_path(reddit_obj: dict) -> str: + """Compute the output mp4 path from a content object. Shared with main.py.""" + title_raw = reddit_obj.get("thread_title", "video") + filename = f"{name_normalize(title_raw)[:251]}" + platform = settings.config["settings"].get("platform", "reddit") + if platform == "reddit": + subreddit = ( + settings.config.get("reddit", {}) + .get("thread", {}) + .get("subreddit", "unknown") + ) + else: + subreddit = reddit_obj.get("thread_category", platform) + return f"results/{subreddit}/{filename}.mp4" + + def _probe_duration(path: str) -> float: """Get media duration in seconds using PyAV.""" with av.open(path) as container: