18 Commits
1.3.1 ... dev

Author SHA1 Message Date
Richard Mwewa
106c754b2f Merge pull request #7 from bellingcat/dev-1
Dev 1
2023-10-12 14:32:16 +02:00
Richard Mwewa
05c71f36e3 Update downloader.py 2023-10-12 14:31:47 +02:00
Richard Mwewa
70c15338df Merge pull request #6 from bellingcat/dev
Dev
2023-10-12 14:28:08 +02:00
Richard Mwewa
7cb7adc351 Update README.md 2023-10-12 14:27:30 +02:00
Richard Mwewa
3b9d04e333 Update README.md 2023-10-12 14:26:47 +02:00
Richard Mwewa
bb01ea0909 Merge pull request #5 from bellingcat/dev
Dev
2023-10-12 14:11:38 +02:00
Richard Mwewa
118aecd3af Merge pull request #4 from bellingcat/dev-1
Dev 1
2023-10-12 14:10:25 +02:00
Richard Mwewa
84da1c4c8d update 2023-10-12 14:09:05 +02:00
Richard Mwewa
f58a599bdb Update README.md 2023-10-12 13:53:46 +02:00
Richard Mwewa
1e5d166200 Added setup scripts. 2023-10-12 06:57:32 +02:00
Richard Mwewa
79b21427b5 Merge pull request #3 from rly0nheart/rly0nheart-patch-1
Update README.md
2023-07-11 01:59:54 +02:00
Richard Mwewa
4b713b5e46 Update README.md 2023-07-11 01:58:39 +02:00
Richard Mwewa
3a226fb497 Merge pull request #2 from rly0nheart/dev
Dev
2023-07-10 01:50:24 +02:00
Richard Mwewa
d8db916f87 Update Dockerfile 2023-07-10 01:47:59 +02:00
Richard Mwewa
ceb51e6bc1 Update downloader.py 2023-07-10 01:45:36 +02:00
Richard Mwewa
3ecaef799d Update main.py 2023-07-10 01:43:21 +02:00
Richard Mwewa
f9d12e25d4 Update downloader.py 2023-07-10 01:42:46 +02:00
Richard Mwewa
af302995b7 Update and rename setup.py to pyproject.toml
Update and rename setup.py to pyproject.toml
2023-07-10 01:41:36 +02:00
11 changed files with 263 additions and 131 deletions

View File

