From 628baf3eeaa25743e611853186a608f5b8374ab3 Mon Sep 17 00:00:00 2001 From: Arthur Khachaturov Date: Thu, 19 Sep 2024 04:25:07 +0300 Subject: [PATCH] minor updates --- .config/X11/xprofile | 18 +- .config/dunst/dmenu | 18 +- .config/nvim/init.lua | 24 +- .config/nvim/lua/config/autocmd.lua | 11 +- .config/nvim/lua/config/mappings.lua | 9 + .config/nvim/lua/config/options.lua | 2 +- .config/nvim/lua/lsp/init.lua | 12 +- .config/nvim/lua/lsp/lua_ls.lua | 17 +- .config/nvim/lua/plugins/init.lua | 4 +- .config/nvim/lua/plugins/telescope.lua | 2 +- .config/nvim/lua/plugins/trouble.lua | 10 +- .config/nvim/lua/utils/close_buffer.lua | 15 -- .config/nvim/lua/utils/lazy.lua | 18 -- .config/tmux/keybinds.conf | 4 +- .config/tmux/plugins.conf | 4 +- .config/tmux/theme.conf | 3 +- .config/tmux/tmux.conf | 3 + .config/zsh/.zprofile | 1 + .config/zsh/.zshenv | 7 +- .config/zsh/.zshrc | 10 +- .config/zsh/modes.sh | 62 +++++ .config/zsh/modes/cpp.sh | 18 ++ .local/bin/scripts/{ip.me => ie} | 2 +- .local/bin/scripts/md | 1 + .local/bin/scripts/my.itmo | 320 ++++++++++++++++++++++++ .local/bin/scripts/tg | 90 +++++++ .local/bin/scripts/vpnd | 40 +-- .local/bin/source/src_venv | 4 +- .local/bin/statusbar/sb-battery | 37 ++- .local/bin/statusbar/sb-status | 5 + .local/bin/statusbar/sb-vpn | 2 +- .xinitrc | 5 +- 32 files changed, 655 insertions(+), 123 deletions(-) delete mode 100644 .config/nvim/lua/utils/close_buffer.lua delete mode 100644 .config/nvim/lua/utils/lazy.lua create mode 100644 .config/zsh/modes.sh create mode 100644 .config/zsh/modes/cpp.sh rename .local/bin/scripts/{ip.me => ie} (77%) create mode 100755 .local/bin/scripts/my.itmo create mode 100755 .local/bin/scripts/tg create mode 100755 .local/bin/statusbar/sb-status diff --git a/.config/X11/xprofile b/.config/X11/xprofile index abf2f22..75c56b7 100644 --- a/.config/X11/xprofile +++ b/.config/X11/xprofile @@ -1,8 +1,18 @@ systemctl --user import-environment DISPLAY -export TERMINAL=/usr/bin/alacritty -export TERM=/usr/bin/alacritty -export _JAVA_AWT_WM_NONREPARENTING=1 -export AWT_TOOLKIT=MToolkit +set -a + +TERMINAL=/usr/bin/alacritty +TERM=/usr/bin/alacritty +_JAVA_AWT_WM_NONREPARENTING=1 +AWT_TOOLKIT=MToolkit + +MOZ_USE_XINPUT2=1 + +XDG_CURRENT_DESKTOP="gtk" +XDG_SESSION_DESKTOP="$XDG_CURRENT_DESKTOP" +WINDOW_MANAGER="dwm" + +set +a # vim: ft=bash diff --git a/.config/dunst/dmenu b/.config/dunst/dmenu index 14242dc..e483388 100755 --- a/.config/dunst/dmenu +++ b/.config/dunst/dmenu @@ -3,15 +3,15 @@ declare -A cases while read -r element; do - case "$element" in - "#Open"*) - cases["open"]+=$element - ;; - "#Mark as read"*) - cases["read"]+=$element - ;; - *) cases["$element"]="$element" - esac + case "$element" in + "#Open"*) + cases["open"]+=$element + ;; + "#Mark as read"*) + cases["read"]+=$element + ;; + *) cases["$element"]="$element" + esac done diff --git a/.config/nvim/init.lua b/.config/nvim/init.lua index c9a7b62..6320cf5 100644 --- a/.config/nvim/init.lua +++ b/.config/nvim/init.lua @@ -1,15 +1,29 @@ -- Remap leader key to -vim.g.mapleader = ' ' -vim.g.maplocalleader = ' ' -vim.keymap.set({ 'n', 'v' }, '', '', { silent = true }) --- Load modules +-- Load basic configuration require("config") -require("utils.lazy").lazy_init() + +-- Init lazy.nvim plugin manager +local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim' +if not vim.loop.fs_stat(lazypath) then + vim.fn.system { + 'git', + 'clone', + '--filter=blob:none', + 'https://github.com/folke/lazy.nvim.git', + '--branch=stable', + lazypath, + } +end +vim.opt.rtp:prepend(lazypath) + +-- Load plugins require("lazy").setup("plugins", { change_detection = { enabled = false, notify = false, }, }) + +-- Load lsp configuration require("lsp") diff --git a/.config/nvim/lua/config/autocmd.lua b/.config/nvim/lua/config/autocmd.lua index f787cfa..86fe6de 100644 --- a/.config/nvim/lua/config/autocmd.lua +++ b/.config/nvim/lua/config/autocmd.lua @@ -1,5 +1,14 @@ -- Set proper tabstop for go vim.api.nvim_create_autocmd('FileType', { pattern = "go", - command = "setlocal tabstop=4", + command = "setlocal tabstop=4 noexpandtab", +}) + +-- Remove trailing whitespaces on save +vim.api.nvim_create_autocmd('BufWritePre', { + callback = function () + local view = vim.fn.winsaveview() + vim.cmd('%s/\\s\\+$//e') + vim.fn.winrestview(view) + end }) diff --git a/.config/nvim/lua/config/mappings.lua b/.config/nvim/lua/config/mappings.lua index a1f6a0f..d2ccc50 100644 --- a/.config/nvim/lua/config/mappings.lua +++ b/.config/nvim/lua/config/mappings.lua @@ -1,5 +1,10 @@ local map = vim.keymap.set +-- Remap leader to +vim.g.mapleader = ' ' +vim.g.maplocalleader = ' ' +map({ 'n', 'v' }, '', '', { silent = true }) + -- Unbind keys map('n', '', '') map({ 'n', 'v' }, 'H', '') @@ -28,6 +33,10 @@ map({ 'n', 'v' }, 'm', '10>') map({ 'n', 'v' }, 'N', '6-') map({ 'n', 'v' }, 'M', '6+') +-- quickfix buffer +map('n', '', ':cn', { silent = true }) +map('n', '', ':cp', { silent = true }) + -- Remap to remove last word map('i', '', '') diff --git a/.config/nvim/lua/config/options.lua b/.config/nvim/lua/config/options.lua index d3ca167..24ec558 100644 --- a/.config/nvim/lua/config/options.lua +++ b/.config/nvim/lua/config/options.lua @@ -16,7 +16,7 @@ vim.o.autoindent = true vim.o.smartindent = true vim.o.smarttab = true vim.o.breakindent = true -vim.o.softtabstop = -1 +vim.o.softtabstop = 4 -- Save undo history vim.o.undofile = true diff --git a/.config/nvim/lua/lsp/init.lua b/.config/nvim/lua/lsp/init.lua index 71886a0..e1f49a3 100644 --- a/.config/nvim/lua/lsp/init.lua +++ b/.config/nvim/lua/lsp/init.lua @@ -6,6 +6,8 @@ local servers = { rust_analyzer = {}, bashls = {}, hls = {}, + eslint = {}, + ts_ls = {}, } vim.lsp.set_log_level("debug") @@ -43,7 +45,15 @@ for server_name, config in pairs(servers) do lspconfig[server_name].setup({ capabilities = capabilities, on_attach = on_attach, - settings = { [server_name] = config }, + settings = { + [server_name] = config ~= {} and { + settings = { + [server_name] = { + config + } + } + } or {} + }, }) end diff --git a/.config/nvim/lua/lsp/lua_ls.lua b/.config/nvim/lua/lsp/lua_ls.lua index 65af628..4540403 100644 --- a/.config/nvim/lua/lsp/lua_ls.lua +++ b/.config/nvim/lua/lsp/lua_ls.lua @@ -1,10 +1,11 @@ return { - settings = { - ['lua_ls'] = { - Lua = { - workspace = { checkThirdParty = false }, - telemetry = { enable = false }, - }, - } - } + Lua = { + workspace = { + checkThirdParty = true, + library = { + vim.env.VIMRUNTIME + } + }, + telemetry = { enable = false }, + }, } diff --git a/.config/nvim/lua/plugins/init.lua b/.config/nvim/lua/plugins/init.lua index d688540..666f7bf 100644 --- a/.config/nvim/lua/plugins/init.lua +++ b/.config/nvim/lua/plugins/init.lua @@ -1,11 +1,13 @@ return { 'rcarriga/nvim-notify', + 'psliwka/vim-smoothie', 'stefandtw/quickfix-reflector.vim', + 'tpope/vim-sleuth', { 'akinsho/bufferline.nvim', opts = {}, dependencies = { 'navarasu/onedark.nvim' } }, { 'ethanholz/nvim-lastplace', opts = {} }, { 'kylechui/nvim-surround', version = '*', event = 'VeryLazy', opts = {} }, { 'lukas-reineke/indent-blankline.nvim', main = 'ibl', opts = {} }, - { 'norcalli/nvim-colorizer.lua', opts={'*'}, dependencies = { 'navarasu/onedark.nvim' } }, + { 'norcalli/nvim-colorizer.lua', opts={ '*' }, dependencies = { 'navarasu/onedark.nvim' } }, { 'wakatime/vim-wakatime', event = 'VeryLazy' }, { 'williamboman/mason.nvim', opts = {} }, } diff --git a/.config/nvim/lua/plugins/telescope.lua b/.config/nvim/lua/plugins/telescope.lua index 208c769..b78d0ff 100644 --- a/.config/nvim/lua/plugins/telescope.lua +++ b/.config/nvim/lua/plugins/telescope.lua @@ -36,6 +36,6 @@ return { vim.keymap.set('n', 'of', require('telescope.builtin').oldfiles) vim.keymap.set('n', 'af', require('telescope.builtin').git_files) vim.keymap.set('n', 'sf', require('telescope.builtin').find_files) - vim.keymap.set('n', 'sw', require('telescope.builtin').grep_string) + vim.keymap.set('n', 'fw', require('telescope.builtin').grep_string) end } diff --git a/.config/nvim/lua/plugins/trouble.lua b/.config/nvim/lua/plugins/trouble.lua index 76c74c8..c4b9cc3 100644 --- a/.config/nvim/lua/plugins/trouble.lua +++ b/.config/nvim/lua/plugins/trouble.lua @@ -3,11 +3,9 @@ return { opts = {}, cmd = "Trouble", keys = { - { "xx", "Trouble diagnostics toggle" }, -- ?? - { "xX", "Trouble diagnostics toggle filter.buf=0" }, -- useless? - { "cs", "Trouble symbols toggle focus=false" }, -- nice as well - { "cl", "Trouble lsp toggle focus=false win.position=right" }, -- nicee - { "xL", "Trouble loclist toggle" }, -- ?? - { "xQ", "Trouble qflist toggle" }, -- ?? + { "ew", "Trouble diagnostics toggle" }, + { "ef", "Trouble diagnostics toggle filter.buf=0" }, + { "cs", "Trouble symbols toggle focus=false" }, + { "cl", "Trouble lsp toggle focus=false win.position=right" }, }, } diff --git a/.config/nvim/lua/utils/close_buffer.lua b/.config/nvim/lua/utils/close_buffer.lua deleted file mode 100644 index 7bc0dbd..0000000 --- a/.config/nvim/lua/utils/close_buffer.lua +++ /dev/null @@ -1,15 +0,0 @@ -local M = { } - -function M.close_buffer(force) - if #vim.fn.filter(vim.fn.range(1, vim.fn.bufnr '$'), 'buflisted(v:val)') <= 1 then - vim.api.nvim_command("qa" .. (force and "!" or "")) - else - local tree = require("nvim-tree.api").tree - - tree.toggle({ focus = false }) - vim.api.nvim_command("bd" .. (force and "!" or "")) - tree.toggle({ focus = false }) - end -end - -return M diff --git a/.config/nvim/lua/utils/lazy.lua b/.config/nvim/lua/utils/lazy.lua deleted file mode 100644 index 9f88a4b..0000000 --- a/.config/nvim/lua/utils/lazy.lua +++ /dev/null @@ -1,18 +0,0 @@ -local M = { } - -function M.lazy_init() - local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim' - if not vim.loop.fs_stat(lazypath) then - vim.fn.system { - 'git', - 'clone', - '--filter=blob:none', - 'https://github.com/folke/lazy.nvim.git', - '--branch=stable', - lazypath, - } - end - vim.opt.rtp:prepend(lazypath) -end - -return M diff --git a/.config/tmux/keybinds.conf b/.config/tmux/keybinds.conf index 09161df..2d1d583 100644 --- a/.config/tmux/keybinds.conf +++ b/.config/tmux/keybinds.conf @@ -21,8 +21,8 @@ bind ';' "command-prompt" bind C-r "source-file ~/.config/tmux/tmux.conf" # Session binds -bind C-n "new-session -c '#{pane_current_path}' -s '#{b:pane_current_path}'" -bind C-x "set-option -g detach-on-destroy on; kill-session" +# bind C-n "new-session -c '#{pane_current_path}' -s '#{b:pane_current_path}'" +# bind C-x "set-option -g detach-on-destroy on; kill-session" bind X "set-option -g detach-on-destroy off; kill-session; set-option -g detach-on-destroy on" # Select mode diff --git a/.config/tmux/plugins.conf b/.config/tmux/plugins.conf index 802a667..8a5fb8e 100644 --- a/.config/tmux/plugins.conf +++ b/.config/tmux/plugins.conf @@ -9,8 +9,8 @@ set -g @batt_icon_charge_tier6 ' ' set -g @batt_icon_charge_tier5 ' ' set -g @batt_icon_charge_tier4 ' ' set -g @batt_icon_charge_tier3 ' ' -set -g @batt_icon_charge_tier2 ' ' -set -g @batt_icon_charge_tier1 '! !' +set -g @batt_icon_charge_tier2 '❗' +set -g @batt_icon_charge_tier1 '❗' # better-mouse-mode set -g @emulate-scroll-for-no-mouse-alternate-buffer "on" diff --git a/.config/tmux/theme.conf b/.config/tmux/theme.conf index caf8dab..8e95033 100644 --- a/.config/tmux/theme.conf +++ b/.config/tmux/theme.conf @@ -13,10 +13,11 @@ set -g status-justify "left" # status set -g status-left-length 0 set -g status-left "#[bg=#{@theme-active-bg},fg=#{@theme-active-fg}]#{?client_prefix,[#{session_name}],#[bg=#{@theme-bg},fg=#{@theme-fg}][#{session_name}]}#[bg=#{@theme-bg},fg=#{@theme-fg}] " -set -g status-right "#{battery_icon_charge} #{battery_percentage} | %a %m/%d %I:%M %P" +set -g status-right "#(sb-battery -s) | %a %m/%d %I:%M %P" set -g window-status-format " #I:#W " set -g window-status-current-format "#[bg=#{@theme-active-bg},fg=#{@theme-active-fg}, bold]#{?window_zoomed_flag, #I:#W 󰊓 , #I:#W }" set -g window-status-style "bg=#{@theme-bg},fg=#{@theme-fg}" +set -g status-interval "5" # pane styles set -g pane-border-style "fg=#{@theme-active-bg}" diff --git a/.config/tmux/tmux.conf b/.config/tmux/tmux.conf index a535e36..d7e1cfa 100644 --- a/.config/tmux/tmux.conf +++ b/.config/tmux/tmux.conf @@ -28,6 +28,9 @@ setw -g pane-base-index 1 set -g visual-activity off set -g monitor-activity off +# number windows with respect for base-index +set -g renumber-windows on + # Resize all windows to max size? setw -g aggressive-resize on diff --git a/.config/zsh/.zprofile b/.config/zsh/.zprofile index 8e2bade..b8cd215 100644 --- a/.config/zsh/.zprofile +++ b/.config/zsh/.zprofile @@ -1,4 +1,5 @@ # if [ -z "$SSH_AUTH_SOCK" ]; then # eval "$(ssh-agent -s)" # fi + systemctl --user import-environment XDG_CURRENT_DESKTOP diff --git a/.config/zsh/.zshenv b/.config/zsh/.zshenv index 360248b..3718ae0 100644 --- a/.config/zsh/.zshenv +++ b/.config/zsh/.zshenv @@ -1,7 +1,7 @@ set -a PATH="$HOME/.local/share/go/bin:$PATH" -PATH="${$(find -L ~/.local/bin -type d -printf %p:)%%:}:$PATH" +PATH="${$(find -L ~/.local/bin ! -name '.*' -type d -printf %p:)%%:}:$PATH" # lc vars LANGUAGE="en_US.UTF-8" @@ -26,11 +26,6 @@ GPG_TTY="$(tty)" MANPAGER="sh -c 'col -bx | batcat -l man -p'" MANROFFOPT="-c" MTR_OPTIONS="-t" -MOZ_USE_XINPUT2=1 - -XDG_CURRENT_DESKTOP="gtk" -XDG_SESSION_DESKTOP="$XDG_CURRENT_DESKTOP" -WINDOW_MANAGER="dwm" SUDO_ASKPASS="${HOME}/.local/bin/scripts/dmenu_askpass" SSH_ASKPASS="${HOME}/.local/bin/scripts/ssh-askpass" diff --git a/.config/zsh/.zshrc b/.config/zsh/.zshrc index 4d31258..a7059fc 100644 --- a/.config/zsh/.zshrc +++ b/.config/zsh/.zshrc @@ -1,8 +1,7 @@ -[[ $- != *i* ]] && return - -[ "$TERM" = "linux" ] && export TERM=fbterm +# [[ $- != *i* ]] && return # idk why would it be tho... . ~/.cargo/env +. ~/.config/zsh/modes.sh # ls colors eval "$(dircolors -b)" @@ -40,7 +39,7 @@ bindkey "^[n" backward-word bindkey "^[m" forward-word # completions -autoload -Uz compinit +autoload -Uz compinit compinit zstyle ':completion:*' menu select @@ -86,6 +85,7 @@ alias 7z="7zz" # for whatever reason 7z provides 7zz binary in debian alias wt="watch -d -cn 0.1 " alias cal="ncal -b" alias .e="source .env" +alias tp="taskell ${HOME}/.projects.md" # function aliases bl() { brightnessctl set "$1"% &> /dev/null; } @@ -126,4 +126,4 @@ alias ta="tmux a -t" stty -ixon # print tasks on startup -cat ~/.taskell.md +cat ~/.taskell.md | grep -v '>.*' diff --git a/.config/zsh/modes.sh b/.config/zsh/modes.sh new file mode 100644 index 0000000..e5c8188 --- /dev/null +++ b/.config/zsh/modes.sh @@ -0,0 +1,62 @@ +mode::enable() { + local mode_path="${HOME}/.config/zsh/modes/$1.sh" + [ ! -f "$mode_path" ] && echo "Mode not found!" && return 1 + export MODE__ACTIVE_MODE="$1" + + export MODE__OLD_PS1="$PS1" + export PS1="($1) $PS1" + + # shellcheck disable=SC1090 + source "$mode_path" +} + + +mode::disable() { + export PS1="${MODE__OLD_PS1}" + local mode_path="${HOME}/.config/zsh/modes/$MODE__ACTIVE_MODE.sh" + + unset -v "MODE__OLD_PS1" + unset -v "MODE__ACTIVE_MODE" + + for mode_variable in $(perl -ne '/^(?>declare\s+(?>--?\w+\s*)+\s*)?\s*([\x21-\x3c\x3e-\x7e]+)=.*$/ && print "$1\n"' < "$mode_path"); do + unset "$mode_variable" + done + + for mode_function in $(perl -ne '/^(?>function\s+)?([\x21-\x7e]+)\s*\(\)/ && print "$1\n"' < "$mode_path"); do + unset -f "$mode_function" + done + + for mode_alias in $(perl -ne '/^alias ([\x21-\x3c\x3e-\x7e]+)=/ && print "$1\n"'< "$mode_path"); do + unalias "$mode_alias" + done +} + + +mode::help() { + echo "USAGE" + echo " mode [-h]" + echo " mode " + echo " mode" + echo + echo "OPTIONS" + echo " -h for help lol" + echo + echo "AVAILABLE MODES" + for mode in ~/.config/zsh/modes/*; do + printf " %s\n" "$(basename "${mode%.sh}")" + done +} + +m() { + [ -n "$MODE__ACTIVE_MODE" ] && mode::disable && return + local cmd="${1:?}" + + case "$cmd" in + "-h"|"--help") + mode::help + ;; + *) + mode::enable "$cmd" || mode::help >&2 + ;; + esac +} diff --git a/.config/zsh/modes/cpp.sh b/.config/zsh/modes/cpp.sh new file mode 100644 index 0000000..bf09306 --- /dev/null +++ b/.config/zsh/modes/cpp.sh @@ -0,0 +1,18 @@ +# shellcheck disable=SC2139 + +CPP_MODE__BUILD_DIR="./cmake-build" + +alias cb="cmake --build ${CPP_MODE__BUILD_DIR}" +alias cg="cmake -B ${CPP_MODE__BUILD_DIR} -DCMAKE_EXPORT_COMPILE_COMMANDS=1 && ln -sf ${CPP_MODE__BUILD_DIR}/compile_commands.json ." + +cpp_mode::find_exec() { + find "${CPP_MODE__BUILD_DIR}/$1" -maxdepth 1 -type f -executable +} + +ct() { + eval "$(cpp_mode::find_exec "tests")" +} + +cr() { + eval "$(cpp_mode::find_exec)" +} diff --git a/.local/bin/scripts/ip.me b/.local/bin/scripts/ie similarity index 77% rename from .local/bin/scripts/ip.me rename to .local/bin/scripts/ie index 4c00639..863089b 100755 --- a/.local/bin/scripts/ip.me +++ b/.local/bin/scripts/ie @@ -1,6 +1,6 @@ #!/bin/bash -API_ENDPOINT='http://ip-api.com/json/?fields=7876383' +API_ENDPOINT="http://ip-api.com/json/$1"'?fields=7876383' printf "%s" "$(curl "${API_ENDPOINT}" 2>/dev/null)" | jq -r '[ "IP: \(.query)", "Country: \(.country)", "City: \(.city)", "ISP: \(.isp)", "ASN: \(.as)" ][] | "\(.)"' # vim: ft=bash diff --git a/.local/bin/scripts/md b/.local/bin/scripts/md index c01dfe8..8a79782 100755 --- a/.local/bin/scripts/md +++ b/.local/bin/scripts/md @@ -35,6 +35,7 @@ main() { "--variable=published_time=$(date -Iseconds -d"$(stat "$1" | grep 'Birth:' | sed 's/.*Birth:\s//')")" ) pandoc "${pandoc_options[@]}" <(shift_header "$1") > "$FILENAME" 2>/dev/null && + echo "$FILENAME" && firefox "$FILENAME" 2>/dev/null & disown sleep 5 diff --git a/.local/bin/scripts/my.itmo b/.local/bin/scripts/my.itmo new file mode 100755 index 0000000..b800565 --- /dev/null +++ b/.local/bin/scripts/my.itmo @@ -0,0 +1,320 @@ +#!/bin/sh +# shellcheck disable=all +"exec" "${HOME}/.local/share/venv/statusbar/bin/python3" "-u" "$0" "$@" + +import datetime +import json +import os +import signal +import sys +import time +import traceback + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Literal, Any, TypeVar +from urllib.parse import urlparse + +import requests +from bs4 import BeautifulSoup, Tag + +EMOJI_BY_STATUS = { + 0: '🟡', + 1: '🟢', + 2: '🔴', +} +TIMEOUT = 30 +CONFIG_FILE = f"{os.environ['HOME']}/.my.itmo" +CACHE_FILE = f"{os.environ['HOME']}/.cache/my_itmo.cache" +SECRET_FILE = f"{os.environ['HOME']}/.secrets/my_itmo.secret" +PIPE_FILE = f"{os.environ['XDG_RUNTIME_DIR']}/my.itmo.pipe" + +T = TypeVar('T') + + +def run_forever(fn: Callable, *args, **kwargs): + while True: + try: + fn(*args, **kwargs) + except Exception: + print(traceback.format_exc()) + + +def run_until_successful(fn: Callable[..., T], *args, **kwargs) -> T: + while True: + try: + return fn(*args, **kwargs) + except Exception: + pass + else: + break # no it's not >( # pyright: ignore + + +def send_message(chat_id: int, text: str, token: str): + requests.post(f"https://api.telegram.org/bot{token}/sendMessage", data={ + 'chat_id': chat_id, + 'parse_mode': 'HTML', + 'text': text + }) + + +@dataclass +class StatusObject: + id: int + name: str + notice: str + status: Literal[0, 1, 2] + status_name: str + updated_at: datetime.datetime + created_at: datetime.datetime + + @staticmethod + def from_dict(data: dict[str, Any]): + data['updated_at'] = datetime.datetime.strptime(data['updated_at'].replace("+03:00", ''), '%Y-%m-%dT%H:%M:%S') + data['created_at'] = datetime.datetime.strptime(data['created_at'].replace("+03:00", ''), '%Y-%m-%dT%H:%M:%S') + + return StatusObject(**data) + + +class ApiException(Exception): + status_code: int + body: str + + def __init__(self, status_code: int, body: str): + super().__init__(status_code, body) + self.status_code = status_code + self.body = body + + def __str__(self): + return f'Status code: {self.status_code}\nBody: {self.body}' + + +class Api: + _session: requests.Session + _username: str + _password: str + _access_token: str + _refresh_token: str + _expires_in: int + _refresh_expires_in: int + + def __init__(self, username: str, password: str, *, access_token: str | None = None, + refresh_token: str | None = None, expires_in: int | None = None, + refresh_expires_in: int | None = None, cookies: Any | None = None): + self._session = requests.Session() + self._username = username + self._password = password + self._refresh_token = refresh_token if refresh_token else '' + self._expires_in = expires_in if expires_in else 0 + self._refresh_expires_in = refresh_expires_in if refresh_expires_in else 0 + if cookies: self._session.cookies.update(cookies) + self._access_token = access_token if access_token else '' + if access_token: self._session.headers.update({'Authorization': f'Bearer {access_token}'}) + self._ensure_authorized() + + + def _first_auth(self): + self._session.headers.clear() + self._session.cookies.clear() + code_request = run_until_successful(self._session.get, 'https://id.itmo.ru/auth/realms/itmo/protocol/openid-connect/auth', params={ + 'protocol': 'oauth2', + 'response_type': 'code', + 'client_id': 'student-personal-cabinet', + 'redirect_uri': 'https://my.itmo.ru/login/callback', + 'scope': 'openid profile', + }, timeout=2) + soup = BeautifulSoup(code_request.text, features='html.parser') + form = soup.find('form') + if not isinstance(form, Tag): + raise ApiException(code_request.status_code, code_request.text) + + url = form.get_attribute_list('action')[0] + auth_request = run_until_successful(self._session.post, url, data={'username': self._username, 'password': self._password}) + if auth_request.status_code != 200: + raise ApiException(auth_request.status_code, auth_request.text) + + parsed_url_params = {a.split('=')[0]: a.split('=')[1] for a in urlparse(auth_request.url).query.split('&')} + + self._get_and_save_tokens({ + 'code' : parsed_url_params['code'], + 'client_id': 'student-personal-cabinet', + 'redirect_uri': 'https://my.itmo.ru/login/callback', + 'audience': '', + 'response_type': 'code', + 'grant_type': 'authorization_code', + 'code_verifier': '' + }) + + + def _renew(self): + self._session.headers.clear() + self._session.cookies.clear() + self._get_and_save_tokens({ + 'refresh_token': self._refresh_token, + 'scopes': 'openid profile', + 'client_id': 'student-personal-cabinet', + 'grant_type': 'refresh_token' + }) + + + def _get_and_save_tokens(self, data: Any): + tokens_request = run_until_successful(self._session.post, 'https://id.itmo.ru/auth/realms/itmo/protocol/openid-connect/token', data=data, timeout=2) + if tokens_request.status_code != 200: + raise ApiException(tokens_request.status_code, tokens_request.text) + tokens = tokens_request.json() + self._access_token = tokens['access_token'] + self._expires_in = int(time.time()) + tokens['expires_in'] - 10 + self._refresh_expires_in = int(time.time()) + tokens['refresh_expires_in'] - 10 + self._refresh_token = tokens['refresh_token'] + self._session.headers.update({"Authorization": f"Bearer {tokens_request.json()['access_token']}"}) + + + def _ensure_authorized(self): + current_time = int(time.time()) + + if self._access_token and self._expires_in > current_time: + return + elif self._refresh_token and self._refresh_expires_in > current_time: + self._renew() + else: + self._first_auth() + + + def get_status_list(self): + self._ensure_authorized() + r = run_until_successful(self._session.get, 'https://my.itmo.ru/api/requests/my', timeout=2) + + if r.status_code != 200 or r.json()['error_code'] != 0: + raise ApiException(r.status_code, r.text) + + return [StatusObject.from_dict(obj) for obj in r.json()['result']] + + + def to_dict(self) -> Any: + return { + 'username': self._username, + 'password': self._password, + 'access_token': self._access_token, + 'refresh_token': self._refresh_token, + 'expires_in': self._expires_in, + 'refresh_expires_in': self._refresh_expires_in, + 'cookies': self._session.cookies.get_dict() + } + + + @staticmethod + def from_dict(data: Any): + return Api( + data['username'], + data['password'], + access_token = data['access_token'], + refresh_token = data['refresh_token'], + expires_in = data['expires_in'], + refresh_expires_in = data['refresh_expires_in'], + cookies = data['cookies'], + ) + + +def listen_for_messages(api: Api, timeout=TIMEOUT, filter_func: Callable[[StatusObject], bool] | None = None): + prev_msg = None + while True: + msg = list(filter(filter_func, api.get_status_list())) + + if not msg or msg == prev_msg: + time.sleep(timeout) + continue + + prev_msg = msg + yield msg + time.sleep(timeout) + + +format_status = lambda status: f"{EMOJI_BY_STATUS[status.status]} {status.notice.split('.')[0].strip()}" +format_message = lambda status: f"{EMOJI_BY_STATUS[status.status]} {status.name}\n\n{status.notice}" + + +class IDsFilter: + _ids: list[str] + _update_time: float + + def __init__(self): + self._ids = [] + self._update_dict() + + + def __call__(self, status: StatusObject) -> bool: + if self._update_time + TIMEOUT < time.time(): + self._update_dict() + + return str(status.id) in self._ids + + def _update_dict(self): + self._update_time = time.time() + try: + with open(CONFIG_FILE) as file: + self._ids = file.read().strip().replace(' ', '').split(',') + except Exception: + self._ids = [] + + +class LastUpdateFilter: + _update_time: datetime.datetime + + def __init__(self, ignore_now = False) -> None: + self._update_time = datetime.datetime.fromtimestamp(0) if not ignore_now else datetime.datetime.now() + + def __call__(self, status: StatusObject): + return status.updated_at >= self._update_time + + def update(self): + self._update_time = datetime.datetime.now() + + +def main(): + api = None + + if os.path.isfile(CACHE_FILE): + with open(CACHE_FILE) as file: + api = Api.from_dict(json.load(file)) + + if os.path.isfile(SECRET_FILE): + with open(SECRET_FILE) as secret_file: + data = json.load(secret_file) + + owner_id = data['owner_id'] + bot_token = data['bot_token'] + + if not api: + api = Api(data['username'], data['password']) + else: + print("Missing secret file!", file=sys.stderr) + exit(1) + + def die(*_): + with open(CACHE_FILE, 'w') as file: + json.dump(api.to_dict(), file) + if os.path.isfile(PIPE_FILE): + os.remove(PIPE_FILE) + exit(0) + + signal.signal(signal.SIGTERM, die) + signal.signal(signal.SIGINT, die) + + for message in listen_for_messages(api, filter_func=IDsFilter()): + with open(PIPE_FILE, 'w') as file: + print('\n'.join(map(format_status, message))) + file.write(' '.join(map(format_status, message))) + + # update_filter = LastUpdateFilter(ignore_now=True) + # for message in listen_for_messages(api, filter_func=update_filter): + # formatted_messages = list(map(format_message, message)) + # print('\n---\n'.join(formatted_messages)) + # for message in formatted_messages: + # send_message(owner_id, message, bot_token) + # update_filter.update() + + +if __name__ == "__main__": + run_forever(main) + +# vim: ft=python diff --git a/.local/bin/scripts/tg b/.local/bin/scripts/tg new file mode 100755 index 0000000..7b5a6e6 --- /dev/null +++ b/.local/bin/scripts/tg @@ -0,0 +1,90 @@ +#!/bin/bash + +{ read -r _TELEGRAM__BOT_TOKEN; read -r _TELEGRAM__USER_ID; } < "${HOME}/.secrets/telegram.secret" + +declare -A _TELEGRAM__PARSE_MODES=( + [md]=MarkdownV2 + [html]=HTML +) + +declare -A tg__params=() +tg__code=0 + + +tg::send_message() { + declare -a curl_params=() + + for name in "${!tg__params[@]}"; do + curl_params+=("--data-urlencode" "$name=${tg__params[$name]}") + done + + while IFS= read -d '' -n 4096 -r chunk; do + [ ${tg__code} -eq 1 ] && + txt=(--data-urlencode "text=
${chunk}
") || + txt=(--data-urlencode "text=${chunk}") + curl -X POST "https://api.telegram.org/bot${_TELEGRAM__BOT_TOKEN}/sendMessage" \ + --data-urlencode "chat_id=$_TELEGRAM__USER_ID" \ + "${curl_params[@]}" \ + "${txt[@]}" + done + [ -n "$chunk" ] && { + [ ${tg__code} -eq 1 ] && + txt=(--data-urlencode "text=
${chunk}
") || + txt=(--data-urlencode "text=${chunk}") + curl -X POST "https://api.telegram.org/bot${_TELEGRAM__BOT_TOKEN}/sendMessage" \ + --data-urlencode "chat_id=$_TELEGRAM__USER_ID" \ + "${curl_params[@]}" \ + "${txt[@]}" + } +} + + +(return 0 2>/dev/null) && return + + +help() { + echo "ABSTRACT" + echo " Read data from fd0 and send it to Telegram." + echo + echo "USAGE" + echo " tg [PARAMS]" + echo + echo "PARAMS" + echo " -c, --code Wrap text with
 tags"
