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
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 is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.

View File

@@ -1,53 +1,60 @@
# 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
**Install from PyPI**
## Install from PyPI
```
pip install facebook-downloader
```
> You will need to have the FireFox browser installed and geckodriver properly set up.
### Note
> You will need to have the FireFox browser installed on your pc
>> 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**
## Building from source
**1.** Clone the repository
```
tar -xvzf geckodriver*
```
**3. Add geckodriver to your system path**
```
export PATH=$PATH:/path/to/downloaded/geckodriver
git clone https://github.com/bellingcat/facebook-downloader
```
### Note
> If you encounter issues with the above commands, then you should run them as root
**2.** Navigate to the cloned repository
```
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
## Package
```
facebook_downloader <video-url>
```
## Note
> The url format should be as follows; https://www.facebook.com/PageName/videos/VideoID
## Docker
```
docker run --tty --volume $PWD/downloads:/app/downloads my-facebook-downloader <facebook_video_url>
```
# Optional Arguments
@@ -59,6 +66,6 @@ facebook_downloader <video-url>
# Donations
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!

View File

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

View File

@@ -1,92 +1,141 @@
import os
import argparse
import requests
import argparse
from tqdm import tqdm
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from . import __version__, __author__
class FacebookDownloader:
def __init__(self):
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')
self.__base_url = "https://getfvid.com"
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('-o', '--output', help='output filename')
self.args = parser.parse_args()
parser.add_argument('-o', '--output', help='output filename', default="")
parser.add_argument('-v', '--version', action='version', version=__version__)
self.__args = parser.parse_args()
option = webdriver.FirefoxOptions()
option.add_argument('--headless')
self.driver = webdriver.Firefox(options=option)
@staticmethod
def __format_output_filename(user_defined_name) -> str:
"""
Formats the output file's name.
self.program_version_number = "2022.1.2.1"
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
:param user_defined_name: User-defined name for the file.
:return: Formatted/Reconstructed name of the file.
"""
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")
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
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.
"""
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 download_type(self):
def check_updates(self):
"""
The elements change according to what file type will be downloaded
So, we pass an option to specify what file type we want, by default the file is an HD video
Checks if the program's version tag matches the tag of the latest release on GitHub.
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"
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"
Gets the web element according to the specified command-line arguments.
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"
else:
download_type_element = "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[1]/a"
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):
self.path_finder()
self.check_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(self.args.url) # write facebook url in the entry field
url_entry_field.send_keys(Keys.ENTER) # press enter
print('Please standby (20 seconds)...')
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)
"""
Opens https://getfvid.com with selenium and uses the specified facebook video link as a query.
"""
# Open the base url.
self.__driver.get(self.__base_url)
# 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')
# Open the download url and stream the content to a file.
with requests.get(download_url, stream=True) as response:
response.raise_for_status()
with open(os.path.join('downloads', f'{self.args.output}.mp4'), 'wb') as file:
for chunk in tqdm(response.iter_content(chunk_size=8192), desc=f'Downloading: {self.args.output}.mp4'):
with open(os.path.join(self.__downloads_directory,
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)
print(f'Downloaded: {file.name}')
self.driver.close()
print(f"* Downloaded: {file.name}")
# Close driver.
self.__driver.close()

View File

@@ -1,12 +1,25 @@
from facebook_downloader.downloader import FacebookDownloader
def main():
def start_downloader():
try:
start = FacebookDownloader()
start.download_video()
# Initialise the FaceBookDownloader instance.
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:
print('Process interrupted with Ctrl+C.')
print("Process interrupted with Ctrl+C.")
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."