@@ -7,10 +7,9 @@ COPY . .
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl firefox-esr \ ca-certificates curl firefox-esr \
&& rm -fr /var/lib/apt/lists/* \ && rm -fr /var/lib/apt/lists/* \
&& curl -L https://github.com/mozilla/geckodriver/releases/download/v0.32.0/geckodriver-v0.32.0-linux64.tar.gz | tar xz -C /usr/local/bin \ && curl -L https://github.com/mozilla/geckodriver/releases/download/v0.32.0/geckodriver-v0.33.0-linux64.tar.gz | tar xz -C /usr/local/bin \
&& apt-get purge -y ca-certificates curl && apt-get purge -y ca-certificates curl
RUN pip install --upgrade pip && pip install build && python -m build RUN pip install .
RUN pip install dist/*.whl
ENTRYPOINT ["facebook_downloader"] ENTRYPOINT ["facebook_downloader"]

View File

@@ -9,56 +9,52 @@ A program for downloading videos from Facebook, given a video url
``` ```
pip install facebook-downloader pip install facebook-downloader
``` ```
> You will need to have the FireFox browser installed and geckodriver properly set up.
### Note ## Building from source
> You will need to have the FireFox browser installed on your pc (for the PyPI Package) **1.** Clone the repository
>> The program is dependent on selenium, so in order to run it, you will have to download and properly setup geckodriver (setup instructions available below)
# Docker
## Pull the image
``` ```
docker pull rly0nheart/facebook-downloader:facebook-downloader git clone https://github.com/bellingcat/facebook-downloader
```
# Geckodriver setup
## Linux
**1. Go to the geckodriver [releases page](https://github.com/mozilla/geckodriver/releases/). Find the latest version of the driver for your platform and download it**
**2. Extract the downloaded file**
```
tar -xvzf geckodriver*
```
**3. Add geckodriver to your system path**
```
export PATH=$PATH:/path/to/downloaded/geckodriver
``` ```
### Note **2.** Navigate to the cloned repository
> If you encounter issues with the above commands, then you should run them as root ```
cd facebook-downloader
```
### Building the Docker container
```
docker build --tty my-facebook-downloader .
```
### Building the facebook-downloader package
#### Linux
Find the `install.sh` script and run it
```
./install.sh
```
> This assumes the script was already made executable with the `chmod +x uninstall.sh` command.
## Windows
**1. Go to the geckodriver [releases page](https://github.com/mozilla/geckodriver/releases/). Find the geckodriver.exe binary for your platform and download it**
**2. Move the downloaded executable to** *C:\Users\yourusername\AppData\Local\Programs\Python\Python310*
### Note
> The numbers on the directory 'Python310' will depend on the version of Python you have
## Mac OS
* [Set up Selenium & GeckoDriver (Mac)](https://medium.com/dropout-analytics/selenium-and-geckodriver-on-mac-b411dbfe61bc)
#### Windows
**1.** Navigate to the facebook-downloader directory
Find the `install.ps1` script and run it
```
.\install.ps1
```
> The installation scripts will download and setup geckodriver, then install **facebook-downloader**.
# Usage # Usage
## Package
``` ```
facebook_downloader <video-url> facebook_downloader <video-url>
``` ```
# Docker ## Docker
``` ```
docker run -it -v $PWD/downloads:/app/downloads facebook-downloader <facebook_url> docker run --tty --volume $PWD/downloads:/app/downloads my-facebook-downloader <facebook_video_url>
``` ```
## Note
> The url format should be as follows; https://www.facebook.com/PageName/videos/VideoID
# Optional Arguments # Optional Arguments
@@ -70,6 +66,6 @@ facebook_downloader <video-url>
# Donations # Donations
If you would like to donate, you could Buy A Coffee for the developer using the button below If you would like to donate, you could Buy A Coffee for the developer using the button below
<a href="https://www.buymeacoffee.com/189381184" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a> <a href="https://www.buymeacoffee.com/_rly0nheart"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=_rly0nheart&button_colour=40DCA5&font_colour=ffffff&font_family=Comic&outline_colour=000000&coffee_colour=FFDD00" /></a>
Your support will be much appreciated! Your support will be much appreciated!

View File

@@ -0,0 +1,2 @@
__author__ = "Richard Mwewa"
__version__ = "1.4.0"

View File

@@ -1,6 +1,6 @@
import os import os
import argparse
import requests import requests
import argparse
from tqdm import tqdm from tqdm import tqdm
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
@@ -8,84 +8,134 @@ from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions from selenium.webdriver.support import expected_conditions
from . import __version__, __author__
class FacebookDownloader: class FacebookDownloader:
def __init__(self): def __init__(self):
parser = argparse.ArgumentParser(description='facebook-downloader — by Richard Mwewa') self.__base_url = "https://getfvid.com"
parser.add_argument('url', help='facebook video url (eg. https://www.facebook.com/PageName/videos/VideoID') self.__update_check_endpoint = "https://api.github.com/repos/rly0nheart/facebook-downloader/releases/latest"
self.__home_directory = os.path.expanduser("~")
self.__downloads_directory = os.path.join(self.__home_directory, "facebook-downloader")
__option = webdriver.FirefoxOptions()
__option.add_argument('--headless')
self.__driver = webdriver.Firefox(options=__option)
parser = argparse.ArgumentParser(description=f'facebook-downloader — by {__author__}',
epilog='Facebook video downloader.')
parser.add_argument('url', help='facebook video url')
parser.add_argument('-a', '--audio', help='download file as audio', action='store_true') parser.add_argument('-a', '--audio', help='download file as audio', action='store_true')
parser.add_argument('-o', '--output', help='output filename') parser.add_argument('-o', '--output', help='output filename', default="")
self.args = parser.parse_args() parser.add_argument('-v', '--version', action='version', version=__version__)
self.__args = parser.parse_args()
option = webdriver.FirefoxOptions() @staticmethod
option.add_argument('--headless') def __format_output_filename(user_defined_name) -> str:
self.driver = webdriver.Firefox(options=option) """
Formats the output file's name.
self.program_version_number = "1.3.1" :param user_defined_name: User-defined name for the file.
self.downloading_url = "https://getfvid.com" :return: Formatted/Reconstructed name of the file.
self.update_check_endpoint = "https://api.github.com/repos/rly0nheart/facebook-downloader/releases/latest" """
from datetime import datetime
dt_now = datetime.now()
if os.name == "nt":
output_name = dt_now.strftime(f"{user_defined_name}_%d-%m-%Y %I-%M-%S%p-facebook-downloader.mp4")
else:
output_name = dt_now.strftime(f"{user_defined_name}_%d-%m-%Y %I:%M:%S%p-facebook-downloader.mp4")
def notice(self): return output_name
def notice(self) -> str:
"""
Returns the program's license notice and current version.
:return: License notice.
:rtype: str
"""
return f""" return f"""
facebook-downloader v{self.program_version_number} Copyright (C) 2023 Richard Mwewa facebook-downloader v{__version__} Copyright (C) 2022-2023 Richard Mwewa
This program is free software: you can redistribute it and/or modify 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 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. the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
""" """
def check_updates(self): def check_updates(self):
print(self.notice()) """
response = requests.get(self.update_check_endpoint).json() Checks if the program's version tag matches the tag of the latest release on GitHub.
if response['tag_name'] == self.program_version_number: If the tags match, assume the program is up-to-date.
"""Ignore if the program is up to date""" """
pass with requests.get(self.__update_check_endpoint) as response:
else: remote_version = response.json().get('tag_name')
print(f"[UPDATE] A new release is available ({response['tag_name']}). Run 'pip install --upgrade facebook-downloader' to get the updates.") if remote_version != __version__:
print(f"* A new release is available -> facebook-downloader v{remote_version}.\n"
f"* Run 'pip install --upgrade facebook-downloader' to get the updates.\n")
def __get_download_type_element(self) -> str:
"""
Gets the web element according to the specified command-line arguments.
def download_type(self): ELements
--------
- 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
:return: Web element
""" """
The elements change according to what file type will be downloaded if self.__args.audio:
So, we pass an option to specify what file type we want, by default the file is an HD video
"""
"""
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"
"""
if self.args.audio:
download_type_element = "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[3]/a" download_type_element = "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[3]/a"
else: else:
download_type_element = "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[1]/a" download_type_element = "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[1]/a"
return download_type_element return download_type_element
def path_finder(self) -> None:
"""
Creates the facebook-videos directory.
def path_finder(self): :return: None
os.makedirs("downloads", exist_ok=True) """
# Construct and create the directory if it doesn't already exist
os.makedirs(os.path.join(self.__home_directory, "facebook-downloader"), exist_ok=True)
def download_video(self): def download_video(self):
self.path_finder() """
self.check_updates() Opens https://getfvid.com with selenium and uses the specified facebook video link as a query.
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 # Open the base url.
url_entry_field.send_keys(self.args.url) # write facebook url in the entry field self.__driver.get(self.__base_url)
url_entry_field.send_keys(Keys.ENTER) # press enter
print('[INFO] Loading web resource, please wait...')
# self.driver.refresh
download_btn = WebDriverWait(self.driver, 20).until(expected_conditions.presence_of_element_located((By.XPATH, self.download_type()))) # Find the download button (this clicks the first button which returns a video in hd) # Locate the facebook video url entry field.
url_entry_field = self.__driver.find_element(By.NAME, "url")
# Write the given facebook video url in the entry field.
url_entry_field.send_keys(self.__args.url)
# Press ENTER.
url_entry_field.send_keys(Keys.ENTER)
print("* Loading web resources... Please wait.")
# Find the download button (this clicks the first button which returns a video in hd).
download_btn = WebDriverWait(self.__driver, 20).until(
expected_conditions.presence_of_element_located((By.XPATH,
self.__get_download_type_element())))
# Get the video download url from the download button.
download_url = download_btn.get_attribute('href') download_url = download_btn.get_attribute('href')
# Open the download url and stream the content to a file.
with requests.get(download_url, stream=True) as response: with requests.get(download_url, stream=True) as response:
response.raise_for_status() response.raise_for_status()
with open(os.path.join('downloads', f'{self.args.output}.mp4'), 'wb') as file: with open(os.path.join(self.__downloads_directory,
for chunk in tqdm(response.iter_content(chunk_size=8192), desc=f'[INFO] Downloading: {self.args.output}.mp4'): self.__format_output_filename(self.__args.output)), 'wb') as file:
for chunk in tqdm(response.iter_content(chunk_size=8192),
desc=f"* Downloading: {file.name}"):
file.write(chunk) file.write(chunk)
print(f'[INFO] Downloaded: {file.name}') print(f"* Downloaded: {file.name}")
self.driver.close()
# Close driver.
self.__driver.close()

View File

@@ -1,12 +1,25 @@
from facebook_downloader.downloader import FacebookDownloader from facebook_downloader.downloader import FacebookDownloader
def main():
def start_downloader():
try: try:
start = FacebookDownloader() # Initialise the FaceBookDownloader instance.
start.download_video() program = FacebookDownloader()
# Create directory where downloaded videos will be stored.
program.path_finder()
# Print program's license notice.
print(program.notice())
# Check for latest releases.
program.check_updates()
# Start video download.
program.download_video()
except KeyboardInterrupt: except KeyboardInterrupt:
print('[WARNING] Process interrupted with Ctrl+C.') print("Process interrupted with Ctrl+C.")
except Exception as e: except Exception as e:
print('[ERROR]', e) print(f"An error occurred: {e}")

27
install.ps1 Normal file
View File

@@ -0,0 +1,27 @@
# Define URL for GeckoDriver
$geckoURL = "https://github.com/mozilla/geckodriver/releases/download/v0.33.0/geckodriver-v0.33.0-win64.zip"
# Define target directories for installation
$geckoDir = "$env:USERPROFILE\facebook-downloader\GeckoDriver"
# Function to download a file
function DownloadFile([string]$url, [string]$path) {
Invoke-WebRequest -Uri $url -OutFile $path
}
# Check if GeckoDriver directory exists, if not create and download
if (-Not (Test-Path $geckoDir)) {
New-Item -Path $geckoDir -ItemType Directory
Write-Host "Downloading GeckoDriver..."
DownloadFile $geckoURL "$geckoDir\geckodriver.zip"
# Unzipping the GeckoDriver
Expand-Archive -Path "$geckoDir\geckodriver.zip" -DestinationPath $geckoDir
Remove-Item "$geckoDir\geckodriver.zip"
}
# Add the geckodriver directory to PATH
[Environment]::SetEnvironmentVariable("PATH", [Environment]::GetEnvironmentVariable("PATH", [EnvironmentVariableTarget]::User) + ";$geckoDir", [EnvironmentVariableTarget]::User)
pip install .
Write-Host "Setup complete."

9
install.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
# Download geckodriver .tar.gz file and pipe it to 'tar' to extract the geckodriver binary directly into /usr/bin.
curl -L https://github.com/mozilla/geckodriver/releases/download/v0.33.0/geckodriver-v0.33.0-linux64.tar.gz | \
tar xz -C /usr/bin
# Install Python packages defined in the current directory's setup.py/pyproject.toml file. (pyproject.toml in this case)
pip3 install .
echo "Setup complete."

33
pyproject.toml Normal file
View File

@@ -0,0 +1,33 @@
[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"
[project]
name = "facebook-downloader"
version = "1.4.0"
description = "A Facebook video downloader."
readme = "README.md"
requires-python = ">=3.9"
license = {file = "LICENSE"}
dependencies = ["tqdm", "requests", "selenium"]
keywords = ["searchcode", "search-engine", "codesearch", "api-wrapper"]
authors = [{name = "Richard Mwewa", email = "rly0nheart@duck.com"}]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
"Natural Language :: English",
]
[tool.setuptools]
packages = ["facebook_downloader"]
[project.urls]
homepage = "https://pypi.org/project/facebook-downloader"
documentation = "https://github.com/bellingcat/facebook-downloader/wiki"
repository = "https://github.com/bellingcat/facebook-downloader"
[project.scripts]
facebook_downloader = "facebook_downloader.main:start_downloader"

View File

@@ -1,31 +0,0 @@
import setuptools
with open('README.md', 'r', encoding='utf-8') as file:
long_description = file.read()
setuptools.setup(
name='facebook-downloader',
version='1.3.1',
author='Richard Mwewa',
author_email='rly0nheart@duck.com',
packages=['facebook_downloader'],
description='Facebook video downloader',
long_description=long_description,
long_description_content_type='text/markdown',
url='https://github.com/rly0nheart/facebook-downloader',
license='GNU General Public License v3 (GPLv3)',
install_requires=['requests', 'selenium', 'tqdm'],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Information Technology',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Operating System :: OS Independent',
'Natural Language :: English',
'Programming Language :: Python :: 3'
],
entry_points={
'console_scripts': [
'facebook_downloader=facebook_downloader.main:main',
]
},
)

26
uninstall.ps1 Normal file
View File

@@ -0,0 +1,26 @@
# Define target directory for removal
$geckoDir = "$env:USERPROFILE\facebook-downloader\GeckoDriver"
# Function to remove directory
function RemoveDir([string]$dirPath) {
if (Test-Path $dirPath) {
Remove-Item -Path $dirPath -Recurse -Force
Write-Host "Removed directory: $dirPath"
} else {
Write-Host "Directory $dirPath does not exist."
}
}
# Remove GeckoDriver directory
RemoveDir $geckoDir
# Remove the geckodriver directory from PATH
$pathEnv = [Environment]::GetEnvironmentVariable("PATH", [EnvironmentVariableTarget]::User)
$newPath = ($pathEnv -split ";" | Where-Object { $_ -ne $geckoDir }) -join ";"
[Environment]::SetEnvironmentVariable("PATH", $newPath, [EnvironmentVariableTarget]::User)
Write-Host "Removed GeckoDriver directory from PATH."
# Uninstall facebook-downloader Python package
pip uninstall facebook-downloader -y
Write-Host "Cleanup complete."

8
uninstall.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
# Remove the geckodriver binary from /usr/bin
sudo rm /usr/bin/geckodriver -v
# Uninstall tor2tor
pip3 uninstall facebook-downloader -y -v
echo "Cleanup complete."