121 lines
3.4 KiB
Python
121 lines
3.4 KiB
Python
import subprocess
|
|
import os
|
|
import json
|
|
import pyperclip
|
|
from qutebrowser.api import interceptor
|
|
import urwid
|
|
import sys
|
|
|
|
PASS_STORE = os.path.expanduser("~/.password-store")
|
|
IGNORE_DOMAINS = ["localhost", "127.0.0.1"]
|
|
|
|
def get_pass_entries():
|
|
"""Returns a list of all password entries in the store."""
|
|
result = subprocess.run(["pass", "ls"], capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
return []
|
|
return [line.strip().replace(" ", "") for line in result.stdout.split("\n") if line]
|
|
|
|
|
|
def find_matching_entry(url):
|
|
"""Finds the closest matching pass entry for a given URL."""
|
|
domain = url.split("/")[2] if "://" in url else url
|
|
if domain in IGNORE_DOMAINS:
|
|
return None
|
|
|
|
entries = get_pass_entries()
|
|
|
|
for entry in entries:
|
|
if domain in entry.lower():
|
|
return entry
|
|
return None
|
|
|
|
|
|
def get_password(entry):
|
|
"""Retrieves the password from pass."""
|
|
result = subprocess.run(["pass", entry], capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
return None
|
|
lines = result.stdout.strip().split("\n")
|
|
|
|
username = None
|
|
password = lines[0] # First line is password
|
|
|
|
if len(lines) > 1:
|
|
for line in lines[1:]:
|
|
if line.startswith("username:"):
|
|
username = line.split(":", 1)[1].strip()
|
|
|
|
return username, password
|
|
|
|
|
|
def show_tui(entries):
|
|
"""Shows a TUI menu for manual password selection."""
|
|
def on_select(button, choice):
|
|
global selected_entry
|
|
selected_entry = choice
|
|
raise urwid.ExitMainLoop()
|
|
|
|
body = [urwid.Text("Select a password entry:"), urwid.Divider()]
|
|
for entry in entries:
|
|
button = urwid.Button(entry)
|
|
urwid.connect_signal(button, 'click', on_select, entry)
|
|
body.append(urwid.AttrMap(button, None, focus_map='reversed'))
|
|
|
|
main_widget = urwid.ListBox(urwid.SimpleFocusListWalker(body))
|
|
urwid.MainLoop(main_widget).run()
|
|
|
|
return selected_entry
|
|
|
|
|
|
def autofill_password(url):
|
|
"""Handles autofilling the password in qutebrowser."""
|
|
entry = find_matching_entry(url)
|
|
|
|
if not entry:
|
|
entries = get_pass_entries()
|
|
entry = show_tui(entries)
|
|
|
|
if entry:
|
|
username, password = get_password(entry)
|
|
if not password:
|
|
print("Error: No password found.")
|
|
return
|
|
|
|
js_script = """
|
|
(function() {
|
|
let inputs = document.querySelectorAll('input[type="password"]');
|
|
if (inputs.length === 0) {
|
|
alert('No password field found!');
|
|
return;
|
|
}
|
|
let passwordField = inputs[0];
|
|
let form = passwordField.closest('form');
|
|
let userField = form.querySelector('input[type="text"], input[type="email"]');
|
|
|
|
if (!userField) {
|
|
alert('No username field found!');
|
|
return;
|
|
}
|
|
|
|
userField.value = "%s";
|
|
passwordField.value = "%s";
|
|
})();
|
|
""" % (username or "", password)
|
|
|
|
with open("/tmp/qute_autofill.js", "w") as f:
|
|
f.write(js_script)
|
|
|
|
subprocess.run(["qutebrowser", ":jseval file:///tmp/qute_autofill.js"])
|
|
else:
|
|
print("No entry selected.")
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python qute_pass_manager.py <current_url>")
|
|
sys.exit(1)
|
|
|
|
url = sys.argv[1]
|
|
autofill_password(url)
|
|
|