diff --git a/modules/launcher/plugins/otp.py b/modules/launcher/plugins/otp.py index 59476ac..ddf271d 100644 --- a/modules/launcher/plugins/otp.py +++ b/modules/launcher/plugins/otp.py @@ -18,6 +18,7 @@ import utils.icons as icons try: import pyotp + PYOTP_AVAILABLE = True except ImportError: PYOTP_AVAILABLE = False @@ -42,9 +43,11 @@ class OTPPlugin(PluginBase): def initialize(self): """Initialize the OTP plugin.""" if not PYOTP_AVAILABLE: - raise RuntimeError("pyotp is required for OTP functionality. Install with: pip install pyotp") + raise RuntimeError( + "pyotp is required for OTP functionality. Install with: pip install pyotp" + ) - self.set_triggers(["otp", "2fa", "totp", "auth"]) + self.set_triggers(["otp", "otp "]) self._load_secrets() self._ensure_config_file() self._start_refresh_thread() @@ -59,7 +62,7 @@ class OTPPlugin(PluginBase): """Load secrets from JSON file.""" try: if self.secrets_file.exists(): - with open(self.secrets_file, 'r', encoding='utf-8') as f: + with open(self.secrets_file, "r", encoding="utf-8") as f: self.secrets = json.load(f) else: self.secrets = {} @@ -71,25 +74,21 @@ class OTPPlugin(PluginBase): """Save secrets to JSON file.""" try: self.secrets_file.parent.mkdir(parents=True, exist_ok=True) - with open(self.secrets_file, 'w', encoding='utf-8') as f: + with open(self.secrets_file, "w", encoding="utf-8") as f: json.dump(self.secrets, f, indent=2) except Exception as e: print(f"Error saving OTP secrets: {e}") - subprocess.run([ - "notify-send", "OTP Manager Error", - f"Failed to save config: {str(e)}", - "-i", "dialog-error" - ], check=False) def _ensure_config_file(self): """Ensure the config file exists.""" if not self.secrets_file.exists(): self.secrets_file.parent.mkdir(parents=True, exist_ok=True) - with open(self.secrets_file, 'w', encoding='utf-8') as f: + with open(self.secrets_file, "w", encoding="utf-8") as f: json.dump({}, f, indent=2) def _start_refresh_thread(self): """Start background thread for auto-refreshing tokens.""" + def refresh_loop(): while not self.stop_refresh.wait(1): current_time = time.time() @@ -113,16 +112,19 @@ class OTPPlugin(PluginBase): def do_update(): try: for obj in gc.get_objects(): - if (hasattr(obj, '__class__') and - obj.__class__.__name__ == 'Launcher' and - hasattr(obj, 'results_box') and - hasattr(obj, 'visible') and obj.visible and - hasattr(obj, 'results') and obj.results): - + if ( + hasattr(obj, "__class__") + and obj.__class__.__name__ == "Launcher" + and hasattr(obj, "results_box") + and hasattr(obj, "visible") + and obj.visible + and hasattr(obj, "results") + and obj.results + ): has_otp_results = any( result.data and result.data.get("type") == "totp" for result in obj.results - if hasattr(result, 'data') and result.data + if hasattr(result, "data") and result.data ) if has_otp_results: @@ -141,9 +143,14 @@ class OTPPlugin(PluginBase): try: time_display = self._get_time_remaining_with_blink() for child in results_box.get_children(): - if (hasattr(child, '__class__') and child.__class__.__name__ == 'ResultItem' and - hasattr(child, 'result') and hasattr(child.result, 'data') and - child.result.data and child.result.data.get("type") == "totp"): + if ( + hasattr(child, "__class__") + and child.__class__.__name__ == "ResultItem" + and hasattr(child, "result") + and hasattr(child.result, "data") + and child.result.data + and child.result.data.get("type") == "totp" + ): self._update_result_item_content(child, time_display) except Exception as e: print(f"Error updating result labels: {e}") @@ -168,7 +175,9 @@ class OTPPlugin(PluginBase): if current_totp_code != old_code: result_item.result.data["code"] = current_totp_code self._find_and_update_title_label(result_item, current_totp_code) - result_item.result.action = lambda code=current_totp_code: self._copy_to_clipboard(code) + result_item.result.action = ( + lambda code=current_totp_code: self._copy_to_clipboard(code) + ) new_subtitle_markup = f"{display_name} • {time_display} remaining" self._find_and_update_subtitle_label(result_item, new_subtitle_markup) @@ -177,10 +186,11 @@ class OTPPlugin(PluginBase): def _find_and_update_title_label(self, result_item, new_title): """Find the title label widget and update its text.""" + def find_title_label(widget): - if hasattr(widget, 'get_name') and widget.get_name() == "result-item-title": + if hasattr(widget, "get_name") and widget.get_name() == "result-item-title": return widget - if hasattr(widget, 'get_children'): + if hasattr(widget, "get_children"): for child in widget.get_children(): found = find_title_label(child) if found: @@ -188,15 +198,19 @@ class OTPPlugin(PluginBase): return None title_label = find_title_label(result_item) - if title_label and hasattr(title_label, 'set_label'): + if title_label and hasattr(title_label, "set_label"): title_label.set_label(new_title) def _find_and_update_subtitle_label(self, result_item, new_markup): """Find the subtitle label widget and update its markup.""" + def find_subtitle_label(widget): - if hasattr(widget, 'get_name') and widget.get_name() == "result-item-subtitle": + if ( + hasattr(widget, "get_name") + and widget.get_name() == "result-item-subtitle" + ): return widget - if hasattr(widget, 'get_children'): + if hasattr(widget, "get_children"): for child in widget.get_children(): found = find_subtitle_label(child) if found: @@ -204,7 +218,7 @@ class OTPPlugin(PluginBase): return None subtitle_label = find_subtitle_label(result_item) - if subtitle_label and hasattr(subtitle_label, 'set_markup'): + if subtitle_label and hasattr(subtitle_label, "set_markup"): subtitle_label.set_markup(new_markup) def _copy_to_clipboard(self, text: str): @@ -213,7 +227,11 @@ class OTPPlugin(PluginBase): try: subprocess.run(["wl-copy"], input=text.encode(), check=True) except subprocess.SubprocessError: - subprocess.run(["xclip", "-selection", "clipboard"], input=text.encode(), check=True) + subprocess.run( + ["xclip", "-selection", "clipboard"], + input=text.encode(), + check=True, + ) except Exception as e: print(f"Failed to copy to clipboard: {e}") @@ -227,25 +245,32 @@ class OTPPlugin(PluginBase): try: # Try to access the launcher through the fabric Application from fabric import Application + app = Application.get_default() - if app and hasattr(app, 'launcher'): + if app and hasattr(app, "launcher"): launcher = app.launcher - if launcher and hasattr(launcher, 'search_entry'): + if launcher and hasattr(launcher, "search_entry"): # Clear the search entry and set it to just "otp " launcher.search_entry.set_text("otp ") # Position cursor at the end launcher.search_entry.set_position(-1) # Trigger the search to show default OTP view - if hasattr(launcher, '_perform_search'): + if hasattr(launcher, "_perform_search"): launcher._perform_search("otp ") return False # Fallback: try to find launcher instance through other means import gc + for obj in gc.get_objects(): - if hasattr(obj, '__class__') and obj.__class__.__name__ == 'Launcher': - if hasattr(obj, 'search_entry') and hasattr(obj, '_perform_search'): + if ( + hasattr(obj, "__class__") + and obj.__class__.__name__ == "Launcher" + ): + if hasattr(obj, "search_entry") and hasattr( + obj, "_perform_search" + ): obj.search_entry.set_text("otp ") obj.search_entry.set_position(-1) obj._perform_search("otp ") @@ -266,38 +291,14 @@ class OTPPlugin(PluginBase): """Remove an account and trigger refresh to return to default OTP view.""" try: if account_name in self.secrets: - # Get account info for notification - account_data = self.secrets[account_name] - issuer = account_data.get("issuer", "") - display_name = f"{issuer} - {account_name}" if issuer else account_name - # Remove the account del self.secrets[account_name] self._save_secrets() - # Show notification - subprocess.run([ - "notify-send", "OTP Manager", - f"Removed '{display_name}' successfully", - "-i", "dialog-information" - ], check=False) - # Trigger refresh to return to default OTP view self._trigger_refresh() - else: - # Account not found - subprocess.run([ - "notify-send", "OTP Manager Error", - f"Account '{account_name}' not found", - "-i", "dialog-error" - ], check=False) except Exception as e: print(f"Error removing account {account_name}: {e}") - subprocess.run([ - "notify-send", "OTP Manager Error", - f"Failed to remove account: {str(e)}", - "-i", "dialog-error" - ], check=False) def _generate_totp(self, secret: str) -> Optional[str]: """Generate TOTP code from secret.""" @@ -315,7 +316,7 @@ class OTPPlugin(PluginBase): """Get time remaining with blinking effect.""" time_remaining = self._get_time_remaining() current_second = int(time.time()) - should_blink = (current_second % 2 == 0) + should_blink = current_second % 2 == 0 if should_blink: return f"{time_remaining}s" @@ -363,61 +364,64 @@ class OTPPlugin(PluginBase): def _handle_direct_add(self, account_name: str, secret_or_uri: str) -> List[Result]: """Handle direct addition of OTP account.""" if not account_name or not secret_or_uri: - return [Result( - title="Invalid format", - subtitle="Usage: add or add ``````", - icon_markup=icons.question, - action=lambda: None, - relevance=1.0, - plugin_name=self.display_name, - data={"type": "help", "keep_launcher_open": True} - )] - - # Debug info - print(f"OTP Debug: Adding account '{account_name}' with secret/URI: '{secret_or_uri[:20]}...'") + return [ + Result( + title="Invalid format", + subtitle="Usage: add or add ``````", + icon_markup=icons.question, + action=lambda: None, + relevance=1.0, + plugin_name=self.display_name, + data={"type": "help", "keep_launcher_open": True}, + ) + ] try: if secret_or_uri.startswith("otpauth://"): - print("OTP Debug: Processing as otpauth URI") return self._handle_otpauth_uri(account_name, secret_or_uri) else: - print("OTP Debug: Processing as Base32 secret") return self._handle_base32_secret(account_name, secret_or_uri) except Exception as e: print(f"OTP Debug: Error in _handle_direct_add: {e}") - return [Result( - title="Error adding account", - subtitle=f"Debug: {str(e)}", - icon_markup=icons.cancel, - action=lambda: None, - relevance=0.5, - plugin_name=self.display_name, - data={"type": "error", "keep_launcher_open": True} - )] + return [ + Result( + title="Error adding account", + subtitle=f"Debug: {str(e)}", + icon_markup=icons.cancel, + action=lambda: None, + relevance=0.5, + plugin_name=self.display_name, + data={"type": "error", "keep_launcher_open": True}, + ) + ] def _list_otp_codes(self) -> List[Result]: """List all OTP codes with current tokens.""" results = [] if not self.secrets: - results.append(Result( - title="No OTP accounts configured", - subtitle="Use 'add ' to add your first account", - icon_markup=icons.key, - action=lambda: None, - relevance=1.0, - plugin_name=self.display_name, - data={"type": "empty", "keep_launcher_open": True} - )) - results.append(Result( - title="Available commands:", - subtitle="add | remove ", - icon_markup=icons.info, - action=lambda: None, - relevance=0.9, - plugin_name=self.display_name, - data={"type": "help", "keep_launcher_open": True} - )) + results.append( + Result( + title="No OTP accounts configured", + subtitle="Use 'add ' to add your first account", + icon_markup=icons.key, + action=lambda: None, + relevance=1.0, + plugin_name=self.display_name, + data={"type": "empty", "keep_launcher_open": True}, + ) + ) + results.append( + Result( + title="Available commands:", + subtitle="add | remove ", + icon_markup=icons.info, + action=lambda: None, + relevance=0.9, + plugin_name=self.display_name, + data={"type": "help", "keep_launcher_open": True}, + ) + ) return results time_display = self._get_time_remaining_with_blink() @@ -429,26 +433,37 @@ class OTPPlugin(PluginBase): if totp_code: display_name = f"{issuer} - {account_name}" if issuer else account_name - results.append(Result( - title=f"{totp_code}", - subtitle_markup=f"{display_name} • {time_display} remaining", - icon_markup=icons.key, - action=lambda code=totp_code: self._copy_to_clipboard(code), - relevance=1.0, - plugin_name=self.display_name, - data={"type": "totp", "account": account_name, "code": totp_code} - # Note: No keep_launcher_open flag - launcher will close after copying - )) + results.append( + Result( + title=f"{totp_code}", + subtitle_markup=f"{display_name} • {time_display} remaining", + icon_markup=icons.key, + action=lambda code=totp_code: self._copy_to_clipboard(code), + relevance=1.0, + plugin_name=self.display_name, + data={ + "type": "totp", + "account": account_name, + "code": totp_code, + }, + ) + ) else: - results.append(Result( - title=f"Error: {account_name}", - subtitle="Invalid secret or configuration", - icon_markup=icons.cancel, - action=lambda: None, - relevance=0.5, - plugin_name=self.display_name, - data={"type": "error", "account": account_name, "keep_launcher_open": True} - )) + results.append( + Result( + title=f"Error: {account_name}", + subtitle="Invalid secret or configuration", + icon_markup=icons.cancel, + action=lambda: None, + relevance=0.5, + plugin_name=self.display_name, + data={ + "type": "error", + "account": account_name, + "keep_launcher_open": True, + }, + ) + ) return results @@ -466,54 +481,71 @@ class OTPPlugin(PluginBase): totp_code = self._generate_totp(secret) if totp_code: - display_name = f"{account_data.get('issuer', '')} - {account_name}" if account_data.get('issuer') else account_name + display_name = ( + f"{account_data.get('issuer', '')} - {account_name}" + if account_data.get("issuer") + else account_name + ) time_display = self._get_time_remaining_with_blink() - results.append(Result( - title=f"{totp_code}", - subtitle_markup=f"{display_name} • {time_display} remaining", - icon_markup=icons.key, - action=lambda code=totp_code: self._copy_to_clipboard(code), - relevance=1.0, - plugin_name=self.display_name, - data={"type": "totp", "account": account_name, "code": totp_code} - # Note: No keep_launcher_open flag - launcher will close after copying - )) + results.append( + Result( + title=f"{totp_code}", + subtitle_markup=f"{display_name} • { + time_display + } remaining", + icon_markup=icons.key, + action=lambda code=totp_code: self._copy_to_clipboard(code), + relevance=1.0, + plugin_name=self.display_name, + data={ + "type": "totp", + "account": account_name, + "code": totp_code, + }, + ) + ) if not results: - results.append(Result( - title=f"No accounts found for '{query}'", - subtitle="Use 'add ' to create new account", - icon_markup=icons.magnifier, - action=lambda: None, - relevance=0.5, - plugin_name=self.display_name, - data={"type": "no_results", "keep_launcher_open": True} - )) - results.append(Result( - title="Available commands:", - subtitle="add | remove ", - icon_markup=icons.info, - action=lambda: None, - relevance=0.4, - plugin_name=self.display_name, - data={"type": "help", "keep_launcher_open": True} - )) + results.append( + Result( + title=f"No accounts found for '{query}'", + subtitle="Use 'add ' to create new account", + icon_markup=icons.magnifier, + action=lambda: None, + relevance=0.5, + plugin_name=self.display_name, + data={"type": "no_results", "keep_launcher_open": True}, + ) + ) + results.append( + Result( + title="Available commands:", + subtitle="add | remove ", + icon_markup=icons.info, + action=lambda: None, + relevance=0.4, + plugin_name=self.display_name, + data={"type": "help", "keep_launcher_open": True}, + ) + ) return results def _handle_add_command(self, account_name: str) -> List[Result]: """Handle manual addition of OTP secret.""" if not account_name: - return [Result( - title="Enter account name", - subtitle="Usage: add ", - icon_markup=icons.question, - action=lambda: None, - relevance=1.0, - plugin_name=self.display_name, - data={"type": "help", "keep_launcher_open": True} - )] + return [ + Result( + title="Enter account name", + subtitle="Usage: add ", + icon_markup=icons.question, + action=lambda: None, + relevance=1.0, + plugin_name=self.display_name, + data={"type": "help", "keep_launcher_open": True}, + ) + ] return [ Result( @@ -523,7 +555,11 @@ class OTPPlugin(PluginBase): action=lambda: None, relevance=1.0, plugin_name=self.display_name, - data={"type": "instruction", "account": account_name, "keep_launcher_open": True} + data={ + "type": "instruction", + "account": account_name, + "keep_launcher_open": True, + }, ), Result( title="Base32 Secret Format:", @@ -532,7 +568,7 @@ class OTPPlugin(PluginBase): action=lambda: None, relevance=0.9, plugin_name=self.display_name, - data={"type": "help", "keep_launcher_open": True} + data={"type": "help", "keep_launcher_open": True}, ), Result( title="otpauth URI Format:", @@ -541,7 +577,7 @@ class OTPPlugin(PluginBase): action=lambda: None, relevance=0.9, plugin_name=self.display_name, - data={"type": "help", "keep_launcher_open": True} + data={"type": "help", "keep_launcher_open": True}, ), Result( title="Remove Account:", @@ -550,8 +586,8 @@ class OTPPlugin(PluginBase): action=lambda: None, relevance=0.8, plugin_name=self.display_name, - data={"type": "help", "keep_launcher_open": True} - ) + data={"type": "help", "keep_launcher_open": True}, + ), ] def _handle_remove_command(self, account_name: str) -> List[Result]: @@ -561,25 +597,29 @@ class OTPPlugin(PluginBase): results = [] if not self.secrets: - results.append(Result( - title="No OTP accounts to remove", - subtitle="Use 'add ' to add accounts first", - icon_markup=icons.info, - action=lambda: None, - relevance=1.0, - plugin_name=self.display_name, - data={"type": "empty", "keep_launcher_open": True} - )) + results.append( + Result( + title="No OTP accounts to remove", + subtitle="Use 'add ' to add accounts first", + icon_markup=icons.info, + action=lambda: None, + relevance=1.0, + plugin_name=self.display_name, + data={"type": "empty", "keep_launcher_open": True}, + ) + ) else: - results.append(Result( - title="Select account to remove:", - subtitle="Type: remove to remove an account", - icon_markup=icons.trash, - action=lambda: None, - relevance=1.0, - plugin_name=self.display_name, - data={"type": "help", "keep_launcher_open": True} - )) + results.append( + Result( + title="Select account to remove:", + subtitle="Type: remove to remove an account", + icon_markup=icons.trash, + action=lambda: None, + relevance=1.0, + plugin_name=self.display_name, + data={"type": "help", "keep_launcher_open": True}, + ) + ) # Get time display for consistency with main OTP view time_display = self._get_time_remaining_with_blink() @@ -594,39 +634,56 @@ class OTPPlugin(PluginBase): totp_code = self._generate_totp(secret) if totp_code: - results.append(Result( - title=f"{totp_code}", - subtitle_markup=f"Remove {display_name} • {time_display} remaining", - icon_markup=icons.trash, - action=lambda account=acc_name: self._remove_account_and_refresh(account), - relevance=0.9, - plugin_name=self.display_name, - data={"type": "remove_option", "account": acc_name, "code": totp_code, "keep_launcher_open": True} - )) + results.append( + Result( + title=f"{totp_code}", + subtitle_markup=f"Type: remove {acc_name} • { + time_display + } remaining", + icon_markup=icons.trash, + action=lambda: None, # No action - shows instruction only + relevance=0.9, + plugin_name=self.display_name, + data={ + "type": "remove_instruction", + "account": acc_name, + "code": totp_code, + "keep_launcher_open": True, + }, + ) + ) else: - results.append(Result( - title=f"Error: {acc_name}", - subtitle=f"Remove {display_name} (Invalid secret)", - icon_markup=icons.trash, - action=lambda account=acc_name: self._remove_account_and_refresh(account), - relevance=0.8, - plugin_name=self.display_name, - data={"type": "remove_option", "account": acc_name, "keep_launcher_open": True} - )) + results.append( + Result( + title=f"Error: {acc_name}", + subtitle=f"Type: remove {acc_name} (Invalid secret)", + icon_markup=icons.trash, + action=lambda: None, # No action - shows instruction only + relevance=0.8, + plugin_name=self.display_name, + data={ + "type": "remove_instruction", + "account": acc_name, + "keep_launcher_open": True, + }, + ) + ) return results # Check if account exists if account_name not in self.secrets: - return [Result( - title=f"Account '{account_name}' not found", - subtitle="Use 'remove' to see all available accounts", - icon_markup=icons.cancel, - action=lambda: None, - relevance=0.5, - plugin_name=self.display_name, - data={"type": "error", "keep_launcher_open": True} - )] + return [ + Result( + title=f"Account '{account_name}' not found", + subtitle="Use 'remove' to see all available accounts", + icon_markup=icons.cancel, + action=lambda: None, + relevance=0.5, + plugin_name=self.display_name, + data={"type": "error", "keep_launcher_open": True}, + ) + ] # Get account info for confirmation account_data = self.secrets[account_name] @@ -638,55 +695,62 @@ class OTPPlugin(PluginBase): del self.secrets[account_name] self._save_secrets() - return [Result( - title=f"✓ Removed '{display_name}'", - subtitle="OTP account removed successfully", - icon_markup=icons.check, - action=lambda: self._trigger_refresh(), - relevance=1.0, - plugin_name=self.display_name, - data={"type": "success", "keep_launcher_open": True} - )] + return [ + Result( + title=f"✓ Removed '{display_name}'", + subtitle="OTP account removed successfully", + icon_markup=icons.check, + action=lambda: self._trigger_refresh(), + relevance=1.0, + plugin_name=self.display_name, + data={"type": "success", "keep_launcher_open": True}, + ) + ] except Exception as e: - return [Result( - title="Error removing account", - subtitle=f"Failed to remove account: {str(e)}", - icon_markup=icons.cancel, - action=lambda: None, - relevance=0.5, - plugin_name=self.display_name, - data={"type": "error", "keep_launcher_open": True} - )] - - + return [ + Result( + title="Error removing account", + subtitle=f"Failed to remove account: {str(e)}", + icon_markup=icons.cancel, + action=lambda: None, + relevance=0.5, + plugin_name=self.display_name, + data={"type": "error", "keep_launcher_open": True}, + ) + ] def _handle_base32_secret(self, account_name: str, secret: str) -> List[Result]: """Handle raw Base32 secret.""" try: # Clean up the secret - remove spaces, dashes, and convert to uppercase - clean_secret = secret.replace(" ", "").replace("-", "").replace("_", "").upper() + clean_secret = ( + secret.replace(" ", "").replace("-", "").replace("_", "").upper() + ) # Remove any non-base32 characters import re - clean_secret = re.sub(r'[^A-Z2-7]', '', clean_secret) + + clean_secret = re.sub(r"[^A-Z2-7]", "", clean_secret) # Add padding if needed (Base32 requires padding to multiple of 8) while len(clean_secret) % 8 != 0: - clean_secret += '=' + clean_secret += "=" # Validate Base32 format try: base64.b32decode(clean_secret) except Exception as e: - return [Result( - title="Invalid Base32 secret", - subtitle=f"Error: {str(e)}. Please check the secret format", - icon_markup=icons.cancel, - action=lambda: None, - relevance=0.5, - plugin_name=self.display_name, - data={"type": "error", "keep_launcher_open": True} - )] + return [ + Result( + title="Invalid Base32 secret", + subtitle=f"Error: {str(e)}. Please check the secret format", + icon_markup=icons.cancel, + action=lambda: None, + relevance=0.5, + plugin_name=self.display_name, + data={"type": "error", "keep_launcher_open": True}, + ) + ] # Test if the secret can generate a valid TOTP try: @@ -695,44 +759,52 @@ class OTPPlugin(PluginBase): if not test_code or len(test_code) != 6: raise ValueError("Generated invalid TOTP code") except Exception as e: - return [Result( - title="Invalid secret for TOTP", - subtitle=f"Cannot generate TOTP: {str(e)}", - icon_markup=icons.cancel, - action=lambda: None, - relevance=0.5, - plugin_name=self.display_name, - data={"type": "error", "keep_launcher_open": True} - )] + return [ + Result( + title="Invalid secret for TOTP", + subtitle=f"Cannot generate TOTP: {str(e)}", + icon_markup=icons.cancel, + action=lambda: None, + relevance=0.5, + plugin_name=self.display_name, + data={"type": "error", "keep_launcher_open": True}, + ) + ] self.secrets[account_name] = { "secret": clean_secret, "issuer": "", "algorithm": "SHA1", "digits": 6, - "period": 30 + "period": 30, } self._save_secrets() - return [Result( - title=f"✓ Added '{account_name}'", - subtitle=f"OTP account added successfully (secret: {clean_secret[:4]}...)", - icon_markup=icons.check, - action=lambda: self._trigger_refresh(), - relevance=1.0, - plugin_name=self.display_name, - data={"type": "success", "keep_launcher_open": True} - )] + return [ + Result( + title=f"✓ Added '{account_name}'", + subtitle=f"OTP account added successfully (secret: { + clean_secret[:4] + }...)", + icon_markup=icons.check, + action=lambda: self._trigger_refresh(), + relevance=1.0, + plugin_name=self.display_name, + data={"type": "success", "keep_launcher_open": True}, + ) + ] except Exception as e: - return [Result( - title="Error adding account", - subtitle=f"Unexpected error: {str(e)}", - icon_markup=icons.cancel, - action=lambda: None, - relevance=0.5, - plugin_name=self.display_name, - data={"type": "error", "keep_launcher_open": True} - )] + return [ + Result( + title="Error adding account", + subtitle=f"Unexpected error: {str(e)}", + icon_markup=icons.cancel, + action=lambda: None, + relevance=0.5, + plugin_name=self.display_name, + data={"type": "error", "keep_launcher_open": True}, + ) + ] def _handle_otpauth_uri(self, account_name: str, uri: str) -> List[Result]: """Handle otpauth:// URI.""" @@ -741,15 +813,17 @@ class OTPPlugin(PluginBase): parsed = urlparse(uri) if parsed.scheme != "otpauth" or parsed.netloc != "totp": - return [Result( - title="Invalid otpauth URI", - subtitle="Only otpauth://totp/ URIs are supported", - icon_markup=icons.cancel, - action=lambda: None, - relevance=0.5, - plugin_name=self.display_name, - data={"type": "error", "keep_launcher_open": True} - )] + return [ + Result( + title="Invalid otpauth URI", + subtitle="Only otpauth://totp/ URIs are supported", + icon_markup=icons.cancel, + action=lambda: None, + relevance=0.5, + plugin_name=self.display_name, + data={"type": "error", "keep_launcher_open": True}, + ) + ] if not account_name: account_path = parsed.path.lstrip("/") @@ -767,42 +841,48 @@ class OTPPlugin(PluginBase): period = int(params.get("period", ["30"])[0]) if not secret: - return [Result( - title="No secret in URI", - subtitle="otpauth URI must contain a secret parameter", - icon_markup=icons.cancel, - action=lambda: None, - relevance=0.5, - plugin_name=self.display_name, - data={"type": "error", "keep_launcher_open": True} - )] + return [ + Result( + title="No secret in URI", + subtitle="otpauth URI must contain a secret parameter", + icon_markup=icons.cancel, + action=lambda: None, + relevance=0.5, + plugin_name=self.display_name, + data={"type": "error", "keep_launcher_open": True}, + ) + ] self.secrets[account_name] = { "secret": secret, "issuer": issuer, "algorithm": algorithm, "digits": digits, - "period": period + "period": period, } self._save_secrets() display_name = f"{issuer} - {account_name}" if issuer else account_name - return [Result( - title=f"✓ Added '{display_name}'", - subtitle="OTP account added from URI", - icon_markup=icons.check, - action=lambda: self._trigger_refresh(), - relevance=1.0, - plugin_name=self.display_name, - data={"type": "success", "keep_launcher_open": True} - )] + return [ + Result( + title=f"✓ Added '{display_name}'", + subtitle="OTP account added from URI", + icon_markup=icons.check, + action=lambda: self._trigger_refresh(), + relevance=1.0, + plugin_name=self.display_name, + data={"type": "success", "keep_launcher_open": True}, + ) + ] except Exception as e: - return [Result( - title="Error parsing otpauth URI", - subtitle=str(e), - icon_markup=icons.cancel, - action=lambda: None, - relevance=0.5, - plugin_name=self.display_name, - data={"type": "error", "keep_launcher_open": True} - )] \ No newline at end of file + return [ + Result( + title="Error parsing otpauth URI", + subtitle=str(e), + icon_markup=icons.cancel, + action=lambda: None, + relevance=0.5, + plugin_name=self.display_name, + data={"type": "error", "keep_launcher_open": True}, + ) + ]