Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
YaBoiMega0 authored Sep 22, 2024
1 parent db8b126 commit 44c7237
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 0 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# What is this?
Using _selenium_ and some manual user input, this script automatically downloads all videos of one or more TikTok users.
A rewrite of [TikTokLoader](https://github.com/NicoWeio/TikTokLoader) that... works.

# Setup and usage
- Create a python 3.12.1 venv in a folder called ".venv" with `py -m venv .\.venv` then enter the venv by running `.\.venv\Scripts\activate` in the terminal
- Run `pip install -r requirements.txt` (specific versions probably not needed but recommended)
- Double click `run.bat` if on windows and follow instructions
- On other OS's, run `main.py` directly and edit the username variable at the bottom of the script (account picker functionality is not supported outside windows)

# How does it work?
- The script first collects a username or list of several usernames from an accounts.txt file if you want several accounts to be downloaded without having to watch them all
- It then opens a selenium firefox browser and gets the user to manually complete a CAPTCHA test, this convinces TikTok to trust that browser session, so all the rest of the downloading is done by copying the cookies and headers of that browser session to all requests.
- It scrolls to the bottom of a users profile page to load every video, and uses the selenium webdriver built in `find_elements` to get the URL of all videos.
- It then accesses each video one by one, generating a one time access source URL (which is printed to the terminal) and then downloads the video from there using requests (and the verified browser session details)
- The videos are saved in incremental numbers in a subfolder copying the username inputted.

> [!WARNING]
> THE SCRIPT MAY ONLY BE USED IN ACCORDANCE WITH TIKTOK TERMS OF SERVICE, I AM NOT RESPONSIBLE FOR ANY MISUSE
70 changes: 70 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import time, os, sys
from colorama import Fore
from snatch import download_all, get_user_videos


def typewriter_animation(message):
for char in message:
print(char, end='', flush=True)
time.sleep(0.005)
print()


def show_title(instant: bool = False):
os.system("title TikSnatch")
os.system("cls")
print(Fore.GREEN , "")

title = '''
████████╗██╗██╗ ██╗███████╗███╗ ██╗ █████╗ ████████╗ ██████╗██╗ ██╗
╚══██╔══╝██║██║ ██╔╝██╔════╝████╗ ██║██╔══██╗╚══██╔══╝██╔════╝██║ ██║
██║ ██║█████╔╝ ███████╗██╔██╗ ██║███████║ ██║ ██║ ███████║
██║ ██║██╔═██╗ ╚════██║██║╚██╗██║██╔══██║ ██║ ██║ ██╔══██║
██║ ██║██║ ██╗███████║██║ ╚████║██║ ██║ ██║ ╚██████╗██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
'''
if instant:
print(title, end='', flush=True)
else:
typewriter_animation(title)


def download_everything(target_username: str, auto_download: bool = False):

download_dir: str = os.path.join(os.getcwd(), target_username)
if not os.path.exists(download_dir):
os.makedirs(download_dir)
urls: list[str | None] = get_user_videos(target_username)
if urls:
print(Fore.GREEN, '\n[+] Successfully snatched all video urls\n')
download_all(urls, download_dir, auto_download)

time.sleep(1)
print(Fore.GREEN, '\n[+] Snatch Successfull !\n')


def main():

print("[+] Enter 'list' to read from file accounts.txt")
target_username = input("[+] Enter Target Username --> ")
if target_username == "list":
if os.path.exists('accounts.txt') == False:
print(Fore.RED, "[+] accounts.txt not found")
sys.exit()
with open('accounts.txt', 'r') as f:
lines = f.readlines()
for line in lines:
if line:
download_everything(line.strip(), True) # REMOVE True TO DISABLE AUTO DOWNLOAD
time.sleep(1)
show_title(instant=True)

else:
download_everything(target_username, True) # REMOVE True TO DISABLE AUTO DOWNLOAD


if __name__ == "__main__":
show_title()
main()
24 changes: 24 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
attrs==24.2.0
certifi==2024.8.30
cffi==1.17.1
charset-normalizer==3.3.2
colorama==0.4.6
h11==0.14.0
idna==3.10
markdown-it-py==3.0.0
mdurl==0.1.2
outcome==1.3.0.post0
pycparser==2.22
Pygments==2.18.0
PySocks==1.7.1
requests==2.32.3
rich==13.8.1
selenium==4.25.0
sniffio==1.3.1
sortedcontainers==2.4.0
trio==0.26.2
trio-websocket==0.11.1
typing_extensions==4.12.2
urllib3==2.2.3
websocket-client==1.8.0
wsproto==1.2.0
2 changes: 2 additions & 0 deletions run.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@echo off
.\.venv\Scripts\python.exe main.py
178 changes: 178 additions & 0 deletions snatch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import functools
import subprocess
import time
import os
import requests

from rich.console import Console
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.firefox.options import Options

console = Console()
ERRORLEVEL = 0

def get_driver(downloading: bool = False, download_dir: str = None):
# Returns a web driver and makes it play nicely with interrupt signals
subprocess_Popen = subprocess.Popen
subprocess.Popen = functools.partial(subprocess_Popen, process_group=0)
if downloading:
profile = webdriver.FirefoxProfile()
profile.set_preference('browser.download.folderList', 2)
profile.set_preference('browser.download.dir', download_dir)
profile.set_preference('browser.helperApps.neverAsk.saveToDisk', 'video/mp4,video/mpeg,video/quicktime,video/x-ms-wmv,video/x-flv,video/webm')
profile.set_preference('browser.download.manager.showWhenStarting', False)
profile.set_preference('browser.download.manager.useWindow', False)
profile.set_preference('browser.download.manager.focusWhenStarting', False)
profile.set_preference('browser.download.manager.alertOnEXEOpen', False)
profile.set_preference('browser.download.manager.showAlertOnComplete', False)
profile.set_preference('browser.download.manager.closeWhenDone', True)

options = Options()
options.profile = profile
driver = webdriver.Firefox(options=options.profile)
else:
driver = webdriver.Firefox()
subprocess.Popen = subprocess_Popen
return driver


def extract_video_url(driver, url) -> str | None:
driver.get(url)
try:
wait = WebDriverWait(driver, 60)
if is_captcha_present(driver):
console.print("[-] Please solve the CAPTCHA in the browser window.", style="yellow")
wait.until_not(EC.presence_of_element_located((By.XPATH, '//div[contains(@class, "captcha")]')))
wait.until(EC.presence_of_element_located((By.TAG_NAME, 'video')))
except Exception as e:
console.print(f"[x] Error or timeout while waiting for video element: {e}", style="red")
return None

# Extract the video URL using JavaScript
video_element = driver.find_element(By.TAG_NAME, 'video')
video_url = driver.execute_script("return arguments[0].currentSrc;", video_element)

if video_url:
return video_url
else:
console.print("[x] No video URL found in the video element.", style="red")
return None

def is_captcha_present(driver) -> bool:
# Check for common CAPTCHA elements
captcha_elements = [
(By.XPATH, '//div[contains(@class, "captcha")]'),
(By.XPATH, '//iframe[contains(@src, "captcha")]'),
]
for by, value in captcha_elements:
try:
driver.find_element(by, value)
return True
except:
continue
return False


def get_user_videos(username) -> list[str | None]:
# Create the link from the username
username = username.lstrip('@')
url = f'https://www.tiktok.com/@{username}'

driver = get_driver()
driver.get(url)

wait = WebDriverWait(driver, 600) # Keep the webdriver active for up to 10 minutes

time.sleep(5) # Wait 5 seconds for the page to load
# Check for CAPTCHA
if is_captcha_present(driver):
console.print("[-] Please solve the CAPTCHA in the browser window.", style="yellow")
wait.until_not(EC.presence_of_element_located((By.XPATH, '//div[contains(@class, "captcha")]')))

# Scroll to load all videos
SCROLL_PAUSE_TIME = 2
last_height = driver.execute_script("return document.body.scrollHeight")
while True:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(SCROLL_PAUSE_TIME)

if is_captcha_present(driver):
console.print("[-] Please solve the CAPTCHA in the browser window.", style="yellow")
wait.until_not(EC.presence_of_element_located((By.XPATH, '//div[contains(@class, "captcha")]')))
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
console.print(f"[+] Successfully loaded all videos", style="green")
break
last_height = new_height

# Collect video URLs from the user's profile page
video_elements = driver.find_elements(By.XPATH, '//a[contains(@href, "/video/")]')
video_urls = [elem.get_attribute('href') for elem in video_elements]
video_urls = list(set(video_urls))
driver.quit()
return video_urls


def download_all(urls, download_dir: str = None, auto_download: bool =False) -> None:
driver = get_driver()
global filename
filename = 0
for url in urls:
source_url = extract_video_url(driver, url)
if source_url:
filename += 1
console.print(f"[+] Extracted source URL: {source_url}", style="green")
download_video(driver, source_url, download_dir if auto_download else None)
else:
console.print("[x] Failed to extract video URL.", style="red")
driver.quit()

def download_video(driver, source_url, download_dir: str = None):
console.print("[+] Opening in browser...", style="green")
driver.get(source_url)

if download_dir:
# Automatic download procedure
console.print("[+] Extracting cookies from browser session...", style="green")
selenium_cookies = driver.get_cookies()
session = requests.Session()
for cookie in selenium_cookies:
session.cookies.set(cookie['name'], cookie['value'])
headers = {
'User-Agent': driver.execute_script("return navigator.userAgent;"),
'Referer': source_url,
}

try:
global filename
file_path = os.path.join(download_dir, f"{filename:04}.mp4")
console.print(f"[+] Downloading video {filename:04} to {download_dir}", style="green")

response = session.get(source_url, headers=headers, stream=True)
response.raise_for_status()

with open(file_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)

console.print(f"[+] Video downloaded successfully to {file_path}", style="green")
return file_path

except Exception as e:
console.print(f"Error downloading video: {e}", style="red")
return None
else:
# Manual download procedure
console.print("[-] Please use your browser's functionality to download the video.", style="yellow")
input("After downloading the video, press Enter to continue...")



if __name__ == "__main__":
username = "@username"
video_urls = get_user_videos(username)
download_all(video_urls)

0 comments on commit 44c7237

Please sign in to comment.