Open-AutoTools 0.0.3rc4__py3-none-any.whl → 0.0.4__py3-none-any.whl
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.
- autotools/autocaps/commands.py +3 -7
- autotools/autocaps/core.py +5 -4
- autotools/autoip/commands.py +8 -12
- autotools/autoip/core.py +151 -200
- autotools/autolower/commands.py +3 -7
- autotools/autolower/core.py +4 -3
- autotools/autopassword/commands.py +27 -33
- autotools/autopassword/core.py +32 -73
- autotools/autotest/__init__.py +2 -0
- autotools/autotest/commands.py +206 -0
- autotools/cli.py +123 -62
- autotools/utils/commands.py +13 -0
- autotools/utils/loading.py +14 -6
- autotools/utils/performance.py +424 -0
- autotools/utils/requirements.py +21 -0
- autotools/utils/text.py +16 -0
- autotools/utils/updates.py +30 -22
- autotools/utils/version.py +69 -63
- open_autotools-0.0.4.dist-info/METADATA +84 -0
- open_autotools-0.0.4.dist-info/RECORD +30 -0
- {Open_AutoTools-0.0.3rc4.dist-info → open_autotools-0.0.4.dist-info}/WHEEL +1 -1
- {Open_AutoTools-0.0.3rc4.dist-info → open_autotools-0.0.4.dist-info}/entry_points.txt +0 -3
- Open_AutoTools-0.0.3rc4.dist-info/METADATA +0 -308
- Open_AutoTools-0.0.3rc4.dist-info/RECORD +0 -44
- autotools/autocaps/tests/__init__.py +0 -1
- autotools/autocaps/tests/test_autocaps_core.py +0 -45
- autotools/autocaps/tests/test_autocaps_integration.py +0 -46
- autotools/autodownload/__init__.py +0 -0
- autotools/autodownload/commands.py +0 -38
- autotools/autodownload/core.py +0 -373
- autotools/autoip/tests/__init__.py +0 -1
- autotools/autoip/tests/test_autoip_core.py +0 -72
- autotools/autoip/tests/test_autoip_integration.py +0 -92
- autotools/autolower/tests/__init__.py +0 -1
- autotools/autolower/tests/test_autolower_core.py +0 -45
- autotools/autolower/tests/test_autolower_integration.py +0 -46
- autotools/autospell/__init__.py +0 -3
- autotools/autospell/commands.py +0 -123
- autotools/autospell/core.py +0 -222
- autotools/autotranslate/__init__.py +0 -3
- autotools/autotranslate/commands.py +0 -42
- autotools/autotranslate/core.py +0 -52
- autotools/test/__init__.py +0 -3
- autotools/test/commands.py +0 -120
- {Open_AutoTools-0.0.3rc4.dist-info → open_autotools-0.0.4.dist-info/licenses}/LICENSE +0 -0
- {Open_AutoTools-0.0.3rc4.dist-info → open_autotools-0.0.4.dist-info}/top_level.txt +0 -0
autotools/autodownload/core.py
DELETED
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
import os
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from urllib.parse import urlsplit
|
|
5
|
-
from tqdm import tqdm
|
|
6
|
-
import yt_dlp
|
|
7
|
-
import platform
|
|
8
|
-
import subprocess
|
|
9
|
-
import json
|
|
10
|
-
from rich.progress import Progress
|
|
11
|
-
from ..utils.loading import LoadingAnimation
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# FUNCTION TO GET DEFAULT DOWNLOAD DIRECTORY
|
|
15
|
-
def get_default_download_dir():
|
|
16
|
-
return Path(os.getenv('USERPROFILE') if os.name == 'nt' else Path.home()) / 'Downloads'
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# FUNCTION TO GET FILENAME FROM URL WITH DEFAULT AND EXTENSION HANDLING
|
|
20
|
-
def get_filename_from_url(url):
|
|
21
|
-
filename = os.path.basename(urlsplit(url).path)
|
|
22
|
-
if not filename: # IF NO FILENAME IN URL
|
|
23
|
-
return "downloaded_file"
|
|
24
|
-
if not Path(filename).suffix: # IF NO EXTENSION IN FILENAME
|
|
25
|
-
return f"{filename}.bin"
|
|
26
|
-
return filename
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# FUNCTION TO OPEN DOWNLOAD DIRECTORY AFTER DOWNLOAD IS COMPLETE
|
|
30
|
-
def open_download_folder(path):
|
|
31
|
-
"""OPEN THE DOWNLOAD FOLDER IN THE DEFAULT FILE MANAGER"""
|
|
32
|
-
# SKIP IN CI ENVIRONMENT
|
|
33
|
-
if os.environ.get('CI'):
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
try:
|
|
37
|
-
if platform.system() == 'Darwin': # MACOS
|
|
38
|
-
subprocess.run(['open', str(path)], check=True)
|
|
39
|
-
elif platform.system() == 'Windows': # WINDOWS
|
|
40
|
-
os.startfile(str(path))
|
|
41
|
-
else: # LINUX
|
|
42
|
-
subprocess.run(['xdg-open', str(path)], check=True)
|
|
43
|
-
except Exception as e:
|
|
44
|
-
print(f"Failed to open download folder: {e}")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# FUNCTION TO VALIDATE YOUTUBE URL FORMAT
|
|
48
|
-
def validate_youtube_url(url):
|
|
49
|
-
"""BASIC URL VALIDATION WITH PROPER FORMAT CHECK"""
|
|
50
|
-
# CHECK IF URL CONTAINS YOUTUBE DOMAIN
|
|
51
|
-
is_youtube = any(domain in url for domain in ["youtube.com", "youtu.be", "music.youtube.com"])
|
|
52
|
-
|
|
53
|
-
# CHECK IF URL HAS PROPER VIDEO ID FORMAT
|
|
54
|
-
has_video_id = False
|
|
55
|
-
if "youtube.com/watch" in url and "v=" in url:
|
|
56
|
-
has_video_id = True
|
|
57
|
-
elif "youtu.be/" in url and len(url.split("youtu.be/")[1]) > 0:
|
|
58
|
-
has_video_id = True
|
|
59
|
-
elif any(pattern in url for pattern in ["/watch/", "/shorts/", "/live/"]):
|
|
60
|
-
path_parts = url.split("/")
|
|
61
|
-
has_video_id = len(path_parts[-1]) > 0
|
|
62
|
-
elif "attribution_link" in url and "watch?v=" in url:
|
|
63
|
-
has_video_id = True
|
|
64
|
-
|
|
65
|
-
return is_youtube and has_video_id
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# FUNCTION TO DOWNLOAD FILES WITH REQUESTS, INCLUDING ERROR HANDLING AND PROGRESS BAR
|
|
69
|
-
def download_file(url):
|
|
70
|
-
download_dir = get_default_download_dir()
|
|
71
|
-
filename = get_filename_from_url(url)
|
|
72
|
-
dest_file = download_dir / filename
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
with requests.get(url, stream=True) as response:
|
|
76
|
-
response.raise_for_status()
|
|
77
|
-
|
|
78
|
-
total_size = int(response.headers.get('content-length', 0))
|
|
79
|
-
block_size = 1024 # 1KB
|
|
80
|
-
|
|
81
|
-
with tqdm(total=total_size if total_size else None, unit='iB', unit_scale=True, desc=f"Downloading {filename}", leave=True) as tqdm_bar:
|
|
82
|
-
with open(dest_file, 'wb') as file:
|
|
83
|
-
for chunk in response.iter_content(chunk_size=block_size):
|
|
84
|
-
if chunk:
|
|
85
|
-
file.write(chunk)
|
|
86
|
-
tqdm_bar.update(len(chunk))
|
|
87
|
-
|
|
88
|
-
# AUTOMATICALLY OPEN DOWNLOAD FOLDER AFTER FILE DOWNLOAD IS COMPLETE
|
|
89
|
-
open_download_folder(download_dir)
|
|
90
|
-
except requests.exceptions.RequestException as e:
|
|
91
|
-
print(f"Error during file download: {e}")
|
|
92
|
-
|
|
93
|
-
# FUNCTION TO GET CONSENT FILE PATH
|
|
94
|
-
def get_consent_file_path():
|
|
95
|
-
"""GET PATH TO STORE CONSENT STATUS"""
|
|
96
|
-
# INFO: delete consent file with "rm -f ~/.autotools/consent.json" if you want to force new consent in local development
|
|
97
|
-
return Path.home() / '.autotools' / 'consent.json'
|
|
98
|
-
|
|
99
|
-
# FUNCTION TO LOAD CONSENT STATUS
|
|
100
|
-
def load_consent_status():
|
|
101
|
-
"""LOAD SAVED CONSENT STATUS"""
|
|
102
|
-
try:
|
|
103
|
-
consent_file = get_consent_file_path()
|
|
104
|
-
|
|
105
|
-
# FORCE NEW CONSENT IF FILE DOESN'T EXIST OR IS EMPTY
|
|
106
|
-
if not consent_file.exists():
|
|
107
|
-
return False
|
|
108
|
-
|
|
109
|
-
# READ CONSENT STATUS
|
|
110
|
-
with open(consent_file) as f:
|
|
111
|
-
data = json.load(f)
|
|
112
|
-
return data.get('youtube_consent', False)
|
|
113
|
-
except Exception:
|
|
114
|
-
# IF ANY ERROR OCCURS, FORCE NEW CONSENT
|
|
115
|
-
return False
|
|
116
|
-
|
|
117
|
-
# FUNCTION TO SAVE CONSENT STATUS
|
|
118
|
-
def save_consent_status(status):
|
|
119
|
-
"""SAVE CONSENT STATUS"""
|
|
120
|
-
try:
|
|
121
|
-
consent_file = get_consent_file_path()
|
|
122
|
-
consent_file.parent.mkdir(exist_ok=True)
|
|
123
|
-
|
|
124
|
-
# SAVE CONSENT STATUS TO FILE
|
|
125
|
-
with open(consent_file, 'w') as f:
|
|
126
|
-
json.dump({'youtube_consent': status}, f)
|
|
127
|
-
return True
|
|
128
|
-
except Exception:
|
|
129
|
-
# IF SAVING FAILS, RETURN FALSE TO FORCE NEW CONSENT NEXT TIME
|
|
130
|
-
return False
|
|
131
|
-
|
|
132
|
-
# FUNCTION TO GET USER CONSENT WITH INTERACTIVE PROMPT
|
|
133
|
-
def get_user_consent():
|
|
134
|
-
"""GET USER CONSENT WITH INTERACTIVE PROMPT"""
|
|
135
|
-
print("\n⚠️ Important Notice:")
|
|
136
|
-
print("This tool will:")
|
|
137
|
-
print("1. Download video content from YouTube")
|
|
138
|
-
print("2. Save files to your local machine")
|
|
139
|
-
print("3. Use mobile API for better compatibility")
|
|
140
|
-
|
|
141
|
-
# GET USER CONSENT WITH INTERACTIVE PROMPT
|
|
142
|
-
while True:
|
|
143
|
-
response = input("\nDo you consent to these actions? (yes/no): ").lower()
|
|
144
|
-
if response in ['yes', 'y']:
|
|
145
|
-
save_consent_status(True)
|
|
146
|
-
return True
|
|
147
|
-
elif response in ['no', 'n']:
|
|
148
|
-
save_consent_status(False)
|
|
149
|
-
return False
|
|
150
|
-
print("Please answer 'yes' or 'no'")
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
# FUNCTION TO CHECK IF VIDEO EXISTS AND GET USER CONSENT FOR REPLACEMENT
|
|
154
|
-
def check_existing_video(info, format='mp4'):
|
|
155
|
-
"""CHECK IF VIDEO EXISTS AND ASK FOR REPLACEMENT"""
|
|
156
|
-
download_dir = get_default_download_dir()
|
|
157
|
-
title = info.get('title', '').replace('/', '_') # SANITIZE TITLE
|
|
158
|
-
filename = f"{title}.{format}"
|
|
159
|
-
filepath = download_dir / filename
|
|
160
|
-
|
|
161
|
-
# CHECK IF FILE EXISTS AND ASK FOR REPLACEMENT
|
|
162
|
-
if filepath.exists():
|
|
163
|
-
print(f"\n⚠️ File already exists: {filename}")
|
|
164
|
-
while True:
|
|
165
|
-
response = input("Do you want to replace it? (yes/no): ").lower()
|
|
166
|
-
if response in ['yes', 'y']:
|
|
167
|
-
return True
|
|
168
|
-
elif response in ['no', 'n']:
|
|
169
|
-
# OPEN DOWNLOADS FOLDER TO SHOW EXISTING FILE
|
|
170
|
-
open_download_folder(download_dir)
|
|
171
|
-
return False
|
|
172
|
-
print("Please answer 'yes' or 'no'")
|
|
173
|
-
return True
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# FUNCTION TO DOWNLOAD YOUTUBE VIDEOS WITH YT-DLP AND SPECIFIED FORMAT AND QUALITY
|
|
177
|
-
def download_youtube_video(url, format='mp4', quality='best'):
|
|
178
|
-
"""DOWNLOAD VIDEO WITH CONSENT CHECK"""
|
|
179
|
-
# VALIDATE URL FIRST
|
|
180
|
-
if not validate_youtube_url(url):
|
|
181
|
-
print("\n❌ Invalid YouTube URL")
|
|
182
|
-
return False
|
|
183
|
-
|
|
184
|
-
# CHECK FOR SAVED CONSENT FIRST AND GET NEW CONSENT IF NEEDED
|
|
185
|
-
if not load_consent_status() and not get_user_consent():
|
|
186
|
-
print("\n❌ Download cancelled by user")
|
|
187
|
-
return False
|
|
188
|
-
|
|
189
|
-
# FIRST CHECK VIDEO INFO AND EXISTENCE
|
|
190
|
-
try:
|
|
191
|
-
with yt_dlp.YoutubeDL({
|
|
192
|
-
'quiet': True,
|
|
193
|
-
'no_warnings': True,
|
|
194
|
-
'extractor_args': {'youtube': {
|
|
195
|
-
'player_client': ['web', 'android'],
|
|
196
|
-
'formats': ['missing_pot'] # ALLOW FORMATS WITHOUT PO TOKEN
|
|
197
|
-
}}
|
|
198
|
-
}) as ydl:
|
|
199
|
-
info = ydl.extract_info(url, download=False)
|
|
200
|
-
formats = info.get('formats', [])
|
|
201
|
-
if not formats:
|
|
202
|
-
print("\n❌ No formats available for this video")
|
|
203
|
-
return False
|
|
204
|
-
|
|
205
|
-
# FIND BEST AVAILABLE QUALITY
|
|
206
|
-
best_height = 0
|
|
207
|
-
for f in formats:
|
|
208
|
-
height = f.get('height')
|
|
209
|
-
if height is not None and height > best_height:
|
|
210
|
-
best_height = height
|
|
211
|
-
|
|
212
|
-
# IF NO VALID HEIGHT FOUND, DEFAULT TO 1080P
|
|
213
|
-
if best_height == 0:
|
|
214
|
-
best_height = 1080
|
|
215
|
-
|
|
216
|
-
# IF QUALITY IS 'BEST', USE THE BEST AVAILABLE
|
|
217
|
-
if quality == 'best':
|
|
218
|
-
height = best_height
|
|
219
|
-
# ASK FOR CONFIRMATION IF 4K OR HIGHER (ONLY FOR MP4)
|
|
220
|
-
if format == 'mp4' and height >= 2160:
|
|
221
|
-
print(f"\n⚠️ This video is available in {height}p quality!")
|
|
222
|
-
while True:
|
|
223
|
-
response = input(f"Do you want to download in {height}p quality? (yes/no): ").lower()
|
|
224
|
-
if response in ['no', 'n']:
|
|
225
|
-
height = 1080
|
|
226
|
-
print("\nDowngrading to 1080p quality.")
|
|
227
|
-
break
|
|
228
|
-
elif response in ['yes', 'y']:
|
|
229
|
-
break
|
|
230
|
-
print("Please answer 'yes' or 'no'")
|
|
231
|
-
else:
|
|
232
|
-
# EXTRACT HEIGHT FROM QUALITY STRING
|
|
233
|
-
try:
|
|
234
|
-
height = int(quality.lower().replace('p', ''))
|
|
235
|
-
except ValueError:
|
|
236
|
-
height = 1080 # DEFAULT TO 1080P IF INVALID FORMAT
|
|
237
|
-
|
|
238
|
-
# CHECK IF FILE EXISTS AND GET REPLACEMENT CONSENT
|
|
239
|
-
force_download = check_existing_video(info, format)
|
|
240
|
-
if not force_download:
|
|
241
|
-
print("\n❌ Download cancelled - file already exists")
|
|
242
|
-
return False
|
|
243
|
-
|
|
244
|
-
# OPEN DOWNLOADS FOLDER IF STARTING NEW DOWNLOAD OR REPLACING
|
|
245
|
-
download_dir = get_default_download_dir()
|
|
246
|
-
open_download_folder(download_dir)
|
|
247
|
-
|
|
248
|
-
except Exception as e:
|
|
249
|
-
print(f"\n❌ Error checking video: {str(e)}")
|
|
250
|
-
return False
|
|
251
|
-
|
|
252
|
-
loading = LoadingAnimation()
|
|
253
|
-
|
|
254
|
-
# START LOADING FOR DOWNLOAD PROCESS
|
|
255
|
-
with loading:
|
|
256
|
-
loading._spinner.start()
|
|
257
|
-
print("\n🔍 Starting download...")
|
|
258
|
-
|
|
259
|
-
print(f"\n🎥 Downloading video from: {url}")
|
|
260
|
-
if format == 'mp3':
|
|
261
|
-
print(f"📋 Format: {format}\n")
|
|
262
|
-
else:
|
|
263
|
-
print(f"📋 Format: {format}, Quality: {height}p\n")
|
|
264
|
-
|
|
265
|
-
# YT-DLP PERMISSION OPTIONS FOR DOWNLOADING YOUTUBE VIDEOS
|
|
266
|
-
ydl_opts = {
|
|
267
|
-
'format': f'bestvideo[height={height}][ext=mp4]+bestaudio[ext=m4a]/bestvideo[height<={height}][ext=mp4]+bestaudio[ext=m4a]/best[height<={height}][ext=mp4]/best[ext=mp4]/best' if format == 'mp4' else 'bestaudio/best',
|
|
268
|
-
'postprocessors': [{
|
|
269
|
-
'key': 'FFmpegExtractAudio',
|
|
270
|
-
'preferredcodec': 'mp3',
|
|
271
|
-
'preferredquality': '192',
|
|
272
|
-
}] if format == 'mp3' else [],
|
|
273
|
-
'quiet': True,
|
|
274
|
-
'no_warnings': True,
|
|
275
|
-
'progress': True,
|
|
276
|
-
'progress_hooks': [lambda d: update_progress(d)],
|
|
277
|
-
'extractor_args': {
|
|
278
|
-
'youtube': {
|
|
279
|
-
'player_client': ['web', 'android'], # USE WEB CLIENT FIRST
|
|
280
|
-
'formats': ['missing_pot'], # ALLOW FORMATS WITHOUT PO TOKEN
|
|
281
|
-
'player_skip': ['configs', 'webpage'] # SKIP UNNECESSARY CONFIGS
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
'http_headers': {
|
|
285
|
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
286
|
-
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
287
|
-
'Accept-Language': 'en-us,en;q=0.5',
|
|
288
|
-
'Sec-Fetch-Mode': 'navigate'
|
|
289
|
-
},
|
|
290
|
-
'outtmpl': str(download_dir / '%(title)s.%(ext)s'), # SET OUTPUT TEMPLATE
|
|
291
|
-
'overwrites': True, # FORCE OVERWRITE IF USER CONSENTED
|
|
292
|
-
'no_check_certificates': True, # SKIP CERTIFICATE VALIDATION
|
|
293
|
-
'cookiesfrombrowser': ('chrome',), # USE CHROME COOKIES IF AVAILABLE
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
try:
|
|
297
|
-
# THEN DOWNLOAD
|
|
298
|
-
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
299
|
-
ydl.download([url])
|
|
300
|
-
print("\n✅ Download completed successfully!")
|
|
301
|
-
return True
|
|
302
|
-
except Exception as e:
|
|
303
|
-
error_msg = str(e)
|
|
304
|
-
if "Requested format is not available" in error_msg:
|
|
305
|
-
print("\n❌ Format not available. Available formats are:")
|
|
306
|
-
for f in formats:
|
|
307
|
-
print(f"- {f.get('format_id', 'N/A')}: {f.get('ext', 'N/A')} ({f.get('format_note', 'N/A')})")
|
|
308
|
-
else:
|
|
309
|
-
print(f"\n❌ ERROR: {error_msg}")
|
|
310
|
-
return False
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
# FUNCTION TO LIST AVAILABLE FORMATS FOR A YOUTUBE VIDEO
|
|
314
|
-
def list_available_formats(url):
|
|
315
|
-
try:
|
|
316
|
-
with yt_dlp.YoutubeDL({'quiet': True}) as ydl:
|
|
317
|
-
info_dict = ydl.extract_info(url, download=False)
|
|
318
|
-
formats = info_dict.get('formats', None)
|
|
319
|
-
if formats:
|
|
320
|
-
for f in formats:
|
|
321
|
-
print(f"Format: {f['format_id']}, Resolution: {f.get('resolution')}, Extension: {f['ext']}")
|
|
322
|
-
except yt_dlp.utils.DownloadError as e:
|
|
323
|
-
print(f"Error fetching formats: {e}")
|
|
324
|
-
except Exception as e:
|
|
325
|
-
print(f"Unexpected error: {e}")
|
|
326
|
-
|
|
327
|
-
pbar = None # GLOBAL VARIABLE TO STORE PROGRESS BAR
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
# FUNCTION TO UPDATE PROGRESS BAR
|
|
331
|
-
def update_progress(d):
|
|
332
|
-
global pbar
|
|
333
|
-
if d['status'] == 'downloading':
|
|
334
|
-
total = d.get('total_bytes', 0)
|
|
335
|
-
downloaded = d.get('downloaded_bytes', 0)
|
|
336
|
-
|
|
337
|
-
if pbar is None:
|
|
338
|
-
pbar = tqdm(total=total, unit='B', unit_scale=True, desc="⏳ Downloading", leave=True, ncols=80, bar_format='{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt} [{rate_fmt}]')
|
|
339
|
-
|
|
340
|
-
if total > 0:
|
|
341
|
-
pbar.n = downloaded
|
|
342
|
-
pbar.total = total
|
|
343
|
-
pbar.refresh()
|
|
344
|
-
|
|
345
|
-
elif d['status'] == 'finished' and pbar:
|
|
346
|
-
pbar.close()
|
|
347
|
-
pbar = None
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
# FUNCTION TO DOWNLOAD FILE WITH SPECIFIC HANDLING AND FOLDER OPENING
|
|
351
|
-
def download_file_with_tqdm(url):
|
|
352
|
-
download_dir = get_default_download_dir()
|
|
353
|
-
filename = get_filename_from_url(url)
|
|
354
|
-
dest_file = download_dir / filename
|
|
355
|
-
|
|
356
|
-
try:
|
|
357
|
-
with requests.get(url, stream=True) as response:
|
|
358
|
-
response.raise_for_status()
|
|
359
|
-
|
|
360
|
-
total_size = int(response.headers.get('content-length', 0))
|
|
361
|
-
block_size = 1024 # 1KB
|
|
362
|
-
|
|
363
|
-
with tqdm(total=total_size if total_size else None, unit='iB', unit_scale=True, desc=f"Downloading {filename}", leave=True) as tqdm_bar:
|
|
364
|
-
with open(dest_file, 'wb') as file:
|
|
365
|
-
for chunk in response.iter_content(chunk_size=block_size):
|
|
366
|
-
if chunk:
|
|
367
|
-
file.write(chunk)
|
|
368
|
-
tqdm_bar.update(len(chunk))
|
|
369
|
-
|
|
370
|
-
# AUTOMATICALLY OPEN DOWNLOAD FOLDER AFTER FILE DOWNLOAD IS COMPLETE
|
|
371
|
-
open_download_folder(download_dir)
|
|
372
|
-
except requests.exceptions.RequestException as e:
|
|
373
|
-
print(f"Error during file download: {e}")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# INIT FILE FOR TESTS
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from unittest.mock import patch, Mock
|
|
3
|
-
from autotools.autoip.core import get_public_ip, get_local_ip, get_ip_info
|
|
4
|
-
|
|
5
|
-
# MOCK DATA
|
|
6
|
-
MOCK_IP_INFO = {
|
|
7
|
-
'ip': '8.8.8.8',
|
|
8
|
-
'city': 'Mountain View',
|
|
9
|
-
'region': 'California',
|
|
10
|
-
'country': 'US',
|
|
11
|
-
'loc': '37.4056,-122.0775',
|
|
12
|
-
'org': 'Google LLC',
|
|
13
|
-
'timezone': 'America/Los_Angeles'
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
# UNIT TESTS
|
|
17
|
-
|
|
18
|
-
# TEST FOR PUBLIC IP RETRIEVAL
|
|
19
|
-
@patch('requests.get')
|
|
20
|
-
def test_get_public_ip(mock_get):
|
|
21
|
-
"""TEST PUBLIC IP RETRIEVAL"""
|
|
22
|
-
mock_get.return_value.text = "1.2.3.4"
|
|
23
|
-
ip = get_public_ip()
|
|
24
|
-
assert ip == "1.2.3.4"
|
|
25
|
-
mock_get.assert_called_once()
|
|
26
|
-
|
|
27
|
-
# TEST FOR LOCAL IP RETRIEVAL
|
|
28
|
-
@patch('socket.socket')
|
|
29
|
-
@patch('netifaces.gateways')
|
|
30
|
-
@patch('netifaces.ifaddresses')
|
|
31
|
-
def test_get_local_ip(mock_ifaddresses, mock_gateways, mock_socket):
|
|
32
|
-
"""TEST LOCAL IP RETRIEVAL"""
|
|
33
|
-
# MOCK NETIFACES
|
|
34
|
-
mock_gateways.return_value = {'default': {2: ('192.168.1.1', 'eth0')}}
|
|
35
|
-
mock_ifaddresses.return_value = {2: [{'addr': '192.168.1.100'}]}
|
|
36
|
-
|
|
37
|
-
ip = get_local_ip()
|
|
38
|
-
assert ip == "192.168.1.100"
|
|
39
|
-
|
|
40
|
-
# TEST FOR IP INFO RETRIEVAL
|
|
41
|
-
@patch('requests.get')
|
|
42
|
-
def test_get_ip_info(mock_get):
|
|
43
|
-
"""TEST IP INFO RETRIEVAL"""
|
|
44
|
-
mock_get.return_value.json.return_value = MOCK_IP_INFO
|
|
45
|
-
info = get_ip_info()
|
|
46
|
-
assert isinstance(info, dict)
|
|
47
|
-
assert info == MOCK_IP_INFO
|
|
48
|
-
|
|
49
|
-
# TEST FOR IP INFO WITH SPECIFIC IP
|
|
50
|
-
@patch('requests.get')
|
|
51
|
-
def test_get_ip_info_with_ip(mock_get):
|
|
52
|
-
"""TEST IP INFO WITH SPECIFIC IP"""
|
|
53
|
-
mock_get.return_value.json.return_value = MOCK_IP_INFO
|
|
54
|
-
test_ip = "8.8.8.8" # GOOGLE DNS
|
|
55
|
-
info = get_ip_info(test_ip)
|
|
56
|
-
assert isinstance(info, dict)
|
|
57
|
-
assert info['ip'] == test_ip
|
|
58
|
-
assert 'Google' in info['org']
|
|
59
|
-
|
|
60
|
-
# TEST FOR IP INFO WITH INVALID IP
|
|
61
|
-
def test_get_ip_info_invalid():
|
|
62
|
-
"""TEST IP INFO WITH INVALID IP"""
|
|
63
|
-
with pytest.raises(ValueError):
|
|
64
|
-
get_ip_info("invalid.ip.address")
|
|
65
|
-
|
|
66
|
-
# TEST FOR IP INFO WITH PRIVATE IP
|
|
67
|
-
def test_get_ip_info_private():
|
|
68
|
-
"""TEST IP INFO WITH PRIVATE IP"""
|
|
69
|
-
private_ips = ["192.168.1.1", "10.0.0.1", "172.16.0.1"]
|
|
70
|
-
for ip in private_ips:
|
|
71
|
-
with pytest.raises(ValueError):
|
|
72
|
-
get_ip_info(ip)
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from unittest.mock import patch, Mock
|
|
3
|
-
from click.testing import CliRunner
|
|
4
|
-
from autotools.cli import autoip
|
|
5
|
-
|
|
6
|
-
# MOCK DATA
|
|
7
|
-
MOCK_IP_INFO = {
|
|
8
|
-
'ip': '8.8.8.8',
|
|
9
|
-
'city': 'Mountain View',
|
|
10
|
-
'region': 'California',
|
|
11
|
-
'country': 'US',
|
|
12
|
-
'loc': '37.4056,-122.0775',
|
|
13
|
-
'org': 'Google LLC',
|
|
14
|
-
'timezone': 'America/Los_Angeles'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
# INTEGRATION TESTS
|
|
18
|
-
|
|
19
|
-
# TEST FOR BASIC CLI FUNCTIONALITY
|
|
20
|
-
@patch('autotools.autoip.core.get_local_ips')
|
|
21
|
-
@patch('autotools.autoip.core.get_public_ips')
|
|
22
|
-
def test_autoip_cli_basic(mock_public_ips, mock_local_ips):
|
|
23
|
-
"""TEST BASIC CLI FUNCTIONALITY"""
|
|
24
|
-
mock_local_ips.return_value = {
|
|
25
|
-
'ipv4': ['192.168.1.100'],
|
|
26
|
-
'ipv6': ['fe80::1']
|
|
27
|
-
}
|
|
28
|
-
mock_public_ips.return_value = {
|
|
29
|
-
'ipv4': '1.2.3.4',
|
|
30
|
-
'ipv6': None
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
runner = CliRunner()
|
|
34
|
-
result = runner.invoke(autoip)
|
|
35
|
-
assert result.exit_code == 0
|
|
36
|
-
assert "192.168.1.100" in result.output
|
|
37
|
-
assert "1.2.3.4" in result.output
|
|
38
|
-
assert "fe80::1" in result.output
|
|
39
|
-
|
|
40
|
-
# TEST FOR CONNECTIVITY TEST
|
|
41
|
-
@patch('autotools.autoip.core.test_connectivity')
|
|
42
|
-
def test_autoip_cli_test(mock_test):
|
|
43
|
-
"""TEST CONNECTIVITY TEST"""
|
|
44
|
-
mock_test.return_value = [
|
|
45
|
-
('Google DNS', True, 20),
|
|
46
|
-
('CloudFlare', False, None)
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
runner = CliRunner()
|
|
50
|
-
result = runner.invoke(autoip, ['--test'])
|
|
51
|
-
assert result.exit_code == 0
|
|
52
|
-
assert "Google DNS" in result.output
|
|
53
|
-
assert "CloudFlare" in result.output
|
|
54
|
-
assert "✓ 20ms" in result.output
|
|
55
|
-
assert "✗ Failed" in result.output
|
|
56
|
-
|
|
57
|
-
# TEST FOR SPEED TEST
|
|
58
|
-
@patch('autotools.autoip.core.run_speedtest')
|
|
59
|
-
def test_autoip_cli_speed(mock_speed):
|
|
60
|
-
"""TEST SPEED TEST"""
|
|
61
|
-
mock_speed.return_value = True
|
|
62
|
-
|
|
63
|
-
runner = CliRunner()
|
|
64
|
-
result = runner.invoke(autoip, ['--speed'])
|
|
65
|
-
assert result.exit_code == 0
|
|
66
|
-
assert "Running speed test" in result.output
|
|
67
|
-
assert "completed successfully" in result.output
|
|
68
|
-
|
|
69
|
-
# TEST FOR LOCATION INFO DISPLAY
|
|
70
|
-
@patch('autotools.autoip.core.get_ip_info')
|
|
71
|
-
def test_autoip_cli_location(mock_get_info):
|
|
72
|
-
"""TEST LOCATION INFO DISPLAY"""
|
|
73
|
-
mock_get_info.return_value = MOCK_IP_INFO
|
|
74
|
-
|
|
75
|
-
runner = CliRunner()
|
|
76
|
-
result = runner.invoke(autoip, ['--location'])
|
|
77
|
-
assert result.exit_code == 0
|
|
78
|
-
assert "Mountain View" in result.output
|
|
79
|
-
assert "California" in result.output
|
|
80
|
-
assert "Google LLC" in result.output
|
|
81
|
-
|
|
82
|
-
# TEST FOR HELP DISPLAY
|
|
83
|
-
def test_autoip_cli_help():
|
|
84
|
-
"""TEST HELP DISPLAY"""
|
|
85
|
-
runner = CliRunner()
|
|
86
|
-
result = runner.invoke(autoip, ['--help'])
|
|
87
|
-
assert result.exit_code == 0
|
|
88
|
-
assert "Usage:" in result.output
|
|
89
|
-
assert "Options:" in result.output
|
|
90
|
-
assert "--test" in result.output
|
|
91
|
-
assert "--speed" in result.output
|
|
92
|
-
assert "--location" in result.output
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# INIT FILE FOR TESTS
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from autotools.autolower.core import autolower_transform
|
|
3
|
-
|
|
4
|
-
# UNIT TESTS
|
|
5
|
-
|
|
6
|
-
# TEST FOR BASIC STRING TRANSFORMATION
|
|
7
|
-
def test_autolower_transform_basic():
|
|
8
|
-
"""TEST BASIC STRING TRANSFORMATION"""
|
|
9
|
-
assert autolower_transform("HELLO") == "hello"
|
|
10
|
-
assert autolower_transform("Hello World") == "hello world"
|
|
11
|
-
assert autolower_transform("123") == "123"
|
|
12
|
-
|
|
13
|
-
# TEST FOR EMPTY STRING
|
|
14
|
-
def test_autolower_transform_empty():
|
|
15
|
-
"""TEST EMPTY STRING"""
|
|
16
|
-
assert autolower_transform("") == ""
|
|
17
|
-
|
|
18
|
-
# TEST FOR SPECIAL CHARACTERS
|
|
19
|
-
def test_autolower_transform_special_chars():
|
|
20
|
-
"""TEST STRING WITH SPECIAL CHARACTERS"""
|
|
21
|
-
assert autolower_transform("HELLO@WORLD.COM") == "hello@world.com"
|
|
22
|
-
assert autolower_transform("HELLO-WORLD!") == "hello-world!"
|
|
23
|
-
|
|
24
|
-
# TEST FOR MIXED CASE STRING
|
|
25
|
-
def test_autolower_transform_mixed_case():
|
|
26
|
-
"""TEST MIXED CASE STRING"""
|
|
27
|
-
assert autolower_transform("HeLLo WoRLD") == "hello world"
|
|
28
|
-
|
|
29
|
-
# TEST FOR WHITESPACE
|
|
30
|
-
def test_autolower_transform_whitespace():
|
|
31
|
-
"""TEST STRING WITH WHITESPACE"""
|
|
32
|
-
assert autolower_transform(" HELLO WORLD ") == " hello world "
|
|
33
|
-
assert autolower_transform("\tHELLO\nWORLD") == "\thello\nworld"
|
|
34
|
-
|
|
35
|
-
# TEST FOR NUMBERS
|
|
36
|
-
def test_autolower_transform_numbers():
|
|
37
|
-
"""TEST STRING WITH NUMBERS"""
|
|
38
|
-
assert autolower_transform("HELLO123WORLD") == "hello123world"
|
|
39
|
-
assert autolower_transform("123HELLO456WORLD789") == "123hello456world789"
|
|
40
|
-
|
|
41
|
-
# TEST FOR UNICODE CHARACTERS
|
|
42
|
-
def test_autolower_transform_unicode():
|
|
43
|
-
"""TEST UNICODE CHARACTERS"""
|
|
44
|
-
assert autolower_transform("HÉLLO WÖRLD") == "héllo wörld"
|
|
45
|
-
assert autolower_transform("こんにちは") == "こんにちは" # JAPANESE SHOULD REMAIN UNCHANGED
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from click.testing import CliRunner
|
|
3
|
-
from autotools.cli import autolower
|
|
4
|
-
|
|
5
|
-
# INTEGRATION TESTS
|
|
6
|
-
|
|
7
|
-
# TEST FOR BASIC CLI FUNCTIONALITY
|
|
8
|
-
def test_autolower_cli_basic():
|
|
9
|
-
"""TEST BASIC CLI FUNCTIONALITY"""
|
|
10
|
-
runner = CliRunner()
|
|
11
|
-
result = runner.invoke(autolower, ["HELLO WORLD"])
|
|
12
|
-
assert result.exit_code == 0
|
|
13
|
-
assert "hello world" in result.output
|
|
14
|
-
|
|
15
|
-
# TEST FOR EMPTY INPUT
|
|
16
|
-
def test_autolower_cli_empty():
|
|
17
|
-
"""TEST CLI WITH EMPTY INPUT"""
|
|
18
|
-
runner = CliRunner()
|
|
19
|
-
result = runner.invoke(autolower, [""])
|
|
20
|
-
assert result.exit_code == 0
|
|
21
|
-
assert "" in result.output
|
|
22
|
-
|
|
23
|
-
# TEST FOR SPECIAL CHARACTERS
|
|
24
|
-
def test_autolower_cli_special_chars():
|
|
25
|
-
"""TEST CLI WITH SPECIAL CHARACTERS"""
|
|
26
|
-
runner = CliRunner()
|
|
27
|
-
result = runner.invoke(autolower, ["HELLO@WORLD.COM"])
|
|
28
|
-
assert result.exit_code == 0
|
|
29
|
-
assert "hello@world.com" in result.output
|
|
30
|
-
|
|
31
|
-
# TEST FOR UNICODE CHARACTERS
|
|
32
|
-
def test_autolower_cli_unicode():
|
|
33
|
-
"""TEST CLI WITH UNICODE CHARACTERS"""
|
|
34
|
-
runner = CliRunner()
|
|
35
|
-
result = runner.invoke(autolower, ["HÉLLO WÖRLD"])
|
|
36
|
-
assert result.exit_code == 0
|
|
37
|
-
assert "héllo wörld" in result.output
|
|
38
|
-
|
|
39
|
-
# TEST FOR MULTIPLE ARGUMENTS
|
|
40
|
-
def test_autolower_cli_multiple_args():
|
|
41
|
-
"""TEST CLI WITH MULTIPLE ARGUMENTS"""
|
|
42
|
-
runner = CliRunner()
|
|
43
|
-
result = runner.invoke(autolower, ["HELLO", "WORLD"])
|
|
44
|
-
assert result.exit_code == 0
|
|
45
|
-
# SHOULD ONLY PROCESS FIRST ARGUMENT
|
|
46
|
-
assert "hello" in result.output
|
autotools/autospell/__init__.py
DELETED