quteの変更

このコミットが含まれているのは:
守矢諏訪子 2022-01-27 12:36:51 +09:00
コミット 03ec6545ac
2個のファイルの変更314行の追加11行の削除

ファイルの表示

@ -46,9 +46,15 @@ c.url.start_pages = 'https://www.technicalsuwako.jp'
c.url.searchengines = {'DEFAULT': 'https://duckduckgo.com/?q={}'}
# Custom key
config.bind('M', 'hint links spawn vlc {hint-url}') # 動画URLはVLCに開く
config.bind('M', 'hint links spawn mpv {hint-url}') # 動画URLはMPVに開く
config.bind('Z', 'hint links spawn st -e yt-dlp {hint-url}') # YouTubeの動画URLを保存する
config.bind('t', ':set-cmd-text -s :open -t') # 新しいタブに開く
config.bind('ef', 'spawn --userscript qute-pass')
config.bind('eu', 'spawn --userscript qute-pass --username-only')
config.bind('ep', 'spawn --userscript qute-pass --password-only')
config.bind('eo', 'spawn --userscript qute-pass --otp-only')
config.bind('t', ':set-cmd-text -s :open -t') # 新しいタブに開くi
config.bind('ps', 'tab-pin') # タブにピン留め
config.bind('pt', ':set-cmd-text -s :open -p') # 新しいURLをプライベートウィンドウに開く
config.bind('po', ':set-cmd-text -s :open -p {url}'); # 現在のURLをプライベートウィンドウに開く
config.bind('xb', 'config-cycle statusbar.show always never') # 下にある状況バーの有無
@ -56,8 +62,9 @@ config.bind('xt', 'config-cycle tabs.show always never') #上にあるタブバ
config.bind('xx', 'config-cycle statusbar.show always never;; config-cycle tabs.show always never') # 両方のバー同時にの有無
config.bind('xd', 'config-cycle colors.webpage.darkmode.enabled true false') # ダークモードの有無
config.bind('xj', 'config-cycle content.javascript.enabled true false') # JSの有無
config.bind('xp', 'config-cycle content.proxy system socks://localhost:9050/') # 普通↔Torの交換
config.bind('xp', 'config-cycle content.proxy system socks://localhost:9050/ socks://localhost:4447/') # 普通↔Tor↔I2Pの交換
config.bind(';i', 'hint images spawn --output-messages wget -P "/home/suwako/ダウンロード" {hint-url}') # 画像等の保存
config.bind(';I', 'hint images fill :open -b {hint-url}')
config.bind('yc', 'hint images yank');
config.bind('gh', 'back') # 戻る
config.bind('gl', 'forward') # 次
@ -67,17 +74,15 @@ c.content.proxy = 'socks://localhost:9050/' # デフォルトtorは有効、
c.content.javascript.enabled = False #デフォルトjavascriptは無効
c.content.images = True # デフォルト:画像は有効
# 動画は遅すぎるから、peertubeでtorを使わない。そうして、テク諏訪でトラッキングはだから、torか普通か違いがない
#config.set('content.proxy', 'system', 'https://video.076.ne.jp/*')
#config.set('content.proxy', 'system', 'https://*.technicalsuwako.jp/*')
# 下記のサイトだけでjavascriptを使える
config.set('content.javascript.enabled', True, 'https://git.076.ne.jp/*')
config.set('content.javascript.enabled', True, 'https://social.076.ne.jp/*')
config.set('content.javascript.enabled', True, 'https://odysee.com/*')
config.set('content.javascript.enabled', True, 'https://bitchute.com/*')
config.set('content.javascript.enabled', True, 'https://duckduckgo.com/*')
config.set('content.javascript.enabled', True, 'https://video.076.ne.jp/*')
config.set('content.javascript.enabled', True, 'http://127.0.0.1/*')
config.set('content.javascript.enabled', True, 'https://tradeogre.com/*')
config.set('content.javascript.enabled', True, 'https://flote.app/*')
config.set('content.javascript.enabled', True, 'https://app.slack.com/*')
config.set('content.javascript.enabled', True, 'https://coin.z.com/*')
config.set('content.javascript.enabled', True, 'https://bank.gmo-aozora.com/*')
config.set('content.javascript.enabled', True, 'chrome-devtools://*')
config.set('content.javascript.enabled', True, 'devtools://*')
config.set('content.javascript.enabled', True, 'chrome://*/*')
@ -86,6 +91,11 @@ config.set('content.javascript.enabled', True, 'qute://*/*')
#config.set('content.images', True, 'chrome-devtools://*')
#config.set('content.images', True, 'devtools://*')
# ファイルブラウザはrangerに設定しますので、rangerをインストールすることが必要です。
config.set('fileselect.handler', 'external');
config.set('fileselect.single_file.command', ['alacritty', '--class', 'ranger,ranger', '-e', 'ranger', '--choosefile', '{}']);
config.set('fileselect.multiple_files.command', ['alacritty', '--class', 'ranger,ranger', '-e', 'ranger', '--choosefiles', '{}']);
# テーマ
c.window.hide_decoration = True
c.colors.completion.category.bg = 'qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #46195a, stop:1 #341242)'
@ -120,7 +130,13 @@ c.colors.statusbar.command.private.fg = '#fcfcfc'
c.colors.statusbar.insert.bg = '#27ae60'
c.colors.statusbar.insert.fg = '#fcfcfc'
c.colors.statusbar.normal.bg = '#31363b'
# 普通オレンジ、Tor黒、I2P
if config.get('content.proxy') == 'socks://localhost:9050/':
c.colors.statusbar.normal.bg = '#31363b'
elif config.get('content.proxy') == 'socks://localhost:4447/':
c.colors.statusbar.normal.bg = '#3daee9'
else:
c.colors.statusbar.normal.bg = '#f67400'
c.colors.statusbar.normal.fg = '#fcfcfc'
c.colors.statusbar.passthrough.bg = '#1d99f3'
c.colors.statusbar.passthrough.fg = '#fcfcfc'

