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.
@@ -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
- try:
48
- # USE YT-DLP TO CHECK IF THE URL IS VALID
49
- with yt_dlp.YoutubeDL({'quiet': True, 'no_warnings': True}) as ydl:
50
- ydl.extract_info(url, download=False)
51
- return True
52
- except yt_dlp.utils.DownloadError as e:
53
- print(f"Invalid YouTube URL: {e}")
54
- return False
55
- except Exception as e:
56
- print(f"Unexpected error during URL validation: {e}")
57
- return False
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
- consent_file = get_consent_file_path()
94
-
95
- # CHECK IF CONSENT FILE EXISTS
96
- if consent_file.exists():
97
- try:
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
- 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
103
116
 
104
117
  # FUNCTION TO SAVE CONSENT STATUS
105
118
  def save_consent_status(status):
106
119
  """SAVE CONSENT STATUS"""
107
- consent_file = get_consent_file_path()
108
- consent_file.parent.mkdir(exist_ok=True)
109
-
110
- # SAVE CONSENT STATUS TO FILE
111
- with open(consent_file, 'w') as f:
112
- json.dump({'youtube_consent': status}, f)
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. Access your Chrome browser cookies")
120
- print("2. Use them to authenticate with YouTube")
121
- print("3. Download video content to your local machine")
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
- # CHECK IF CONSENT IS REQUIRED
139
- if not load_consent_status():
140
- if not get_user_consent():
141
- print("\n❌ Download cancelled by user")
142
- return False
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
- print(f"📋 Format: {format}, Quality: {quality}\n")
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[ext=mp3]/best',
149
- 'quiet': False,
150
- 'no_warnings': False,
151
- 'cookiesfrombrowser': ('chrome',),
152
- 'extractor_args': {'youtube': {'player_client': ['android']}},
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
- 'progress_hooks': [lambda d: print(f"⏳ {d['_percent_str']} of {d.get('_total_bytes_str', 'Unknown size')}") if d['status'] == 'downloading' else None]
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
- print(f"\n❌ ERROR: {str(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}")
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 tqdm_progress_hook(d):
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="YouTube Download", leave=True)
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
- pbar.n = downloaded
201
- pbar.refresh()
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)
@@ -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)