txa-m 2.1.1__tar.gz → 2.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {txa_m-2.1.1 → txa_m-2.1.3}/PKG-INFO +5 -1
- {txa_m-2.1.1 → txa_m-2.1.3}/setup.py +3 -1
- {txa_m-2.1.1 → txa_m-2.1.3}/txa_m.egg-info/PKG-INFO +5 -1
- {txa_m-2.1.1 → txa_m-2.1.3}/txa_mediafire/cli.py +85 -78
- {txa_m-2.1.1 → txa_m-2.1.3}/README.md +0 -0
- {txa_m-2.1.1 → txa_m-2.1.3}/setup.cfg +0 -0
- {txa_m-2.1.1 → txa_m-2.1.3}/txa_m.egg-info/SOURCES.txt +0 -0
- {txa_m-2.1.1 → txa_m-2.1.3}/txa_m.egg-info/dependency_links.txt +0 -0
- {txa_m-2.1.1 → txa_m-2.1.3}/txa_m.egg-info/entry_points.txt +0 -0
- {txa_m-2.1.1 → txa_m-2.1.3}/txa_m.egg-info/requires.txt +0 -0
- {txa_m-2.1.1 → txa_m-2.1.3}/txa_m.egg-info/top_level.txt +0 -0
- {txa_m-2.1.1 → txa_m-2.1.3}/txa_mediafire/__init__.py +0 -0
- {txa_m-2.1.1 → txa_m-2.1.3}/txa_mediafire/translations.json +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: txa-m
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.3
|
|
4
4
|
Summary: A modern, high-speed downloader for MediaFire files and folders
|
|
5
5
|
Home-page: https://github.com/TXAVLOG/TXA-MEDIAFIRE
|
|
6
6
|
Author: TXA
|
|
7
|
+
Author-email: viptretrauc@gmail.com
|
|
8
|
+
License: MIT
|
|
7
9
|
Project-URL: Bug Tracker, https://github.com/TXAVLOG/TXA-MEDIAFIRE/issues
|
|
8
10
|
Project-URL: Source Code, https://github.com/TXAVLOG/TXA-MEDIAFIRE
|
|
9
11
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -18,10 +20,12 @@ Requires-Dist: requests
|
|
|
18
20
|
Requires-Dist: gazpacho
|
|
19
21
|
Requires-Dist: rich
|
|
20
22
|
Dynamic: author
|
|
23
|
+
Dynamic: author-email
|
|
21
24
|
Dynamic: classifier
|
|
22
25
|
Dynamic: description
|
|
23
26
|
Dynamic: description-content-type
|
|
24
27
|
Dynamic: home-page
|
|
28
|
+
Dynamic: license
|
|
25
29
|
Dynamic: project-url
|
|
26
30
|
Dynamic: requires-dist
|
|
27
31
|
Dynamic: requires-python
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="txa-m",
|
|
5
|
-
version="2.1.
|
|
5
|
+
version="2.1.3",
|
|
6
6
|
packages=find_packages(),
|
|
7
7
|
install_requires=[
|
|
8
8
|
"requests",
|
|
@@ -19,6 +19,8 @@ setup(
|
|
|
19
19
|
],
|
|
20
20
|
},
|
|
21
21
|
author="TXA",
|
|
22
|
+
author_email="viptretrauc@gmail.com",
|
|
23
|
+
license="MIT",
|
|
22
24
|
description="A modern, high-speed downloader for MediaFire files and folders",
|
|
23
25
|
long_description=open("README.md", encoding="utf-8").read() if open("README.md", encoding="utf-8") else "",
|
|
24
26
|
long_description_content_type="text/markdown",
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: txa-m
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.3
|
|
4
4
|
Summary: A modern, high-speed downloader for MediaFire files and folders
|
|
5
5
|
Home-page: https://github.com/TXAVLOG/TXA-MEDIAFIRE
|
|
6
6
|
Author: TXA
|
|
7
|
+
Author-email: viptretrauc@gmail.com
|
|
8
|
+
License: MIT
|
|
7
9
|
Project-URL: Bug Tracker, https://github.com/TXAVLOG/TXA-MEDIAFIRE/issues
|
|
8
10
|
Project-URL: Source Code, https://github.com/TXAVLOG/TXA-MEDIAFIRE
|
|
9
11
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -18,10 +20,12 @@ Requires-Dist: requests
|
|
|
18
20
|
Requires-Dist: gazpacho
|
|
19
21
|
Requires-Dist: rich
|
|
20
22
|
Dynamic: author
|
|
23
|
+
Dynamic: author-email
|
|
21
24
|
Dynamic: classifier
|
|
22
25
|
Dynamic: description
|
|
23
26
|
Dynamic: description-content-type
|
|
24
27
|
Dynamic: home-page
|
|
28
|
+
Dynamic: license
|
|
25
29
|
Dynamic: project-url
|
|
26
30
|
Dynamic: requires-dist
|
|
27
31
|
Dynamic: requires-python
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import base64
|
|
4
4
|
import hashlib
|
|
5
|
-
import http.client
|
|
6
|
-
import urllib.parse
|
|
7
5
|
import importlib.resources
|
|
8
6
|
import json
|
|
9
7
|
import platform
|
|
@@ -11,8 +9,6 @@ import subprocess
|
|
|
11
9
|
import sys
|
|
12
10
|
from re import findall, search
|
|
13
11
|
from time import sleep
|
|
14
|
-
from io import BytesIO
|
|
15
|
-
from gzip import GzipFile
|
|
16
12
|
from datetime import datetime
|
|
17
13
|
from requests import get
|
|
18
14
|
from gazpacho import Soup
|
|
@@ -21,7 +17,7 @@ from os import path, makedirs, remove, environ
|
|
|
21
17
|
from threading import BoundedSemaphore, Thread, Event, Lock
|
|
22
18
|
|
|
23
19
|
# --- Rich UI Imports ---
|
|
24
|
-
from rich.console import Console
|
|
20
|
+
from rich.console import Console, Group
|
|
25
21
|
from rich.panel import Panel
|
|
26
22
|
from rich.text import Text
|
|
27
23
|
from rich.progress import (
|
|
@@ -41,7 +37,7 @@ from rich.theme import Theme
|
|
|
41
37
|
from rich import box
|
|
42
38
|
|
|
43
39
|
# --- Configuration ---
|
|
44
|
-
APP_VERSION = "2.1.
|
|
40
|
+
APP_VERSION = "2.1.3"
|
|
45
41
|
|
|
46
42
|
# Default ignore lists
|
|
47
43
|
IGNORE_EXTENSIONS = {".pyc", ".pyo", ".pyd", ".DS_Store", "Thumbs.db"}
|
|
@@ -568,7 +564,8 @@ def main():
|
|
|
568
564
|
event = Event()
|
|
569
565
|
limiter = BoundedSemaphore(args.threads)
|
|
570
566
|
|
|
571
|
-
|
|
567
|
+
# Progress for Bytes (Total + Individual)
|
|
568
|
+
p_bytes = Progress(
|
|
572
569
|
SpinnerColumn(),
|
|
573
570
|
TextColumn("[progress.description]{task.description}"),
|
|
574
571
|
BarColumn(bar_width=None),
|
|
@@ -579,23 +576,38 @@ def main():
|
|
|
579
576
|
TransferSpeedColumn(),
|
|
580
577
|
"•",
|
|
581
578
|
TimeRemainingColumn(),
|
|
579
|
+
console=console,
|
|
580
|
+
expand=True
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
# Progress for File Counts
|
|
584
|
+
p_count = Progress(
|
|
585
|
+
TextColumn("[progress.description]{task.description}"),
|
|
586
|
+
BarColumn(bar_width=None),
|
|
587
|
+
"[progress.percentage]{task.percentage:>3.0f}%",
|
|
582
588
|
"•",
|
|
583
|
-
|
|
589
|
+
TextColumn("[bold blue]{task.completed}/{task.total}[/bold blue] " + T["files_count"].split()[0]),
|
|
584
590
|
console=console,
|
|
585
591
|
expand=True
|
|
586
592
|
)
|
|
587
593
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
594
|
+
ui_group = Group(
|
|
595
|
+
Panel(p_count, title=f"[bold cyan]{T['files_count']}", border_style="cyan"),
|
|
596
|
+
Panel(p_bytes, title=f"[bold magenta]{T['total_progress']}", border_style="magenta")
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
with Live(ui_group, console=console, refresh_per_second=10):
|
|
600
|
+
overall_task = p_bytes.add_task(f"[bold]{T['total_progress']}", total=stats.total_size)
|
|
601
|
+
file_count_task = p_count.add_task(f"[bold]{T['files_count']}", total=stats.total_files)
|
|
591
602
|
|
|
592
603
|
def update_overall():
|
|
593
|
-
|
|
594
|
-
|
|
604
|
+
p_bytes.update(overall_task, completed=stats.downloaded_bytes)
|
|
605
|
+
p_count.update(file_count_task, completed=stats.downloaded_files,
|
|
606
|
+
description=f"[bold]{T['files_count']} ({stats.downloaded_files}/{stats.total_files})")
|
|
595
607
|
|
|
596
608
|
threads = []
|
|
597
609
|
for file_info, destination in all_tasks:
|
|
598
|
-
threads.append(Thread(target=download_file_worker, args=(file_info, destination, event, limiter,
|
|
610
|
+
threads.append(Thread(target=download_file_worker, args=(file_info, destination, event, limiter, p_bytes, update_overall)))
|
|
599
611
|
|
|
600
612
|
for t in threads:
|
|
601
613
|
t.start()
|
|
@@ -698,7 +710,6 @@ def download_file_worker(file, output_dir, event, limiter, progress, update_cb):
|
|
|
698
710
|
|
|
699
711
|
try:
|
|
700
712
|
# We ensure dir exists in main loop, but recursive structures might need this
|
|
701
|
-
# Using output_dir which already includes subfolder path from discover_all_files
|
|
702
713
|
makedirs(output_dir, exist_ok=True)
|
|
703
714
|
|
|
704
715
|
if path.exists(file_path):
|
|
@@ -710,93 +721,89 @@ def download_file_worker(file, output_dir, event, limiter, progress, update_cb):
|
|
|
710
721
|
stats.downloaded_files += 1
|
|
711
722
|
stats.downloaded_bytes += file_size
|
|
712
723
|
update_cb()
|
|
713
|
-
limiter.release()
|
|
714
724
|
return
|
|
715
725
|
|
|
716
726
|
if event.is_set():
|
|
717
|
-
limiter.release()
|
|
718
727
|
return
|
|
719
728
|
|
|
720
729
|
# Show task now that we know we might download it
|
|
721
730
|
task_id = progress.add_task(f"[cyan]{filename}[/cyan]", total=file_size, visible=True)
|
|
722
731
|
|
|
723
732
|
download_link = file["links"]["normal_download"]
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
conn.close()
|
|
733
|
-
with GzipFile(fileobj=BytesIO(compressed_data)) as f:
|
|
734
|
-
html = f.read().decode("utf-8", errors="ignore")
|
|
735
|
-
|
|
736
|
-
soup = Soup(html)
|
|
737
|
-
download_btn = soup.find("a", {"id": "downloadButton"})
|
|
738
|
-
real_link = None
|
|
739
|
-
|
|
740
|
-
if download_btn and download_btn.attrs.get("href"):
|
|
741
|
-
real_link = download_btn.attrs["href"]
|
|
742
|
-
|
|
743
|
-
if not real_link and download_btn and download_btn.attrs.get("data-scrambled-url"):
|
|
744
|
-
try:
|
|
745
|
-
real_link = base64.b64decode(download_btn.attrs["data-scrambled-url"]).decode("utf-8")
|
|
746
|
-
except Exception: pass
|
|
747
|
-
|
|
748
|
-
if not real_link:
|
|
749
|
-
# Fallback Strategy: Regex
|
|
750
|
-
console.log(f"[dim]{T['try_fallback']}: {filename}[/dim]")
|
|
751
|
-
match = search(r'href=["\'](https?://download[^"\']+)["\']', html)
|
|
752
|
-
if match:
|
|
753
|
-
real_link = match.group(1)
|
|
754
|
-
|
|
755
|
-
if not real_link:
|
|
756
|
-
progress.console.log(f"[error]{T['fail_link']} {filename}[/error]")
|
|
757
|
-
with stats.lock: stats.failed += 1
|
|
758
|
-
if task_id is not None: progress.remove_task(task_id)
|
|
759
|
-
limiter.release()
|
|
760
|
-
return
|
|
761
|
-
|
|
762
|
-
parsed_url = urllib.parse.urlparse(real_link)
|
|
763
|
-
conn = http.client.HTTPConnection(parsed_url.netloc, timeout=60)
|
|
764
|
-
conn.request("GET", parsed_url.path + ("?" + parsed_url.query if parsed_url.query else ""), headers=HEADERS)
|
|
765
|
-
response = conn.getresponse()
|
|
733
|
+
|
|
734
|
+
# Step 1: Get the real download button/link
|
|
735
|
+
response = get(download_link, headers=HEADERS, timeout=60)
|
|
736
|
+
if response.status_code != 200:
|
|
737
|
+
progress.console.log(f"[error]{T['http_err']} {response.status_code} for {filename}[/error]")
|
|
738
|
+
with stats.lock: stats.failed += 1
|
|
739
|
+
if task_id is not None: progress.remove_task(task_id)
|
|
740
|
+
return
|
|
766
741
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
742
|
+
html = response.text
|
|
743
|
+
soup = Soup(html)
|
|
744
|
+
download_btn = soup.find("a", {"id": "downloadButton"})
|
|
745
|
+
real_link = None
|
|
746
|
+
|
|
747
|
+
if download_btn and download_btn.attrs.get("href"):
|
|
748
|
+
real_link = download_btn.attrs["href"]
|
|
749
|
+
|
|
750
|
+
if not real_link and download_btn and download_btn.attrs.get("data-scrambled-url"):
|
|
751
|
+
try:
|
|
752
|
+
real_link = base64.b64decode(download_btn.attrs["data-scrambled-url"]).decode("utf-8")
|
|
753
|
+
except Exception: pass
|
|
754
|
+
|
|
755
|
+
if not real_link:
|
|
756
|
+
# Fallback Strategy: Regex
|
|
757
|
+
match = search(r'href=[\"\'](https?://download[^\"\']+)[\"\']', html)
|
|
758
|
+
if match:
|
|
759
|
+
real_link = match.group(1)
|
|
760
|
+
|
|
761
|
+
if not real_link:
|
|
762
|
+
progress.console.log(f"[error]{T['fail_link']} {filename}[/error]")
|
|
770
763
|
with stats.lock: stats.failed += 1
|
|
771
764
|
if task_id is not None: progress.remove_task(task_id)
|
|
772
|
-
limiter.release()
|
|
773
765
|
return
|
|
774
|
-
|
|
766
|
+
|
|
767
|
+
# Step 2: Download the actual file
|
|
768
|
+
res = get(real_link, headers=HEADERS, stream=True, timeout=60)
|
|
769
|
+
if res.status_code != 200:
|
|
770
|
+
progress.console.log(f"[error]{T['http_err']} {res.status_code} for {filename}[/error]")
|
|
771
|
+
with stats.lock: stats.failed += 1
|
|
772
|
+
if task_id is not None: progress.remove_task(task_id)
|
|
773
|
+
return
|
|
774
|
+
|
|
775
|
+
downloaded_for_this_file = 0
|
|
775
776
|
with open(file_path, "wb") as f:
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
if not chunk:
|
|
777
|
+
for chunk in res.iter_content(chunk_size=65536):
|
|
778
|
+
if event.is_set():
|
|
779
779
|
break
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
780
|
+
if chunk:
|
|
781
|
+
f.write(chunk)
|
|
782
|
+
chunk_len = len(chunk)
|
|
783
|
+
downloaded_for_this_file += chunk_len
|
|
784
|
+
with stats.lock:
|
|
785
|
+
stats.downloaded_bytes += chunk_len
|
|
786
|
+
progress.update(task_id, advance=chunk_len)
|
|
787
|
+
update_cb()
|
|
785
788
|
|
|
786
|
-
conn.close()
|
|
787
789
|
# Remove task when done to keep the list clean
|
|
788
790
|
if task_id is not None:
|
|
789
791
|
progress.remove_task(task_id)
|
|
790
792
|
|
|
791
793
|
if event.is_set():
|
|
792
794
|
if path.exists(file_path): remove(file_path)
|
|
793
|
-
limiter.release()
|
|
794
795
|
return
|
|
795
796
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
797
|
+
# Verify if we actually got data
|
|
798
|
+
if downloaded_for_this_file > 0:
|
|
799
|
+
with stats.lock:
|
|
800
|
+
stats.downloaded_files += 1
|
|
801
|
+
save_to_history(filename, format_size(file_size))
|
|
802
|
+
update_cb()
|
|
803
|
+
else:
|
|
804
|
+
progress.console.log(f"[error]{T['err_download']} {filename}: 0 bytes received[/error]")
|
|
805
|
+
with stats.lock: stats.failed += 1
|
|
806
|
+
if path.exists(file_path): remove(file_path)
|
|
800
807
|
|
|
801
808
|
except Exception as e:
|
|
802
809
|
console.log(f"[error]{T['err_download']} {filename}:[/error] {e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|