diff --git a/LICENSE b/LICENSE index f288702..9bcd512 100644 --- a/LICENSE +++ b/LICENSE @@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - Copyright (C) + Facebook video downloader + Copyright (C) 2022 Richard Mwewa This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) + Facebook-Downloader Copyright (C) 2022 Richard Mwewa This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. diff --git a/README.md b/README.md index 73d7619..4782620 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,62 @@ # Facebook-Downloader -Facebook video downloader +A program for downloading Facebook videos + +# Installation +**1. Clone the project** +``` +git clone https://github.com/rly0nheart/Facebook-Downloader.git +``` + +**2. Move to Facebook-Downloader directory** +``` +cd Facebook-Downloader +``` + +**3. Install dependencies** +## Note +> *This will install tqdm, selenium, and requests* +> > *You will need to have Firefox installed to run the program* +> > > *For user convenience, the program will come with a geckodriver.exe binary* +``` +pip install -r requirements.txt +``` + +# Usage +``` +python downloader.py +``` + +> *Alternatively, you could grant execution permission to the downloader and run it as shown below* + +**1. Grant execution permission** +``` +chmod +x downloader.py +``` + +**2. Run downloader** +``` +./downloader.py +``` + +## Example +``` +python downloader.py https://www.facebook.com/PageName/videos/VideoID +``` + +## Note +> Upon run, the downloader will first check for updates. If found, users will be prompted to download the updates + + +# Optional Arguments +| Flag | Description | +|---------|:-----------:| +| *-A/--audio* | download audio only (coming soon) | +| *-o/--output* | output filename | +| *-v/--version* | show program's version number and exit | + +# Donations +If you would like to donate, you could Buy A Coffee for the developer using the button below + +Buy Me A Coffee + +Your support will be much appreciated! diff --git a/downloader.py b/downloader.py new file mode 100644 index 0000000..a11dca4 --- /dev/null +++ b/downloader.py @@ -0,0 +1,96 @@ +import time +import logging +import argparse +import requests +from tqdm import tqdm +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys + + +class FacebookDownloader: + + def __init__(self, args): + self.option = webdriver.FirefoxOptions() + self.option.add_argument('--headless') + self.driver = webdriver.Firefox(options=self.option) + self.program_version_number = "2022.1.0.0" + self.downloading_url = "https://getfvid.com" + self.update_check_endpoint = "https://api.github.com/repos/rly0nheart/Facebook-Downloader/releases/latest" + + + def notice(self): + notice_msg = f""" + Facebook-Downloader {self.program_version_number} Copyright (C) 2022 Richard Mwewa + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + """ + return notice_msg + + + def check_and_get_updates(self): + print(self.notice()) + response = requests.get(self.update_check_endpoint).json() + if response['tag_name'] == self.program_version_number: + """Ignore if the program is up to date""" + pass + else: + update_prompt = input(f"[?] A new release is available ({response['tag_name']}). Would you like to install it? (y/n) ") + if update_prompt.lower() == "y": + files_to_update = ['downloader.py', 'geckodriver.exe', 'README.md', 'requirements.txt'] + for file in tqdm(files_to_update, desc=f'Updating'): + data = requests.get(f'https://raw.githubusercontent.com/rly0nheart/Facebook-Downloader/master/{file}') + with open(file, "wb") as f: + f.write(data.content) + f.close() + exit(f"Updated: Re-run program.") + else: + pass + + + def download_video(self): + self.check_and_get_updates() + self.driver.get(self.downloading_url) # Opening getfvid.com, a website that downloads facebook videos + url_entry_field = self.driver.find_element(By.NAME, "url") # Find the url entry field + url_entry_field.send_keys(args.url) # write facebook url in the entry field + url_entry_field.send_keys(Keys.ENTER) # press enter + time.sleep(20) # Sleep for at least 20 seconds to wait for the next page to load + self.driver.refresh + + """ + HD: "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[1]/a" + SD: "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[2]/a" + Audio: "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[3]/a" + """ + + download_btn = self.driver.find_element(By.XPATH, '/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[1]/a') # Find the download button (this clicks the first button which returns a video in hd) + download_url = download_btn.get_attribute('href') + + with requests.get(download_url, stream=True) as response: + response.raise_for_status() + with open(f'downloads/{args.output}.mp4', 'wb') as file: + for chunk in tqdm(response.iter_content(chunk_size=8192), desc=f'Downloading: {args.output}.mp4'): + file.write(chunk) + print(f'Downloaded: {file.name}') + self.driver.close() + + +parser = argparse.ArgumentParser(description='Facebook-Downloader — by Richard Mwewa') +parser.add_argument('url', help='facebook video url (eg. https://www.facebook.com/PageName/videos/VideoID') +parser.add_argument('-A', '--audio', help=argparse.SUPPRESS, action='store_true') +parser.add_argument('-o', '--output', help='output filename') +args = parser.parse_args() +logging.basicConfig(format='[%(asctime)s] %(levelname)s: %(message)s', datefmt='%I:%M:%S%p', level='NOTSET') + +if __name__ == "__main__": + try: + FacebookDownloader(args).download_video() + + except KeyboardInterrupt: + logging.warning('Process interrupted with Ctrl+C.') + + except Exception as e: + logging.error(e) \ No newline at end of file diff --git a/geckodriver.exe b/geckodriver.exe new file mode 100644 index 0000000..87b2b58 Binary files /dev/null and b/geckodriver.exe differ