tangled
alpha
login
or
join now
selman.me
/
rc.d
0
fork
atom
this repo has no description
0
fork
atom
overview
issues
pulls
pipelines
.qutebrowser: update 1password script, config
Selman Kayrancioglu
1 year ago
2bd4c55f
fe298241
+241
-88
4 changed files
expand all
collapse all
unified
split
private_dot_qutebrowser
config.py
userscripts
executable_1password.sh
executable_code_select.py
executable_qute-1pass
+21
-1
private_dot_qutebrowser/config.py
···
18
18
# c.fonts.tabs.unselected = "bold default_size default_family"
19
19
# c.fonts.completion.category = "bold default_size default_family"
20
20
# c.fonts.completion.entry = "bold default_size default_family"
21
21
-
c.window.hide_decoration = False
21
21
+
c.window.hide_decoration = True
22
22
c.colors.webpage.darkmode.enabled = False
23
23
c.editor.command = ["/usr/local/bin/zed", "--wait", "{file}:{line}:{column0}"]
24
24
+
c.url.start_pages = ["about:blank"]
25
25
+
c.scrolling.smooth = True
26
26
+
c.statusbar.show = "always"
27
27
+
c.url.default_page = "https://kagi.com"
24
28
c.url.start_pages = ["about:blank"]
25
29
26
30
# All keybindings organized by mode
···
44
48
"<Alt-d>": "fake-key <Ctrl-Delete>",
45
49
"<Alt-Backspace>": "fake-key <Ctrl-Backspace>",
46
50
"<Ctrl-y>": "insert-text {primary}",
51
51
+
"<Ctrl-x><Ctrl-e>": "edit-text",
47
52
},
48
53
"normal": {
49
54
# Navigation
50
55
"\\ff": "cmd-set-text -s :tab-select",
56
56
+
"gt": "tab-next",
57
57
+
"gT": "tab-prev",
58
58
+
"\\nh": "search",
51
59
"<Ctrl-o>": "back",
52
60
"<Ctrl-i>": "forward",
61
61
+
"<Ctrl-[>": "back",
62
62
+
"<Ctrl-]>": "forward",
53
63
# Tab position toggle
54
64
"\\tt": "config-cycle tabs.position left top",
55
65
# Overlay killer
···
69
79
"cmd-later 10s set content.javascript.clipboard none",
70
80
]
71
81
),
82
82
+
"\\<Ctrl-f>": "spawn --userscript qute-1pass",
83
83
+
"\\\\": "clear-messages",
84
84
+
"\\<Ctrl-c>": "hint code userscript code_select.py",
85
85
+
"\\<Ctrl-x><Ctrl-e>": "edit-text",
72
86
},
73
87
"command": {
74
88
"<Ctrl+e>": "edit-command",
75
89
},
76
90
}
77
91
92
92
+
93
93
+
c.hints.selectors["code"] = [
94
94
+
# Selects all code tags whose direct parent is not a pre tag
95
95
+
":not(pre) > code",
96
96
+
"pre",
97
97
+
]
78
98
# Domain-specific clipboard settings
79
99
for pattern in [
80
100
r"*://github.com",
-87
private_dot_qutebrowser/userscripts/executable_1password.sh
···
1
1
-
#!/usr/bin/env bash
2
2
-
3
3
-
set -e
4
4
-
5
5
-
# JS field injection code from https://github.com/qutebrowser/qutebrowser/blob/master/misc/userscripts/password_fill
6
6
-
javascript_escape() {
7
7
-
# print the first argument in an escaped way, such that it can safely
8
8
-
# be used within javascripts double quotes
9
9
-
# shellcheck disable=SC2001
10
10
-
sed "s,[\\\\'\"],\\\\&,g" <<<"$1"
11
11
-
}
12
12
-
13
13
-
js() {
14
14
-
cat <<EOF
15
15
-
function isVisible(elem) {
16
16
-
var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null);
17
17
-
if (style.getPropertyValue("visibility") !== "visible" ||
18
18
-
style.getPropertyValue("display") === "none" ||
19
19
-
style.getPropertyValue("opacity") === "0") {
20
20
-
return false;
21
21
-
}
22
22
-
return elem.offsetWidth > 0 && elem.offsetHeight > 0;
23
23
-
};
24
24
-
function hasPasswordField(form) {
25
25
-
var inputs = form.getElementsByTagName("input");
26
26
-
for (var j = 0; j < inputs.length; j++) {
27
27
-
var input = inputs[j];
28
28
-
if (input.type == "password") {
29
29
-
return true;
30
30
-
}
31
31
-
}
32
32
-
return false;
33
33
-
};
34
34
-
function loadData2Form (form) {
35
35
-
var inputs = form.getElementsByTagName("input");
36
36
-
for (var j = 0; j < inputs.length; j++) {
37
37
-
var input = inputs[j];
38
38
-
if (isVisible(input) && (input.type == "text" || input.type == "email")) {
39
39
-
input.focus();
40
40
-
input.value = "$(javascript_escape "${USERNAME}")";
41
41
-
input.dispatchEvent(new Event('change'));
42
42
-
input.blur();
43
43
-
}
44
44
-
if (input.type == "password") {
45
45
-
input.focus();
46
46
-
input.value = "$(javascript_escape "${PASSWORD}")";
47
47
-
input.dispatchEvent(new Event('change'));
48
48
-
input.blur();
49
49
-
}
50
50
-
}
51
51
-
};
52
52
-
var forms = document.getElementsByTagName("form");
53
53
-
for (i = 0; i < forms.length; i++) {
54
54
-
if (hasPasswordField(forms[i])) {
55
55
-
loadData2Form(forms[i]);
56
56
-
}
57
57
-
}
58
58
-
EOF
59
59
-
}
60
60
-
61
61
-
URL=$(echo "$QUTE_URL" | awk -F/ '{print $3}' | sed 's/www.//g')
62
62
-
echo "message-info 'Looking for password for $URL...'" >>$QUTE_FIFO
63
63
-
64
64
-
UUID_URL=$(op item list --format=json | jq -r '.[] | "\(.id):\(.urls[]?.href)" ' | grep "$URL") || $(echo "message-error 'No entry found for $URL'" >>$QUTE_FIFO)
65
65
-
66
66
-
IFS=: read -r UUID var2 <<<"$UUID_URL"
67
67
-
ITEM=$(op item get --reveal --format=json "$UUID")
68
68
-
69
69
-
PASSWORD=$(echo "$ITEM" | jq -r '.fields[]? | select(.purpose=="PASSWORD") | .value')
70
70
-
71
71
-
if [ -n "$PASSWORD" ]; then
72
72
-
TITLE=$(echo "$ITEM" | jq -r '.title')
73
73
-
USERNAME=$(echo "$ITEM" | jq -r '.fields[]? | select(.purpose=="USERNAME") | .value')
74
74
-
75
75
-
printjs() {
76
76
-
js | sed 's,//.*$,,' | tr '\n' ' '
77
77
-
}
78
78
-
echo "jseval -q $(printjs)" >>"$QUTE_FIFO"
79
79
-
80
80
-
# TOTP=$(echo "$ITEM" | op get totp --session="$TOKEN" "$UUID")
81
81
-
# if [ -n "$TOTP" ]; then
82
82
-
# echo "$TOTP" | pbcopy
83
83
-
# echo "One time password for $TITLE: $TOTP in clipboard" | terminal-notifier -title "Qutebrowser 1Password" -sound default
84
84
-
# fi
85
85
-
else
86
86
-
echo "No password found for $URL" | terminal-notifier -title "Qutebrowser 1Password" -sound default
87
87
-
fi
+64
private_dot_qutebrowser/userscripts/executable_code_select.py
···
1
1
+
#!/usr/bin/env python3
2
2
+
3
3
+
import os
4
4
+
import html
5
5
+
import re
6
6
+
import sys
7
7
+
import xml.etree.ElementTree as ET
8
8
+
try:
9
9
+
import pyperclip
10
10
+
except ImportError:
11
11
+
try:
12
12
+
import pyclip as pyperclip
13
13
+
except ImportError:
14
14
+
PYPERCLIP = False
15
15
+
else:
16
16
+
PYPERCLIP = True
17
17
+
else:
18
18
+
PYPERCLIP = True
19
19
+
20
20
+
21
21
+
def parse_text_content(element):
22
22
+
# https://stackoverflow.com/a/35591507/15245191
23
23
+
magic = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
24
24
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" [
25
25
+
<!ENTITY nbsp ' '>
26
26
+
]>'''
27
27
+
root = ET.fromstring(magic + element)
28
28
+
text = ET.tostring(root, encoding="unicode", method="text")
29
29
+
text = html.unescape(text)
30
30
+
return text
31
31
+
32
32
+
33
33
+
def send_command_to_qute(command):
34
34
+
with open(os.environ.get("QUTE_FIFO"), "w") as f:
35
35
+
f.write(command)
36
36
+
37
37
+
38
38
+
def main():
39
39
+
delimiter = sys.argv[1] if len(sys.argv) > 1 else ";"
40
40
+
# For info on qute environment vairables, see
41
41
+
# https://github.com/qutebrowser/qutebrowser/blob/master/doc/userscripts.asciidoc
42
42
+
element = os.environ.get("QUTE_SELECTED_HTML")
43
43
+
code_text = parse_text_content(element)
44
44
+
re_remove_dollars = re.compile(r"^(\$ )", re.MULTILINE)
45
45
+
code_text = re.sub(re_remove_dollars, '', code_text)
46
46
+
if PYPERCLIP:
47
47
+
pyperclip.copy(code_text)
48
48
+
send_command_to_qute(
49
49
+
"message-info 'copied to clipboard: {info}{suffix}'".format(
50
50
+
info=code_text.splitlines()[0].replace("'", "\""),
51
51
+
suffix="..." if len(code_text.splitlines()) > 1 else ""
52
52
+
)
53
53
+
)
54
54
+
else:
55
55
+
# Qute's yank command won't copy accross multiple lines so we
56
56
+
# compromise by placing lines on a single line seperated by the
57
57
+
# specified delimiter
58
58
+
code_text = re.sub("(\n)+", delimiter, code_text)
59
59
+
code_text = code_text.replace("'", "\"")
60
60
+
send_command_to_qute("yank inline '{code}'\n".format(code=code_text))
61
61
+
62
62
+
63
63
+
if __name__ == "__main__":
64
64
+
main()
+156
private_dot_qutebrowser/userscripts/executable_qute-1pass
···
1
1
+
#!/usr/bin/env bash
2
2
+
3
3
+
# Temporary script awaiting merge of https://github.com/qutebrowser/qutebrowser/pull/7469
4
4
+
5
5
+
set +e
6
6
+
7
7
+
# JS field injection code from https://github.com/qutebrowser/qutebrowser/blob/master/misc/userscripts/password_fill
8
8
+
javascript_escape() {
9
9
+
# print the first argument in an escaped way, such that it can safely
10
10
+
# be used within javascripts double quotes
11
11
+
# shellcheck disable=SC2001
12
12
+
sed "s,[\\\\'\"\/],\\\\&,g" <<<"$1"
13
13
+
}
14
14
+
15
15
+
js() {
16
16
+
cat <<EOF
17
17
+
function isVisible(elem) {
18
18
+
var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null);
19
19
+
if (style.getPropertyValue("visibility") !== "visible" ||
20
20
+
style.getPropertyValue("display") === "none" ||
21
21
+
style.getPropertyValue("opacity") === "0") {
22
22
+
return false;
23
23
+
}
24
24
+
return elem.offsetWidth > 0 && elem.offsetHeight > 0;
25
25
+
};
26
26
+
function hasPasswordField(form) {
27
27
+
var inputs = form.getElementsByTagName("input");
28
28
+
for (var j = 0; j < inputs.length; j++) {
29
29
+
var input = inputs[j];
30
30
+
if (input.type == "password") {
31
31
+
return true;
32
32
+
}
33
33
+
}
34
34
+
return false;
35
35
+
};
36
36
+
function loadData2Form (form) {
37
37
+
var inputs = form.getElementsByTagName("input");
38
38
+
for (var j = 0; j < inputs.length; j++) {
39
39
+
var input = inputs[j];
40
40
+
if (isVisible(input) && (input.type == "text" || input.type == "email")) {
41
41
+
input.focus();
42
42
+
input.value = "$(javascript_escape "${USERNAME}")";
43
43
+
input.dispatchEvent(new Event('change'));
44
44
+
input.blur();
45
45
+
}
46
46
+
if (input.type == "password") {
47
47
+
input.focus();
48
48
+
input.value = "$(javascript_escape "${PASSWORD}")";
49
49
+
input.dispatchEvent(new Event('change'));
50
50
+
input.blur();
51
51
+
}
52
52
+
}
53
53
+
};
54
54
+
var forms = document.getElementsByTagName("form");
55
55
+
for (i = 0; i < forms.length; i++) {
56
56
+
if (hasPasswordField(forms[i])) {
57
57
+
loadData2Form(forms[i]);
58
58
+
}
59
59
+
}
60
60
+
EOF
61
61
+
}
62
62
+
63
63
+
URL=$(echo "$QUTE_URL" | awk -F/ '{print $3}' | sed 's/www.//g')
64
64
+
65
65
+
dmenu-prompt() {
66
66
+
local PROMPT="$1"
67
67
+
choose -p "$PROMPT"
68
68
+
}
69
69
+
70
70
+
clipboard-copy() {
71
71
+
pbcopy
72
72
+
}
73
73
+
74
74
+
PATH+=:/opt/homebrew/bin
75
75
+
76
76
+
if ! command -v op >/dev/null; then
77
77
+
echo "message-error 'Missing required command-line tool: op'" >>"$QUTE_FIFO"
78
78
+
exit 1
79
79
+
fi
80
80
+
81
81
+
if ! command -v jq >/dev/null; then
82
82
+
echo "message-error 'Missing required command-line tool: jq'" >>"$QUTE_FIFO"
83
83
+
exit 1
84
84
+
fi
85
85
+
86
86
+
OP_VERSION="$(op --version)"
87
87
+
OP_MAJOR_VERSION="$(echo "$OP_VERSION" | grep --only-matching '^[0-9]')"
88
88
+
89
89
+
if [[ "$OP_MAJOR_VERSION" -lt 2 ]]; then
90
90
+
echo "message-error 'Requires op CLI v2.0.0 or higher, but got: $OP_VERSION'" >>"$QUTE_FIFO"
91
91
+
exit 1
92
92
+
fi
93
93
+
94
94
+
echo "message-info 'Looking for password for $URL...'" >>"$QUTE_FIFO"
95
95
+
96
96
+
JQ_TITLE_EXPR='.title + " (in vault \"" + .vault.name + "\")"'
97
97
+
98
98
+
if ! LIST_ITEM_OUT="$(op item list --format=json)"; then
99
99
+
echo "message-error 'Failed to list items.'" >>"$QUTE_FIFO"
100
100
+
exit 1
101
101
+
fi
102
102
+
103
103
+
MATCHING_ITEMS="$(echo "$LIST_ITEM_OUT" | jq --arg url "$URL" '[.[] | select((.urls//[])[].href | test($url))]')"
104
104
+
MATCHING_COUNT="$(echo "$MATCHING_ITEMS" | jq -r 'length')"
105
105
+
106
106
+
UUID=""
107
107
+
if [[ "$MATCHING_COUNT" == 1 ]]; then
108
108
+
UUID="$(echo "$MATCHING_ITEMS" | jq -r '.[0].id')"
109
109
+
TITLE="$(echo "$MATCHING_ITEMS" | jq -r '.[0] | '"$JQ_TITLE_EXPR")"
110
110
+
echo "message-info 'Found 1 entry for $URL: $TITLE'" >>"$QUTE_FIFO"
111
111
+
elif [[ "$MATCHING_COUNT" -gt 1 ]]; then
112
112
+
echo "message-info 'Found $MATCHING_COUNT entries for $URL'" >>"$QUTE_FIFO"
113
113
+
TITLE="$(echo "$MATCHING_ITEMS" | jq -r '.[] | '"$JQ_TITLE_EXPR" | dmenu-prompt "Select item for $URL")" || TITLE=""
114
114
+
if [ -n "$TITLE" ]; then
115
115
+
UUID=$(echo "$MATCHING_ITEMS" | jq --arg title "$TITLE" -r '[.[] | select('"$JQ_TITLE_EXPR"' == $title).id] | first // ""') || UUID=""
116
116
+
else
117
117
+
UUID=""
118
118
+
fi
119
119
+
else
120
120
+
echo "message-error 'No entry found for $URL'" >>"$QUTE_FIFO"
121
121
+
TITLE="$(echo "$LIST_ITEM_OUT" | jq -r '.[] | '"$JQ_TITLE_EXPR" | dmenu-prompt)" || TITLE=""
122
122
+
if [ -n "$TITLE" ]; then
123
123
+
UUID=$(echo "$LIST_ITEM_OUT" | jq --arg title "$TITLE" -r '[.[] | select('"$JQ_TITLE_EXPR"' == $title).id] | first // ""') || UUID=""
124
124
+
else
125
125
+
UUID=""
126
126
+
fi
127
127
+
fi
128
128
+
129
129
+
if [[ -z "$UUID" ]]; then
130
130
+
echo "message-error 'No item picked.'" >>"$QUTE_FIFO"
131
131
+
exit 1
132
132
+
fi
133
133
+
134
134
+
ITEM="$(op item get --format=json "$UUID")"
135
135
+
136
136
+
TITLE="$(echo "$ITEM" | jq -r "$JQ_TITLE_EXPR")"
137
137
+
PASSWORD="$(echo "$ITEM" | jq -r '[.fields[] | select(.purpose=="PASSWORD") | .value] | first // ""')"
138
138
+
139
139
+
if [ -z "$PASSWORD" ]; then
140
140
+
echo "message-error 'No password found in $TITLE'" >>"$QUTE_FIFO"
141
141
+
exit 1
142
142
+
fi
143
143
+
144
144
+
USERNAME="$(echo "$ITEM" | jq -r '[.fields[] | select(.purpose=="USERNAME") | .value] | first // ""')"
145
145
+
146
146
+
printjs() {
147
147
+
js | sed 's,//.*$,,' | tr '\n' ' '
148
148
+
}
149
149
+
echo "jseval -q $(printjs)" >>"$QUTE_FIFO"
150
150
+
echo "message-info 'Using credentials from: $TITLE'" >>"$QUTE_FIFO"
151
151
+
152
152
+
TOTP="$(echo "$ITEM" | op item get --otp "$UUID")" || TOTP=""
153
153
+
if [ -n "$TOTP" ]; then
154
154
+
echo "$TOTP" | clipboard-copy
155
155
+
echo "message-info 'Pasted one time password for $TITLE to clipboard'" >>"$QUTE_FIFO"
156
156
+
fi