use crate::types::{DownloadError, DownloadJob};
use md_api::Api;
use md_api::types::{InfoType, Key};
use md_api::utils::get_client_builder;
use reqwest::{Client, Proxy};
use std::collections::BinaryHeap;
use std::path::PathBuf;
pub mod types;
pub mod utils;
pub struct MediafireDownloader {
api_client: Api,
download_client: Client,
reverse_downloads: bool,
}
impl MediafireDownloader {
pub fn new(max_retries: u64) -> Result<Self, DownloadError> {
Ok(Self {
api_client: Api::new(max_retries).map_err(|e| DownloadError::ApiError(e))?,
download_client: get_client_builder()
.build()
.map_err(|_| DownloadError::ClientInitError)?,
reverse_downloads: false,
})
}
pub fn reverse_downloads(self, value: bool) -> Self {
Self {
reverse_downloads: value,
..self
}
}
pub fn set_proxies(
self,
proxies: Option<Vec<String>>,
proxy_downloads: bool,
) -> Result<Self, DownloadError> {
match proxies {
Some(proxies) => {
let download_client = {
if proxy_downloads {
let mut client = Client::builder();
for proxy in &proxies {
client = client.proxy(
Proxy::all(proxy.clone())
.map_err(|_| DownloadError::InvalidProxy(proxy.clone()))?,
);
}
client.build().map_err(|_| DownloadError::ClientInitError)?
} else {
self.download_client
}
};
Ok(Self {
api_client: self
.api_client
.with_proxies(proxies)
.map_err(|e| DownloadError::ApiError(e))?,
download_client: download_client,
..self
})
}
None => Ok(self),
}
}
pub async fn get_download_jobs(
self: &Self,
urls: &Vec<String>,
output_path: PathBuf,
) -> Result<BinaryHeap<DownloadJob>, DownloadError> {
let mut download_queue: BinaryHeap<DownloadJob> = BinaryHeap::new();
for key in self
.api_client
.extract_keys_from_url(urls)
.map_err(|e| DownloadError::ApiError(e))?
{
download_queue.extend(
self.fetch_items(key, 1, output_path.clone(), &|_| {})
.await?,
);
}
Ok(download_queue)
}
pub async fn get_download_jobs_with_progress<F>(
self: &Self,
urls: &Vec<String>,
output_path: PathBuf,
progress: F,
) -> Result<BinaryHeap<DownloadJob>, DownloadError>
where
F: Fn(String),
{
let mut download_queue: BinaryHeap<DownloadJob> = BinaryHeap::new();
for key in self
.api_client
.extract_keys_from_url(urls)
.map_err(|e| DownloadError::ApiError(e))?
{
download_queue.extend(
self.fetch_items(key, 1, output_path.clone(), &progress)
.await?,
);
}
Ok(download_queue)
}
async fn fetch_items<F>(
self: &Self,
key: Key,
chunk: u64,
output_path: PathBuf,
progress: &F,
) -> Result<BinaryHeap<DownloadJob>, DownloadError>
where
F: Fn(String),
{
let mut download_queue: BinaryHeap<DownloadJob> = BinaryHeap::new();
let mut folder_stack: Vec<(Key, PathBuf, u64)> = vec![(key, output_path, chunk)];
while let Some((current_key, current_output_path, current_chunk)) = folder_stack.pop() {
let data_type = self
.api_client
.get_info(¤t_key)
.await
.map_err(|e| DownloadError::ApiError(e))?;
match data_type {
InfoType::File(file_info) => {
download_queue.push(DownloadJob::new(
file_info.filename.clone(),
current_output_path.join(&file_info.filename),
file_info.size,
file_info.links.normal_download,
file_info.hash,
self.reverse_downloads,
self.api_client.clone(),
self.download_client.clone(),
));
}
InfoType::Folder(folder_info) => {
progress(folder_info.name.clone());
let folder_content = self
.api_client
.get_folder_and_file_content(¤t_key, current_chunk)
.await
.map_err(|e| DownloadError::ApiError(e))?
.folder_content;
if let Some(files) = folder_content.files {
for file in files {
download_queue.push(DownloadJob::new(
file.filename.clone(),
current_output_path
.join(&folder_info.name)
.join(&file.filename),
file.size,
file.links.normal_download,
file.hash,
self.reverse_downloads,
self.api_client.clone(),
self.download_client.clone(),
));
}
}
if let Some(folders) = folder_content.folders {
for folder in folders {
let subfolder_key = Key::Folder(folder.folderkey);
let subfolder_output = current_output_path.join(&folder_info.name);
folder_stack.push((subfolder_key, subfolder_output, 1));
}
}
if folder_content.more_chunks == "yes" {
folder_stack.push((current_key, current_output_path, current_chunk + 1));
}
}
}
}
Ok(download_queue)
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use std::sync::LazyLock;
use crate::{MediafireDownloader, types::DownloadError};
const URL: LazyLock<Vec<String>> =
LazyLock::new(|| vec!["https://www.mediafire.com/folder/akjcex4b8dgui".to_string()]);
static DOWNLOADER: LazyLock<MediafireDownloader> =
LazyLock::new(|| MediafireDownloader::new(5).unwrap());
#[tokio::test]
async fn files() -> Result<(), DownloadError> {
let jobs = DOWNLOADER
.get_download_jobs_with_progress(&URL, PathBuf::from("."), |job| println!("{job}"))
.await?;
assert!(jobs.len() == 136);
Ok(())
}
#[tokio::test]
async fn download() -> Result<(), DownloadError> {
let mut jobs = DOWNLOADER
.get_download_jobs(&URL, PathBuf::from("."))
.await?;
if let Some(job) = jobs.pop() {
println!("{job:?}");
job.download_with_progress(|status| println!("{status:?}"))
.await?
}
Ok(())
}
#[tokio::test]
async fn malware_detected_urls() -> Result<(), DownloadError> {
let urls = vec![
"https://www.mediafire.com/file/7f8x0azhs3pb1wm".to_string(),
"https://www.mediafire.com/file/fauj29155dj6ol6".to_string(),
];
let mut jobs = DOWNLOADER
.get_download_jobs(&urls, PathBuf::from("."))
.await?;
while let Some(job) = jobs.pop() {
println!("{job:?}");
job.download_with_progress(|status| println!("{status:?}"))
.await?
}
Ok(())
}
}