Open-AutoTools 0.0.3rc2__py3-none-any.whl → 0.0.3rc4__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.
- {Open_AutoTools-0.0.3rc2.dist-info → Open_AutoTools-0.0.3rc4.dist-info}/METADATA +110 -109
- {Open_AutoTools-0.0.3rc2.dist-info → Open_AutoTools-0.0.3rc4.dist-info}/RECORD +22 -9
- autotools/autocaps/commands.py +17 -0
- autotools/autodownload/commands.py +38 -0
- autotools/autodownload/core.py +190 -50
- autotools/autoip/commands.py +29 -0
- autotools/autolower/commands.py +17 -0
- autotools/autopassword/commands.py +76 -0
- autotools/autopassword/core.py +1 -0
- autotools/autospell/commands.py +123 -0
- autotools/autotranslate/commands.py +42 -0
- autotools/cli.py +30 -493
- autotools/test/__init__.py +3 -0
- autotools/test/commands.py +120 -0
- autotools/utils/__init__.py +5 -0
- autotools/utils/loading.py +16 -0
- autotools/utils/updates.py +30 -0
- autotools/utils/version.py +74 -0
- {Open_AutoTools-0.0.3rc2.dist-info → Open_AutoTools-0.0.3rc4.dist-info}/LICENSE +0 -0
- {Open_AutoTools-0.0.3rc2.dist-info → Open_AutoTools-0.0.3rc4.dist-info}/WHEEL +0 -0
- {Open_AutoTools-0.0.3rc2.dist-info → Open_AutoTools-0.0.3rc4.dist-info}/entry_points.txt +0 -0
- {Open_AutoTools-0.0.3rc2.dist-info → Open_AutoTools-0.0.3rc4.dist-info}/top_level.txt +0 -0
autotools/autodownload/core.py
CHANGED
|
@@ -7,6 +7,8 @@ import yt_dlp
|
|
|
7
7
|
import platform
|
|
8
8
|
import subprocess
|
|
9
9
|
import json
|
|
10
|
+
from rich.progress import Progress
|
|
11
|
+
from ..utils.loading import LoadingAnimation
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
# FUNCTION TO GET DEFAULT DOWNLOAD DIRECTORY
|
|
@@ -44,17 +46,23 @@ def open_download_folder(path):
|
|
|
44
46
|
|
|
45
47
|
# FUNCTION TO VALIDATE YOUTUBE URL FORMAT
|
|
46
48
|
def validate_youtube_url(url):
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
58
66
|
|
|
59
67
|
|
|
60
68
|
# FUNCTION TO DOWNLOAD FILES WITH REQUESTS, INCLUDING ERROR HANDLING AND PROGRESS BAR
|
|
@@ -85,40 +93,50 @@ def download_file(url):
|
|
|
85
93
|
# FUNCTION TO GET CONSENT FILE PATH
|
|
86
94
|
def get_consent_file_path():
|
|
87
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
|
|
88
97
|
return Path.home() / '.autotools' / 'consent.json'
|
|
89
98
|
|
|
90
99
|
# FUNCTION TO LOAD CONSENT STATUS
|
|
91
100
|
def load_consent_status():
|
|
92
101
|
"""LOAD SAVED CONSENT STATUS"""
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
with open(consent_file) as f:
|
|
99
|
-
return json.load(f).get('youtube_consent', False)
|
|
100
|
-
except:
|
|
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():
|
|
101
107
|
return False
|
|
102
|
-
|
|
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
|
|
103
116
|
|
|
104
117
|
# FUNCTION TO SAVE CONSENT STATUS
|
|
105
118
|
def save_consent_status(status):
|
|
106
119
|
"""SAVE CONSENT STATUS"""
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
113
131
|
|
|
114
132
|
# FUNCTION TO GET USER CONSENT WITH INTERACTIVE PROMPT
|
|
115
133
|
def get_user_consent():
|
|
116
134
|
"""GET USER CONSENT WITH INTERACTIVE PROMPT"""
|
|
117
135
|
print("\n⚠️ Important Notice:")
|
|
118
136
|
print("This tool will:")
|
|
119
|
-
print("1.
|
|
120
|
-
print("2.
|
|
121
|
-
print("3.
|
|
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")
|
|
122
140
|
|
|
123
141
|
# GET USER CONSENT WITH INTERACTIVE PROMPT
|
|
124
142
|
while True:
|
|
@@ -132,40 +150,163 @@ def get_user_consent():
|
|
|
132
150
|
print("Please answer 'yes' or 'no'")
|
|
133
151
|
|
|
134
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
|
+
|
|
135
176
|
# FUNCTION TO DOWNLOAD YOUTUBE VIDEOS WITH YT-DLP AND SPECIFIED FORMAT AND QUALITY
|
|
136
177
|
def download_youtube_video(url, format='mp4', quality='best'):
|
|
137
178
|
"""DOWNLOAD VIDEO WITH CONSENT CHECK"""
|
|
138
|
-
#
|
|
139
|
-
if not
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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...")
|
|
143
258
|
|
|
144
259
|
print(f"\n🎥 Downloading video from: {url}")
|
|
145
|
-
|
|
260
|
+
if format == 'mp3':
|
|
261
|
+
print(f"📋 Format: {format}\n")
|
|
262
|
+
else:
|
|
263
|
+
print(f"📋 Format: {format}, Quality: {height}p\n")
|
|
146
264
|
|
|
265
|
+
# YT-DLP PERMISSION OPTIONS FOR DOWNLOADING YOUTUBE VIDEOS
|
|
147
266
|
ydl_opts = {
|
|
148
|
-
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' if format == 'mp4' else 'bestaudio
|
|
149
|
-
'
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
+
},
|
|
153
284
|
'http_headers': {
|
|
154
|
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
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',
|
|
155
286
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
156
287
|
'Accept-Language': 'en-us,en;q=0.5',
|
|
157
288
|
'Sec-Fetch-Mode': 'navigate'
|
|
158
289
|
},
|
|
159
|
-
'
|
|
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
|
|
160
294
|
}
|
|
161
295
|
|
|
162
296
|
try:
|
|
297
|
+
# THEN DOWNLOAD
|
|
163
298
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
164
299
|
ydl.download([url])
|
|
165
300
|
print("\n✅ Download completed successfully!")
|
|
166
301
|
return True
|
|
167
302
|
except Exception as e:
|
|
168
|
-
|
|
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}")
|
|
169
310
|
return False
|
|
170
311
|
|
|
171
312
|
|
|
@@ -187,23 +328,22 @@ pbar = None # GLOBAL VARIABLE TO STORE PROGRESS BAR
|
|
|
187
328
|
|
|
188
329
|
|
|
189
330
|
# FUNCTION TO UPDATE PROGRESS BAR
|
|
190
|
-
def
|
|
331
|
+
def update_progress(d):
|
|
191
332
|
global pbar
|
|
192
|
-
|
|
193
333
|
if d['status'] == 'downloading':
|
|
194
334
|
total = d.get('total_bytes', 0)
|
|
195
335
|
downloaded = d.get('downloaded_bytes', 0)
|
|
196
336
|
|
|
197
337
|
if pbar is None:
|
|
198
|
-
pbar = tqdm(total=total, unit='B', unit_scale=True, desc="
|
|
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}]')
|
|
199
339
|
|
|
200
|
-
|
|
201
|
-
|
|
340
|
+
if total > 0:
|
|
341
|
+
pbar.n = downloaded
|
|
342
|
+
pbar.total = total
|
|
343
|
+
pbar.refresh()
|
|
202
344
|
|
|
203
345
|
elif d['status'] == 'finished' and pbar:
|
|
204
|
-
pbar.n = pbar.total
|
|
205
346
|
pbar.close()
|
|
206
|
-
print("Download completed")
|
|
207
347
|
pbar = None
|
|
208
348
|
|
|
209
349
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from .core import run
|
|
3
|
+
from ..utils.loading import LoadingAnimation
|
|
4
|
+
from ..utils.updates import check_for_updates
|
|
5
|
+
|
|
6
|
+
@click.command()
|
|
7
|
+
@click.option('--test', '-t', is_flag=True, help='Run connectivity tests')
|
|
8
|
+
@click.option('--speed', '-s', is_flag=True, help='Run internet speed test')
|
|
9
|
+
@click.option('--monitor', '-m', is_flag=True, help='Monitor network traffic')
|
|
10
|
+
@click.option('--interval', '-i', default=1, help='Monitoring interval in seconds')
|
|
11
|
+
@click.option('--ports', '-p', is_flag=True, help='Check common ports status')
|
|
12
|
+
@click.option('--dns', '-d', is_flag=True, help='Show DNS servers')
|
|
13
|
+
@click.option('--location', '-l', is_flag=True, help='Show IP location info')
|
|
14
|
+
@click.option('--no-ip', '-n', is_flag=True, help='Hide IP addresses')
|
|
15
|
+
def autoip(test, speed, monitor, interval, ports, dns, location, no_ip):
|
|
16
|
+
"""Display network information and diagnostics.
|
|
17
|
+
|
|
18
|
+
Shows local and public IP addresses, runs network diagnostics,
|
|
19
|
+
performs speed tests, monitors traffic with custom intervals,
|
|
20
|
+
checks ports, displays DNS information and provides geolocation data."""
|
|
21
|
+
with LoadingAnimation():
|
|
22
|
+
output = run(test=test, speed=speed, monitor=monitor, interval=interval,
|
|
23
|
+
ports=ports, dns=dns, location=location, no_ip=no_ip)
|
|
24
|
+
click.echo(output)
|
|
25
|
+
|
|
26
|
+
# UPDATE CHECK AT THE END
|
|
27
|
+
update_msg = check_for_updates()
|
|
28
|
+
if update_msg:
|
|
29
|
+
click.echo(update_msg)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from .core import autolower_transform
|
|
3
|
+
from ..utils.loading import LoadingAnimation
|
|
4
|
+
from ..utils.updates import check_for_updates
|
|
5
|
+
|
|
6
|
+
@click.command()
|
|
7
|
+
@click.argument('text', nargs=-1)
|
|
8
|
+
def autolower(text):
|
|
9
|
+
"""Convert text to lowercase."""
|
|
10
|
+
with LoadingAnimation():
|
|
11
|
+
result = autolower_transform(" ".join(text))
|
|
12
|
+
click.echo(result)
|
|
13
|
+
|
|
14
|
+
# UPDATE CHECK AT THE END
|
|
15
|
+
update_msg = check_for_updates()
|
|
16
|
+
if update_msg:
|
|
17
|
+
click.echo(update_msg)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import base64
|
|
3
|
+
from .core import generate_password, generate_encryption_key, analyze_password_strength
|
|
4
|
+
from ..utils.loading import LoadingAnimation
|
|
5
|
+
from ..utils.updates import check_for_updates
|
|
6
|
+
|
|
7
|
+
@click.command()
|
|
8
|
+
@click.option('--length', '-l', default=12, help='Password length (default: 12)')
|
|
9
|
+
@click.option('--no-uppercase', '-u', is_flag=True, help='Exclude uppercase letters')
|
|
10
|
+
@click.option('--no-numbers', '-n', is_flag=True, help='Exclude numbers')
|
|
11
|
+
@click.option('--no-special', '-s', is_flag=True, help='Exclude special characters')
|
|
12
|
+
@click.option('--min-special', '-m', default=1, help='Minimum number of special characters')
|
|
13
|
+
@click.option('--min-numbers', '-d', default=1, help='Minimum number of numbers')
|
|
14
|
+
@click.option('--analyze', '-a', is_flag=True, help='Analyze password strength')
|
|
15
|
+
@click.option('--gen-key', '-g', is_flag=True, help='Generate encryption key')
|
|
16
|
+
@click.option('--password-key', '-p', help='Generate key from password')
|
|
17
|
+
def autopassword(length, no_uppercase, no_numbers, no_special,
|
|
18
|
+
min_special, min_numbers, analyze, gen_key, password_key):
|
|
19
|
+
"""Generate secure passwords and encryption keys."""
|
|
20
|
+
|
|
21
|
+
def show_analysis(text, prefix=""):
|
|
22
|
+
"""Helper function to show password/key analysis"""
|
|
23
|
+
if analyze:
|
|
24
|
+
with LoadingAnimation():
|
|
25
|
+
analysis = analyze_password_strength(text)
|
|
26
|
+
click.echo(f"\n{prefix}Strength Analysis:")
|
|
27
|
+
click.echo(f"Strength: {analysis['strength']}")
|
|
28
|
+
click.echo(f"Score: {analysis['score']}/5")
|
|
29
|
+
if analysis['suggestions']:
|
|
30
|
+
click.echo("\nSuggestions for improvement:")
|
|
31
|
+
for suggestion in analysis['suggestions']:
|
|
32
|
+
click.echo(f"- {suggestion}")
|
|
33
|
+
|
|
34
|
+
# GENERATE KEY
|
|
35
|
+
if gen_key:
|
|
36
|
+
with LoadingAnimation():
|
|
37
|
+
key = generate_encryption_key()
|
|
38
|
+
key_str = key.decode()
|
|
39
|
+
click.echo(f"Encryption Key: {key_str}")
|
|
40
|
+
if analyze:
|
|
41
|
+
show_analysis(key_str, "Key ")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# GENERATE KEY FROM PASSWORD
|
|
45
|
+
if password_key:
|
|
46
|
+
with LoadingAnimation():
|
|
47
|
+
key, salt = generate_encryption_key(password_key)
|
|
48
|
+
key_str = key.decode()
|
|
49
|
+
click.echo(f"Derived Key: {key_str}")
|
|
50
|
+
click.echo(f"Salt: {base64.b64encode(salt).decode()}")
|
|
51
|
+
if analyze:
|
|
52
|
+
click.echo("\nAnalyzing source password:")
|
|
53
|
+
show_analysis(password_key, "Password ")
|
|
54
|
+
click.echo("\nAnalyzing generated key:")
|
|
55
|
+
show_analysis(key_str, "Key ")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# GENERATE PASSWORD
|
|
59
|
+
with LoadingAnimation():
|
|
60
|
+
password = generate_password(
|
|
61
|
+
length=length,
|
|
62
|
+
use_uppercase=not no_uppercase,
|
|
63
|
+
use_numbers=not no_numbers,
|
|
64
|
+
use_special=not no_special,
|
|
65
|
+
min_special=min_special,
|
|
66
|
+
min_numbers=min_numbers,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# SHOW PASSWORD
|
|
70
|
+
click.echo(f"Generated Password: {password}")
|
|
71
|
+
show_analysis(password, "Password ")
|
|
72
|
+
|
|
73
|
+
# UPDATE CHECK AT THE END
|
|
74
|
+
update_msg = check_for_updates()
|
|
75
|
+
if update_msg:
|
|
76
|
+
click.echo(update_msg)
|
autotools/autopassword/core.py
CHANGED
|
@@ -21,6 +21,7 @@ def generate_password(length=12, use_uppercase=True, use_numbers=True, use_speci
|
|
|
21
21
|
|
|
22
22
|
# ENSURE MINIMUM REQUIREMENTS
|
|
23
23
|
password = []
|
|
24
|
+
password.append(secrets.choice(lowercase))
|
|
24
25
|
if use_uppercase:
|
|
25
26
|
password.append(secrets.choice(uppercase))
|
|
26
27
|
if use_numbers:
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import json as json_module
|
|
3
|
+
from .core import SpellChecker
|
|
4
|
+
from ..utils.loading import LoadingAnimation
|
|
5
|
+
from ..utils.updates import check_for_updates
|
|
6
|
+
|
|
7
|
+
@click.command()
|
|
8
|
+
@click.argument('texts', nargs=-1)
|
|
9
|
+
@click.option('--lang', '-l', default='auto', help='Language code (auto for detection)')
|
|
10
|
+
@click.option('--fix', '-f', is_flag=True, help='Auto-fix text and copy to clipboard')
|
|
11
|
+
@click.option('--copy', '-c', is_flag=True, help='Copy result to clipboard')
|
|
12
|
+
@click.option('--list-languages', is_flag=True, help='List supported languages')
|
|
13
|
+
@click.option('--json', '-j', is_flag=True, help='Output results as JSON')
|
|
14
|
+
@click.option('--ignore', '-i', multiple=True,
|
|
15
|
+
type=click.Choice(['spelling', 'grammar', 'style', 'punctuation']),
|
|
16
|
+
help='Error types to ignore')
|
|
17
|
+
@click.option('--interactive', '-n', is_flag=True,
|
|
18
|
+
help='Interactive mode - confirm each correction')
|
|
19
|
+
@click.option('--output', '-o', type=click.Path(),
|
|
20
|
+
help='Save corrections to file')
|
|
21
|
+
def autospell(texts: tuple, lang: str, fix: bool, copy: bool, list_languages: bool,
|
|
22
|
+
json: bool, ignore: tuple, interactive: bool, output: str):
|
|
23
|
+
"""Check and fix text for spelling, grammar, style, and punctuation errors.
|
|
24
|
+
|
|
25
|
+
Provides comprehensive text analysis with support for multiple languages,
|
|
26
|
+
interactive corrections, and various output formats (text/JSON).
|
|
27
|
+
Can ignore specific error types: spelling, grammar, style, or punctuation."""
|
|
28
|
+
checker = SpellChecker()
|
|
29
|
+
|
|
30
|
+
# LIST ALL SUPPORTED LANGUAGES
|
|
31
|
+
if list_languages:
|
|
32
|
+
with LoadingAnimation():
|
|
33
|
+
languages = checker.get_supported_languages()
|
|
34
|
+
if json:
|
|
35
|
+
result = {'languages': languages}
|
|
36
|
+
click.echo(json_module.dumps(result, indent=2))
|
|
37
|
+
else:
|
|
38
|
+
click.echo("\nSupported Languages:")
|
|
39
|
+
for lang in languages:
|
|
40
|
+
click.echo(f"{lang['code']:<8} {lang['name']}")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# --CHECK AND FIX SPELLING/GRAMMAR IN TEXT
|
|
44
|
+
for text in texts:
|
|
45
|
+
if not text:
|
|
46
|
+
click.echo("Error: Please provide text to check")
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
# --FIX OPTION: SPELLING/GRAMMAR IN TEXT
|
|
50
|
+
if fix:
|
|
51
|
+
# CORRECT TEXT WITH SPELL CHECKER
|
|
52
|
+
with LoadingAnimation():
|
|
53
|
+
corrected = checker.fix_text(text, lang, copy_to_clipboard=True,
|
|
54
|
+
ignore=ignore, interactive=interactive)
|
|
55
|
+
result = {'corrected_text': corrected}
|
|
56
|
+
|
|
57
|
+
# OUTPUT RESULTS AS JSON
|
|
58
|
+
if json:
|
|
59
|
+
click.echo(json_module.dumps(result, indent=2))
|
|
60
|
+
else:
|
|
61
|
+
# LANGUAGE INFORMATION
|
|
62
|
+
with LoadingAnimation():
|
|
63
|
+
check_result = checker.check_text(text, lang)
|
|
64
|
+
lang_info = check_result['language']
|
|
65
|
+
click.echo(f"\nLanguage detected: {lang_info['name']} ({lang_info['code']})")
|
|
66
|
+
click.echo(f"Confidence: {lang_info['confidence']:.2%}")
|
|
67
|
+
click.echo("\nCorrected text (copied to clipboard):")
|
|
68
|
+
click.echo(corrected)
|
|
69
|
+
|
|
70
|
+
# SAVE CORRECTIONS TO FILE
|
|
71
|
+
if output:
|
|
72
|
+
with open(output, 'w', encoding='utf-8') as f:
|
|
73
|
+
if json:
|
|
74
|
+
json_module.dump(result, f, indent=2)
|
|
75
|
+
else:
|
|
76
|
+
f.write(corrected)
|
|
77
|
+
else:
|
|
78
|
+
# CHECK SPELLING/GRAMMAR IN TEXT
|
|
79
|
+
with LoadingAnimation():
|
|
80
|
+
check_result = checker.check_text(text, lang)
|
|
81
|
+
|
|
82
|
+
# OUTPUT RESULTS AS JSON
|
|
83
|
+
if json:
|
|
84
|
+
click.echo(json_module.dumps(check_result, indent=2))
|
|
85
|
+
else:
|
|
86
|
+
lang_info = check_result['language']
|
|
87
|
+
click.echo(f"\nLanguage detected: {lang_info['name']} ({lang_info['code']})")
|
|
88
|
+
click.echo(f"Confidence: {lang_info['confidence']:.2%}")
|
|
89
|
+
click.echo(f"Total errors found: {check_result['statistics']['total_errors']}")
|
|
90
|
+
|
|
91
|
+
# CORRECTIONS SUGGESTED
|
|
92
|
+
if check_result['corrections']:
|
|
93
|
+
click.echo("\nCorrections suggested:")
|
|
94
|
+
for i, corr in enumerate(check_result['corrections'], 1):
|
|
95
|
+
click.echo(f"\n{i}. [{corr['severity'].upper()}] {corr['message']}")
|
|
96
|
+
click.echo(f" Context: {corr['context']}")
|
|
97
|
+
if corr['replacements']:
|
|
98
|
+
click.echo(f" Suggestions: {', '.join(corr['replacements'][:3])}")
|
|
99
|
+
|
|
100
|
+
# SAVE CHECK RESULT TO FILE
|
|
101
|
+
if output:
|
|
102
|
+
with open(output, 'w', encoding='utf-8') as f:
|
|
103
|
+
if json:
|
|
104
|
+
json_module.dump(check_result, f, indent=2)
|
|
105
|
+
else:
|
|
106
|
+
# WRITE A HUMAN-READABLE REPORT
|
|
107
|
+
f.write(f"Language: {lang_info['name']} ({lang_info['code']})\n")
|
|
108
|
+
f.write(f"Confidence: {lang_info['confidence']:.2%}\n")
|
|
109
|
+
f.write(f"Total errors: {check_result['statistics']['total_errors']}\n\n")
|
|
110
|
+
|
|
111
|
+
# CORRECTIONS SUGGESTED
|
|
112
|
+
if check_result['corrections']:
|
|
113
|
+
f.write("Corrections suggested:\n")
|
|
114
|
+
for i, corr in enumerate(check_result['corrections'], 1):
|
|
115
|
+
f.write(f"\n{i}. [{corr['severity'].upper()}] {corr['message']}\n")
|
|
116
|
+
f.write(f" Context: {corr['context']}\n")
|
|
117
|
+
if corr['replacements']:
|
|
118
|
+
f.write(f" Suggestions: {', '.join(corr['replacements'][:3])}\n")
|
|
119
|
+
|
|
120
|
+
# UPDATE CHECK AT THE END
|
|
121
|
+
update_msg = check_for_updates()
|
|
122
|
+
if update_msg:
|
|
123
|
+
click.echo(update_msg)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from .core import translate_text, get_supported_languages
|
|
3
|
+
from ..utils.loading import LoadingAnimation
|
|
4
|
+
from ..utils.updates import check_for_updates
|
|
5
|
+
|
|
6
|
+
@click.command()
|
|
7
|
+
@click.argument('text', required=False)
|
|
8
|
+
@click.option('--to', default='en', help='Target language (default: en)')
|
|
9
|
+
@click.option('--from', 'from_lang', help='Source language (default: auto-detect)')
|
|
10
|
+
@click.option('--list-languages', is_flag=True, help='List all supported languages')
|
|
11
|
+
@click.option('--copy', is_flag=True, help='Copy translation to clipboard')
|
|
12
|
+
@click.option('--detect', is_flag=True, help='Show detected source language')
|
|
13
|
+
@click.option('--output', '-o', type=click.Path(), help='Save translation to file')
|
|
14
|
+
def autotranslate(text: str, to: str, from_lang: str, list_languages: bool,
|
|
15
|
+
copy: bool, detect: bool, output: str):
|
|
16
|
+
"""Translate text to specified language.
|
|
17
|
+
|
|
18
|
+
Supports automatic language detection, multiple target languages,
|
|
19
|
+
clipboard operations and file output. Use --list-languages to see
|
|
20
|
+
all supported language codes."""
|
|
21
|
+
# LIST ALL SUPPORTED LANGUAGES
|
|
22
|
+
if list_languages:
|
|
23
|
+
with LoadingAnimation():
|
|
24
|
+
click.echo("\nSupported Languages:")
|
|
25
|
+
for code, name in get_supported_languages().items():
|
|
26
|
+
click.echo(f"{code:<8} {name}")
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
# CHECK IF TEXT IS PROVIDED
|
|
30
|
+
if not text:
|
|
31
|
+
click.echo("Error: Please provide text to translate")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
with LoadingAnimation():
|
|
35
|
+
result = translate_text(text, to_lang=to, from_lang=from_lang,
|
|
36
|
+
copy=copy, detect_lang=detect, output=output)
|
|
37
|
+
click.echo(result)
|
|
38
|
+
|
|
39
|
+
# UPDATE CHECK AT THE END
|
|
40
|
+
update_msg = check_for_updates()
|
|
41
|
+
if update_msg:
|
|
42
|
+
click.echo(update_msg)
|