webtools-cli 1.2.4__tar.gz → 1.2.6__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.
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/PKG-INFO +3 -3
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/README.md +1 -1
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/pyproject.toml +2 -2
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools/core.py +19 -216
- webtools_cli-1.2.6/webtools/install.py +168 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools_cli.egg-info/PKG-INFO +3 -3
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools_cli.egg-info/requires.txt +1 -1
- webtools_cli-1.2.4/webtools/install.py +0 -17
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/LICENSE +0 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/setup.cfg +0 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools/__init__.py +0 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools/__main__.py +0 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools/cli.py +0 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools/web/index.html +0 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools/web/script.js +0 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools/web/style.css +0 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools_cli.egg-info/SOURCES.txt +0 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools_cli.egg-info/dependency_links.txt +0 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools_cli.egg-info/entry_points.txt +0 -0
- {webtools_cli-1.2.4 → webtools_cli-1.2.6}/webtools_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: webtools-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.6
|
|
4
4
|
Summary: Advanced Web Intelligence & Scraping Toolkit with CLI and Web UI
|
|
5
5
|
Author: Abhinav Adarsh
|
|
6
6
|
License-Expression: MIT
|
|
@@ -30,7 +30,7 @@ Requires-Dist: mtranslate
|
|
|
30
30
|
Requires-Dist: colorama
|
|
31
31
|
Requires-Dist: playwright
|
|
32
32
|
Requires-Dist: pycryptodome
|
|
33
|
-
Requires-Dist: tenacity
|
|
33
|
+
Requires-Dist: tenacity<9.0.0,>=8.2.3
|
|
34
34
|
Requires-Dist: pyreadline3; platform_system == "Windows"
|
|
35
35
|
Provides-Extra: playwright
|
|
36
36
|
Requires-Dist: playwright; extra == "playwright"
|
|
@@ -54,7 +54,7 @@ WebTools CLI is an advanced web intelligence suite for researchers, OSINT enthus
|
|
|
54
54
|
|
|
55
55
|
- **🎯 Stealth & Speed**: Smart proxy rotation and Turbo-Fetch logic for evasion and performance.
|
|
56
56
|
- **🧠 AI-Powered**: Automated content summarization, sentiment analysis, and readability scoring.
|
|
57
|
-
- **☁️ Cloud-Native**: Integrated Mega.nz storage
|
|
57
|
+
- **☁️ Cloud-Native**: Integrated Mega.nz storage with **Seamless Mono-Installer** (one-click, conflict-free setup).
|
|
58
58
|
- **🔧 Security-Centric**: Built-in honeypot detection, threat leveling, and image forensic analysis.
|
|
59
59
|
- **💻 Terminal-First**: Designed for power users who live in the command line.
|
|
60
60
|
- **🛡️ Cross-Platform**: Works seamlessly on Windows, Linux, and macOS (with auto-download for Windows tunnels).
|
|
@@ -16,7 +16,7 @@ WebTools CLI is an advanced web intelligence suite for researchers, OSINT enthus
|
|
|
16
16
|
|
|
17
17
|
- **🎯 Stealth & Speed**: Smart proxy rotation and Turbo-Fetch logic for evasion and performance.
|
|
18
18
|
- **🧠 AI-Powered**: Automated content summarization, sentiment analysis, and readability scoring.
|
|
19
|
-
- **☁️ Cloud-Native**: Integrated Mega.nz storage
|
|
19
|
+
- **☁️ Cloud-Native**: Integrated Mega.nz storage with **Seamless Mono-Installer** (one-click, conflict-free setup).
|
|
20
20
|
- **🔧 Security-Centric**: Built-in honeypot detection, threat leveling, and image forensic analysis.
|
|
21
21
|
- **💻 Terminal-First**: Designed for power users who live in the command line.
|
|
22
22
|
- **🛡️ Cross-Platform**: Works seamlessly on Windows, Linux, and macOS (with auto-download for Windows tunnels).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "webtools-cli"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.6"
|
|
8
8
|
description = "Advanced Web Intelligence & Scraping Toolkit with CLI and Web UI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -37,7 +37,7 @@ dependencies = [
|
|
|
37
37
|
"colorama",
|
|
38
38
|
"playwright",
|
|
39
39
|
"pycryptodome",
|
|
40
|
-
"tenacity>=8.2.3",
|
|
40
|
+
"tenacity>=8.2.3,<9.0.0",
|
|
41
41
|
"pyreadline3; platform_system == 'Windows'",
|
|
42
42
|
]
|
|
43
43
|
|
|
@@ -7,59 +7,24 @@ if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8':
|
|
|
7
7
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
|
8
8
|
except Exception: pass
|
|
9
9
|
|
|
10
|
-
# --- AUTO-INSTALLER FOR
|
|
10
|
+
# --- AUTO-INSTALLER FOR PLAYWRIGHT BROWSERS ---
|
|
11
11
|
def ensure_dependencies():
|
|
12
|
-
"""
|
|
13
|
-
# Dependencies that must be imported successfully
|
|
14
|
-
# Format: module_name -> (pip_package_name, min_version_prefix, forced_upgrade)
|
|
15
|
-
check_deps = {
|
|
16
|
-
"mega": ("mega.py", None, False),
|
|
17
|
-
"Crypto": ("pycryptodome", None, True), # Always ensure pycryptodome over pycrypto
|
|
18
|
-
"tenacity": ("tenacity>=8.2.3", "8", True)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
missing_or_broken = []
|
|
22
|
-
|
|
23
|
-
# 1. Clean up "pycrypto" which causes SyntaxError on Python 3
|
|
24
|
-
# If we catch a SyntaxError from 'from mega import Mega', it's almost certainly pycrypto
|
|
12
|
+
"""Ensures Playwright browsers are installed if the library is present"""
|
|
25
13
|
try:
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
except:
|
|
41
|
-
if forced: missing_or_broken.append(pkg_name)
|
|
42
|
-
except:
|
|
43
|
-
missing_or_broken.append(pkg_name)
|
|
44
|
-
|
|
45
|
-
if missing_or_broken:
|
|
46
|
-
print(f"\n[!] Critical dependency issues detected: {', '.join(missing_or_broken)}")
|
|
47
|
-
print("[*] Running Forced Clean Repair (Python 3.11+ Stability Patch)...")
|
|
48
|
-
try:
|
|
49
|
-
# Step A: Uninstall known troublemakers
|
|
50
|
-
subprocess.call([sys.executable, "-m", "pip", "uninstall", "-y", "pycrypto", "tenacity"],
|
|
51
|
-
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
52
|
-
|
|
53
|
-
# Step B: Install modern core deps
|
|
54
|
-
subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", "pycryptodome", "tenacity>=8.2.3", "requests"])
|
|
55
|
-
|
|
56
|
-
# Step C: Install mega.py WITHOUT dependencies to avoid downgrading tenacity
|
|
57
|
-
subprocess.check_call([sys.executable, "-m", "pip", "install", "mega.py", "--no-deps"])
|
|
58
|
-
|
|
59
|
-
print("[+] Environment repaired successfully! Please restart the app if it fails next.\n")
|
|
60
|
-
except Exception as e:
|
|
61
|
-
print(f"[-] Auto-repair failed: {e}")
|
|
62
|
-
print("[!] Please run manually: pip uninstall pycrypto tenacity && pip install pycryptodome tenacity>=8.2.3 mega.py --no-deps")
|
|
14
|
+
from playwright.sync_api import sync_playwright
|
|
15
|
+
# Simple check for browser existence
|
|
16
|
+
with sync_playwright() as p:
|
|
17
|
+
try:
|
|
18
|
+
p.chromium.launch(headless=True).close()
|
|
19
|
+
except:
|
|
20
|
+
print("\n[*] Playwright found but browsers are missing. Auto-installing...")
|
|
21
|
+
subprocess.check_call([sys.executable, "-m", "playwright", "install", "chromium"])
|
|
22
|
+
except ImportError:
|
|
23
|
+
# pip install handled this, but just in case
|
|
24
|
+
pass
|
|
25
|
+
except Exception as e:
|
|
26
|
+
# Non-critical failure, don't block startup
|
|
27
|
+
pass
|
|
63
28
|
|
|
64
29
|
ensure_dependencies()
|
|
65
30
|
|
|
@@ -82,174 +47,12 @@ from collections import Counter
|
|
|
82
47
|
from flask import Flask, render_template_string, send_from_directory, request, jsonify, send_file, after_this_request
|
|
83
48
|
from PIL import Image,ExifTags,ImageChops,ImageEnhance
|
|
84
49
|
from io import BytesIO
|
|
85
|
-
from mega import Mega
|
|
86
|
-
try:
|
|
87
|
-
from mega.errors import RequestError
|
|
88
|
-
from mega.crypto import (
|
|
89
|
-
base64_to_a32, decrypt_key, base64_url_decode, a32_to_str,
|
|
90
|
-
encrypt_key, str_to_a32, mpi_to_int, base64_url_encode
|
|
91
|
-
)
|
|
92
|
-
from Crypto.PublicKey import RSA
|
|
93
|
-
import binascii
|
|
94
|
-
except ImportError:
|
|
95
|
-
class RequestError(Exception): pass
|
|
96
|
-
|
|
97
|
-
# --- MONKEY PATCH FOR MEGA.PY (Fixes TypeError in AES calls) ---
|
|
98
|
-
try:
|
|
99
|
-
from Crypto.Cipher import AES
|
|
100
|
-
import mega.mega as mega_module
|
|
101
|
-
_original_aes_new = AES.new
|
|
102
|
-
|
|
103
|
-
def _patched_aes_new(key, *args, **kwargs):
|
|
104
|
-
# Convert key to bytes if string
|
|
105
|
-
if isinstance(key, str):
|
|
106
|
-
key = key.encode('latin-1')
|
|
107
|
-
|
|
108
|
-
# Convert positional arguments (like IV/mode) to bytes if strings
|
|
109
|
-
new_args = list(args)
|
|
110
|
-
for i in range(len(new_args)):
|
|
111
|
-
if isinstance(new_args[i], str):
|
|
112
|
-
new_args[i] = new_args[i].encode('latin-1')
|
|
113
|
-
|
|
114
|
-
# Convert keyword arguments (IV, nonce, etc.) to bytes if strings
|
|
115
|
-
for k in kwargs:
|
|
116
|
-
if isinstance(kwargs[k], str):
|
|
117
|
-
kwargs[k] = kwargs[k].encode('latin-1')
|
|
118
|
-
|
|
119
|
-
return _original_aes_new(key, *new_args, **kwargs)
|
|
120
|
-
|
|
121
|
-
# Apply patch globally
|
|
122
|
-
AES.new = _patched_aes_new
|
|
123
|
-
if hasattr(mega_module, 'AES'):
|
|
124
|
-
mega_module.AES.new = _patched_aes_new
|
|
125
|
-
except Exception:
|
|
126
|
-
pass
|
|
127
50
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Patched version of Mega library to handle fragile API responses.
|
|
131
|
-
The original library crashes if the API returns non-JSON or an empty string.
|
|
132
|
-
"""
|
|
133
|
-
def _login_process(self, resp, password):
|
|
134
|
-
"""
|
|
135
|
-
Overridden to fix 'Invalid RSA public exponent' error in newer pycryptodome.
|
|
136
|
-
The original code passes 0 as the exponent, which is rejected.
|
|
137
|
-
"""
|
|
138
|
-
encrypted_master_key = base64_to_a32(resp['k'])
|
|
139
|
-
self.master_key = decrypt_key(encrypted_master_key, password)
|
|
140
|
-
if 'tsid' in resp:
|
|
141
|
-
tsid = base64_url_decode(resp['tsid'])
|
|
142
|
-
key_encrypted = a32_to_str(
|
|
143
|
-
encrypt_key(str_to_a32(tsid[:16]), self.master_key)
|
|
144
|
-
)
|
|
145
|
-
if key_encrypted == tsid[-16:]:
|
|
146
|
-
self.sid = resp['tsid']
|
|
147
|
-
elif 'csid' in resp:
|
|
148
|
-
encrypted_rsa_private_key = base64_to_a32(resp['privk'])
|
|
149
|
-
rsa_private_key = decrypt_key(
|
|
150
|
-
encrypted_rsa_private_key, self.master_key
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
private_key_str = a32_to_str(rsa_private_key)
|
|
154
|
-
self.rsa_private_key = [0, 0, 0, 0]
|
|
155
|
-
|
|
156
|
-
# This loop parses the 4 components: p, q, d, u
|
|
157
|
-
for i in range(4):
|
|
158
|
-
l = int(((private_key_str[0]) * 256 + (private_key_str[1]) + 7) / 8) + 2
|
|
159
|
-
self.rsa_private_key[i] = mpi_to_int(private_key_str[:l])
|
|
160
|
-
private_key_str = private_key_str[l:]
|
|
161
|
-
|
|
162
|
-
encrypted_sid = mpi_to_int(base64_url_decode(resp['csid']))
|
|
163
|
-
|
|
164
|
-
# The Fix: Calculate the real 'e' from p, q, d
|
|
165
|
-
p = self.rsa_private_key[0]
|
|
166
|
-
q = self.rsa_private_key[1]
|
|
167
|
-
d = self.rsa_private_key[2]
|
|
168
|
-
n = p * q
|
|
169
|
-
phi = (p - 1) * (q - 1)
|
|
170
|
-
|
|
171
|
-
try:
|
|
172
|
-
# Calculate real e = d^-1 mod phi
|
|
173
|
-
e = pow(d, -1, phi)
|
|
174
|
-
rsa_decrypter = RSA.construct((n, e, d, p, q))
|
|
175
|
-
except:
|
|
176
|
-
# Fallback to 65537 if inverse fails (shouldn't happen)
|
|
177
|
-
e = 65537
|
|
178
|
-
rsa_decrypter = RSA.construct((n, e, d, p, q), consistency_check=False)
|
|
179
|
-
|
|
180
|
-
# Handle potential library differences in how the key is accessed
|
|
181
|
-
key_obj = rsa_decrypter.key if hasattr(rsa_decrypter, 'key') else rsa_decrypter
|
|
182
|
-
|
|
183
|
-
# Decrypt sid integer
|
|
184
|
-
sid_int = key_obj._decrypt(encrypted_sid)
|
|
185
|
-
|
|
186
|
-
# Convert to hex then to bytes, ensuring we handle the 0 padding if any
|
|
187
|
-
sid_hex = '%x' % sid_int
|
|
188
|
-
if len(sid_hex) % 2:
|
|
189
|
-
sid_hex = '0' + sid_hex
|
|
190
|
-
sid_bytes = binascii.unhexlify(sid_hex)
|
|
191
|
-
|
|
192
|
-
self.sid = base64_url_encode(sid_bytes[:43])
|
|
193
|
-
|
|
194
|
-
def _api_request(self, data):
|
|
195
|
-
params = {'id': self.sequence_num}
|
|
196
|
-
self.sequence_num += 1
|
|
197
|
-
|
|
198
|
-
if self.sid:
|
|
199
|
-
params.update({'sid': self.sid})
|
|
200
|
-
|
|
201
|
-
if not isinstance(data, list):
|
|
202
|
-
data = [data]
|
|
203
|
-
|
|
204
|
-
url = '{0}://g.api.{1}/cs'.format(self.schema, self.domain)
|
|
205
|
-
headers = {
|
|
206
|
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
|
207
|
-
'Accept': '*/*',
|
|
208
|
-
'Accept-Language': 'en-US,en;q=0.9',
|
|
209
|
-
'Origin': 'https://mega.nz',
|
|
210
|
-
'Referer': 'https://mega.nz/'
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
try:
|
|
214
|
-
req = requests.post(
|
|
215
|
-
url,
|
|
216
|
-
params=params,
|
|
217
|
-
data=json.dumps(data),
|
|
218
|
-
headers=headers,
|
|
219
|
-
timeout=self.timeout,
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
content = req.text.strip() if req.text else ""
|
|
223
|
-
|
|
224
|
-
if not content:
|
|
225
|
-
if req.status_code == 402:
|
|
226
|
-
raise Exception("Mega.nz is temporarily blocking this connection (Status 402). This usually happens due to rate-limiting. Please try again later or use a different network.")
|
|
227
|
-
raise Exception(f"Mega API returned empty response (Status {req.status_code})")
|
|
228
|
-
|
|
229
|
-
try:
|
|
230
|
-
json_resp = json.loads(content)
|
|
231
|
-
except json.JSONDecodeError:
|
|
232
|
-
# Try to parse as integer error code
|
|
233
|
-
try:
|
|
234
|
-
val = int(content)
|
|
235
|
-
raise RequestError(val)
|
|
236
|
-
except (ValueError, RequestError):
|
|
237
|
-
if content.startswith("-") and content[1:].isdigit():
|
|
238
|
-
raise RequestError(int(content))
|
|
239
|
-
raise Exception(f"Mega API error: {content[:100]}")
|
|
240
|
-
|
|
241
|
-
if isinstance(json_resp, int):
|
|
242
|
-
if json_resp == -3:
|
|
243
|
-
time.sleep(0.5)
|
|
244
|
-
return self._api_request(data=data)
|
|
245
|
-
raise RequestError(json_resp)
|
|
246
|
-
|
|
247
|
-
return json_resp[0]
|
|
248
|
-
except Exception:
|
|
249
|
-
raise
|
|
51
|
+
# Internal Mega Client (Conflict-free and Python 3.11+ compatible)
|
|
52
|
+
from .install import Mega, RequestError
|
|
250
53
|
|
|
251
54
|
# Global Mega session state
|
|
252
|
-
mega_engine =
|
|
55
|
+
mega_engine = Mega()
|
|
253
56
|
mega_sessions = {} # Mapping: email -> {'client': MegaClient, 'info': user_info, 'quota': quota}
|
|
254
57
|
active_mega_email = None
|
|
255
58
|
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Installation and Utility Utilities for WebTools CLI"""
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import random
|
|
6
|
+
import binascii
|
|
7
|
+
import requests
|
|
8
|
+
import subprocess
|
|
9
|
+
from Crypto.Cipher import AES
|
|
10
|
+
from Crypto.PublicKey import RSA
|
|
11
|
+
from Crypto.Util import Counter
|
|
12
|
+
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
|
|
13
|
+
|
|
14
|
+
# --- PLAYWRIGHT SETUP ---
|
|
15
|
+
|
|
16
|
+
def install_playwright_browsers():
|
|
17
|
+
"""Install Playwright Chromium browser after package installation"""
|
|
18
|
+
try:
|
|
19
|
+
print("📦 Installing Playwright Chromium browser...")
|
|
20
|
+
subprocess.run([sys.executable, "-m", "playwright", "install", "chromium"], check=True)
|
|
21
|
+
print("✓ Playwright Chromium installed successfully")
|
|
22
|
+
except subprocess.CalledProcessError:
|
|
23
|
+
print("⚠️ Failed to install Playwright browsers. Run manually: playwright install chromium")
|
|
24
|
+
except Exception as e:
|
|
25
|
+
print(f"⚠️ Playwright browser installation skipped: {e}")
|
|
26
|
+
|
|
27
|
+
# --- INTERNAL MEGA CLIENT (Conflict-free & Python 3.11+ compatible) ---
|
|
28
|
+
|
|
29
|
+
class RequestError(Exception):
|
|
30
|
+
"""Exception for Mega API requests"""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def base64_url_decode(data):
|
|
34
|
+
data += '=' * (4 - len(data) % 4)
|
|
35
|
+
return binascii.a2b_base64(data.replace('-', '+').replace('_', '/'))
|
|
36
|
+
|
|
37
|
+
def base64_url_encode(data):
|
|
38
|
+
return binascii.b2a_base64(data).decode('utf-8').strip().replace('+', '-').replace('/', '_').replace('=', '')
|
|
39
|
+
|
|
40
|
+
def a32_to_str(a):
|
|
41
|
+
return binascii.unhexlify(''.join(format(i, '08x') for i in a))
|
|
42
|
+
|
|
43
|
+
def str_to_a32(s):
|
|
44
|
+
if len(s) % 4:
|
|
45
|
+
s += b'\0' * (4 - len(s) % 4)
|
|
46
|
+
return [int(binascii.hexlify(s[i:i + 4]), 16) for i in range(0, len(s), 4)]
|
|
47
|
+
|
|
48
|
+
def base64_to_a32(s):
|
|
49
|
+
return str_to_a32(base64_url_decode(s))
|
|
50
|
+
|
|
51
|
+
def a32_to_base64(a):
|
|
52
|
+
return base64_url_encode(a32_to_str(a))
|
|
53
|
+
|
|
54
|
+
def aes_cbc_encrypt(data, key):
|
|
55
|
+
cipher = AES.new(a32_to_str(key), AES.MODE_CBC, b'\0' * 16)
|
|
56
|
+
return str_to_a32(cipher.encrypt(a32_to_str(data)))
|
|
57
|
+
|
|
58
|
+
def aes_cbc_decrypt(data, key):
|
|
59
|
+
cipher = AES.new(a32_to_str(key), AES.MODE_CBC, b'\0' * 16)
|
|
60
|
+
return str_to_a32(cipher.decrypt(a32_to_str(data)))
|
|
61
|
+
|
|
62
|
+
def decrypt_key(a, k):
|
|
63
|
+
res = []
|
|
64
|
+
for i in range(0, len(a), 4):
|
|
65
|
+
res += aes_cbc_decrypt(a[i:i + 4], k)
|
|
66
|
+
return res
|
|
67
|
+
|
|
68
|
+
def encrypt_key(a, k):
|
|
69
|
+
res = []
|
|
70
|
+
for i in range(0, len(a), 4):
|
|
71
|
+
res += aes_cbc_encrypt(a[i:i + 4], k)
|
|
72
|
+
return res
|
|
73
|
+
|
|
74
|
+
def prepare_key(a):
|
|
75
|
+
v = [0x93C467E3, 0x7DB0C7A4, 0xD1BE3F81, 0x0152CB56]
|
|
76
|
+
for _ in range(0x10000):
|
|
77
|
+
for j in range(0, len(a), 4):
|
|
78
|
+
key = [0, 0, 0, 0]
|
|
79
|
+
for k in range(4):
|
|
80
|
+
if j + k < len(a):
|
|
81
|
+
key[k] = a[j + k]
|
|
82
|
+
v = aes_cbc_encrypt(v, key)
|
|
83
|
+
return v
|
|
84
|
+
|
|
85
|
+
def mpi_to_int(s):
|
|
86
|
+
return int(binascii.hexlify(s[2:]), 16)
|
|
87
|
+
|
|
88
|
+
class Mega:
|
|
89
|
+
def __init__(self):
|
|
90
|
+
self.sid = None
|
|
91
|
+
self.api_url = "https://g.api.mega.co.nz/cs"
|
|
92
|
+
self.sequence_num = random.randint(0, 0xFFFFFFFF)
|
|
93
|
+
|
|
94
|
+
def login(self, email, password):
|
|
95
|
+
password_aes = prepare_key(str_to_a32(password.encode('utf-8')))
|
|
96
|
+
user_hash = self._stringhash(email, password_aes)
|
|
97
|
+
resp = self._api_request({'a': 'us', 'user': email, 'uh': user_hash})
|
|
98
|
+
|
|
99
|
+
if isinstance(resp, int) and resp < 0:
|
|
100
|
+
raise RequestError(f"Login failed: {resp}")
|
|
101
|
+
|
|
102
|
+
encrypted_key = base64_to_a32(resp['k'])
|
|
103
|
+
self.master_key = decrypt_key(encrypted_key, password_aes)
|
|
104
|
+
|
|
105
|
+
if 'tsid' in resp:
|
|
106
|
+
self.sid = resp['tsid']
|
|
107
|
+
elif 'csid' in resp:
|
|
108
|
+
encrypted_rsa_private_key = base64_to_a32(resp['privk'])
|
|
109
|
+
rsa_private_key = decrypt_key(encrypted_rsa_private_key, self.master_key)
|
|
110
|
+
private_key_str = a32_to_str(rsa_private_key)
|
|
111
|
+
components = [0, 0, 0, 0]
|
|
112
|
+
for i in range(4):
|
|
113
|
+
l = int(((private_key_str[0]) * 256 + (private_key_str[1]) + 7) / 8) + 2
|
|
114
|
+
components[i] = mpi_to_int(private_key_str[:l])
|
|
115
|
+
private_key_str = private_key_str[l:]
|
|
116
|
+
encrypted_sid = mpi_to_int(base64_url_decode(resp['csid']))
|
|
117
|
+
p, q, d, _ = components
|
|
118
|
+
n, phi = p * q, (p - 1) * (q - 1)
|
|
119
|
+
try: e = pow(d, -1, phi)
|
|
120
|
+
except: e = 65537
|
|
121
|
+
rsa_decrypter = RSA.construct((n, e, d, p, q))
|
|
122
|
+
sid_int = rsa_decrypter._decrypt(encrypted_sid)
|
|
123
|
+
sid_hex = '%x' % sid_int
|
|
124
|
+
if len(sid_hex) % 2: sid_hex = '0' + sid_hex
|
|
125
|
+
self.sid = base64_url_encode(binascii.unhexlify(sid_hex)[:43])
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def _stringhash(self, email, aes_key):
|
|
129
|
+
s = email.encode('utf-8')
|
|
130
|
+
h = [0, 0, 0, 0]
|
|
131
|
+
for i, b in enumerate(s): h[i % 4] ^= b
|
|
132
|
+
for _ in range(0x4000): h = aes_cbc_encrypt(h, aes_key)
|
|
133
|
+
return a32_to_base64([h[0], h[2]])
|
|
134
|
+
|
|
135
|
+
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10),
|
|
136
|
+
retry=retry_if_exception_type(requests.exceptions.RequestException))
|
|
137
|
+
def _api_request(self, data):
|
|
138
|
+
url = f"{self.api_url}?id={self.sequence_num}"
|
|
139
|
+
if self.sid: url += f"&sid={self.sid}"
|
|
140
|
+
self.sequence_num += 1
|
|
141
|
+
response = requests.post(url, json=[data], timeout=30)
|
|
142
|
+
response.raise_for_status()
|
|
143
|
+
result = response.json()
|
|
144
|
+
if isinstance(result, list):
|
|
145
|
+
res = result[0]
|
|
146
|
+
if isinstance(res, int) and res < 0: raise RequestError(f"API Error {res}")
|
|
147
|
+
return res
|
|
148
|
+
return result
|
|
149
|
+
|
|
150
|
+
def get_storage_space(self):
|
|
151
|
+
resp = self._api_request({'a': 'uq', 'strg': 1})
|
|
152
|
+
return {'total': resp['mstrg'], 'used': resp['cstrg']}
|
|
153
|
+
|
|
154
|
+
def upload(self, filename, dest_folder=None):
|
|
155
|
+
size = os.path.getsize(filename)
|
|
156
|
+
resp = self._api_request({'a': 'u', 's': size})
|
|
157
|
+
upload_url = resp['p']
|
|
158
|
+
with open(filename, 'rb') as f: data = f.read()
|
|
159
|
+
r = requests.post(upload_url, data=data, timeout=60)
|
|
160
|
+
upload_token = r.text
|
|
161
|
+
file_key = [random.randint(0, 0xFFFFFFFF) for _ in range(6)]
|
|
162
|
+
attribs = {'n': os.path.basename(filename)}
|
|
163
|
+
encoded_attribs = base64_url_encode(b'MEGA' + json.dumps(attribs).encode('utf-8'))
|
|
164
|
+
data = {'a': 'p', 't': dest_folder or 'ROOT', 'n': [{'h': upload_token, 't': 0, 'a': encoded_attribs, 'k': a32_to_base64(file_key[:4])}]}
|
|
165
|
+
return self._api_request(data)
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
install_playwright_browsers()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: webtools-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.6
|
|
4
4
|
Summary: Advanced Web Intelligence & Scraping Toolkit with CLI and Web UI
|
|
5
5
|
Author: Abhinav Adarsh
|
|
6
6
|
License-Expression: MIT
|
|
@@ -30,7 +30,7 @@ Requires-Dist: mtranslate
|
|
|
30
30
|
Requires-Dist: colorama
|
|
31
31
|
Requires-Dist: playwright
|
|
32
32
|
Requires-Dist: pycryptodome
|
|
33
|
-
Requires-Dist: tenacity
|
|
33
|
+
Requires-Dist: tenacity<9.0.0,>=8.2.3
|
|
34
34
|
Requires-Dist: pyreadline3; platform_system == "Windows"
|
|
35
35
|
Provides-Extra: playwright
|
|
36
36
|
Requires-Dist: playwright; extra == "playwright"
|
|
@@ -54,7 +54,7 @@ WebTools CLI is an advanced web intelligence suite for researchers, OSINT enthus
|
|
|
54
54
|
|
|
55
55
|
- **🎯 Stealth & Speed**: Smart proxy rotation and Turbo-Fetch logic for evasion and performance.
|
|
56
56
|
- **🧠 AI-Powered**: Automated content summarization, sentiment analysis, and readability scoring.
|
|
57
|
-
- **☁️ Cloud-Native**: Integrated Mega.nz storage
|
|
57
|
+
- **☁️ Cloud-Native**: Integrated Mega.nz storage with **Seamless Mono-Installer** (one-click, conflict-free setup).
|
|
58
58
|
- **🔧 Security-Centric**: Built-in honeypot detection, threat leveling, and image forensic analysis.
|
|
59
59
|
- **💻 Terminal-First**: Designed for power users who live in the command line.
|
|
60
60
|
- **🛡️ Cross-Platform**: Works seamlessly on Windows, Linux, and macOS (with auto-download for Windows tunnels).
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""Post-install script to setup Playwright browsers"""
|
|
2
|
-
import subprocess
|
|
3
|
-
import sys
|
|
4
|
-
|
|
5
|
-
def install_playwright_browsers():
|
|
6
|
-
"""Install Playwright Chromium browser after package installation"""
|
|
7
|
-
try:
|
|
8
|
-
print("📦 Installing Playwright Chromium browser...")
|
|
9
|
-
subprocess.run([sys.executable, "-m", "playwright", "install", "chromium"], check=True)
|
|
10
|
-
print("✓ Playwright Chromium installed successfully")
|
|
11
|
-
except subprocess.CalledProcessError:
|
|
12
|
-
print("⚠️ Failed to install Playwright browsers. Run manually: playwright install chromium")
|
|
13
|
-
except Exception as e:
|
|
14
|
-
print(f"⚠️ Playwright browser installation skipped: {e}")
|
|
15
|
-
|
|
16
|
-
if __name__ == "__main__":
|
|
17
|
-
install_playwright_browsers()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|