+    echo "    -h, --help                 Print this message"
+    echo "    -p, --parse-mode           Telegram parse mode (html, md)"
+    echo "    -r, --print-response       Don't silence Telegram response"
+    echo "        --tg      Set POST parameter with NAME to VALUE"
+}
+
+
+die() {
+    echo "[ERROR] $1" >&2
+    echo
+    help
+    exit 1
+}
+
+
+main() {
+    while [ $# -gt 0 ]; do
+        case "$1" in
+            '-h'|'--help') help; exit 0 ;;
+            '-p'|'--parse-mode') shift; tg__params[parse_mode]="${_TELEGRAM__PARSE_MODES[$1]}" ;;
+            '-c'|'--code') tg__code=1; tg__params[parse_mode]='HTML' ;;
+            '-r'|'--print-response') print_response=1 ;;
+            '--tg'*) tg__params["${1#--tg}"]="$2"; shift ;;
+        esac
+        shift
+    done
+
+    [ -t 0 ] && die "Can't run on stdin!"
+
+    resp="$(tg::send_message "$@" 2>/dev/null)"
+
+    [ -n "${print_response:+_}" ] && echo "$resp"
+
+    [ "$(jq .ok <<<"$resp")" = "true" ] || jq <<< "$resp" >&2
+}
+
+main "$@"
diff --git a/.local/bin/scripts/vpnd b/.local/bin/scripts/vpnd
index c59786c..e6c2db5 100755
--- a/.local/bin/scripts/vpnd
+++ b/.local/bin/scripts/vpnd
@@ -10,40 +10,40 @@ trap 'die' SIGTERM SIGQUIT SIGINT
 
 declare -a CONFIGS
 for config in /etc/wireguard/*; do
-	config="$(basename "$config")"
-	CONFIGS+=("${config%.conf}")
+    config="$(basename "$config")"
+    CONFIGS+=("${config%.conf}")
 done
 
 COMMANDS=("up" "down")
 
 die() {
-	rm $PIPE
-	exit 0
+    rm $PIPE
+    exit 0
 }
 
 in_arr() {
-	declare -n arr="$2"
+    declare -n arr="$2"
 
-	for value in "${arr[@]}"; do
-		[ "$value" = "$1" ] && return 0
-	done
-	return 1
+    for value in "${arr[@]}"; do
+        [ "$value" = "$1" ] && return 0
+    done
+    return 1
 }
 
 main() {
-	mkfifo $PIPE -m666
+    mkfifo $PIPE -m666
 
-	while :; do
-		read -r cmd ifname < $PIPE
+    while :; do
+        read -r cmd ifname < $PIPE
 
-		if ! in_arr "$ifname" "CONFIGS"; then
-			echo "ERROR: Invalid interface $ifname" > $PIPE
-		elif ! in_arr "$cmd" "COMMANDS"; then
-			echo "ERROR: Invalid command $cmd" > $PIPE
-		else
-			wg-quick "$cmd" "$ifname" > $PIPE 2>&1
-		fi
-	done
+        if ! in_arr "$ifname" "CONFIGS"; then
+            echo "ERROR: Invalid interface $ifname" > $PIPE
+        elif ! in_arr "$cmd" "COMMANDS"; then
+            echo "ERROR: Invalid command $cmd" > $PIPE
+        else
+            wg-quick "$cmd" "$ifname" > $PIPE 2>&1
+        fi
+    done
 }
 
 main
diff --git a/.local/bin/source/src_venv b/.local/bin/source/src_venv
index 896435f..1a4ec80 100644
--- a/.local/bin/source/src_venv
+++ b/.local/bin/source/src_venv
@@ -41,9 +41,9 @@ while [ "$#" -gt 0 ]; do
         '-c'|'--create') OPERATION=c;;
         '-d'|'--deactivate') OPERATION=d; return;;
         '-r'|'--remove') OPERATION=r;;
-        '-f'|'--folder') shift; VENV_FOLDER_NAME="$1";;
+        '-f'|'--folder') shift; VENV_FOLDER_PATH="$1";;
         -*) help; return;;
-        *) shift; VENV_FOLDER_PATH="$1";;
+        *) VENV_FOLDER_NAME="$1";;
     esac
     shift
 done
diff --git a/.local/bin/statusbar/sb-battery b/.local/bin/statusbar/sb-battery
index 9fc1ef3..b49a6e4 100755
--- a/.local/bin/statusbar/sb-battery
+++ b/.local/bin/statusbar/sb-battery
@@ -4,22 +4,37 @@ shopt -s extglob
 
 declare -a batteries
 
+status_by_charge() {
+    capacity="$1"
+
+    if [ "$capacity" -ge 95 ]; then
+        echo ' '
+    elif [ "$capacity" -ge 65 ]; then
+        echo ' '
+    elif [ "$capacity" -ge 50 ]; then
+        echo ' '
+    elif [ "$capacity" -gt 20 ]; then
+        echo ' '
+    fi
+}
+
 for battery_path in /sys/class/power_supply/!(AC*); do
-	status="$(cat "${battery_path}/status")" 
+	status="$(cat "${battery_path}/status")"
+	capacity="$(cat "${battery_path}/capacity")"
+        sep=$([ "$1" == "-s" ] && echo " ")
 
 	case "${status}" in
-		"Full") status_string="⚡" ;;
-		"Discharging") status_string="🔋" ;;
-		"Charging") status_string="🔌" ;;
-		"Not charging") status_string="🛑" ;;
-		"Unknown") status_string="♻️" ;;
-		*) status_string="??" ;;
+		"Full") status_symbol=" " ;;
+                "Discharging") status_symbol="$(status_by_charge "${capacity}")" ;;
+		"Charging") status_symbol="󱐥 " ;;
+		"Not charging") status_symbol="󱐤 " ;;
+		"Unknown") status_symbol="󰒲 " ;;
+		*) status_symbol="?? " ;;
 	esac
 
-	capacity="$(cat "${battery_path}/capacity")"
-	[ "$status" = "Discharging" ] && [ "$capacity" -le 25 ] && status_string="❗"
-
-	batteries+=("${status_string} ${capacity}%")
+        [ "$capacity" -eq 100 ] && status_symbol=" "
+	[ "$status" = "Discharging" ] && [ "$capacity" -le 20 ] && { status_symbol="❗"; [ -n "$sep" ] && sep="" || sep=" "; }
+	batteries+=("${status_symbol}${sep}${capacity}%")
 done
 
 echo "${batteries[@]}"
diff --git a/.local/bin/statusbar/sb-status b/.local/bin/statusbar/sb-status
new file mode 100755
index 0000000..0693431
--- /dev/null
+++ b/.local/bin/statusbar/sb-status
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+FILEPATH="${XDG_RUNTIME_DIR}/my.itmo.pipe"
+
+[ -f "${FILEPATH}" ] && cat "${FILEPATH}"
diff --git a/.local/bin/statusbar/sb-vpn b/.local/bin/statusbar/sb-vpn
index ca3d275..4e19481 100755
--- a/.local/bin/statusbar/sb-vpn
+++ b/.local/bin/statusbar/sb-vpn
@@ -3,5 +3,5 @@
 IFNAME="$(ip link show | grep 'wg_' | cut -d ' ' -f 2 | sed 's/://' | sed 's/wg_//' | tr '[:lower:]' '[:upper:]' | sed 's/_D/ (dpi)/')"
 
 if [ -n "${IFNAME}" ]; then
-    echo "🛡️ ${IFNAME}"
+    echo "󰦝 ${IFNAME}"
 fi
diff --git a/.xinitrc b/.xinitrc
index cb967ae..bfcd495 100644
--- a/.xinitrc
+++ b/.xinitrc
@@ -10,7 +10,8 @@ export XDG_CURRENT_DESKTOP="gtk"
 export XDG_SESSION_DESKTOP="$XDG_CURRENT_DESKTOP"
 export WINDOW_MANAGER="dwm"
 
-exec ssh-agent "${HOME}"/.local/src/dwm/dwm
-# exec ssh-agent /usr/bin/dwm
+eval "$(ssh-agent)"
+
+exec "${HOME}"/.local/src/dwm/dwm
 
 # vim: ft=sh