36 Commits

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
Richard Mwewa
ddd14c151d Update README.md 2023-01-02 05:14:30 +02:00
Richard Mwewa
18213172aa Switched to semantic versioning 2023-01-02 05:11:48 +02:00
Richard Mwewa
99dc9e3ea7 Update README.md 2023-01-02 05:03:04 +02:00
Richard Mwewa
1126a72a9f Update README.md 2023-01-02 04:57:08 +02:00
Richard Mwewa
de660b30f4 Update README.md 2023-01-02 04:56:22 +02:00
Richard Mwewa
07bf9a0220 Update main.py 2023-01-02 04:49:33 +02:00
Richard Mwewa
0a8454fcf9 Switched to semantic versioning 2023-01-02 04:32:51 +02:00
Richard Mwewa
8abc59474b Update main.py 2023-01-02 04:13:52 +02:00
Richard Mwewa
885676d826 Update downloader.py 2023-01-02 04:12:24 +02:00
Richard Mwewa
59d8a30d85 Update LICENSE 2023-01-02 04:06:54 +02:00
Richard Mwewa
fa12e7e477 Update Dockerfile 2022-12-02 23:15:58 +02:00
Richard Mwewa
d38eb6aefe Update Dockerfile 2022-12-02 23:06:09 +02:00
Richard Mwewa
eda82c051d Update Dockerfile 2022-11-27 02:40:29 +02:00
Richard Mwewa
e1c15ddb0f Update README.md 2022-11-03 01:32:31 +02:00
Richard Mwewa
47844da5b8 Merge pull request #1 from rly0nheart/dev
Create Dockerfile
2022-11-03 01:25:26 +02:00
Richard Mwewa
aebb6f0e65 Update README.md 2022-10-31 21:36:03 +02:00
Richard Mwewa
37e028c769 Create Dockerfile 2022-10-31 19:17:02 +02:00
Richard Mwewa
a65a1f65d5 Update README.md 2022-10-21 23:28:18 +02:00
12 changed files with 284 additions and 126 deletions

15
Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM python:latest
WORKDIR /app
COPY . .
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl firefox-esr \
&& rm -fr /var/lib/apt/lists/* \
&& 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
RUN pip install .
ENTRYPOINT ["facebook_downloader"]

View File

@@ -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 If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode: notice like this when it starts in an interactive mode:
Facebook-Downloader Copyright (C) 2022 Richard Mwewa Facebook-Downloader Copyright (C) 2023 Richard Mwewa
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details. under certain conditions; type `show c' for details.

View File

@@ -1,53 +1,60 @@
# Facebook Downloader # Facebook Downloader
A program for downloading videos from Facebook, given a video url
A program for downloading videos from facebook, given a video url [![Upload Python Package](https://github.com/rly0nheart/facebook-downloader/actions/workflows/python-publish.yml/badge.svg)](https://github.com/rly0nheart/facebook-downloader/actions/workflows/python-publish.yml)
[![CodeQL](https://github.com/rly0nheart/facebook-downloader/actions/workflows/codeql.yml/badge.svg)](https://github.com/rly0nheart/facebook-downloader/actions/workflows/codeql.yml)
# Installation # Installation
**Install from PyPI** ## Install from PyPI
``` ```
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 **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)
# 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* git clone https://github.com/bellingcat/facebook-downloader
```
**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>
``` ```
## Note ## Docker
> The url format should be as follows; https://www.facebook.com/PageName/videos/VideoID ```
docker run --tty --volume $PWD/downloads:/app/downloads my-facebook-downloader <facebook_video_url>
```
# Optional Arguments # Optional Arguments
@@ -59,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,92 +1,141 @@
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
from selenium.webdriver.common.keys import Keys 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 = "2022.1.2.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
def notice(self): dt_now = datetime.now()
notice_msg = f""" if os.name == "nt":
facebook-downloader {self.program_version_number} Copyright (C) 2022 Richard Mwewa 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")
return output_name
def notice(self) -> str:
"""
Returns the program's license notice and current version.
:return: License notice.
:rtype: str
"""
return f"""
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.
""" """
print(notice_msg)
def check_updates(self):
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:
print(f"[!] A new release is available ({response['tag_name']}). Run 'pip install --upgrade facebook-downloader' to get the updates.")
def check_updates(self):
def download_type(self):
""" """
The elements change according to what file type will be downloaded Checks if the program's version tag matches the tag of the latest release on GitHub.
So, we pass an option to specify what file type we want, by default the file is an HD video If the tags match, assume the program is up-to-date.
""" """
with requests.get(self.__update_check_endpoint) as response:
remote_version = response.json().get('tag_name')
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:
""" """
HD: "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[1]/a" Gets the web element according to the specified command-line arguments.
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"
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
""" """
if self.args.audio: 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.
:return: None
"""
# 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 path_finder(self):
os.makedirs("downloads", 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('Please standby (20 seconds)...') # Locate the facebook video url entry field.
self.driver.refresh url_entry_field = self.__driver.find_element(By.NAME, "url")
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) # 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'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'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('Process interrupted with Ctrl+C.') print("Process interrupted with Ctrl+C.")
except Exception as e: except Exception as e:
print('An error occured:', 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='2022.1.2.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."