287
.config/qutebrowser/greasemonkey/qute-pass ノーマルファイル
ファイルの表示

@ -0,0 +1,287 @@
#!/usr/bin/env python3
# Copyright 2017 Chris Braun (cryzed) <cryzed@googlemail.com>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
"""
Insert login information using pass and a dmenu-compatible application (e.g. dmenu, rofi -dmenu, ...). A short
demonstration can be seen here: https://i.imgur.com/KN3XuZP.gif.
"""
USAGE = """The domain of the site has to appear as a segment in the pass path,
for example: "github.com/cryzed" or "websites/github.com". Alternatively the
parameter `--unfiltered` may be used to get a list of all passwords. How the
username and password are determined is freely configurable using the CLI
arguments. As an example, if you instead store the username as part of the
secret (and use a site's name as filename), instead of the default configuration,
use `--username-target secret` and `--username-pattern "username: (.+)"`.
The login information is inserted by emulating key events using qutebrowser's
fake-key command in this manner: [USERNAME]<Tab>[PASSWORD], which is compatible
with almost all login forms.
If you use gopass with multiple mounts, use the CLI switch --mode gopass to switch to gopass mode.
Suggested bindings similar to Uzbl's `formfiller` script:
config.bind('<z><l>', 'spawn --userscript qute-pass')
config.bind('<z><u><l>', 'spawn --userscript qute-pass --username-only')
config.bind('<z><p><l>', 'spawn --userscript qute-pass --password-only')
config.bind('<z><o><l>', 'spawn --userscript qute-pass --otp-only')
"""
EPILOG = """Dependencies: tldextract (Python 3 module), pass, pass-otp (optional).
For issues and feedback please use: https://github.com/cryzed/qutebrowser-userscripts.
WARNING: The login details are viewable as plaintext in qutebrowser's debug log (qute://log) and might be shared if
you decide to submit a crash report!"""
import argparse
import enum
import fnmatch
import functools
import os
import re
import shlex
import subprocess
import sys
import tldextract
def expanded_path(path):
# Expand potential ~ in paths, since this script won't be called from a shell that does it for us
expanded = os.path.expanduser(path)
# Add trailing slash if not present
return os.path.join(expanded, '')
argument_parser = argparse.ArgumentParser(description=__doc__, usage=USAGE, epilog=EPILOG)
argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL'))
argument_parser.add_argument('--password-store', '-p',
default=expanded_path(os.getenv('PASSWORD_STORE_DIR', default='~/.password-store')),
help='Path to your pass password-store (only used in pass-mode)', type=expanded_path)
argument_parser.add_argument('--mode', '-M', choices=['pass', 'gopass'], default="pass",
help='Select mode [gopass] to use gopass instead of the standard pass.')
argument_parser.add_argument('--username-pattern', '-u', default=r'.*/(.+)',
help='Regular expression that matches the username')
argument_parser.add_argument('--username-target', '-U', choices=['path', 'secret'], default='path',
help='The target for the username regular expression')
argument_parser.add_argument('--password-pattern', '-P', default=r'(.*)',
help='Regular expression that matches the password')
argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu',
help='Invocation used to execute a dmenu-provider')
argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', action='store_false',
help="Don't automatically enter insert mode")
argument_parser.add_argument('--io-encoding', '-i', default='UTF-8',
help='Encoding used to communicate with subprocesses')
argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
help='Merge pass candidates for fully-qualified and registered domain name')
argument_parser.add_argument('--extra-url-suffixes', '-s', default='',
help='Comma-separated string containing extra suffixes (e.g local)')
argument_parser.add_argument('--unfiltered', dest='unfiltered', action='store_true',
help='Show an unfiltered selection of all passwords in the store')
argument_parser.add_argument('--always-show-selection', dest='always_show_selection', action='store_true',
help='Always show selection, even if there is only a single match')
group = argument_parser.add_mutually_exclusive_group()
group.add_argument('--username-only', '-e', action='store_true', help='Only insert username')
group.add_argument('--password-only', '-w', action='store_true', help='Only insert password')
group.add_argument('--otp-only', '-o', action='store_true', help='Only insert OTP code')
stderr = functools.partial(print, file=sys.stderr)
class ExitCodes(enum.IntEnum):
SUCCESS = 0
FAILURE = 1
# 1 is automatically used if Python throws an exception
NO_PASS_CANDIDATES = 2
COULD_NOT_MATCH_USERNAME = 3
COULD_NOT_MATCH_PASSWORD = 4
class CouldNotMatchUsername(Exception):
pass
class CouldNotMatchPassword(Exception):
pass
def qute_command(command):
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
fifo.write(command + '\n')
fifo.flush()
def find_pass_candidates(domain, unfiltered=False):
candidates = []
if arguments.mode == "gopass":
all_passwords = subprocess.run(["gopass", "list", "--flat" ], stdout=subprocess.PIPE).stdout.decode("UTF-8").splitlines()
for password in all_passwords:
if unfiltered or domain in password:
candidates.append(password)
else:
for path, directories, file_names in os.walk(arguments.password_store, followlinks=True):
secrets = fnmatch.filter(file_names, '*.gpg')
if not secrets:
continue
# Strip password store path prefix to get the relative pass path
pass_path = path[len(arguments.password_store):]
split_path = pass_path.split(os.path.sep)
for secret in secrets:
secret_base = os.path.splitext(secret)[0]
if not unfiltered and domain not in (split_path + [secret_base]):
continue
candidates.append(os.path.join(pass_path, secret_base))
return candidates
def _run_pass(pass_arguments):
# The executable is conveniently named after it's mode [pass|gopass].
pass_command = [arguments.mode]
env = os.environ.copy()
env['PASSWORD_STORE_DIR'] = arguments.password_store
process = subprocess.run(pass_command + pass_arguments, env=env, stdout=subprocess.PIPE)
return process.stdout.decode(arguments.io_encoding).strip()
def pass_(path):
return _run_pass(['show', path])
def pass_otp(path):
if arguments.mode == "gopass":
return _run_pass(['otp', '-o', path])
return _run_pass(['otp', path])
def dmenu(items, invocation):
command = shlex.split(invocation)
process = subprocess.run(command, input='\n'.join(items).encode(arguments.io_encoding), stdout=subprocess.PIPE)
return process.stdout.decode(arguments.io_encoding).strip()
def fake_key_raw(text):
for character in text:
# Escape all characters by default, space requires special handling
sequence = '" "' if character == ' ' else '\{}'.format(character)
qute_command('fake-key {}'.format(sequence))
def extract_password(secret, pattern):
match = re.match(pattern, secret)
if not match:
raise CouldNotMatchPassword("Pattern did not match target")
try:
return match.group(1)
except IndexError:
raise CouldNotMatchPassword("Pattern did not contain capture group, please use capture group. Example: (.*)")
def extract_username(target, pattern):
match = re.search(pattern, target, re.MULTILINE)
if not match:
raise CouldNotMatchUsername("Pattern did not match target")
try:
return match.group(1)
except IndexError:
raise CouldNotMatchUsername("Pattern did not contain capture group, please use capture group. Example: (.*)")
def main(arguments):
if not arguments.url:
argument_parser.print_help()
return ExitCodes.FAILURE
extractor = tldextract.TLDExtract(extra_suffixes=arguments.extra_url_suffixes.split(','))
extract_result = extractor(arguments.url)
# Try to find candidates using targets in the following order: fully-qualified domain name (includes subdomains),
# the registered domain name, the IPv4 address if that's what the URL represents and finally the private domain
# (if a non-public suffix was used).
candidates = set()
attempted_targets = []
private_domain = ''
if not extract_result.suffix:
private_domain = ('.'.join((extract_result.subdomain, extract_result.domain))
if extract_result.subdomain else extract_result.domain)
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.ipv4, private_domain]):
attempted_targets.append(target)
target_candidates = find_pass_candidates(target, unfiltered=arguments.unfiltered)
if not target_candidates:
continue
candidates.update(target_candidates)
if not arguments.merge_candidates:
break
else:
if not candidates:
stderr('No pass candidates for URL {!r} found! (I tried {!r})'.format(arguments.url, attempted_targets))
return ExitCodes.NO_PASS_CANDIDATES
if len(candidates) == 1 and not arguments.always_show_selection:
selection = candidates.pop()
else:
selection = dmenu(sorted(candidates), arguments.dmenu_invocation)
# Nothing was selected, simply return
if not selection:
return ExitCodes.SUCCESS
# If username-target is path and user asked for username-only, we don't need to run pass.
# Or if using otp-only, it will run pass on its own.
secret = None
if not (arguments.username_target == 'path' and arguments.username_only) and not arguments.otp_only:
secret = pass_(selection)
username_target = selection if arguments.username_target == 'path' else secret
try:
if arguments.username_only:
fake_key_raw(extract_username(username_target, arguments.username_pattern))
elif arguments.password_only:
fake_key_raw(extract_password(secret, arguments.password_pattern))
elif arguments.otp_only:
otp = pass_otp(selection)
fake_key_raw(otp)
else:
# Enter username and password using fake-key and <Tab> (which seems to work almost universally), then switch
# back into insert-mode, so the form can be directly submitted by hitting enter afterwards
fake_key_raw(extract_username(username_target, arguments.username_pattern))
qute_command('fake-key <Tab>')
fake_key_raw(extract_password(secret, arguments.password_pattern))
except CouldNotMatchPassword as e:
stderr('Failed to match password, target: secret, error: {}'.format(e))
return ExitCodes.COULD_NOT_MATCH_PASSWORD
except CouldNotMatchUsername as e:
stderr('Failed to match username, target: {}, error: {}'.format(arguments.username_target, e))
return ExitCodes.COULD_NOT_MATCH_USERNAME
if arguments.insert_mode:
qute_command('mode-enter insert')
return ExitCodes.SUCCESS
if __name__ == '__main__':
arguments = argument_parser.parse_args()
sys.exit(main(arguments))