feat: telegram listener pushes command list to bot menu via setMyCommands
Map entries may now carry a menu description (/cmd::desc=bash) shown in
the bot's "/" menu; it falls back to the bash command (~40 chars) when
omitted. The command list is pushed automatically after every map edit,
on --enable, and at daemon start, plus a manual --sync-commands flag
(wired into POS_FLAGS completion). Names are validated against Telegram's
lowercase [a-z0-9_] rule — invalid ones are skipped from the menu with a
warning but still resolve when typed; an empty map clears the menu.
Fixes found by unit-testing the sync path: map_has always returned false
(awk END{exit 1} overrode the match), map_set had a local cross-reference
that broke under set -u, and warn() leaked into the generated JSON (now
stderr). Docs + AGENT_TODO updated; make gen && make check pass.
This commit is contained in:
@@ -16,6 +16,8 @@ summary (newest last).
|
|||||||
|
|
||||||
## Done
|
## Done
|
||||||
|
|
||||||
|
- **2026-08-09** — Telegram listener pushes its mapped `/commands` to the bot's `/` menu via `setMyCommands` (auto after every map edit, on `--enable`, and at daemon start; manual `--sync-commands` flag). Map lines may carry a menu description: `/cmd::short description=bash command` (falls back to the bash command, ~40 chars). Names are validated against Telegram's lowercase `[a-z0-9_]` rule — invalid ones are skipped from the menu with a warning but still resolve when typed; empty map clears the menu. Fixed latent bugs found by the sync work: `map_has` (awk `END{exit 1}` overrode the match), and `warn()` went to stdout so it leaked into the generated JSON (now stderr).
|
||||||
|
|
||||||
- **2026-08-09** — `pos config <TAB>` scope completion is now cached at gen time (`_pos_config_scopes` array emitted by `make gen` from the `# POS_CONFIG:` registry) instead of scanning ~40 tools per TAB — a per-keypress subshell storm that wedged interactive shells for minutes on the loaded homelab box. Two stuck `-bash` sessions (69%/38% CPU) killed. `plugin_marker`/`plugin_keys` hardened with `|| true` so `config_keys` no longer aborts mid-scan under `set -euo pipefail` on mixed lib/plugin dirs (installed layout) — fixes missing plugin keys in `pos config entertainment`.
|
- **2026-08-09** — `pos config <TAB>` scope completion is now cached at gen time (`_pos_config_scopes` array emitted by `make gen` from the `# POS_CONFIG:` registry) instead of scanning ~40 tools per TAB — a per-keypress subshell storm that wedged interactive shells for minutes on the loaded homelab box. Two stuck `-bash` sessions (69%/38% CPU) killed. `plugin_marker`/`plugin_keys` hardened with `|| true` so `config_keys` no longer aborts mid-scan under `set -euo pipefail` on mixed lib/plugin dirs (installed layout) — fixes missing plugin keys in `pos config entertainment`.
|
||||||
|
|
||||||
- **2026-08-09** — `pos config <scope>` interactive config editor: reads the `# POS_CONFIG:` registry across tools into a single runtime config (`~/.config/linux_post_install/*.env`, one file per scope, chmod 600); secret masking with show/hide toggle, `digits:`/`num:`/`url:` validation, `-` to clear, blank keeps; `*plugins` marker expands plugin vars (entertainment) from `entertainment-lib.sh`; `desc::example` value-format hints shown in the editor; gen-docs now handles category-less tools (`pos-config`), fixed a `set -e`+`pipefail` bug that truncated the header registry.
|
- **2026-08-09** — `pos config <scope>` interactive config editor: reads the `# POS_CONFIG:` registry across tools into a single runtime config (`~/.config/linux_post_install/*.env`, one file per scope, chmod 600); secret masking with show/hide toggle, `digits:`/`num:`/`url:` validation, `-` to clear, blank keeps; `*plugins` marker expands plugin vars (entertainment) from `entertainment-lib.sh`; `desc::example` value-format hints shown in the editor; gen-docs now handles category-less tools (`pos-config`), fixed a `set -e`+`pipefail` bug that truncated the header registry.
|
||||||
|
|||||||
@@ -553,7 +553,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
|||||||
| `features/autostart.sh` | 14 | Boot-time feature (moved from `bin/`, flag-gated service) |
|
| `features/autostart.sh` | 14 | Boot-time feature (moved from `bin/`, flag-gated service) |
|
||||||
<!-- GEN:START filetable -->
|
<!-- GEN:START filetable -->
|
||||||
| `bin/pos` | 270 | CLI dispatcher with smart arg matching + logging + category help |
|
| `bin/pos` | 270 | CLI dispatcher with smart arg matching + logging + category help |
|
||||||
| `bin/pos-communication-telegram-listener` | 437 | Telegram bot listener: map /command → bash, run them on chat messages |
|
| `bin/pos-communication-telegram-listener` | 508 | Telegram bot listener: map /command → bash, run them on chat messages |
|
||||||
| `bin/pos-communication-telegram-sender` | 270 | Send Telegram messages/files/links/stickers via Bot API (send, test, config set) |
|
| `bin/pos-communication-telegram-sender` | 270 | Send Telegram messages/files/links/stickers via Bot API (send, test, config set) |
|
||||||
| `bin/pos-config` | 80 | Interactive editor for the tools' runtime config (reads # POS_CONFIG: registry) |
|
| `bin/pos-config` | 80 | Interactive editor for the tools' runtime config (reads # POS_CONFIG: registry) |
|
||||||
| `bin/pos-docker-compose` | 366 | Docker Compose service manager (ls/up/down/restart/logs/update/config) |
|
| `bin/pos-docker-compose` | 366 | Docker Compose service manager (ls/up/down/restart/logs/update/config) |
|
||||||
|
|||||||
@@ -224,10 +224,13 @@ The bot token is a secret — it is stored only in `~/.config/linux_post_install
|
|||||||
| `pos communication telegram listener --status` | Shows service state (running/autostart), config + map file paths, and the mapped commands |
|
| `pos communication telegram listener --status` | Shows service state (running/autostart), config + map file paths, and the mapped commands |
|
||||||
| `pos communication telegram listener --enable` | Installs + starts a systemd **user** service (`pos-telegram-listener.service`); the daemon polls `getUpdates` and runs mapped commands |
|
| `pos communication telegram listener --enable` | Installs + starts a systemd **user** service (`pos-telegram-listener.service`); the daemon polls `getUpdates` and runs mapped commands |
|
||||||
| `pos communication telegram listener --disable` | Stops, disables, and removes the service |
|
| `pos communication telegram listener --disable` | Stops, disables, and removes the service |
|
||||||
|
| `pos communication telegram listener --sync-commands` | Push the mapped `/commands` to the bot's `/` menu (`setMyCommands`) — also run automatically after every map edit, on `--enable`, and at daemon start |
|
||||||
| `pos communication telegram listener --run` | Run the polling loop in the foreground (what the service executes) |
|
| `pos communication telegram listener --run` | Run the polling loop in the foreground (what the service executes) |
|
||||||
|
|
||||||
The map file is re-read for every message — edits apply without a restart. The listener only reacts to the owner chat (`TELEGRAM_CHAT_ID`); anyone else's message is ignored. `/help` lists mapped commands; an unmapped command replies "Unknown command". Commands run as your user via `timeout 60 bash -c "…"` (stdout + stderr are replied, truncated to ~3800 chars; empty output → `OK`), so `sudo` inside them needs a NOPASSWD rule. A map value prefixed with `@quiet ` runs the command but does NOT reply — for commands that already send their own notification (e.g. `/status=@quiet pos system health --send`), avoiding a double message. `--enable` warns if linger is off — the service stops when you log out unless you run `sudo loginctl enable-linger $(whoami)`.
|
The map file is re-read for every message — edits apply without a restart. The listener only reacts to the owner chat (`TELEGRAM_CHAT_ID`); anyone else's message is ignored. `/help` lists mapped commands; an unmapped command replies "Unknown command". Commands run as your user via `timeout 60 bash -c "…"` (stdout + stderr are replied, truncated to ~3800 chars; empty output → `OK`), so `sudo` inside them needs a NOPASSWD rule. A map value prefixed with `@quiet ` runs the command but does NOT reply — for commands that already send their own notification (e.g. `/status=@quiet pos system health --send`), avoiding a double message. `--enable` warns if linger is off — the service stops when you log out unless you run `sudo loginctl enable-linger $(whoami)`.
|
||||||
|
|
||||||
|
Map entries may carry an optional **description** shown in the bot's `/` menu: `/cmd::short description=bash command` (the description falls back to the bash command, truncated to ~40 chars, when omitted). After every add/edit/remove the command list is pushed to the bot via `setMyCommands`, so the menu stays in sync; an empty map clears the menu. Telegram only registers lowercase `[a-z0-9_]` names (1–32 chars) — commands like `/Status` or `/my-cmd` are skipped from the menu with a warning but still resolve when typed.
|
||||||
|
|
||||||
### entertainment
|
### entertainment
|
||||||
|
|
||||||
**File:** `bin/pos-entertainment-send`
|
**File:** `bin/pos-entertainment-send`
|
||||||
|
|||||||
@@ -79,6 +79,14 @@ pos communication telegram listener --disable # remove it
|
|||||||
/temp=sensors | grep -i 'Tctl\|package id 0'
|
/temp=sensors | grep -i 'Tctl\|package id 0'
|
||||||
/update=cd /path/to/repo && git pull
|
/update=cd /path/to/repo && git pull
|
||||||
```
|
```
|
||||||
|
- **Bot command menu:** the mapped commands are pushed to the bot's `/` menu
|
||||||
|
(`setMyCommands`) after every map edit, on `--enable`, and at daemon start
|
||||||
|
(force it anytime with `--sync-commands`). Add a short description with the
|
||||||
|
`/cmd::description=bash command` syntax — e.g.
|
||||||
|
`/backup::Encrypted nightly backup=@quiet pos system backup --send` — or it
|
||||||
|
falls back to the bash command. Telegram only registers lowercase `[a-z0-9_]`
|
||||||
|
names (1–32 chars); `/Status` or `/my-cmd` are skipped from the menu but still
|
||||||
|
work when typed. An empty map clears the menu.
|
||||||
- **Owner-only:** the bot only reacts to `TELEGRAM_CHAT_ID` (your own chat);
|
- **Owner-only:** the bot only reacts to `TELEGRAM_CHAT_ID` (your own chat);
|
||||||
others are ignored. `/help` lists mapped commands; unknown → "Unknown command".
|
others are ignored. `/help` lists mapped commands; unknown → "Unknown command".
|
||||||
- **Runs as you:** mapped commands execute as your user with a 60s timeout,
|
- **Runs as you:** mapped commands execute as your user with a 60s timeout,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
# POS: communication telegram-listener — Telegram bot listener: map /command → bash, run them on chat messages
|
# POS: communication telegram-listener — Telegram bot listener: map /command → bash, run them on chat messages
|
||||||
# POS_FLAGS: --enable --disable --status --run
|
# POS_FLAGS: --enable --disable --status --sync-commands --run
|
||||||
|
|
||||||
CONFIG_DIR="$HOME/.config/linux_post_install"
|
CONFIG_DIR="$HOME/.config/linux_post_install"
|
||||||
CONFIG_FILE="$CONFIG_DIR/telegram.env"
|
CONFIG_FILE="$CONFIG_DIR/telegram.env"
|
||||||
@@ -12,7 +12,7 @@ USER_SYSTEMD_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
|
|||||||
|
|
||||||
err() { echo "ERROR: $*" >&2; exit 1; }
|
err() { echo "ERROR: $*" >&2; exit 1; }
|
||||||
log() { echo "[+] $*"; }
|
log() { echo "[+] $*"; }
|
||||||
warn() { echo "[!] $*"; }
|
warn() { echo "[!] $*" >&2; }
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
@@ -25,10 +25,13 @@ Commands:
|
|||||||
--enable Install + start the systemd user service (autostarts on login)
|
--enable Install + start the systemd user service (autostarts on login)
|
||||||
--disable Stop + disable + remove the service
|
--disable Stop + disable + remove the service
|
||||||
--status Show service state and the command map
|
--status Show service state and the command map
|
||||||
|
--sync-commands
|
||||||
|
Push the mapped /commands to the bot's "/" menu (setMyCommands)
|
||||||
--run Run the polling loop in the foreground (used by the service)
|
--run Run the polling loop in the foreground (used by the service)
|
||||||
|
|
||||||
Config: $CONFIG_FILE (TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID)
|
Config: $CONFIG_FILE (TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID)
|
||||||
Map: $MAP_FILE — one '/cmd=bash command' per line
|
Map: $MAP_FILE — '/cmd=bash command' per line (optional
|
||||||
|
'/cmd::short description=bash command' shown in the bot menu)
|
||||||
|
|
||||||
Prefix a map value with '@quiet ' to run the command without replying —
|
Prefix a map value with '@quiet ' to run the command without replying —
|
||||||
for commands that already send their own notification, so you don't get it
|
for commands that already send their own notification, so you don't get it
|
||||||
@@ -42,6 +45,11 @@ The listener only reacts to the owner chat (TELEGRAM_CHAT_ID). Commands run
|
|||||||
as your user, so 'sudo' inside them needs a NOPASSWD rule. The interactive
|
as your user, so 'sudo' inside them needs a NOPASSWD rule. The interactive
|
||||||
editor runs 'bash -n' to syntax-check commands before saving.
|
editor runs 'bash -n' to syntax-check commands before saving.
|
||||||
|
|
||||||
|
The command list is pushed to the bot's "/" menu (setMyCommands) after every
|
||||||
|
map edit, on --enable, and at daemon start; force it anytime with
|
||||||
|
--sync-commands. Telegram only accepts lowercase [a-z0-9_] names — any other
|
||||||
|
command is skipped from the menu but still resolves when typed.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
pos communication telegram listener
|
pos communication telegram listener
|
||||||
pos communication telegram listener --enable
|
pos communication telegram listener --enable
|
||||||
@@ -67,35 +75,46 @@ load_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ── command map (MAP_FILE) ──────────────────────────────────────
|
# ── command map (MAP_FILE) ──────────────────────────────────────
|
||||||
# Lines: /cmd=bash command. Keys keep the leading slash; read via awk so
|
# Lines: /cmd=bash command, or /cmd::description=bash command. Keys keep the
|
||||||
# values may contain '='. The map is re-read per message — edits apply
|
# leading slash; read via awk so values may contain '='. The map is re-read
|
||||||
# without restarting the listener.
|
# per message — edits apply without restarting the listener.
|
||||||
|
|
||||||
map_entries() {
|
map_entries() {
|
||||||
[ -f "$MAP_FILE" ] || return 0
|
[ -f "$MAP_FILE" ] || return 0
|
||||||
grep -E '^/[A-Za-z0-9_.-]+=' "$MAP_FILE" | while IFS= read -r line; do
|
grep -E '^/[A-Za-z0-9_.-]+(::.*)?=' "$MAP_FILE" | while IFS= read -r line; do
|
||||||
printf '%s|%s\n' "${line%%=*}" "${line#*=}"
|
local left="${line%%=*}" cmd desc
|
||||||
|
if [[ "$left" == *::* ]]; then
|
||||||
|
cmd="${left%%::*}"
|
||||||
|
desc="${left#*::}"
|
||||||
|
else
|
||||||
|
cmd="$left"
|
||||||
|
desc=""
|
||||||
|
fi
|
||||||
|
printf '%s|%s|%s\n' "$cmd" "$desc" "${line#*=}"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
map_has() {
|
map_has() {
|
||||||
[ -f "$MAP_FILE" ] && awk -F= -v k="$1" '$1==k{exit 0} END{exit 1}' "$MAP_FILE"
|
[ -f "$MAP_FILE" ] || return 1
|
||||||
|
awk -F= -v k="$1" '$1==k || index($1, k "::")==1 {found=1} END{exit !found}' "$MAP_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
map_get() {
|
map_get() {
|
||||||
[ -f "$MAP_FILE" ] || return 0
|
[ -f "$MAP_FILE" ] || return 0
|
||||||
awk -F= -v k="$1" '$1==k{sub(/^[^=]*=/,""); print}' "$MAP_FILE"
|
awk -F= -v k="$1" '$1==k || index($1, k "::")==1 {sub(/^[^=]*=/,""); print}' "$MAP_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
map_set() {
|
map_set() {
|
||||||
local cmd="$1" value="$2"
|
local cmd="$1" value="$2" desc="${3:-}"
|
||||||
|
local key="$cmd"
|
||||||
mkdir -p "$CONFIG_DIR"
|
mkdir -p "$CONFIG_DIR"
|
||||||
touch "$MAP_FILE"
|
touch "$MAP_FILE"
|
||||||
chmod 600 "$MAP_FILE"
|
chmod 600 "$MAP_FILE"
|
||||||
local tmp
|
local tmp
|
||||||
tmp="$(mktemp)"
|
tmp="$(mktemp)"
|
||||||
awk -v k="$cmd" 'index($0, k "=") != 1 { print }' "$MAP_FILE" > "$tmp"
|
[ -n "$desc" ] && key="${cmd}::${desc}"
|
||||||
printf '%s=%s\n' "$cmd" "$value" >> "$tmp"
|
awk -v k="$cmd" 'index($0, k "=") != 1 && index($0, k "::") != 1 { print }' "$MAP_FILE" > "$tmp"
|
||||||
|
printf '%s=%s\n' "$key" "$value" >> "$tmp"
|
||||||
mv "$tmp" "$MAP_FILE"
|
mv "$tmp" "$MAP_FILE"
|
||||||
chmod 600 "$MAP_FILE"
|
chmod 600 "$MAP_FILE"
|
||||||
}
|
}
|
||||||
@@ -104,20 +123,20 @@ map_del() {
|
|||||||
[ -f "$MAP_FILE" ] || return 0
|
[ -f "$MAP_FILE" ] || return 0
|
||||||
local cmd="$1" tmp
|
local cmd="$1" tmp
|
||||||
tmp="$(mktemp)"
|
tmp="$(mktemp)"
|
||||||
awk -v k="$cmd" 'index($0, k "=") != 1 { print }' "$MAP_FILE" > "$tmp"
|
awk -v k="$cmd" 'index($0, k "=") != 1 && index($0, k "::") != 1 { print }' "$MAP_FILE" > "$tmp"
|
||||||
mv "$tmp" "$MAP_FILE"
|
mv "$tmp" "$MAP_FILE"
|
||||||
chmod 600 "$MAP_FILE"
|
chmod 600 "$MAP_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
MAP_CMDS=(); MAP_VALS=(); MAP_N=0
|
MAP_CMDS=(); MAP_VALS=(); MAP_DESCS=(); MAP_N=0
|
||||||
load_map() {
|
load_map() {
|
||||||
MAP_CMDS=(); MAP_VALS=(); MAP_N=0
|
MAP_CMDS=(); MAP_VALS=(); MAP_DESCS=(); MAP_N=0
|
||||||
[ -f "$MAP_FILE" ] || return 0
|
[ -f "$MAP_FILE" ] || return 0
|
||||||
local i=0 line cmd value
|
local i=0 line cmd desc value
|
||||||
while IFS='|' read -r cmd value; do
|
while IFS='|' read -r cmd desc value; do
|
||||||
[ -n "$cmd" ] || continue
|
[ -n "$cmd" ] || continue
|
||||||
i=$((i + 1))
|
i=$((i + 1))
|
||||||
MAP_CMDS[$i]="$cmd"; MAP_VALS[$i]="$value"
|
MAP_CMDS[$i]="$cmd"; MAP_DESCS[$i]="$desc"; MAP_VALS[$i]="$value"
|
||||||
done <<< "$(map_entries)"
|
done <<< "$(map_entries)"
|
||||||
MAP_N="$i"
|
MAP_N="$i"
|
||||||
}
|
}
|
||||||
@@ -132,6 +151,51 @@ map_cmds_list() {
|
|||||||
printf '%s' "${out:-none}"
|
printf '%s' "${out:-none}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Bot command menu sync (setMyCommands) ──────────────────────
|
||||||
|
# Push the mapped /commands to the bot's "/" menu so they show in the
|
||||||
|
# Telegram UI, not just resolve when typed. Runs after map edits, on
|
||||||
|
# --enable, and at daemon start; force it with --sync-commands.
|
||||||
|
|
||||||
|
description_for() {
|
||||||
|
local value="$1" desc="${2:-}"
|
||||||
|
if [ -n "$desc" ]; then
|
||||||
|
printf '%s' "$desc"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
value="$(strip_quiet "$value")"
|
||||||
|
printf '%s' "${value:0:40}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_commands_json() {
|
||||||
|
load_map
|
||||||
|
local i name desc
|
||||||
|
for ((i=1; i<=MAP_N; i++)); do
|
||||||
|
name="${MAP_CMDS[$i]#/}"
|
||||||
|
if ! [[ "$name" =~ ^[a-z][a-z0-9_]{0,31}$ ]]; then
|
||||||
|
warn "skip '$name' from bot menu — Telegram commands are lowercase [a-z0-9_], 1-32 chars"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
desc="$(description_for "${MAP_VALS[$i]}" "${MAP_DESCS[$i]}")"
|
||||||
|
printf '%s\t%s\n' "$name" "$desc"
|
||||||
|
done | jq -Rr 'split("\t") | {command: .[0], description: .[1]}' | jq -sc '.'
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_bot_commands() {
|
||||||
|
load_config
|
||||||
|
[ -n "${TELEGRAM_BOT_TOKEN:-}" ] || { warn "no bot token — run 'pos communication telegram config set TELEGRAM_BOT_TOKEN=...' first"; return 1; }
|
||||||
|
command -v jq &>/dev/null || { warn "jq not found — cannot sync bot commands"; return 1; }
|
||||||
|
local json n
|
||||||
|
json="$(build_commands_json)"
|
||||||
|
n="$(printf '%s' "$json" | jq 'length')"
|
||||||
|
if curl -fsS -m 30 -X POST "${API}/bot${TELEGRAM_BOT_TOKEN}/setMyCommands" \
|
||||||
|
--data-urlencode "commands=${json}" >/dev/null 2>&1; then
|
||||||
|
log "bot command menu updated ($n commands)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
warn "setMyCommands failed"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
check_syntax() {
|
check_syntax() {
|
||||||
bash -n -c "$(strip_quiet "$1")" 2>&1
|
bash -n -c "$(strip_quiet "$1")" 2>&1
|
||||||
}
|
}
|
||||||
@@ -146,7 +210,7 @@ ui_pick() {
|
|||||||
echo
|
echo
|
||||||
local i
|
local i
|
||||||
for ((i=1; i<=MAP_N; i++)); do
|
for ((i=1; i<=MAP_N; i++)); do
|
||||||
printf ' %2d) %-16s -> %s\n' "$i" "${MAP_CMDS[$i]}" "${MAP_VALS[$i]}"
|
printf ' %2d) %-16s -> %s%s\n' "$i" "${MAP_CMDS[$i]}" "${MAP_VALS[$i]}" "${MAP_DESCS[$i]:+ (${MAP_DESCS[$i]})}"
|
||||||
done
|
done
|
||||||
local idx
|
local idx
|
||||||
read -rp "Entry number: " idx
|
read -rp "Entry number: " idx
|
||||||
@@ -188,7 +252,7 @@ ui_run_command() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ui_add() {
|
ui_add() {
|
||||||
local cmd value out
|
local cmd value desc out
|
||||||
read -rp "/command name (e.g. /status): " cmd
|
read -rp "/command name (e.g. /status): " cmd
|
||||||
[ -n "$cmd" ] || { warn "empty command name"; return; }
|
[ -n "$cmd" ] || { warn "empty command name"; return; }
|
||||||
case "$cmd" in
|
case "$cmd" in
|
||||||
@@ -197,8 +261,10 @@ ui_add() {
|
|||||||
esac
|
esac
|
||||||
read -rp "bash command: " value
|
read -rp "bash command: " value
|
||||||
[ -n "$value" ] || { warn "empty bash command"; return; }
|
[ -n "$value" ] || { warn "empty bash command"; return; }
|
||||||
|
read -rp "description (optional, shown in the bot menu): " desc
|
||||||
if out="$(check_syntax "$value")"; then
|
if out="$(check_syntax "$value")"; then
|
||||||
map_set "$cmd" "$value"
|
map_set "$cmd" "$value" "$desc"
|
||||||
|
sync_bot_commands || true
|
||||||
log "saved $cmd -> $value"
|
log "saved $cmd -> $value"
|
||||||
else
|
else
|
||||||
warn "syntax error — not saved:"
|
warn "syntax error — not saved:"
|
||||||
@@ -213,14 +279,16 @@ ui_add() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ui_edit() {
|
ui_edit() {
|
||||||
local idx cmd value out
|
local idx cmd value desc out
|
||||||
idx="$(ui_pick)" || return
|
idx="$(ui_pick)" || return
|
||||||
cmd="${MAP_CMDS[$idx]}"
|
cmd="${MAP_CMDS[$idx]}"
|
||||||
echo "Editing: $cmd -> ${MAP_VALS[$idx]}"
|
echo "Editing: $cmd -> ${MAP_VALS[$idx]}"
|
||||||
read -rp "bash command: " value
|
read -rp "bash command: " value
|
||||||
[ -n "$value" ] || { warn "empty bash command"; return; }
|
[ -n "$value" ] || { warn "empty bash command"; return; }
|
||||||
|
read -rp "description (current: ${MAP_DESCS[$idx]:-none}): " desc
|
||||||
if out="$(check_syntax "$value")"; then
|
if out="$(check_syntax "$value")"; then
|
||||||
map_set "$cmd" "$value"
|
map_set "$cmd" "$value" "$desc"
|
||||||
|
sync_bot_commands || true
|
||||||
log "updated $cmd -> $value"
|
log "updated $cmd -> $value"
|
||||||
else
|
else
|
||||||
warn "syntax error — not saved:"
|
warn "syntax error — not saved:"
|
||||||
@@ -234,7 +302,7 @@ ui_remove() {
|
|||||||
cmd="${MAP_CMDS[$idx]}"
|
cmd="${MAP_CMDS[$idx]}"
|
||||||
read -rp "Remove '$cmd'? [y/N] " yn
|
read -rp "Remove '$cmd'? [y/N] " yn
|
||||||
case "$yn" in
|
case "$yn" in
|
||||||
y|Y) map_del "$cmd"; log "removed $cmd" ;;
|
y|Y) map_del "$cmd"; sync_bot_commands || true; log "removed $cmd" ;;
|
||||||
*) warn "canceled" ;;
|
*) warn "canceled" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -257,7 +325,7 @@ ui() {
|
|||||||
else
|
else
|
||||||
local i
|
local i
|
||||||
for ((i=1; i<=MAP_N; i++)); do
|
for ((i=1; i<=MAP_N; i++)); do
|
||||||
printf ' %2d) %-16s -> %s\n' "$i" "${MAP_CMDS[$i]}" "${MAP_VALS[$i]}"
|
printf ' %2d) %-16s -> %s%s\n' "$i" "${MAP_CMDS[$i]}" "${MAP_VALS[$i]}" "${MAP_DESCS[$i]:+ (${MAP_DESCS[$i]})}"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
@@ -307,6 +375,7 @@ EOF
|
|||||||
systemctl --user daemon-reload
|
systemctl --user daemon-reload
|
||||||
systemctl --user enable --now "$SERVICE"
|
systemctl --user enable --now "$SERVICE"
|
||||||
log "listener service enabled: $SERVICE"
|
log "listener service enabled: $SERVICE"
|
||||||
|
sync_bot_commands || true
|
||||||
warn "commands run as $(id -un) — 'sudo' inside them needs a NOPASSWD rule"
|
warn "commands run as $(id -un) — 'sudo' inside them needs a NOPASSWD rule"
|
||||||
if command -v loginctl >/dev/null 2>&1; then
|
if command -v loginctl >/dev/null 2>&1; then
|
||||||
if ! loginctl show-user "$(id -un)" 2>/dev/null | grep -q '^Linger=yes'; then
|
if ! loginctl show-user "$(id -un)" 2>/dev/null | grep -q '^Linger=yes'; then
|
||||||
@@ -343,7 +412,7 @@ status() {
|
|||||||
echo "commands: $MAP_N mapped"
|
echo "commands: $MAP_N mapped"
|
||||||
local i
|
local i
|
||||||
for ((i=1; i<=MAP_N; i++)); do
|
for ((i=1; i<=MAP_N; i++)); do
|
||||||
printf ' %-16s -> %s\n' "${MAP_CMDS[$i]}" "${MAP_VALS[$i]}"
|
printf ' %-16s -> %s%s\n' "${MAP_CMDS[$i]}" "${MAP_VALS[$i]}" "${MAP_DESCS[$i]:+ (${MAP_DESCS[$i]})}"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,6 +464,7 @@ run_daemon() {
|
|||||||
load_config
|
load_config
|
||||||
[ -n "${TELEGRAM_BOT_TOKEN:-}" ] || err "No bot token — run 'pos communication telegram config set TELEGRAM_BOT_TOKEN=...'"
|
[ -n "${TELEGRAM_BOT_TOKEN:-}" ] || err "No bot token — run 'pos communication telegram config set TELEGRAM_BOT_TOKEN=...'"
|
||||||
[ -n "${TELEGRAM_CHAT_ID:-}" ] || err "No chat id — run 'pos communication telegram config set TELEGRAM_CHAT_ID=...'"
|
[ -n "${TELEGRAM_CHAT_ID:-}" ] || err "No chat id — run 'pos communication telegram config set TELEGRAM_CHAT_ID=...'"
|
||||||
|
sync_bot_commands || true
|
||||||
|
|
||||||
local offset=0
|
local offset=0
|
||||||
log "listener running (owner chat ${TELEGRAM_CHAT_ID}) — Ctrl+C to stop"
|
log "listener running (owner chat ${TELEGRAM_CHAT_ID}) — Ctrl+C to stop"
|
||||||
@@ -431,6 +501,7 @@ case "${1:-}" in
|
|||||||
--enable) enable_service ;;
|
--enable) enable_service ;;
|
||||||
--disable) disable_service ;;
|
--disable) disable_service ;;
|
||||||
--status) status ;;
|
--status) status ;;
|
||||||
|
--sync-commands) sync_bot_commands ;;
|
||||||
--run) run_daemon ;;
|
--run) run_daemon ;;
|
||||||
"") ui ;;
|
"") ui ;;
|
||||||
*) err "Unknown option '$1' (see --help)" ;;
|
*) err "Unknown option '$1' (see --help)" ;;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Install: source this file in ~/.bashrc or place in /etc/bash_completion.d/
|
# Install: source this file in ~/.bashrc or place in /etc/bash_completion.d/
|
||||||
# GEN:START posflags
|
# GEN:START posflags
|
||||||
declare -A _pos_flags
|
declare -A _pos_flags
|
||||||
_pos_flags[communication-telegram-listener]="--enable --disable --status --run"
|
_pos_flags[communication-telegram-listener]="--enable --disable --status --sync-commands --run"
|
||||||
_pos_flags[communication-telegram-sender]="--type --caption --parse-mode --no-preview --token --chat-id --markdown"
|
_pos_flags[communication-telegram-sender]="--type --caption --parse-mode --no-preview --token --chat-id --markdown"
|
||||||
_pos_flags[entertainment-send]="--print --markdown"
|
_pos_flags[entertainment-send]="--print --markdown"
|
||||||
_pos_flags[network-hotspot]="--foreground"
|
_pos_flags[network-hotspot]="--foreground"
|
||||||
|
|||||||
Reference in New Issue
Block a user