mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-02-04 06:36:35 -08:00
Merge branch 'master' of github.com:ksyasuda/dotfiles
This commit is contained in:
@@ -14,8 +14,8 @@ alias aniwrapper='aniwrapper -D 144'
|
||||
|
||||
## Colorls
|
||||
alias ls='eza -M --group-directories-first --icons --color=always --group --git'
|
||||
alias ll='ls -l'
|
||||
alias la='ls -la'
|
||||
alias ll='ls -lh'
|
||||
alias la='ls -lah'
|
||||
|
||||
alias vimf='vim $(fzf --height=45% --layout=reverse --preview="bat --style=numbers --color=always --line-range :500 {}")'
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ font-size = 12
|
||||
font-feature = +calt
|
||||
font-feature = +liga
|
||||
font-feature = +dlig
|
||||
theme = catppuccin-macchiato
|
||||
theme = Catppuccin Macchiato
|
||||
cursor-style = block
|
||||
window-padding-x = 10
|
||||
window-padding-y = 10
|
||||
@@ -14,7 +14,12 @@ window-width = 180
|
||||
confirm-close-surface = false
|
||||
copy-on-select = clipboard
|
||||
app-notifications = no-clipboard-copy
|
||||
shell-integration = zsh
|
||||
shell-integration-features = title,sudo,ssh-env,ssh-terminfo
|
||||
desktop-notifications = true
|
||||
term=ghostty
|
||||
keybind = all:ctrl+enter=unbind
|
||||
keybind = all:ctrl+shift+j=next_tab
|
||||
keybind = all:ctrl+shift+k=last_tab
|
||||
keybind = all:ctrl+grave_accent=toggle_quick_terminal
|
||||
keybind = shift+enter=text:\x1b\r
|
||||
|
||||
@@ -18,3 +18,6 @@ keybind = all:ctrl+enter=unbind
|
||||
keybind = all:ctrl+grave_accent=toggle_quick_terminal
|
||||
shell-integration = zsh
|
||||
keybind = shift+enter=text:\x1b\r
|
||||
shell-integration-features = title,sudo,ssh-env,ssh-terminfo
|
||||
desktop-notifications = true
|
||||
term=ghostty
|
||||
|
||||
@@ -54,7 +54,7 @@ xwayland {
|
||||
# See https://wiki.hyprland.org/Configuring/Keywords/
|
||||
|
||||
# Set programs that you use
|
||||
$terminal = uwsm app -- ghostty
|
||||
$terminal = uwsm app -- ghostty +new-window
|
||||
$fileManager = uwsm app -- thunar
|
||||
$menu = rofi -show drun -run-command "uwsm app -- {cmd}"
|
||||
# $notification_daemon = dunst
|
||||
|
||||
@@ -93,8 +93,12 @@ bindl = , XF86AudioPrev, exec, mpc prev
|
||||
# rofi
|
||||
bind = $mainMod SHIFT, v, exec, uwsm app -sb -- rofi-rbw
|
||||
bind = $mainMod, w, exec, rofi -show window -theme $HOME/.config/rofi/launchers/type-2/style-2.rasi -dpi 96 -theme-str 'window {width: 35%;}'
|
||||
bind = $mainMod SHIFT, w, exec, $HOME/.config/rofi/scripts/rofi-wallpaper.sh
|
||||
bind = $mainMod SHIFT, d, exec, $HOME/.config/rofi/scripts/rofi-docs.sh
|
||||
bind = $mainMod SHIFT, w, exec, "$HOME/.config/rofi/scripts/rofi-wallpaper.sh"
|
||||
bind = $mainMod SHIFT, d, exec, "$HOME/.config/rofi/scripts/rofi-docs.sh"
|
||||
bind = SUPER, j, exec, "$HOME/.config/rofi/scripts/rofi-jellyfin-dir.sh"
|
||||
bind = SUPER, t, exec, "$HOME/.config/rofi/scripts/rofi-launch-texthooker-steam.sh"
|
||||
bind = $mainMod SHIFT, t, exec, "$HOME/projects/scripts/popup-ai-translator.py"
|
||||
bind = SUPER SHIFT, g, exec, "$HOME/.config/rofi/scripts/rofi-vn-helper.sh"
|
||||
|
||||
# ncmcppp
|
||||
bind = $mainMod, n, exec, uwsm app -sb -- ghostty --command=/usr/bin/ncmpcpp
|
||||
@@ -139,3 +143,7 @@ bind = $mainMod, code:112, submap, reset
|
||||
submap = reset
|
||||
|
||||
bind = SUPER, l, exec, hyprlock
|
||||
|
||||
# ANKI
|
||||
bind = $mainMod, a, exec, ~/.config/rofi/scripts/rofi-anki-script.sh
|
||||
bind = $mainMod SHIFT, a, exec, ~/projects/scripts/screenshot-anki.sh -cdMinecraft
|
||||
|
||||
155
.config/mimeapps.list
Normal file
155
.config/mimeapps.list
Normal file
@@ -0,0 +1,155 @@
|
||||
[Added Associations]
|
||||
application/epub+zip=calibre-ebook-viewer.desktop;calibre-ebook-edit.desktop;opencomic.desktop;
|
||||
application/json=notepadqq.desktop;
|
||||
application/octet-stream=nvim.desktop;vim.desktop;emacsclient.desktop;
|
||||
application/pdf=okularApplication_pdf.desktop;google-chrome.desktop;microsoft-edge-beta.desktop;org.inkscape.Inkscape.desktop;chromium.desktop;
|
||||
application/rss+xml=fluent-reader.desktop;
|
||||
application/sql=notepadqq.desktop;nvim.desktop;gvim.desktop;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet=libreoffice-calc.desktop;ms-office-online.desktop;
|
||||
application/x-desktop=nvim.desktop;
|
||||
application/x-extension-htm=google-chrome.desktop;
|
||||
application/x-extension-html=google-chrome.desktop;
|
||||
application/x-extension-shtml=google-chrome.desktop;
|
||||
application/x-extension-xht=google-chrome.desktop;
|
||||
application/x-extension-xhtml=google-chrome.desktop;
|
||||
application/x-ms-dos-executable=wine.desktop;
|
||||
application/x-ms-shortcut=wine.desktop;
|
||||
application/x-yaml=notepadqq.desktop;nvim.desktop;
|
||||
application/xhtml+xml=google-chrome.desktop;microsoft-edge-beta.desktop;qutebrowser.desktop;
|
||||
application/zip=org.gnome.FileRoller.desktop;
|
||||
audio/aac=mpv.desktop;
|
||||
audio/mp4=mpv.desktop;
|
||||
audio/mpeg=mpv.desktop;
|
||||
audio/mpegurl=mpv.desktop;
|
||||
audio/ogg=mpv.desktop;
|
||||
audio/vnd.rn-realaudio=mpv.desktop;
|
||||
audio/vorbis=mpv.desktop;
|
||||
audio/x-flac=mpv.desktop;
|
||||
audio/x-mp3=mpv.desktop;
|
||||
audio/x-mpegurl=mpv.desktop;
|
||||
audio/x-ms-wma=mpv.desktop;
|
||||
audio/x-musepack=mpv.desktop;
|
||||
audio/x-oggflac=mpv.desktop;
|
||||
audio/x-pn-realaudio=mpv.desktop;
|
||||
audio/x-scpls=mpv.desktop;
|
||||
audio/x-vorbis=mpv.desktop;
|
||||
audio/x-vorbis+ogg=mpv.desktop;
|
||||
audio/x-wav=mpv.desktop;
|
||||
image/avif=okularApplication_kimgio.desktop;
|
||||
image/bmp=okularApplication_kimgio.desktop;
|
||||
image/gif=org.gnome.gThumb.desktop;google-chrome.desktop;gimp.desktop;org.kde.gwenview.desktop;okularApplication_kimgio.desktop;
|
||||
image/heif=okularApplication_kimgio.desktop;
|
||||
image/jpeg=okularApplication_kimgio.desktop;
|
||||
image/png=okularApplication_kimgio.desktop;org.gnome.gThumb.desktop;feh.desktop;gimp.desktop;org.kde.gwenview.desktop;
|
||||
image/webp=okularApplication_kimgio.desktop;
|
||||
inode/directory=thunar.desktop;
|
||||
text/csv=libreoffice-calc.desktop;
|
||||
text/html=google-chrome.desktop;
|
||||
text/javascript=notepadqq.desktop;
|
||||
text/plain=notepadqq.desktop;nvim.desktop;vim.desktop;okularApplication_txt.desktop;xed.desktop;
|
||||
text/vnd.trolltech.linguist=mpv.desktop;
|
||||
text/x-log=notepadqq.desktop;
|
||||
video/mp4=mpv.desktop;vlc.desktop;io.github.celluloid_player.Celluloid.desktop;
|
||||
video/webm=mpv.desktop;vlc.desktop;io.github.celluloid_player.Celluloid.desktop;
|
||||
video/x-matroska=mpv.desktop;vlc.desktop;
|
||||
x-scheme-handler/betterdiscord=discord.desktop;
|
||||
x-scheme-handler/bitwarden=bitwarden.desktop;Bitwarden.desktop;
|
||||
x-scheme-handler/chrome=google-chrome.desktop;
|
||||
x-scheme-handler/exodus=Exodus.desktop;
|
||||
x-scheme-handler/geo=google-maps-geo-handler.desktop;
|
||||
x-scheme-handler/http=zen.desktop;firefox.desktop;microsoft-edge-beta.desktop;google-chrome.desktop;
|
||||
x-scheme-handler/https=zen.desktop;firefox.desktop;microsoft-edge-beta.desktop;google-chrome.desktop;
|
||||
x-scheme-handler/mailspring=Mailspring.desktop;
|
||||
x-scheme-handler/mailto=org.mozilla.Thunderbird.desktop;Mailspring.desktop;userapp-Thunderbird-6JYZ12.desktop;
|
||||
x-scheme-handler/mid=userapp-Thunderbird-6JYZ12.desktop;
|
||||
x-scheme-handler/postman=Postman.desktop;
|
||||
x-scheme-handler/ror2mm=r2modman.desktop;
|
||||
x-scheme-handler/ssh=kitty-open.desktop;Termius.desktop;
|
||||
x-scheme-handler/termius=Termius.desktop;
|
||||
x-scheme-handler/tg=org.telegram.desktop.desktop;org.telegram.desktop._f79d601e26a782fd149b3ffb098aae9f.desktop;userapp-Kotatogram Desktop-IP6312.desktop;
|
||||
x-scheme-handler/tonsite=org.telegram.desktop.desktop;
|
||||
x-scheme-handler/tradingview=tradingview.desktop;TradingView.desktop;
|
||||
application/x-wine-extension-ini=nvim.desktop;
|
||||
|
||||
[Default Applications]
|
||||
application/x-extension-htm=google-chrome.desktop
|
||||
application/x-extension-html=google-chrome.desktop
|
||||
application/x-extension-shtml=google-chrome.desktop
|
||||
application/x-extension-xht=google-chrome.desktop
|
||||
application/x-extension-xhtml=google-chrome.desktop
|
||||
audio/aac=mpv.desktop;
|
||||
audio/mp4=mpv.desktop;
|
||||
audio/mpeg=mpv.desktop;
|
||||
audio/mpegurl=mpv.desktop;
|
||||
audio/ogg=mpv.desktop;
|
||||
audio/vnd.rn-realaudio=mpv.desktop;
|
||||
audio/vorbis=mpv.desktop;
|
||||
audio/x-flac=mpv.desktop;
|
||||
audio/x-mp3=mpv.desktop;
|
||||
audio/x-mpegurl=mpv.desktop;
|
||||
audio/x-ms-wma=mpv.desktop;
|
||||
audio/x-musepack=mpv.desktop;
|
||||
audio/x-oggflac=mpv.desktop;
|
||||
audio/x-pn-realaudio=mpv.desktop;
|
||||
audio/x-scpls=mpv.desktop;
|
||||
audio/x-vorbis=mpv.desktop;
|
||||
audio/x-vorbis+ogg=mpv.desktop;
|
||||
audio/x-wav=mpv.desktop;
|
||||
image/avif=okularApplication_kimgio.desktop;
|
||||
image/bmp=okularApplication_kimgio.desktop;
|
||||
image/heif=okularApplication_kimgio.desktop;
|
||||
image/jpeg=okularApplication_kimgio.desktop;
|
||||
image/png=okularApplication_kimgio.desktop;
|
||||
image/webp=okularApplication_kimgio.desktop;
|
||||
inode/directory=Thunar.desktop
|
||||
message/rfc822=userapp-Thunderbird-6JYZ12.desktop
|
||||
text/plain=nvim.desktop;
|
||||
x-scheme-handler/betterdiscord=discord.desktop
|
||||
x-scheme-handler/bitwarden=bitwarden.desktop
|
||||
x-scheme-handler/chrome=chromium.desktop
|
||||
x-scheme-handler/discord-455712169795780630=discord-455712169795780630.desktop
|
||||
x-scheme-handler/discord-712465656758665259=discord-712465656758665259.desktop
|
||||
x-scheme-handler/eclipse+command=_usr_lib_dbeaver_.desktop
|
||||
x-scheme-handler/exodus=Exodus.desktop
|
||||
x-scheme-handler/geo=google-maps-geo-handler.desktop;
|
||||
x-scheme-handler/http=zen.desktop;
|
||||
x-scheme-handler/https=zen.desktop;
|
||||
x-scheme-handler/mailspring=Mailspring.desktop
|
||||
x-scheme-handler/mailto=Mailspring.desktop
|
||||
x-scheme-handler/mid=userapp-Thunderbird-6JYZ12.desktop
|
||||
x-scheme-handler/msteams=teams.desktop
|
||||
x-scheme-handler/postman=Postman.desktop
|
||||
x-scheme-handler/ror2mm=r2modman.desktop
|
||||
x-scheme-handler/ssh=kitty-open.desktop
|
||||
x-scheme-handler/termius=Termius.desktop
|
||||
x-scheme-handler/tg=org.telegram.desktop.desktop
|
||||
x-scheme-handler/tonsite=org.telegram.desktop.desktop
|
||||
x-scheme-handler/tradingview=tradingview.desktop
|
||||
x-scheme-handler/webcal=google-chrome.desktop
|
||||
video/webm=mpv.desktop
|
||||
video/x-matroska=mpv.desktop
|
||||
video/x-ms-wmv=mpv.desktop
|
||||
video/quicktime=mpv.desktop
|
||||
video/x-flv=mpv.desktop
|
||||
video/dv=mpv.desktop
|
||||
video/vnd.avi=mpv.desktop
|
||||
video/x-ogm+ogg=mpv.desktop
|
||||
video/ogg=mpv.desktop
|
||||
video/vnd.rn-realvideo=mpv.desktop
|
||||
video/mp4=mpv.desktop
|
||||
video/mp2t=mpv.desktop
|
||||
video/x-flic=mpv.desktop
|
||||
video/3gpp2=mpv.desktop
|
||||
video/x-theora+ogg=mpv.desktop
|
||||
video/mpeg=mpv.desktop
|
||||
video/vnd.mpegurl=mpv.desktop
|
||||
video/3gpp=mpv.desktop
|
||||
application/json=zen.desktop
|
||||
application/xhtml+xml=zen.desktop
|
||||
application/x-xpinstall=zen.desktop
|
||||
application/xml=zen.desktop
|
||||
application/pdf=zen.desktop
|
||||
text/html=zen.desktop
|
||||
text/vnd.trolltech.linguist=zen.desktop
|
||||
x-scheme-handler/nxm=modorganizer2-nxm-handler.desktop
|
||||
x-scheme-handler/discord-1361252452329848892=discord-1361252452329848892.desktop
|
||||
@@ -14,6 +14,7 @@ deck_name=Minecraft
|
||||
# If you don't have a model for Japanese, get it from
|
||||
# https://tatsumoto.neocities.org/blog/setting-up-anki.html#import-an-example-mining-deck
|
||||
model_name=Lapis
|
||||
# model_name=Kiku
|
||||
|
||||
# Field names as they appear in the selected note type.
|
||||
# If you set `audio_field` or `image_field` empty,
|
||||
|
||||
@@ -131,7 +131,7 @@ return {
|
||||
},
|
||||
-- }}}
|
||||
},
|
||||
strategies = {
|
||||
interactions = {
|
||||
chat = {
|
||||
adapter = "copilot",
|
||||
-- adapter = "openrouter",
|
||||
@@ -182,11 +182,21 @@ return {
|
||||
end,
|
||||
completion_provider = "cmp",
|
||||
},
|
||||
fold_reasoning = true,
|
||||
show_reasoning = true,
|
||||
},
|
||||
inline = {
|
||||
adapter = "copilot",
|
||||
-- adapter = "openrouter",
|
||||
},
|
||||
cmd = {
|
||||
adapter = "copilot",
|
||||
},
|
||||
background = {
|
||||
adapter = {
|
||||
name = "copilot",
|
||||
},
|
||||
},
|
||||
},
|
||||
display = {
|
||||
action_palette = {
|
||||
@@ -232,7 +242,6 @@ return {
|
||||
-- Options for inline diff provider
|
||||
inline = {
|
||||
layout = "buffer", -- float|buffer - Where to display the diff
|
||||
|
||||
diff_signs = {
|
||||
signs = {
|
||||
text = "▌", -- Sign text for normal changes
|
||||
@@ -307,6 +316,31 @@ return {
|
||||
},
|
||||
},
|
||||
},
|
||||
rules = {
|
||||
default = {
|
||||
description = "Collection of common files for all projects",
|
||||
files = {
|
||||
".clinerules",
|
||||
".cursorrules",
|
||||
".goosehints",
|
||||
".rules",
|
||||
".windsurfrules",
|
||||
".github/copilot-instructions.md",
|
||||
"AGENT.md",
|
||||
"AGENTS.md",
|
||||
{ path = "CLAUDE.md", parser = "claude" },
|
||||
{ path = "CLAUDE.local.md", parser = "claude" },
|
||||
{ path = "~/.claude/CLAUDE.md", parser = "claude" },
|
||||
},
|
||||
is_preset = true,
|
||||
},
|
||||
opts = {
|
||||
chat = {
|
||||
enabled = true,
|
||||
default_rules = "default", -- The rule groups to load
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
init = function()
|
||||
require("utils.codecompanion.fidget-spinner"):init()
|
||||
|
||||
@@ -50,7 +50,7 @@ end
|
||||
function M:create_progress_handle(request)
|
||||
local title = " Requesting assistance"
|
||||
.. " ("
|
||||
.. request.data.strategy
|
||||
.. request.data.interaction
|
||||
.. ") from "
|
||||
.. request.data.adapter.formatted_name
|
||||
.. " using "
|
||||
|
||||
@@ -43,10 +43,10 @@ end
|
||||
|
||||
function M:create_progress_handle(request)
|
||||
return progress.handle.create({
|
||||
title = " Requesting assistance (" .. request.data.strategy .. ")",
|
||||
title = " Requesting assistance (" .. request.data.adapter.model .. ")",
|
||||
message = "In progress...",
|
||||
lsp_client = {
|
||||
name = M:llm_role_title(request.data.adapter),
|
||||
name = M:llm_role_title(request.data.adapter.name),
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
44
.config/opencode/oh-my-opencode.jsonc
Normal file
44
.config/opencode/oh-my-opencode.jsonc
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json",
|
||||
"google_auth": true,
|
||||
"agents": {
|
||||
"Sisyphus": {
|
||||
"model": "opencode/glm-4.7-free"
|
||||
},
|
||||
"librarian": {
|
||||
"model": "opencode/glm-4.7-free"
|
||||
},
|
||||
"explore": {
|
||||
"model": "google/antigravity-gemini-3-flash"
|
||||
},
|
||||
"frontend-ui-ux-engineer": {
|
||||
"model": "google/antigravity-gemini-3-pro-high"
|
||||
},
|
||||
"document-writer": {
|
||||
"model": "google/antigravity-gemini-3-flash"
|
||||
},
|
||||
"multimodal-looker": {
|
||||
"model": "google/antigravity-gemini-3-flash"
|
||||
}
|
||||
},
|
||||
"lsp": {
|
||||
"typescript-language-server": {
|
||||
"command": ["typescript-language-server", "--stdio"],
|
||||
"extensions": [".ts", ".tsx"],
|
||||
"priority": 10
|
||||
},
|
||||
"pylsp": {
|
||||
"disabled": true
|
||||
},
|
||||
"pyright": {
|
||||
"command": ["basedpyright-languageserver", "--stdio"],
|
||||
"extensions": [".py"],
|
||||
"priority": 10
|
||||
},
|
||||
"bash-language-server": {
|
||||
"command": ["bash-language-server", "start"],
|
||||
"extensions": [".sh", ".bash"],
|
||||
"priority": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
24
.config/opencode/opencode-notifier.json
Normal file
24
.config/opencode/opencode-notifier.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"sound": false,
|
||||
"notification": true,
|
||||
"timeout": 5,
|
||||
"showProjectName": true,
|
||||
"events": {
|
||||
"permission": { "sound": false, "notification": true },
|
||||
"complete": { "sound": false, "notification": true },
|
||||
"error": { "sound": false, "notification": true },
|
||||
"question": { "sound": false, "notification": true }
|
||||
},
|
||||
"messages": {
|
||||
"permission": "Session needs permission",
|
||||
"complete": "Session has finished",
|
||||
"error": "Session encountered an error",
|
||||
"question": "Session has a question"
|
||||
},
|
||||
"sounds": {
|
||||
"permission": "/path/to/custom/sound.wav",
|
||||
"complete": "/path/to/custom/sound.wav",
|
||||
"error": "/path/to/custom/sound.wav",
|
||||
"question": "/path/to/custom/sound.wav"
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"theme": "catppuccin",
|
||||
"model": "github-copilot/gpt-5.1",
|
||||
"provider": {
|
||||
"openai": {
|
||||
"models": {
|
||||
"gpt-5": {
|
||||
"options": {
|
||||
"reasoningEffort": "high",
|
||||
"textVerbosity": "low",
|
||||
"reasoningSummary": "auto",
|
||||
"include": ["reasoning.encrypted_content"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"agent": {
|
||||
"build": {
|
||||
"mode": "primary",
|
||||
"model": "github-copilot/gpt-5.1",
|
||||
"tools": {
|
||||
"write": true,
|
||||
"edit": true,
|
||||
"bash": true
|
||||
}
|
||||
},
|
||||
"plan": {
|
||||
"mode": "primary",
|
||||
"model": "github-copilot/gpt-5.1-codex",
|
||||
"tools": {
|
||||
"write": false,
|
||||
"edit": false,
|
||||
"bash": false
|
||||
}
|
||||
},
|
||||
"code-reviewer": {
|
||||
"description": "Reviews code for best practices and potential issues",
|
||||
"mode": "subagent",
|
||||
"model": "github-copilot/gpt-5.1",
|
||||
"prompt": "You are a code reviewer. Focus on security, performance, and maintainability.",
|
||||
"tools": {
|
||||
"write": false,
|
||||
"edit": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
225
.config/opencode/opencode.jsonc
Normal file
225
.config/opencode/opencode.jsonc
Normal file
@@ -0,0 +1,225 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugin": [
|
||||
"opencode-openai-codex-auth",
|
||||
"opencode-antigravity-auth@beta",
|
||||
"@mohak34/opencode-notifier@latest",
|
||||
"oh-my-opencode"
|
||||
],
|
||||
"provider": {
|
||||
"openai": {
|
||||
"name": "OpenAI",
|
||||
"options": {
|
||||
"reasoningEffort": "medium",
|
||||
"reasoningSummary": "auto",
|
||||
"textVerbosity": "medium",
|
||||
"include": [
|
||||
"reasoning.encrypted_content"
|
||||
],
|
||||
"store": false
|
||||
},
|
||||
"models": {
|
||||
"gpt-5.2": {
|
||||
"name": "GPT 5.2 (OAuth)",
|
||||
"limit": {
|
||||
"context": 272000,
|
||||
"output": 128000
|
||||
},
|
||||
"modalities": {
|
||||
"input": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"output": [
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"variants": {
|
||||
"none": {
|
||||
"reasoningEffort": "none",
|
||||
"reasoningSummary": "auto",
|
||||
"textVerbosity": "medium"
|
||||
},
|
||||
"low": {
|
||||
"reasoningEffort": "low",
|
||||
"reasoningSummary": "auto",
|
||||
"textVerbosity": "medium"
|
||||
},
|
||||
"medium": {
|
||||
"reasoningEffort": "medium",
|
||||
"reasoningSummary": "auto",
|
||||
"textVerbosity": "medium"
|
||||
},
|
||||
"high": {
|
||||
"reasoningEffort": "high",
|
||||
"reasoningSummary": "detailed",
|
||||
"textVerbosity": "medium"
|
||||
},
|
||||
"xhigh": {
|
||||
"reasoningEffort": "xhigh",
|
||||
"reasoningSummary": "detailed",
|
||||
"textVerbosity": "medium"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gpt-5.2-codex": {
|
||||
"name": "GPT 5.2 Codex (OAuth)",
|
||||
"limit": {
|
||||
"context": 272000,
|
||||
"output": 128000
|
||||
},
|
||||
"modalities": {
|
||||
"input": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"output": [
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"variants": {
|
||||
"low": {
|
||||
"reasoningEffort": "low",
|
||||
"reasoningSummary": "auto",
|
||||
"textVerbosity": "medium"
|
||||
},
|
||||
"medium": {
|
||||
"reasoningEffort": "medium",
|
||||
"reasoningSummary": "auto",
|
||||
"textVerbosity": "medium"
|
||||
},
|
||||
"high": {
|
||||
"reasoningEffort": "high",
|
||||
"reasoningSummary": "detailed",
|
||||
"textVerbosity": "medium"
|
||||
},
|
||||
"xhigh": {
|
||||
"reasoningEffort": "xhigh",
|
||||
"reasoningSummary": "detailed",
|
||||
"textVerbosity": "medium"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gpt-5.1-codex-max": {
|
||||
"name": "GPT 5.1 Codex Max (OAuth)",
|
||||
"limit": {
|
||||
"context": 272000,
|
||||
"output": 128000
|
||||
},
|
||||
"modalities": {
|
||||
"input": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"output": [
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"variants": {
|
||||
"low": {
|
||||
"reasoningEffort": "low",
|
||||
"reasoningSummary": "detailed",
|
||||
"textVerbosity": "medium"
|
||||
},
|
||||
"medium": {
|
||||
"reasoningEffort": "medium",
|
||||
"reasoningSummary": "detailed",
|
||||
"textVerbosity": "medium"
|
||||
},
|
||||
"high": {
|
||||
"reasoningEffort": "high",
|
||||
"reasoningSummary": "detailed",
|
||||
"textVerbosity": "medium"
|
||||
},
|
||||
"xhigh": {
|
||||
"reasoningEffort": "xhigh",
|
||||
"reasoningSummary": "detailed",
|
||||
"textVerbosity": "medium"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"google": {
|
||||
"name": "Google",
|
||||
"models": {
|
||||
"antigravity-gemini-3-pro-high": {
|
||||
"name": "Gemini 3 Pro High (Antigravity)",
|
||||
"thinking": true,
|
||||
"attachment": true,
|
||||
"limit": {
|
||||
"context": 1048576,
|
||||
"output": 65535
|
||||
},
|
||||
"modalities": {
|
||||
"input": [
|
||||
"text",
|
||||
"image",
|
||||
"pdf"
|
||||
],
|
||||
"output": [
|
||||
"text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"antigravity-gemini-3-pro-low": {
|
||||
"name": "Gemini 3 Pro Low (Antigravity)",
|
||||
"thinking": true,
|
||||
"attachment": true,
|
||||
"limit": {
|
||||
"context": 1048576,
|
||||
"output": 65535
|
||||
},
|
||||
"modalities": {
|
||||
"input": [
|
||||
"text",
|
||||
"image",
|
||||
"pdf"
|
||||
],
|
||||
"output": [
|
||||
"text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"antigravity-gemini-3-flash": {
|
||||
"name": "Gemini 3 Flash (Antigravity)",
|
||||
"attachment": true,
|
||||
"limit": {
|
||||
"context": 1048576,
|
||||
"output": 65536
|
||||
},
|
||||
"modalities": {
|
||||
"input": [
|
||||
"text",
|
||||
"image",
|
||||
"pdf"
|
||||
],
|
||||
"output": [
|
||||
"text"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"theme": "catppuccin-macchiato",
|
||||
"share": "manual",
|
||||
"formatter": {
|
||||
"prettier": {
|
||||
"disabled": false
|
||||
},
|
||||
"ruff": {
|
||||
"disabled": false
|
||||
}
|
||||
},
|
||||
"instructions": [
|
||||
"AGENTS.md",
|
||||
"CONTRIBUTING.md",
|
||||
"docs/guidelines.md",
|
||||
".cursor/rules/*.md"
|
||||
],
|
||||
"permission": {
|
||||
"edit": "ask",
|
||||
"bash": "ask"
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
"Pihole2 - https://pihole2.suda.codes/admin",
|
||||
"Proxmox - https://thebox.unicorn-ilish.ts.net",
|
||||
"qBittorrent - https://qbittorrent.suda.codes",
|
||||
"qui - https://qui.suda.codes",
|
||||
"qui - http://pve-main:7476",
|
||||
"Plausible - https://plausible.sudacode.com",
|
||||
"Paperless - https://paperless.suda.codes",
|
||||
"Prometheus - http://prometheus:9090",
|
||||
|
||||
23
.config/rofi/scripts/rofi-anki-script.sh
Executable file
23
.config/rofi/scripts/rofi-anki-script.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CHOICES=(
|
||||
"1. Screenshot (Kiku)"
|
||||
"2. Screenshot (Luna)"
|
||||
"3. Record Audio"
|
||||
)
|
||||
CHOICE=$(printf "%s\n" "${CHOICES[@]}" | rofi -dmenu -i -theme "$HOME/.config/rofi/launchers/type-2/style-2.rasi" -theme-str 'window {width: 25%;} listview {columns: 1; lines: 5;}' -p "Select an option")
|
||||
|
||||
case "$CHOICE" in
|
||||
"1. Screenshot (Kiku)")
|
||||
PICTURE_FIELD=Picture "$HOME/projects/scripts/screenshot-anki.sh"
|
||||
;;
|
||||
"2. Screenshot (Luna)")
|
||||
PICTURE_FIELD=screenshot "$HOME/projects/scripts/screenshot-anki.sh"
|
||||
;;
|
||||
"3. Record Audio")
|
||||
"$HOME/projects/scripts/record-audio.sh"
|
||||
;;
|
||||
*)
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -1,9 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/rofi-menu-helpers.sh"
|
||||
|
||||
BROWSER=/usr/bin/zen-browser
|
||||
OPTIONS=(
|
||||
"Arch Linux (btw)"
|
||||
"Hyprland"
|
||||
DOC_GROUPS=(
|
||||
"Arch Linux (btw)|ARCH"
|
||||
"Hyprland|HYPRLAND"
|
||||
)
|
||||
ARCH=(
|
||||
"Archlinux Wiki|https://wiki.archlinux.org/title/Main_page"
|
||||
@@ -13,56 +16,37 @@ HYPRLAND=(
|
||||
"Hyprland Window Rules|https://wiki.hypr.land/Configuring/Window-Rules/"
|
||||
)
|
||||
|
||||
get_url() {
|
||||
urls=("$@")
|
||||
display_urls=()
|
||||
declare -A url_map
|
||||
for url in "${urls[@]}"; do
|
||||
display_urls+=("${url%%|*}")
|
||||
label="${url%%|*}"
|
||||
url_map["$label"]="${url##*|}"
|
||||
done
|
||||
display_urls+=("Back")
|
||||
url_map["Back"]="Back"
|
||||
|
||||
selection="$(printf "%s\n" "${display_urls[@]}" | rofi -theme-str 'window {width: 25%;} listview {columns: 1; lines: 10;}' -theme ~/.config/rofi/launchers/type-2/style-2.rasi -dmenu -l 5 -i -p "Select Documentation")"
|
||||
url="${url_map[$selection]}"
|
||||
|
||||
if [ -z "$url" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf "%s\n" "$url"
|
||||
select_group() {
|
||||
rofi_select_label_value "Select Documentation Group" DOC_GROUPS
|
||||
}
|
||||
|
||||
get_docs_list() {
|
||||
selection="$(printf "%s\n" "${OPTIONS[@]}" | rofi -theme-str 'window {width: 25%;} listview {columns: 1; lines: 10;}' -theme ~/.config/rofi/launchers/type-2/style-2.rasi -dmenu -l 5 -i -p "Select Documentation Group")"
|
||||
case "$selection" in
|
||||
"Arch Linux (btw)")
|
||||
urls=("${ARCH[@]}")
|
||||
;;
|
||||
"Hyprland")
|
||||
urls=("${HYPRLAND[@]}")
|
||||
;;
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
printf "%s\n" "${urls[@]}"
|
||||
select_url() {
|
||||
local urls_array="$1"
|
||||
rofi_select_label_value "Select Documentation" "$urls_array" "Back"
|
||||
}
|
||||
|
||||
main() {
|
||||
mapfile -t urls < <(get_docs_list)
|
||||
url="$(get_url "${urls[@]}")"
|
||||
if [ -z "$url" ]; then
|
||||
printf "No URL selected.\n"
|
||||
while true; do
|
||||
group_key="$(select_group)" || exit 0
|
||||
case "$group_key" in
|
||||
ARCH)
|
||||
urls_ref=ARCH
|
||||
;;
|
||||
HYPRLAND)
|
||||
urls_ref=HYPRLAND
|
||||
;;
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
selection="$(select_url "$urls_ref")" || exit 0
|
||||
if [[ "$selection" == "Back" ]]; then
|
||||
continue
|
||||
fi
|
||||
$BROWSER "$selection" &>/dev/null &
|
||||
exit 0
|
||||
elif [ "$url" == "Back" ]; then
|
||||
main
|
||||
exit 0
|
||||
fi
|
||||
$BROWSER "$url" &>/dev/null &
|
||||
done
|
||||
}
|
||||
|
||||
main
|
||||
|
||||
89
.config/rofi/scripts/rofi-jellyfin-dir.sh
Executable file
89
.config/rofi/scripts/rofi-jellyfin-dir.sh
Executable file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
BASE_DIR="/truenas/jellyfin"
|
||||
|
||||
. "$HOME/.config/rofi/scripts/rofi-menu-helpers.sh"
|
||||
|
||||
ACTION="xdg-open"
|
||||
|
||||
# Theme for icon display
|
||||
ICON_THEME="$HOME/.config/rofi/launchers/type-3/style-4.rasi"
|
||||
ICON_THEME_STR='configuration {show-icons: true; icon-size: 128; dpi: 96;} window {width: 50%; height: 60%;} listview {columns: 3; lines: 5;}'
|
||||
|
||||
# Map display names to actual directory names
|
||||
declare -A DIR_MAP=(
|
||||
["Anime"]="anime"
|
||||
["Movies"]="movies"
|
||||
["Manga"]="manga"
|
||||
["TV"]="tv"
|
||||
["YouTube"]="youtube"
|
||||
["Books"]="books"
|
||||
["Podcasts"]="podcasts"
|
||||
["Audiobooks"]="audiobooks"
|
||||
)
|
||||
|
||||
DIRS=(
|
||||
"Anime"
|
||||
"Movies"
|
||||
"Manga"
|
||||
"TV"
|
||||
"YouTube"
|
||||
"Books"
|
||||
"Podcasts"
|
||||
"Audiobooks"
|
||||
)
|
||||
|
||||
# Select top-level category
|
||||
CHOICE=$(rofi_select_list "Select a category" DIRS) || exit 1
|
||||
|
||||
# Get the actual directory name
|
||||
ACTUAL_DIR="${DIR_MAP[$CHOICE]}"
|
||||
TARGET_DIR="$BASE_DIR/$ACTUAL_DIR"
|
||||
|
||||
if [[ ! -d "$TARGET_DIR" ]]; then
|
||||
notify-send -u critical "Jellyfin Browser" "Directory not found: $TARGET_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build rofi entries with folder.jpg icons
|
||||
build_icon_menu() {
|
||||
local dir="$1"
|
||||
local entries=""
|
||||
|
||||
while IFS= read -r -d '' subdir; do
|
||||
local name
|
||||
name="$(basename "$subdir")"
|
||||
local icon="$subdir/folder.jpg"
|
||||
|
||||
# Check for folder.jpg, fallback to folder.png, then no icon
|
||||
if [[ -f "$icon" ]]; then
|
||||
entries+="${name}\0icon\x1f${icon}\n"
|
||||
elif [[ -f "$subdir/folder.png" ]]; then
|
||||
entries+="${name}\0icon\x1f${subdir}/folder.png\n"
|
||||
else
|
||||
entries+="${name}\n"
|
||||
fi
|
||||
done < <(find "$dir" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z)
|
||||
|
||||
printf "%b" "$entries"
|
||||
}
|
||||
|
||||
# Show subdirectories with icons
|
||||
SELECTION=$(build_icon_menu "$TARGET_DIR" | rofi -dmenu -i -no-custom \
|
||||
-theme "$ICON_THEME" \
|
||||
-theme-str "$ICON_THEME_STR" \
|
||||
-p "Select from $CHOICE") || exit 1
|
||||
|
||||
# Full path to selected item
|
||||
SELECTED_PATH="$TARGET_DIR/$SELECTION"
|
||||
|
||||
if [[ -d "$SELECTED_PATH" ]]; then
|
||||
# Open in file manager or do something with it
|
||||
# You can customize this action as needed
|
||||
$ACTION "$SELECTED_PATH" &>/dev/null &
|
||||
else
|
||||
notify-send -u critical "Jellyfin Browser" "Path not found: $SELECTED_PATH"
|
||||
exit 1
|
||||
fi
|
||||
12
.config/rofi/scripts/rofi-launch-texthooker-steam.sh
Executable file
12
.config/rofi/scripts/rofi-launch-texthooker-steam.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PROGRAM="$HOME/S/lutris/wineprefix/drive_c/users/steamuser/luna-translator/LunaTranslator.exe"
|
||||
SELECTION="$(protontricks -l | tail -n +2 | rofi -dmenu -theme ~/.config/rofi/launchers/type-2/style-2.rasi -theme-str 'listview {lines: 12; columns: 1;}' -i -p "Select game" | awk '{print $NF}' | tr -d '()')"
|
||||
|
||||
if [[ -z "$SELECTION" ]]; then
|
||||
printf "%s\n" "No game selected"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "%s\n" "Launching $PROGRAM for game ID: $SELECTION"
|
||||
protontricks-launch --appid "$SELECTION" "$PROGRAM" &>/dev/null &
|
||||
68
.config/rofi/scripts/rofi-menu-helpers.sh
Normal file
68
.config/rofi/scripts/rofi-menu-helpers.sh
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Lightweight helpers to build rofi menus with label/value pairs.
|
||||
# Intended to be sourced from other scripts.
|
||||
|
||||
# Allow callers to override theme/args without touching code.
|
||||
: "${ROFI_THEME:=$HOME/.config/rofi/launchers/type-2/style-2.rasi}"
|
||||
: "${ROFI_THEME_STR:="window {width: 25%;} listview {columns: 1; lines: 10;}"}"
|
||||
: "${ROFI_DMENU_ARGS:=-i -l 5}"
|
||||
|
||||
# rofi_menu prompt option...
|
||||
# Prints the selected option to stdout and propagates the rofi exit code
|
||||
# (1 when the user cancels).
|
||||
rofi_menu() {
|
||||
local prompt="$1"
|
||||
shift
|
||||
local -a options=("$@")
|
||||
|
||||
local selection
|
||||
selection="$(printf "%s\n" "${options[@]}" | rofi -dmenu $ROFI_DMENU_ARGS \
|
||||
${ROFI_THEME:+-theme "$ROFI_THEME"} \
|
||||
${ROFI_THEME_STR:+-theme-str "$ROFI_THEME_STR"} \
|
||||
-p "$prompt")"
|
||||
local status=$?
|
||||
[[ $status -ne 0 ]] && return "$status"
|
||||
printf "%s\n" "$selection"
|
||||
}
|
||||
|
||||
# rofi_select_label_value prompt array_name [back_label]
|
||||
# array_name should contain entries shaped as "Label|Value".
|
||||
# Prints the mapped value (or the back label when chosen). Returns 1 on cancel.
|
||||
rofi_select_label_value() {
|
||||
local prompt="$1"
|
||||
local array_name="$2"
|
||||
local back_label="${3:-}"
|
||||
|
||||
# Access caller's array by name
|
||||
local -n kv_source="$array_name"
|
||||
local -A kv_map=()
|
||||
local -a display=()
|
||||
|
||||
for entry in "${kv_source[@]}"; do
|
||||
local label="${entry%%|*}"
|
||||
local value="${entry#*|}"
|
||||
kv_map["$label"]="$value"
|
||||
display+=("$label")
|
||||
done
|
||||
|
||||
if [[ -n "$back_label" ]]; then
|
||||
kv_map["$back_label"]="$back_label"
|
||||
display+=("$back_label")
|
||||
fi
|
||||
|
||||
local selection
|
||||
selection="$(rofi_menu "$prompt" "${display[@]}")" || return "$?"
|
||||
[[ -z "$selection" ]] && return 1
|
||||
printf "%s\n" "${kv_map[$selection]}"
|
||||
}
|
||||
|
||||
# rofi_select_list prompt array_name
|
||||
# Convenience wrapper for plain lists (no label/value mapping).
|
||||
rofi_select_list() {
|
||||
local prompt="$1"
|
||||
local array_name="$2"
|
||||
local -n list_source="$array_name"
|
||||
rofi_menu "$prompt" "${list_source[@]}"
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ THEME="$HOME/.config/rofi/launchers/type-3/style-4.rasi"
|
||||
DIR="$HOME/Pictures/wallpapers/favorites"
|
||||
SELECTED_WALL=$(cd "$DIR" && for a in *.jpg *.png; do echo -en "$a\0icon\x1f$a\n"; done | rofi -dmenu -i -no-custom -theme "$THEME" -p "Select a wallpaper" -theme-str 'configuration {icon-size: 128; dpi: 96;} window {width: 45%; height: 45%;}')
|
||||
PTH="$(printf "%s" "$DIR/$SELECTED_WALL" | tr -s '/')"
|
||||
hyprctl hyprpaper wallpaper "DP-1, $PTH"
|
||||
notify-send -a "rofi-wallpaper" "Wallpaper set to" -i "$PTH" "$PTH"
|
||||
hyprctl hyprpaper preload "$PTH"
|
||||
hyprctl hyprpaper wallpaper "DP-1,$PTH"
|
||||
hyprctl hyprpaper unload "$(cat "$HOME/.wallpaper")"
|
||||
echo "$PTH" >"$HOME/.wallpaper"
|
||||
|
||||
@@ -15,6 +15,7 @@ export QT_QPA_PLATFORMTHEME=qt5ct
|
||||
export GTK_THEME=Dracula
|
||||
export XDG_CONFIG_HOME=$HOME/.config
|
||||
export COMPOSE_BAKE=true
|
||||
export ANKI_WAYLAND=1
|
||||
|
||||
# nvidia
|
||||
export NVD_BACKEND=direct
|
||||
|
||||
@@ -28,6 +28,7 @@ setopt HIST_IGNORE_SPACE # don’t record commands that start with a space
|
||||
FPATH="$HOME/.docker/completions:$FPATH"
|
||||
|
||||
[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path zsh)"
|
||||
eval "$(fnm env --use-on-cd --shell zsh)"
|
||||
|
||||
bindkey -v
|
||||
# zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# RES="$(slurp | grim -g - - | gazou | sed '1d;$d')"
|
||||
# # Truncate RES for display if it's longer than 100 characters
|
||||
# DISPLAY_RES="${RES:0:100}"
|
||||
# if [ ${#RES} -gt 100 ]; then
|
||||
# DISPLAY_RES="${DISPLAY_RES}..."
|
||||
# fi
|
||||
# notify-send "GAZOU" "Text: $DISPLAY_RES"
|
||||
# echo "$RES" | wl-copy
|
||||
|
||||
slurp | grim -g - /tmp/ocr.png || exit 1
|
||||
transformers_ocr recognize --image-path /tmp/ocr.png || exit 1
|
||||
notify-send "tramsformers_ocr" "Text: $DISPLAY_RES"
|
||||
if ! pgrep -af owocr; then
|
||||
notify-send "ocr.sh" "Starting owocr daemon..."
|
||||
owocr -e meikiocr -r clipboard -w clipboard -l ja -n &>/dev/null &
|
||||
fi
|
||||
slurp | grim -g - - | wl-copy
|
||||
notify-send "ocr.sh" "Text: $DISPLAY_RES"
|
||||
|
||||
300
projects/scripts/popup-ai-translator.py
Executable file
300
projects/scripts/popup-ai-translator.py
Executable file
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Japanese Learning Assistant using OpenRouter API
|
||||
Uses Google Gemini Flash 2.0 for AJATT-aligned Japanese analysis
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
# Configuration
|
||||
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
|
||||
MODEL = os.environ.get("OPENROUTER_MODEL", "google/gemini-2.0-flash-001")
|
||||
API_URL = "https://openrouter.ai/api/v1/chat/completions"
|
||||
|
||||
# Try to load API key from file if not in environment
|
||||
if not OPENROUTER_API_KEY:
|
||||
key_file = os.path.expanduser("~/.openrouterapikey")
|
||||
if os.path.isfile(key_file):
|
||||
with open(key_file, "r") as f:
|
||||
OPENROUTER_API_KEY = f.read().strip()
|
||||
|
||||
TRANSLATION_PROMPT = """You are my Japanese-learning assistant. Help me acquire Japanese through deep, AJATT-aligned analysis.
|
||||
|
||||
For every input, output exactly using clean plain text formatting:
|
||||
|
||||
═══════════════════════════════════════
|
||||
1. JAPANESE INPUT
|
||||
═══════════════════════════════════════
|
||||
|
||||
Repeat the original text exactly. Correct only critical OCR/punctuation errors.
|
||||
|
||||
═══════════════════════════════════════
|
||||
2. NATURAL ENGLISH TRANSLATION
|
||||
═══════════════════════════════════════
|
||||
|
||||
Accurate and natural. Preserve tone, formality, and nuance. Avoid literalism.
|
||||
|
||||
═══════════════════════════════════════
|
||||
3. BREAKDOWN
|
||||
═══════════════════════════════════════
|
||||
|
||||
For each token, provide a breakdown of the vocabulary, grammar, and nuance.
|
||||
|
||||
▸ Vocabulary: Part of speech + concise definition
|
||||
▸ Grammar: Particles, conjugations, constructions (contextual usage)
|
||||
▸ Nuance: Implied meaning, connotation, emotional tone, differences from similar expressions
|
||||
|
||||
Core Principles:
|
||||
|
||||
• Preserve native phrasing—never oversimplify
|
||||
• Highlight subtle grammar, register shifts, and pragmatic implications
|
||||
• Encourage pattern recognition; provide contrastive examples (e.g., ~のに vs ~けど)
|
||||
• Focus on real Japanese usage
|
||||
|
||||
Rules:
|
||||
|
||||
• English explanations only (no romaji)
|
||||
• Use the section dividers shown above (═══) for major sections
|
||||
• Use ▸ for sub-items and • for bullet points
|
||||
• Put Japanese terms in 「brackets」
|
||||
• No filler text
|
||||
|
||||
Optional Additions (only when valuable):
|
||||
|
||||
• Synonyms, formality/register notes, cultural insights, common mistakes, extra native examples
|
||||
|
||||
Goal: Deep comprehension, natural grammar internalization, nuanced vocabulary, progress toward Japanese-only understanding."""
|
||||
|
||||
GRAMMAR_PROMPT = """
|
||||
You are a Japanese grammar analysis assistant.
|
||||
|
||||
The user will provide Japanese sentences. Your task is to explain the **grammar and structure in English**, prioritizing how the sentence is constructed rather than translating word-for-word.
|
||||
|
||||
Rules:
|
||||
|
||||
* Always show the original Japanese first.
|
||||
* Provide a short, natural English gloss only if helpful.
|
||||
* Explain grammar patterns, verb forms, particles, and omissions.
|
||||
* Emphasize nuance, implication, and speaker intent.
|
||||
* Avoid unnecessary vocabulary definitions unless they affect the grammar.
|
||||
* Assume the user is actively studying Japanese and wants deep understanding.
|
||||
|
||||
Your explanations should help the user internalize patterns, not memorize translations.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def show_error(message: str) -> None:
|
||||
"""Display an error dialog using zenity."""
|
||||
subprocess.run(
|
||||
["zenity", "--error", "--text", message, "--title", "Error"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
|
||||
def get_clipboard() -> str:
|
||||
"""Get clipboard contents using wl-paste or xclip."""
|
||||
# Try wl-paste first (Wayland)
|
||||
result = subprocess.run(
|
||||
["wl-paste", "--no-newline"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
|
||||
# Fall back to xclip (X11)
|
||||
result = subprocess.run(
|
||||
["xclip", "-selection", "clipboard", "-o"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def get_input() -> str | None:
|
||||
"""Get input from user via zenity entry dialog, pre-populated with clipboard."""
|
||||
clipboard = get_clipboard()
|
||||
|
||||
cmd = [
|
||||
"zenity",
|
||||
"--entry",
|
||||
"--title",
|
||||
"Japanese Assistant",
|
||||
"--text",
|
||||
"Enter Japanese text to analyze (press Enter to send):",
|
||||
"--width",
|
||||
"600",
|
||||
]
|
||||
|
||||
if clipboard:
|
||||
cmd.extend(["--entry-text", clipboard])
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def get_mode() -> str | None:
|
||||
"""Ask user to choose between translation and grammar assistant."""
|
||||
result = subprocess.run(
|
||||
[
|
||||
"zenity",
|
||||
"--list",
|
||||
"--title",
|
||||
"Japanese Assistant",
|
||||
"--text",
|
||||
"Choose analysis mode:",
|
||||
"--column=Mode",
|
||||
"Translation (full analysis)",
|
||||
"Grammar (structure focus)",
|
||||
],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def show_notification(message: str, body: str) -> subprocess.Popen:
|
||||
"""Show a notification using notify-send."""
|
||||
return subprocess.Popen(
|
||||
["notify-send", "-t", "0", "-a", "Japanese Assistant", message, body],
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
|
||||
def close_notification() -> None:
|
||||
"""Close the processing notification."""
|
||||
subprocess.run(
|
||||
["pkill", "-f", "notify-send.*Processing.*Analyzing"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
|
||||
def display_result(content: str) -> None:
|
||||
"""Display the result in a zenity text-info dialog."""
|
||||
subprocess.run(
|
||||
[
|
||||
"zenity",
|
||||
"--text-info",
|
||||
"--title",
|
||||
"Japanese Analysis",
|
||||
"--width",
|
||||
"900",
|
||||
"--height",
|
||||
"700",
|
||||
"--font",
|
||||
"monospace 11",
|
||||
],
|
||||
input=content,
|
||||
text=True,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
|
||||
def make_api_request(user_input: str, system_prompt: str) -> dict:
|
||||
"""Make the API request to OpenRouter."""
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
|
||||
"HTTP-Referer": "https://github.com/sudacode/scripts",
|
||||
"X-Title": "Japanese Learning Assistant",
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": MODEL,
|
||||
"messages": [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_input},
|
||||
],
|
||||
"temperature": 0.7,
|
||||
}
|
||||
|
||||
response = requests.post(API_URL, headers=headers, json=payload, timeout=60)
|
||||
return response.json()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
# Check for API key
|
||||
if not OPENROUTER_API_KEY:
|
||||
show_error("OPENROUTER_API_KEY environment variable is not set.")
|
||||
return 1
|
||||
|
||||
# Ask user for mode
|
||||
mode = get_mode()
|
||||
if not mode:
|
||||
return 0
|
||||
|
||||
# Select appropriate prompt
|
||||
if "Grammar" in mode:
|
||||
system_prompt = GRAMMAR_PROMPT
|
||||
else:
|
||||
system_prompt = TRANSLATION_PROMPT
|
||||
|
||||
# Get input from user
|
||||
user_input = get_input()
|
||||
if not user_input:
|
||||
return 0
|
||||
|
||||
# Show loading notification
|
||||
show_notification("Processing...", f"Analyzing: {user_input[:50]}...")
|
||||
|
||||
try:
|
||||
# Make API request
|
||||
response = make_api_request(user_input, system_prompt)
|
||||
|
||||
# Close loading notification
|
||||
close_notification()
|
||||
|
||||
# Check for errors in response
|
||||
if "error" in response:
|
||||
error_msg = response["error"].get("message", "Unknown error")
|
||||
show_error(error_msg)
|
||||
return 1
|
||||
|
||||
# Extract content from response
|
||||
try:
|
||||
content = response["choices"][0]["message"]["content"]
|
||||
except (KeyError, IndexError):
|
||||
show_error("Failed to parse API response")
|
||||
return 1
|
||||
|
||||
if not content:
|
||||
show_error("Empty response from API")
|
||||
return 1
|
||||
|
||||
# Display result
|
||||
display_result(content)
|
||||
|
||||
except requests.RequestException as e:
|
||||
close_notification()
|
||||
show_error(f"API request failed: {e}")
|
||||
return 1
|
||||
except Exception as e:
|
||||
close_notification()
|
||||
show_error(f"Error: {e}")
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
226
projects/scripts/record-audio.sh
Normal file → Executable file
226
projects/scripts/record-audio.sh
Normal file → Executable file
@@ -1,93 +1,145 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Version 1.2
|
||||
# shoutout to https://gist.github.com/Cephian/f849e326e3522be9a4386b60b85f2f23 for the original script,
|
||||
# https://github.com/xythh/ added the ankiConnect functionality
|
||||
# toggle record computer audio (run once to start, run again to stop)
|
||||
# dependencies: ffmpeg, pulseaudio, curl
|
||||
# Toggle desktop audio recording and attach the result to the newest Anki note
|
||||
# (as tagged by Yomichan). Run once to start recording, run again to stop.
|
||||
# Dependencies: jq, curl, ffmpeg/ffprobe, pulseaudio (parec+pactl), bc, notify-send
|
||||
|
||||
# where recording gets saved, gets deleted after being imported to anki
|
||||
DIRECTORY="$HOME/.cache/"
|
||||
FORMAT="mp3" # ogg or mp3
|
||||
# cut file since it glitches a bit at the end sometimes
|
||||
CUT_DURATION="0.1"
|
||||
#port used by ankiconnect
|
||||
ankiConnectPort="8765"
|
||||
# gets the newest created card, so make sure to create the card first with yomichan
|
||||
newestNoteId=$(curl -s localhost:$ankiConnectPort -X POST -d '{"action": "findNotes", "version": 6, "params": { "query": "is:new"}}' | jq '.result[-1]')
|
||||
#Audio field name
|
||||
audioFieldName="SentenceAudio"
|
||||
set -euo pipefail
|
||||
|
||||
#if there is no newest note, you either have a complete empty anki or ankiconnect isn't running
|
||||
if [ "$newestNoteId" = "" ]; then
|
||||
notify-send "anki connect not found"
|
||||
exit 1
|
||||
fi
|
||||
ANKI_CONNECT_PORT="${ANKI_CONNECT_PORT:-8765}"
|
||||
AUDIO_FIELD_NAME="${AUDIO_FIELD_NAME:-SentenceAudio}"
|
||||
FORMAT="${FORMAT:-mp3}" # mp3 or ogg
|
||||
CUT_DURATION="${CUT_DURATION:-0.1}"
|
||||
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/record-audio"
|
||||
RECORD_TIMEOUT="${RECORD_TIMEOUT:-60}"
|
||||
ANKI_URL="http://localhost:${ANKI_CONNECT_PORT}"
|
||||
|
||||
if pgrep -f "parec"; then
|
||||
pkill -f "parec"
|
||||
else
|
||||
time=$(date +%s)
|
||||
name="$DIRECTORY/$time"
|
||||
wav_file="$name.wav"
|
||||
out_file="$name.$FORMAT"
|
||||
|
||||
if ! [ -d "$DIRECTORY" ]; then
|
||||
mkdir "$DIRECTORY"
|
||||
fi
|
||||
notify-send -t 1000 "Audio recording started"
|
||||
#timeout 1m arecord -t wav -f cd "$wav_file"
|
||||
|
||||
# just grabs last running source... may not always work if your pulseaudio setup is complicated
|
||||
if ! timeout 1m parec -d"$(pactl list sinks | grep -B1 'State: RUNNING' | sed -nE 's/Sink #(.*)/\1/p' | tail -n 1)" --file-format=wav "$wav_file"; then
|
||||
|
||||
notify-send "Error recording " "most likely no audio playing"
|
||||
rm "$wav_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
input_duration=$(ffprobe -v error -select_streams a:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 "$wav_file")
|
||||
output_duration=$(echo "$input_duration"-"$CUT_DURATION" | bc)
|
||||
|
||||
# encode file and delete OG
|
||||
if [ $FORMAT = "ogg" ]; then
|
||||
ffmpeg -i "$wav_file" -vn -codec:a libvorbis -b:a 64k -t "$output_duration" "$out_file"
|
||||
elif [ $FORMAT = "mp3" ]; then
|
||||
ffmpeg -i "$wav_file" -vn -codec:a libmp3lame -qscale:a 1 -t "$output_duration" "$out_file"
|
||||
else
|
||||
notify-send "Record Error" "Unknown format $FORMAT"
|
||||
fi
|
||||
rm "$wav_file"
|
||||
|
||||
# Update newest note with recorded audio
|
||||
curl -s localhost:$ankiConnectPort -X POST -d '{
|
||||
|
||||
"action": "updateNoteFields",
|
||||
"version": 6,
|
||||
"params": {
|
||||
"note": {
|
||||
"id": '"$newestNoteId"',
|
||||
"fields": {
|
||||
"'$audioFieldName'": ""
|
||||
},
|
||||
"audio": [{
|
||||
"path": "'"$out_file"'",
|
||||
"filename": "'"$time"'.'$FORMAT'",
|
||||
"fields": [
|
||||
"'$audioFieldName'"
|
||||
]
|
||||
}]
|
||||
require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
echo "Missing dependency: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}'
|
||||
# opens changed note, comment if you don't want it.
|
||||
curl -s localhost:$ankiConnectPort -X POST -d '{
|
||||
"action": "guiBrowse",
|
||||
"version": 6,
|
||||
"params": {
|
||||
"query": "nid:'"$newestNoteId"'"
|
||||
}
|
||||
}'
|
||||
notify-send -t 1000 "Audio recording copied"
|
||||
rm "$out_file"
|
||||
fi
|
||||
|
||||
notify() {
|
||||
# Best-effort notification; keep script running if notify-send is missing.
|
||||
if command -v notify-send >/dev/null 2>&1; then
|
||||
notify-send -t 1000 "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
get_active_sink() {
|
||||
pactl list sinks short 2>/dev/null | awk '$6=="RUNNING"{print $1; exit 0}'
|
||||
}
|
||||
|
||||
get_newest_note_id() {
|
||||
local response
|
||||
response=$(curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' \
|
||||
-d '{"action":"findNotes","version":6,"params":{"query":"is:new"}}')
|
||||
jq -r '.result[-1] // empty' <<<"$response"
|
||||
}
|
||||
|
||||
update_anki_note() {
|
||||
local note_id="$1" audio_path="$2" filename="$3"
|
||||
|
||||
local payload
|
||||
payload=$(jq -n --argjson noteId "$note_id" --arg field "$AUDIO_FIELD_NAME" \
|
||||
--arg path "$audio_path" --arg filename "$filename" '
|
||||
{action:"updateNoteFields",version:6,
|
||||
params:{note:{id:$noteId,fields:{($field):""},
|
||||
audio:[{path:$path,filename:$filename,fields:[$field]}]}}}')
|
||||
|
||||
curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' -d "$payload" >/dev/null
|
||||
}
|
||||
|
||||
open_note_in_browser() {
|
||||
local note_id="$1"
|
||||
local payload
|
||||
payload=$(jq -n --argjson noteId "$note_id" '
|
||||
{action:"guiBrowse",version:6,params:{query:("nid:" + ($noteId|tostring))}}')
|
||||
curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' -d "$payload" >/dev/null
|
||||
}
|
||||
|
||||
record_audio() {
|
||||
local note_id="$1"
|
||||
local sink
|
||||
sink=$(get_active_sink) || true
|
||||
|
||||
if [[ -z "$sink" ]]; then
|
||||
notify "Record Error" "No running PulseAudio sink found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$CACHE_DIR"
|
||||
|
||||
local timestamp wav_file out_file
|
||||
timestamp=$(date +%s)
|
||||
wav_file="$CACHE_DIR/$timestamp.wav"
|
||||
out_file="$CACHE_DIR/$timestamp.$FORMAT"
|
||||
|
||||
notify "Audio recording started"
|
||||
|
||||
if ! timeout "$RECORD_TIMEOUT" parec -d"$sink" --file-format=wav "$wav_file"; then
|
||||
notify "Record Error" "No audio captured (timeout or sink issue)"
|
||||
rm -f "$wav_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local input_duration output_duration
|
||||
input_duration=$(ffprobe -v error -select_streams a:0 \
|
||||
-show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 "$wav_file")
|
||||
output_duration=$(echo "$input_duration - $CUT_DURATION" | bc -l)
|
||||
|
||||
# Guard against negative durations
|
||||
if [[ $(echo "$output_duration < 0" | bc -l) -eq 1 ]]; then
|
||||
output_duration="0"
|
||||
fi
|
||||
|
||||
case "$FORMAT" in
|
||||
ogg)
|
||||
ffmpeg -nostdin -y -i "$wav_file" -vn -codec:a libvorbis -b:a 64k \
|
||||
-t "$output_duration" "$out_file"
|
||||
;;
|
||||
mp3)
|
||||
ffmpeg -nostdin -y -i "$wav_file" -vn -codec:a libmp3lame -qscale:a 1 \
|
||||
-t "$output_duration" "$out_file"
|
||||
;;
|
||||
*)
|
||||
notify "Record Error" "Unknown format: $FORMAT"
|
||||
rm -f "$wav_file"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
rm -f "$wav_file"
|
||||
|
||||
update_anki_note "$note_id" "$out_file" "$timestamp.$FORMAT"
|
||||
open_note_in_browser "$note_id"
|
||||
|
||||
notify "Audio recording copied"
|
||||
rm -f "$out_file"
|
||||
}
|
||||
|
||||
main() {
|
||||
for cmd in curl jq ffmpeg ffprobe parec pactl bc; do
|
||||
require_cmd "$cmd"
|
||||
done
|
||||
|
||||
if pgrep -x parec >/dev/null 2>&1; then
|
||||
pkill -x parec
|
||||
notify "Audio recording stopped"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local newest_note
|
||||
newest_note=$(get_newest_note_id)
|
||||
|
||||
if [[ -z "$newest_note" ]]; then
|
||||
notify "Anki Connect" "No new notes found or AnkiConnect unavailable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
record_audio "$newest_note"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
261
projects/scripts/screenshot-anki.sh
Normal file → Executable file
261
projects/scripts/screenshot-anki.sh
Normal file → Executable file
@@ -1,75 +1,202 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Version 1.2
|
||||
# click and drag to screenshot dragged portion
|
||||
# click on specific window to screenshot window area
|
||||
# dependencies: imagemagick, xclip,curl maybe xdotool (see comment below)
|
||||
# shoutout to https://gist.github.com/Cephian/f849e326e3522be9a4386b60b85f2f23 for the original script,
|
||||
# https://github.com/xythh/ added the ankiConnect functionality
|
||||
# if anki is running the image is added to your latest note as a jpg, if anki is not running it's added to your clipboard as a png
|
||||
time=$(date +%s)
|
||||
tmp_file="$HOME/.cache/$time"
|
||||
ankiConnectPort="8765"
|
||||
pictureField="Picture"
|
||||
quality="90"
|
||||
# Capture a region with slurp+grim. If AnkiConnect is available, attach the
|
||||
# JPEG to the newest note; otherwise copy a PNG to the clipboard.
|
||||
|
||||
# This gets your notes marked as new and returns the newest one.
|
||||
newestNoteId=$(curl -s localhost:$ankiConnectPort -X POST -d '{"action": "findNotes", "version": 6, "params": { "query": "is:new"}}' | jq '.result[-1]')
|
||||
set -euo pipefail
|
||||
|
||||
# you can remove these two lines if you don't have software which
|
||||
# makes your mouse disappear when you use the keyboard (e.g. xbanish, unclutter)
|
||||
# https://github.com/ImageMagick/ImageMagick/issues/1745#issuecomment-777747494
|
||||
xdotool mousemove_relative 1 1
|
||||
xdotool mousemove_relative -- -1 -1
|
||||
ANKI_CONNECT_PORT="${ANKI_CONNECT_PORT:-8765}"
|
||||
PICTURE_FIELD="${PICTURE_FIELD:-Picture}"
|
||||
QUALITY="${QUALITY:-90}"
|
||||
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/screenshot-anki"
|
||||
ANKI_URL="http://localhost:${ANKI_CONNECT_PORT}"
|
||||
HOSTNAME_SHORT="$(hostname -s 2>/dev/null || hostname)"
|
||||
REQUIREMENTS=(slurp grim wl-copy xdotool curl jq rofi)
|
||||
ROFI_THEME_STR='listview {columns: 2; lines: 3;} window {width: 45%;}'
|
||||
ROFI_THEME="$HOME/.config/rofi/launchers/type-2/style-2.rasi"
|
||||
CAPTURE_MODE=""
|
||||
DECK_NAME=""
|
||||
AUTO_MODE=false
|
||||
|
||||
# if anki connect is running it will return your latest note id, and the following code will run, if anki connect is not running nothing is return.
|
||||
if [ "$newestNoteId" != "" ]; then
|
||||
if ! import -quality $quality "$tmp_file.jpg"; then
|
||||
# most likley reason this returns a error, is for fullscreen applications that take full control which does not allowing imagemagick to select the area, use windowed fullscreen or if running wine use a virtual desktop to avoid this.
|
||||
notify-send "Error screenshoting " "most likely unable to find selection"
|
||||
exit 1
|
||||
fi
|
||||
parse_opts() {
|
||||
while getopts "cd:" opt; do
|
||||
case "$opt" in
|
||||
c)
|
||||
CAPTURE_MODE="window"
|
||||
AUTO_MODE=true
|
||||
;;
|
||||
d)
|
||||
DECK_NAME="$OPTARG"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [-c] [-n DECK_NAME]" >&2
|
||||
echo " -c: Capture current window" >&2
|
||||
echo " -n: Specify note name (e.g., Kiku)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
curl -s localhost:$ankiConnectPort -X POST -d '{
|
||||
"action": "updateNoteFields",
|
||||
"version": 6,
|
||||
"params": {
|
||||
"note": {
|
||||
"id": '"$newestNoteId"',
|
||||
"fields": {
|
||||
"'$pictureField'": ""
|
||||
},
|
||||
"picture": [{
|
||||
"path": "'"$tmp_file"'.jpg",
|
||||
"filename": "paste-'"$time"'.jpg",
|
||||
"fields": [
|
||||
"'$pictureField'"
|
||||
]
|
||||
}]
|
||||
}
|
||||
notify() {
|
||||
if command -v notify-send >/dev/null 2>&1; then
|
||||
notify-send "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
notify "Missing dependency" "$1 is required"
|
||||
exit 1
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
#remove if you don't want anki to show you the card you just edited
|
||||
curl -s localhost:$ankiConnectPort -X POST -d '{
|
||||
"action": "guiBrowse",
|
||||
"version": 6,
|
||||
"params": {
|
||||
"query": "nid:'"$newestNoteId"'"
|
||||
}
|
||||
}'
|
||||
wiggle_mouse() {
|
||||
# Avoid disappearing cursor on some compositors
|
||||
xdotool mousemove_relative 1 1
|
||||
xdotool mousemove_relative -- -1 -1
|
||||
}
|
||||
|
||||
#you can comment this if you do not use notifcations.
|
||||
notify-send "Screenshot Taken" "Added to note"
|
||||
rm "$tmp_file.jpg"
|
||||
else
|
||||
if ! import -quality $quality "$tmp_file.png"; then
|
||||
notify-send "Error screenshoting " "most likely unable to find selection"
|
||||
exit 1
|
||||
fi
|
||||
# we use pngs when copying to clipboard because they have greater support when pasting.
|
||||
xclip -selection clipboard -target image/png -i "$tmp_file.png"
|
||||
rm "$tmp_file.png"
|
||||
#you can comment this if you do not use notifcations.
|
||||
notify-send "Screenshot Taken" "Copied to clipboard"
|
||||
fi
|
||||
drain_enter_key() {
|
||||
# Release lingering Enter press from launching via rofi so it
|
||||
# doesn't reach the next focused window (e.g., a game).
|
||||
xdotool keyup Return 2>/dev/null || true
|
||||
xdotool keyup KP_Enter 2>/dev/null || true
|
||||
}
|
||||
|
||||
capture_region() {
|
||||
local fmt="$1" quality="$2" output="$3"
|
||||
local geometry
|
||||
geometry=$(slurp)
|
||||
if [[ -z "$geometry" ]]; then
|
||||
notify "Screenshot cancelled" "No region selected"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$fmt" == "jpeg" ]]; then
|
||||
grim -g "$geometry" -t jpeg -q "$quality" "$output"
|
||||
else
|
||||
grim -g "$geometry" -t png "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
capture_current_window() {
|
||||
local fmt="$1" quality="$2" output="$3" geometry
|
||||
|
||||
if [[ "$fmt" == "jpeg" ]]; then
|
||||
grim -w "$(hyprctl activewindow -j | jq -r '.address')" -t jpeg -q "$quality" "$output"
|
||||
else
|
||||
grim -w "$(hyprctl activewindow -j | jq -r '.address')" -t png "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
choose_capture_mode() {
|
||||
local selection
|
||||
selection=$(printf "%s\n%s\n" "Region (slurp)" "Current window (Hyprland)" |
|
||||
rofi -dmenu -i \
|
||||
-p "Capture mode" \
|
||||
-mesg "Select capture target" \
|
||||
-no-custom \
|
||||
-no-lazy-grab \
|
||||
-location 0 -yoffset 30 -xoffset 30 \
|
||||
-theme "$ROFI_THEME" \
|
||||
-theme-str "$ROFI_THEME_STR" \
|
||||
-window-title "screenshot-anki")
|
||||
|
||||
if [[ -z "$selection" ]]; then
|
||||
notify "Screenshot cancelled" "No capture mode selected"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$selection" == "Current window (Hyprland)" ]]; then
|
||||
CAPTURE_MODE="window"
|
||||
else
|
||||
CAPTURE_MODE="region"
|
||||
fi
|
||||
}
|
||||
|
||||
copy_to_clipboard() {
|
||||
local file="$1"
|
||||
if ! wl-copy <"$file"; then
|
||||
notify "Error copying screenshot" "wl-copy failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
get_newest_note_id() {
|
||||
local response query="is:new"
|
||||
if [[ -n "$DECK_NAME" ]]; then
|
||||
query="is:new deck:$DECK_NAME"
|
||||
fi
|
||||
response=$(curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' \
|
||||
-d "{\"action\":\"findNotes\",\"version\":6,\"params\":{\"query\":\"$query\"}}")
|
||||
jq -r '.result[-1] // empty' <<<"$response"
|
||||
}
|
||||
|
||||
update_note_with_image() {
|
||||
local note_id="$1" image_path="$2" filename="$3"
|
||||
local payload
|
||||
payload=$(jq -n --argjson noteId "$note_id" --arg field "$PICTURE_FIELD" \
|
||||
--arg path "$image_path" --arg filename "$filename" '
|
||||
{action:"updateNoteFields",version:6,
|
||||
params:{note:{id:$noteId,fields:{($field):""},
|
||||
picture:[{path:$path,filename:$filename,fields:[$field]}]}}}')
|
||||
curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' -d "$payload" >/dev/null
|
||||
}
|
||||
|
||||
open_note_in_browser() {
|
||||
local note_id="$1"
|
||||
local payload
|
||||
payload=$(jq -n --argjson noteId "$note_id" '
|
||||
{action:"guiBrowse",version:6,params:{query:("nid:" + ($noteId|tostring))}}')
|
||||
curl -sS "$ANKI_URL" -X POST -H 'Content-Type: application/json' -d "$payload" >/dev/null
|
||||
}
|
||||
|
||||
main() {
|
||||
parse_opts "$@"
|
||||
|
||||
local requirements=("${REQUIREMENTS[@]}")
|
||||
for cmd in "${requirements[@]}"; do
|
||||
require_cmd "$cmd"
|
||||
done
|
||||
|
||||
mkdir -p "$CACHE_DIR"
|
||||
local timestamp base newest_note image_path
|
||||
timestamp=$(date +%s)
|
||||
base="$CACHE_DIR/$timestamp"
|
||||
|
||||
drain_enter_key
|
||||
|
||||
# Only show interactive menu if not in auto mode
|
||||
if [[ "$AUTO_MODE" == false ]]; then
|
||||
choose_capture_mode
|
||||
fi
|
||||
|
||||
if [[ "$CAPTURE_MODE" == "window" ]]; then
|
||||
require_cmd hyprctl
|
||||
fi
|
||||
|
||||
wiggle_mouse
|
||||
newest_note=$(get_newest_note_id)
|
||||
|
||||
local capture_fn="capture_region"
|
||||
if [[ "$CAPTURE_MODE" == "window" ]]; then
|
||||
capture_fn="capture_current_window"
|
||||
fi
|
||||
|
||||
if [[ -n "$newest_note" ]]; then
|
||||
image_path="$base.jpg"
|
||||
"$capture_fn" "jpeg" "$QUALITY" "$image_path"
|
||||
update_note_with_image "$newest_note" "$image_path" "paste-$timestamp.jpg"
|
||||
open_note_in_browser "$newest_note"
|
||||
notify -i "$image_path" "Screenshot Taken" "Added to Anki note"
|
||||
rm -f "$image_path"
|
||||
else
|
||||
image_path="$base.png"
|
||||
"$capture_fn" "png" "" "$image_path"
|
||||
copy_to_clipboard "$image_path"
|
||||
notify -i "$image_path" "Screenshot Taken" "Copied to clipboard"
|
||||
rm -f "$image_path"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -10,99 +10,99 @@ HYPRLAND_REGEX='.at[0],(.at[1]) .size[0]x(.size[1])'
|
||||
REQUIREMENTS=(grim slurp rofi zenity wl-copy)
|
||||
USE_NOTIFICATIONS=1
|
||||
CHOICES=(
|
||||
"1. Select a region and save - slurp | grim -g - \"$TMP_SCREENSHOT\""
|
||||
"2. Select a region and copy to clipboard - slurp | grim -g - - | wl-copy"
|
||||
"3. Whole screen - grim \"$TMP_SCREENSHOT\""
|
||||
"4. Current window - hyprctl -j activewindow | jq -r \"${HYPRLAND_REGEX}\" | grim -g - \"$TMP_SCREENSHOT\""
|
||||
"5. Edit - slurp | grim -g - - | swappy -f -"
|
||||
"6. Quit - exit 0"
|
||||
"1. Select a region and save - slurp | grim -g - \"$TMP_SCREENSHOT\""
|
||||
"2. Select a region and copy to clipboard - slurp | grim -g - - | wl-copy"
|
||||
"3. Whole screen - grim \"$TMP_SCREENSHOT\""
|
||||
"4. Current window - hyprctl -j activewindow | jq -r \"${HYPRLAND_REGEX}\" | grim -g - \"$TMP_SCREENSHOT\""
|
||||
"5. Edit - slurp | grim -g - - | swappy -f -"
|
||||
"6. Quit - exit 0"
|
||||
)
|
||||
|
||||
notify() {
|
||||
local body="$1"
|
||||
local title="$2"
|
||||
if [[ -z "$body" ]]; then
|
||||
echo "notify: No message provided"
|
||||
return 1
|
||||
fi
|
||||
if [[ -z "$title" ]]; then
|
||||
title="$SCRIPT_NAME"
|
||||
fi
|
||||
local body="$1"
|
||||
local title="$2"
|
||||
if [[ -z "$body" ]]; then
|
||||
echo "notify: No message provided"
|
||||
return 1
|
||||
fi
|
||||
if [[ -z "$title" ]]; then
|
||||
title="$SCRIPT_NAME"
|
||||
fi
|
||||
|
||||
if ((USE_NOTIFICATIONS)); then
|
||||
notify-send "$title" "$body"
|
||||
else
|
||||
printf "%s\n%s\n" "$title" "$body"
|
||||
fi
|
||||
return 0
|
||||
if ((USE_NOTIFICATIONS)); then
|
||||
notify-send "$title" "$body"
|
||||
else
|
||||
printf "%s\n%s\n" "$title" "$body"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
check_deps() {
|
||||
for cmd in "${REQUIREMENTS[@]}"; do
|
||||
if ! command -v "$cmd" &> /dev/null; then
|
||||
echo "Error: $cmd is not installed. Please install it first."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
for cmd in "${REQUIREMENTS[@]}"; do
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
echo "Error: $cmd is not installed. Please install it first."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
CHOICE="$(rofi -dmenu -i -p "Enter option or select from the list" \
|
||||
-mesg "Select a Screenshot Option" \
|
||||
-a 0 -no-custom -location 0 \
|
||||
-yoffset 30 -xoffset 30 \
|
||||
-theme-str 'listview {columns: 2; lines: 3;} window {width: 45%;}' \
|
||||
-window-title "$SCRIPT_NAME" \
|
||||
-format 'i' \
|
||||
<<< "$(printf "%s\n" "${CHOICES[@]%% - *}")")"
|
||||
CHOICE="$(rofi -dmenu -i -p "Enter option or select from the list" \
|
||||
-mesg "Select a Screenshot Option" \
|
||||
-a 0 -no-custom -location 0 \
|
||||
-yoffset 30 -xoffset 30 \
|
||||
-theme-str 'listview {columns: 2; lines: 3;} window {width: 45%;}' \
|
||||
-window-title "$SCRIPT_NAME" \
|
||||
-format 'i' \
|
||||
<<<"$(printf "%s\n" "${CHOICES[@]%% - *}")")"
|
||||
|
||||
if [[ -z "$CHOICE" ]]; then
|
||||
notify "No option selected." ""
|
||||
exit 0
|
||||
fi
|
||||
if [[ -z "$CHOICE" ]]; then
|
||||
notify "No option selected." ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
sleep 0.2 # give time for the rofi window to close
|
||||
CMD="${CHOICES[$CHOICE]#* -}"
|
||||
if [[ -z "$CMD" ]]; then
|
||||
notify "No option selected." ""
|
||||
exit 0
|
||||
fi
|
||||
sleep 0.2 # give time for the rofi window to close
|
||||
CMD="${CHOICES[$CHOICE]#* -}"
|
||||
if [[ -z "$CMD" ]]; then
|
||||
notify "No option selected." ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# For option 2 (copy to clipboard), handle differently
|
||||
if [[ "$CHOICE" == "1" ]]; then
|
||||
if eval "$CMD"; then
|
||||
notify "Screenshot copied to clipboard"
|
||||
exit 0
|
||||
else
|
||||
notify "An error occurred while taking the screenshot."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
# For option 2 (copy to clipboard), handle differently
|
||||
if [[ "$CHOICE" == "1" ]]; then
|
||||
if eval "$CMD"; then
|
||||
notify "Screenshot copied to clipboard"
|
||||
exit 0
|
||||
else
|
||||
notify "An error occurred while taking the screenshot."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! eval "$CMD"; then
|
||||
notify "An error occurred while taking the screenshot."
|
||||
exit 1
|
||||
fi
|
||||
if ! eval "$CMD"; then
|
||||
notify "An error occurred while taking the screenshot."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
notify "screenshot.sh" "Screenshot saved temporarily.\nChoose where to save it permanently"
|
||||
notify "screenshot.sh" "Screenshot saved temporarily.\nChoose where to save it permanently"
|
||||
|
||||
FILE=$(zenity --file-selection --title="Save Screenshot" --filename="$DEFAULT_FILENAME" --save 2> /dev/null)
|
||||
case "$?" in
|
||||
0)
|
||||
if mv "$TMP_SCREENSHOT" "$FILE"; then
|
||||
notify "Screenshot saved to $FILE"
|
||||
else
|
||||
notify "Failed to save screenshot to $FILE"
|
||||
fi
|
||||
;;
|
||||
1)
|
||||
rm -f "$TMP_SCREENSHOT"
|
||||
notify "Screenshot discarded"
|
||||
;;
|
||||
-1)
|
||||
notify "An unexpected error has occurred."
|
||||
;;
|
||||
esac
|
||||
FILE=$(zenity --file-selection --title="Save Screenshot" --filename="$DEFAULT_FILENAME" --save 2>/dev/null)
|
||||
case "$?" in
|
||||
0)
|
||||
if mv "$TMP_SCREENSHOT" "$FILE"; then
|
||||
notify "Screenshot saved to $FILE"
|
||||
else
|
||||
notify "Failed to save screenshot to $FILE"
|
||||
fi
|
||||
;;
|
||||
1)
|
||||
rm -f "$TMP_SCREENSHOT"
|
||||
notify "Screenshot discarded"
|
||||
;;
|
||||
-1)
|
||||
notify "An unexpected error has occurred."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_deps
|
||||
|
||||
10
projects/scripts/songinfo.sh
Executable file
10
projects/scripts/songinfo.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
music_dir="/jellyfin/music"
|
||||
previewdir="$XDG_CONFIG_HOME/ncmpcpp/previews"
|
||||
filename="$(mpc --format "$music_dir"/%file% current)"
|
||||
previewname="$previewdir/$(mpc --format %album% current | base64).png"
|
||||
|
||||
[ -e "$previewname" ] || ffmpeg -y -i "$filename" -an -vf scale=128:128 "$previewname" > /dev/null 2>&1
|
||||
|
||||
notify-send -a "ncmpcpp" "Now Playing" "$(mpc --format '%title% \n%artist% - %album%' current)" -i "$previewname"
|
||||
Reference in New Issue
Block a user