-
Notifications
You must be signed in to change notification settings - Fork 32
feat: add capability to create new entries with alt+n uses clipboard for url that can be overridden #116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: add capability to create new entries with alt+n uses clipboard for url that can be overridden #116
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,4 @@ class Action(Enum): | |
PRINT = "print" | ||
SYNC = "sync" | ||
CANCEL = "cancel" | ||
ADD = "add" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,3 +90,35 @@ def __load_from_rbw(self, name: str, username: str, folder: Optional[str]) -> st | |
|
||
def sync(self): | ||
run(["rbw", "sync"]) | ||
|
||
def generate_password(self, length: int = 16) -> str: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The generate_password method is defined but never used in the codebase. The add_entry method directly calls 'rbw generate' instead of using this method, creating duplicate functionality. Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
"""Generate a new password using rbw generate command.""" | ||
command = ["rbw", "generate", str(length)] | ||
result = run(command, capture_output=True, encoding="utf-8") | ||
|
||
if result.returncode != 0: | ||
raise Exception(f"Failed to generate password: {result.stderr}") | ||
|
||
return result.stdout.strip() | ||
|
||
def add_entry(self, name: str, username: str, uri: Optional[str] = None, folder: Optional[str] = None, password_length: int = 16) -> str: | ||
"""Add a new entry to the password database and return the generated password.""" | ||
command = ["rbw", "generate", str(password_length), name] | ||
|
||
if username: | ||
command.append(username) | ||
|
||
if uri: | ||
command.extend(["--uri", uri]) | ||
|
||
if folder: | ||
command.extend(["--folder", folder]) | ||
|
||
# Run the command to create entry with generated password | ||
result = run(command, capture_output=True, encoding="utf-8") | ||
|
||
if result.returncode != 0: | ||
raise Exception(f"Failed to add entry: {result.stderr}") | ||
|
||
# The generated password is returned in stdout | ||
return result.stdout.strip() |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -53,6 +53,10 @@ def main(self) -> None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if selected_action == Action.CANCEL: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if selected_action == Action.ADD: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.__handle_add_entry() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
entry = self.rbw.fetch_credentials(selected_entry) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if self.args.use_cache: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -126,3 +130,91 @@ def __type_targets(self, detailed_entry: DetailedEntry, targets: List[Target]): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.clipboarder.copy_to_clipboard(detailed_entry.totp) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if self.args.use_notify_send: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
run(["notify-send", "-u", "normal", "-t", "3000", "rofi-rbw", "totp copied to clipboard"], check=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def __handle_add_entry(self) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"""Handle adding a new entry to the password database.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from .abstractionhelper import extract_domain_from_url | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Try to get URL from clipboard as a default | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
clipboard_url = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
clipboard_content = self.clipboarder.read_from_clipboard().strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Check if clipboard contains a URL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if clipboard_content.startswith(('http://', 'https://')) or ('.' in clipboard_content and not clipboard_content.isspace()): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
clipboard_url = clipboard_content if clipboard_content.startswith(('http://', 'https://')) else f"https://{clipboard_content}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+143
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The URL detection logic is overly broad and could match non-URL strings. A string like 'my.file' or 'john.doe' would be treated as a URL, which could lead to incorrect behavior.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback
Comment on lines
+142
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line automatically prefixes 'https://' to any content that contains a dot but doesn't start with a protocol. This could incorrectly convert non-URL strings like filenames or email addresses into invalid URLs.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# If clipboard reading fails, start with empty values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
clipboard_url = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Prompt for URL (pre-filled with clipboard content if available) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
url_prompt = f"URL{f' [{clipboard_url}]' if clipboard_url else ''}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result = self.selector.show_input_dialog(url_prompt, clipboard_url if clipboard_url else "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if result is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return # User cancelled | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
entered_url = result.strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# If user entered something, use that; if empty and we had clipboard content, use clipboard; otherwise no URL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if entered_url: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uri = entered_url if entered_url.startswith(('http://', 'https://')) else f"https://{entered_url}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
elif clipboard_url: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uri = clipboard_url | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uri = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
print("Failed to prompt for URL") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Extract domain for default name if we have a URI | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if uri: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
domain = extract_domain_from_url(uri) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default_name = domain | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default_name = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default_name = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Prompt for entry name (pre-filled with domain if available) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name_prompt = f"Entry name{f' [{default_name}]' if default_name else ''}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result = self.selector.show_input_dialog(name_prompt, default_name if default_name else "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if result is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return # User cancelled | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
entered_name = result.strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if entered_name: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name = entered_name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
elif default_name: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name = default_name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
print("Entry name is required") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
print("Failed to prompt for entry name") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Prompt for username | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
username = self.selector.show_input_dialog("Username", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if username is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return # User cancelled | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
username = username.strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
print("Failed to prompt for username") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Add entry to database (this will generate the password) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
password = self.rbw.add_entry(name, username, uri) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Copy password to clipboard | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.clipboarder.copy_to_clipboard(password) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
print(f"Entry '{name}' added successfully. Password copied to clipboard.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Sync to update the database | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.rbw.sync() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
print(f"Error adding entry: {e}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function imports the 're' module inside the function body. It would be more efficient and conventional to import 're' at the module level.
Copilot uses AI. Check for mistakes.