add entetaiment features

This commit is contained in:
Your Name
2026-08-06 01:48:24 -04:00
parent d722842d20
commit 7e28769ec5
11 changed files with 308 additions and 77 deletions
+12 -12
View File
@@ -36,16 +36,16 @@ Linux_post_install/
├── bin/ # CLI tools — installed to /usr/local/bin/
│ ├── pos # Main dispatcher — smart arg matching to pos-* scripts
<!-- GEN:START tree -->
│ ├── pos-communication-telegram # Send Telegram messages via Bot API (--send, test, config set)
│ ├── pos-communication-telegram # Send Telegram messages/files/links/stickers via Bot API (send, test, config set)
│ ├── pos-docker-compose # Docker Compose service manager (ls/up/down/restart/logs/update/config)
│ ├── pos-docker-health # One-glance container health dashboard (exits 1 if unhealthy)
│ ├── pos-docker-ps # Enhanced container overview (health, IPs, ports, uptime)
│ ├── pos-docker-vbox # Disposable Docker-based VMs (create/enter/start/stop/rm/ls)
│ ├── pos-entertainment-config # Show or edit the entertainment config (ENABLED auto-trigger list, weather location)
│ ├── pos-entertainment-disable # Disable a plugin's auto-trigger (remove it from ENABLED)
│ ├── pos-entertainment-enable # Enable an auto-trigger for a plugin (systemd user timer)
│ ├── pos-entertainment-enable # Enable an auto-trigger for a plugin on a schedule
│ ├── pos-entertainment-send # Run a public-API plugin and send its output via Telegram (default sender)
│ ├── pos-entertainment-status # Show enabled plugins and their timer state
│ ├── pos-entertainment-status # Show enabled plugins and scheduler state
│ ├── pos-media-mp3 # Download audio as MP3 (yt-dlp)
│ ├── pos-media-mp4 # Download video as MP4 (interactive format select)
│ ├── pos-network-checkport # Check TCP port connectivity
@@ -212,16 +212,16 @@ All non-interactive `pos` commands log output to `~/.local/share/linux_post_inst
| Category | Command | Script | Description |
|----------|---------|--------|-------------|
<!-- GEN:START dispatch -->
| communication | telegram | `pos-communication-telegram` | Send Telegram messages via Bot API (--send, test, config set) |
| communication | telegram | `pos-communication-telegram` | Send Telegram messages/files/links/stickers via Bot API (send, test, config set) |
| docker | compose | `pos-docker-compose` | Docker Compose service manager (ls/up/down/restart/logs/update/config) |
| docker | health | `pos-docker-health` | One-glance container health dashboard (exits 1 if unhealthy) |
| docker | ps | `pos-docker-ps` | Enhanced container overview (health, IPs, ports, uptime) |
| docker | vbox | `pos-docker-vbox` | Disposable Docker-based VMs (create/enter/start/stop/rm/ls) |
| entertainment | config | `pos-entertainment-config` | Show or edit the entertainment config (ENABLED auto-trigger list, weather location) |
| entertainment | disable | `pos-entertainment-disable` | Disable a plugin's auto-trigger (remove it from ENABLED) |
| entertainment | enable | `pos-entertainment-enable` | Enable an auto-trigger for a plugin (systemd user timer) |
| entertainment | enable | `pos-entertainment-enable` | Enable an auto-trigger for a plugin on a schedule |
| entertainment | send | `pos-entertainment-send` | Run a public-API plugin and send its output via Telegram (default sender) |
| entertainment | status | `pos-entertainment-status` | Show enabled plugins and their timer state |
| entertainment | status | `pos-entertainment-status` | Show enabled plugins and scheduler state |
| media | mp3 | `pos-media-mp3` | Download audio as MP3 (yt-dlp) |
| media | mp4 | `pos-media-mp4` | Download video as MP4 (interactive format select) |
| network | checkport | `pos-network-checkport` | Check TCP port connectivity |
@@ -375,7 +375,7 @@ All `.service` files in `systemd/` are automatically copied to `/etc/systemd/sys
### Runtime Config
- `~/.config/linux_post_install/compose.env` — Docker Compose global defaults
- `~/.config/linux_post_install/entertainment.env` — entertainment plugin defaults: weather location + `ENABLED` auto-trigger list (`plugin, interval` pairs → systemd user timers via `pos entertainment enable/disable`); auto-installed from `config/entertainment.env` by `postinstall.sh` (no clobber, template printed)
- `~/.config/linux_post_install/entertainment.env` — entertainment plugin defaults: weather location + `ENABLED` auto-trigger list (`plugin, interval` pairs scheduled via `pos entertainment enable/disable`, systemd user timers or cron auto-detected); auto-installed from `config/entertainment.env` by `postinstall.sh` (no clobber, template printed)
- `~/.bashrc` — Modified by postinstall (PATH, bash completion)
### Feature Flags
@@ -490,16 +490,16 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
| `features/autostart.sh` | 14 | Boot-time feature (moved from `bin/`, flag-gated service) |
<!-- GEN:START filetable -->
| `bin/pos` | 213 | CLI dispatcher with smart arg matching + logging + category help |
| `bin/pos-communication-telegram` | 150 | Send Telegram messages via Bot API (--send, test, config set) |
| `bin/pos-communication-telegram` | 270 | Send Telegram messages/files/links/stickers via Bot API (send, test, config set) |
| `bin/pos-docker-compose` | 364 | Docker Compose service manager (ls/up/down/restart/logs/update/config) |
| `bin/pos-docker-health` | 110 | One-glance container health dashboard (exits 1 if unhealthy) |
| `bin/pos-docker-ps` | 128 | Enhanced container overview (health, IPs, ports, uptime) |
| `bin/pos-docker-vbox` | 157 | Disposable Docker-based VMs (create/enter/start/stop/rm/ls) |
| `bin/pos-entertainment-config` | 64 | Show or edit the entertainment config (ENABLED auto-trigger list, weather location) |
| `bin/pos-entertainment-config` | 85 | Show or edit the entertainment config (ENABLED auto-trigger list, weather location) |
| `bin/pos-entertainment-disable` | 32 | Disable a plugin's auto-trigger (remove it from ENABLED) |
| `bin/pos-entertainment-enable` | 49 | Enable an auto-trigger for a plugin (systemd user timer) |
| `bin/pos-entertainment-enable` | 50 | Enable an auto-trigger for a plugin on a schedule |
| `bin/pos-entertainment-send` | 93 | Run a public-API plugin and send its output via Telegram (default sender) |
| `bin/pos-entertainment-status` | 55 | Show enabled plugins and their timer state |
| `bin/pos-entertainment-status` | 55 | Show enabled plugins and scheduler state |
| `bin/pos-media-mp3` | 35 | Download audio as MP3 (yt-dlp) |
| `bin/pos-media-mp4` | 38 | Download video as MP4 (interactive format select) |
| `bin/pos-network-checkport` | 45 | Check TCP port connectivity |
@@ -510,7 +510,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
| `bin/pos-system-backup` | 117 | Encrypted (AES-256) folder snapshots (tar + gpg) |
| `bin/pos-system-firewall` | 285 | Interactive UFW management |
| `bin/pos-usb-server` | 218 | USB Redirector server control (--ls, --share; prompts when args omitted) |
| `completions/pos.bash` | 169 | Dynamic bash completion |
| `completions/pos.bash` | 188 | Dynamic bash completion |
<!-- GEN:END filetable -->
| `apps/install.sh` | 171 | App install/uninstall picker/orchestrator |
+4 -2
View File
@@ -183,12 +183,14 @@ The `entertainment` module routes public-API data to Telegram via the single run
### 1. Create the plugin
Drop an executable script in `entertainment/<name>.sh` with a `# POS_PLUGIN: <name>` marker on line 3 (this is what makes it a plugin — the installed runner lists plugins by this marker, not by `.sh` files, since `/usr/local/bin` is shared with other tooling):
Drop an executable script in `entertainment/<name>.sh` with a `# POS_PLUGIN: <name>` marker on line 3 (this is what makes it a plugin — the installed runner lists plugins by this marker, not by `.sh` files, since `/usr/local/bin` is shared with other tooling). Declare every config key the plugin reads with `# POS_KEYS: <KEY> <description> (required|optional)` lines right after it — `pos entertainment config` prints these in its Keys section and uses them to warn/not-warn on `config set`:
```bash
#!/usr/bin/env bash
set -euo pipefail
# POS_PLUGIN: myplugin
# POS_KEYS: MYPLUGIN_URL <feed url> (required)
# POS_KEYS: MYPLUGIN_TAG <filter tag> (optional)
err() { echo "ERROR: $*" >&2; exit 1; }
command -v curl &>/dev/null || err "curl not found"
@@ -201,7 +203,7 @@ printf 'Title: %s\n' "$data"
### 2. Config (if needed)
Read runtime values from `~/.config/linux_post_install/entertainment.env` (chmod 600, env precedence) — same pattern as `telegram.env`. Example: `weather.sh` uses `WEATHER_LAT`/`WEATHER_LON`.
Read runtime values from `~/.config/linux_post_install/entertainment.env` (chmod 600, env precedence) — same pattern as `telegram.env`. Example: `weather.sh` uses `WEATHER_LAT`/`WEATHER_LON`. Declare each key with a `# POS_KEYS:` header line (see step 1) so `pos entertainment config` lists it and `config set` recognizes it.
### 3. Deps
+25 -8
View File
@@ -188,20 +188,27 @@ Subcommands that need input prompt interactively when args are omitted.
| Command | File | Purpose | Configuration |
|---------|------|---------|---------------|
| `pos communication telegram --send "text"` | `bin/pos-communication-telegram` | Send a text message to a Telegram chat via the Bot API | Token + chat ID from `~/.config/linux_post_install/telegram.env` (`TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`, chmod 600). Precedence: `--token`/`--chat-id` flags > env > config file |
| `pos communication telegram send "text"` | `bin/pos-communication-telegram` | Send a message, link, or media file (auto-detects the type) to a Telegram chat via the Bot API | Token + chat ID from `~/.config/linux_post_install/telegram.env` (`TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`, chmod 600). Precedence: `--token`/`--chat-id` flags > env > config file |
`pos communication telegram` in detail:
| Command | Behavior |
|---------|----------|
| `pos communication telegram --send "text"` | POSTs `sendMessage` to the Bot API (20s timeout); prints `[+] Message sent to chat <id>` or fails with a nonzero exit |
| `pos communication telegram --send "text" --token <t> --chat-id <id>` | One-shot override of token/chat ID |
| `pos communication telegram --send "text" --parse-mode <mode>` | Send with Telegram formatting; `<mode>` is `plain` (default), `markdown`, or `html` (passed as `parse_mode` to the API). Markdown/HTML use raw Telegram syntax — unescaped characters may be rejected by the API (400) |
| `pos communication telegram send "text"` | POSTs `sendMessage` to the Bot API (60s timeout); prints `[+] message sent to chat <id>` or fails with a nonzero exit |
| `pos communication telegram send <value>` | **Auto-detects the type** when `--type` is omitted: existing file → `file` (except `.webp` → sticker, `.gif` → animation, images → photo, video/audio/voice extensions → their type), value starting with `http://`/`https://`/`www.``link`, otherwise `message` |
| `pos communication telegram send <path> --type file [--caption "…"]` | Uploads a file as a `sendDocument` via multipart (`document=@path`); `--caption` adds a caption. Path must exist and be readable |
| `pos communication telegram send <path> [--caption "…"]` | Media uploads via their Bot API endpoint: `--type photo``sendPhoto`, `video``sendVideo`, `audio``sendAudio`, `voice``sendVoice`, `animation``sendAnimation`, `sticker` (`.webp`) → `sendSticker` (captions not supported for stickers) |
| `pos communication telegram send "url" --type link [--no-preview]` | Sends a link as a message (URLs auto-linkify); `--no-preview` adds `disable_web_page_preview=true` |
| `pos communication telegram send "text" --parse-mode <mode>` | Send with Telegram formatting; `<mode>` is `plain` (default), `markdown`, or `html` (passed as `parse_mode` to the API — also applies to captions). Markdown/HTML use raw Telegram syntax — unescaped characters may be rejected by the API (400) |
| `pos communication telegram send … --token <t> --chat-id <id>` | One-shot override of token/chat ID |
| `pos communication telegram --send "text"` | Legacy alias for `send "text"` (kept for the entertainment runner) |
| `pos communication telegram test` | Sends a canned test message using the current config |
| `pos communication telegram config` | Shows current config (bot token masked) |
| `pos communication telegram config set TELEGRAM_BOT_TOKEN=...` | Saves a bot token (600 perms) |
| `pos communication telegram config set TELEGRAM_CHAT_ID=...` | Saves the target chat ID |
`send` option validation: `--caption` is only valid with media types (file/photo/video/audio/voice/animation), `--no-preview` only with `--type message`/`link`, and `--type` only accepts `message|file|link|sticker|photo|video|audio|voice|animation`. An explicit `--type` always overrides auto-detection.
The bot token is a secret — it is stored only in `~/.config/linux_post_install/telegram.env` and never in the repo. Requires network access to `api.telegram.org`.
### entertainment
@@ -216,10 +223,10 @@ The bot token is a secret — it is stored only in `~/.config/linux_post_install
| `pos entertainment send <plugin> --print` | Print the output locally; do not send |
| `pos entertainment send <plugin> --markdown` | Send with `--parse-mode markdown` (via `pos communication telegram`) |
| `pos entertainment config` | Show the config file (`~/.config/linux_post_install/entertainment.env`) |
| `pos entertainment config set KEY=VALUE…` | Set keys (e.g. `ENABLED`, `WEATHER_LAT`) and re-sync timers |
| `pos entertainment enable <plugin> [interval]` | Add plugin to `ENABLED` + create/enable its systemd **user** timer |
| `pos entertainment disable <plugin>` | Remove plugin from `ENABLED` + disable/remove its timer |
| `pos entertainment status` | Enabled plugins + timer state + next fire time |
| `pos entertainment config set KEY=VALUE…` | Set keys (any UPPER_SNAKE key; warns if no installed plugin uses it) and re-sync the schedule |
| `pos entertainment enable <plugin> [interval]` | Add plugin to `ENABLED` + schedule it (systemd user timer or cron, auto-detected) |
| `pos entertainment disable <plugin>` | Remove plugin from `ENABLED` + remove its scheduled job |
| `pos entertainment status` | Enabled plugins + scheduler + schedule state |
**Plugin lookup order:** `$ENTERTAINMENT_DIR` → repo `entertainment/``/usr/local/bin/` (installed by `install.sh` Phase 2, beside the runner). A plugin name matches the file name with or without the `.sh` suffix.
@@ -235,6 +242,16 @@ Plugins:
**Adding a plugin:** drop an executable script in `entertainment/` (e.g. `myfeed.sh`) with a `# POS_PLUGIN: <name>` marker on line 3 — the runner lists and validates plugins by this marker, so non-plugin `.sh` files in the shared `/usr/local/bin` are ignored. The plugin must be non-interactive and print the message to stdout; errors go to stderr (exit nonzero). If it needs coordinates/tokens, read them from `~/.config/linux_post_install/entertainment.env` (chmod 600, env precedence). No registration needed. Dependencies beyond `curl`/`jq` (both in `preinstall.sh` PACKAGES) should be guarded with `command -v … || exit 1`.
**Declaring config keys (pattern):** document every key the plugin reads with one `# POS_KEYS:` line right after `# POS_PLUGIN:``KEY`, a description, and `(required)`/`(optional)`:
```
# POS_PLUGIN: myfeed
# POS_KEYS: MYFEED_URL <feed url> (required)
# POS_KEYS: MYFEED_TAG <filter tag> (optional)
```
The plugin itself still reads the keys as plain env vars (`${MYFEED_URL:-}`). The declaration is what `pos entertainment config` uses to print its Keys section and to decide whether `config set` warns about an undeclared key — add the line whenever a plugin gets a new config key.
**Automation (auto-trigger):** enable plugins on a schedule via the `ENABLED` key in the config — a comma-separated list of `plugin, interval` pairs:
```
+157 -37
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
# POS: communication telegram — Send Telegram messages via Bot API (--send, test, config set)
# POS_FLAGS: --send
# POS: communication telegram — Send Telegram messages/files/links/stickers via Bot API (send, test, config set)
# POS_FLAGS: --send --type --caption --parse-mode --no-preview --token --chat-id
CONFIG_DIR="$HOME/.config/linux_post_install"
CONFIG_FILE="$CONFIG_DIR/telegram.env"
@@ -14,12 +14,29 @@ Usage: pos communication telegram [command] [args]
Send Telegram messages via the Bot API.
Commands:
--send "text" Send a text message to the configured chat
--send "text" --token <t> --chat-id <id> Override token/chat for one send
--send "text" --parse-mode <mode> Format mode: plain (default), markdown, html
test Send a test message using the current config
config Show current config (token masked)
config set KEY=VALUE Set TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID
send <value> [options] Send a message, link, or media (auto-detects the type)
test Send a test message using the current config
config Show current config (token masked)
config set KEY=VALUE Set TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID
Types (auto-detected when --type is omitted):
message Plain text (default)
link Clickable link (value starts with http:// https:// www.)
file Any document (any other existing file)
photo .jpg .jpeg .png .bmp
video .mp4 .mkv .webm .mov .avi
audio .mp3 .wav .flac .m4a .aac
voice .ogg .opus .oga
animation .gif
sticker .webp
Options:
--type <type> Force a type: message|file|link|sticker|photo|video|audio|voice|animation
--caption <text> Caption for file/photo/video/audio/voice/animation
--parse-mode <mode> Format mode: plain (default), markdown, html (message/link/caption)
--no-preview Disable the link's web page preview (message/link only)
--token <t> Override token for one send
--chat-id <id> Override chat id for one send
Config file: $CONFIG_FILE
Keys: TELEGRAM_BOT_TOKEN | TELEGRAM_CHAT_ID
@@ -27,10 +44,13 @@ Keys: TELEGRAM_BOT_TOKEN | TELEGRAM_CHAT_ID
Precedence: CLI flags > environment > config file.
Examples:
pos communication telegram config set TELEGRAM_BOT_TOKEN=123456:ABC-DEF
pos communication telegram config set TELEGRAM_CHAT_ID=987654321
pos communication telegram --send "Backup finished"
pos communication telegram --send "*Backup finished*" --parse-mode markdown
pos communication telegram send "Backup finished"
pos communication telegram send "/path/to/report.pdf" --caption "Daily report"
pos communication telegram send "/path/to/photo.jpg" --caption "Sunset"
pos communication telegram send "/path/to/sticker.webp"
pos communication telegram send "https://example.com" --no-preview
pos communication telegram send "/path/to/video.mp4" --type video
pos communication telegram --send "Old style text message"
pos communication telegram test
EOF
exit 0
@@ -63,14 +83,127 @@ mask_token() {
fi
}
cmd_send() {
local text="$1" parse_mode="${2:-}"
send_request() {
local endpoint="$1"; shift
[ -z "${TELEGRAM_BOT_TOKEN:-}" ] && err "No bot token — run 'pos communication telegram config set TELEGRAM_BOT_TOKEN=...'"
[ -z "${TELEGRAM_CHAT_ID:-}" ] && err "No chat id — run 'pos communication telegram config set TELEGRAM_CHAT_ID=...'"
local args=(--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" --data-urlencode "text=${text}")
curl -fsS -m 60 -X POST "$API/bot${TELEGRAM_BOT_TOKEN}/${endpoint}" "$@" >/dev/null
}
send_message() {
local text="$1" parse_mode="${2:-}" no_preview="${3:-}"
local args=(--data-urlencode "chat_id=${TELEGRAM_CHAT_ID:-}" --data-urlencode "text=${text}")
[ -n "$parse_mode" ] && args+=(--data-urlencode "parse_mode=${parse_mode}")
curl -fsS -m 20 -X POST "$API/bot${TELEGRAM_BOT_TOKEN}/sendMessage" "${args[@]}" >/dev/null
echo "[+] Message sent to chat ${TELEGRAM_CHAT_ID}"
[ "$no_preview" = "1" ] && args+=(--data-urlencode "disable_web_page_preview=true")
send_request sendMessage "${args[@]}"
}
send_multipart() {
local endpoint="$1" file_field="$2" file_path="$3" caption="${4:-}" parse_mode="${5:-}"
[ -f "$file_path" ] && [ -r "$file_path" ] || err "File not found or unreadable: $file_path"
local args=(-F "chat_id=${TELEGRAM_CHAT_ID:-}" -F "${file_field}=@${file_path}")
[ -n "$caption" ] && args+=(-F "caption=${caption}")
[ -n "$parse_mode" ] && args+=(-F "parse_mode=${parse_mode}")
send_request "$endpoint" "${args[@]}"
}
detect_type() {
local value="$1" ext
if [ -f "$value" ] && [ -r "$value" ]; then
ext="${value##*.}"
ext="${ext,,}"
case "$ext" in
webp) echo sticker ;;
gif) echo animation ;;
jpg|jpeg|png|bmp) echo photo ;;
mp4|mkv|webm|mov|avi|m4v) echo video ;;
ogg|opus|oga) echo voice ;;
mp3|wav|flac|m4a|aac) echo audio ;;
*) echo file ;;
esac
elif [[ "$value" =~ ^(https?://|www\.) ]]; then
echo link
else
echo message
fi
}
cmd_send() {
local value="" type="" caption="" parse_mode="" no_preview=0 type_explicit=0 auto=""
while [ $# -gt 0 ]; do
case "$1" in
--type)
[ $# -ge 2 ] || err "--type needs a value"
case "$2" in
message|file|link|sticker|photo|video|audio|voice|animation) type="$2" ;;
*) err "Unknown type '$2' (allowed: message, file, link, sticker, photo, video, audio, voice, animation)" ;;
esac
type_explicit=1
shift 2 ;;
--caption)
[ $# -ge 2 ] || err "--caption needs a value"
caption="$2"; shift 2 ;;
--parse-mode)
[ $# -ge 2 ] || err "--parse-mode needs a value"
case "$2" in
plain|markdown|html) ;;
*) err "Unknown parse mode '$2' (allowed: plain, markdown, html)" ;;
esac
parse_mode="$2"; shift 2 ;;
--no-preview)
no_preview=1; shift ;;
--token)
[ $# -ge 2 ] || err "--token needs a value"
TELEGRAM_BOT_TOKEN="$2"; shift 2 ;;
--chat-id)
[ $# -ge 2 ] || err "--chat-id needs a value"
TELEGRAM_CHAT_ID="$2"; shift 2 ;;
--)
shift
[ $# -ge 1 ] || err "No value given for send"
value="$1"; shift
[ $# -eq 0 ] || err "Unexpected argument '$1'"
;;
-*) err "Unknown option '$1'" ;;
*)
[ -z "$value" ] || err "Unexpected extra argument '$1'"
value="$1"; shift ;;
esac
done
[ -n "$value" ] || err "No value given for send"
if [ "$type_explicit" -eq 0 ]; then
type="$(detect_type "$value")"
auto=1
fi
case "$type" in
message|link)
[ -z "$caption" ] || err "--caption only applies to file/photo/video/audio/voice/animation"
;;
sticker)
[ -z "$caption" ] || err "--caption is not supported for stickers"
;;
esac
case "$type" in
message|link) ;;
*) [ "$no_preview" -eq 0 ] || err "--no-preview only applies to --type message or link" ;;
esac
[ "$parse_mode" = "plain" ] && parse_mode=""
load_config
case "$type" in
message) send_message "$value" "$parse_mode" "$no_preview" ;;
link) send_message "$value" "$parse_mode" "$no_preview" ;;
file) send_multipart sendDocument document "$value" "$caption" "$parse_mode" ;;
sticker) send_multipart sendSticker sticker "$value" "" "" ;;
photo) send_multipart sendPhoto photo "$value" "$caption" "$parse_mode" ;;
video) send_multipart sendVideo video "$value" "$caption" "$parse_mode" ;;
audio) send_multipart sendAudio audio "$value" "$caption" "$parse_mode" ;;
voice) send_multipart sendVoice voice "$value" "$caption" "$parse_mode" ;;
animation) send_multipart sendAnimation animation "$value" "$caption" "$parse_mode" ;;
esac
echo "[+] $type${auto:+ (auto-detected)} sent to chat ${TELEGRAM_CHAT_ID}"
}
cmd_config() {
@@ -108,32 +241,19 @@ cmd="${1:-}"
case "$cmd" in
-h|--help) usage ;;
send)
shift
cmd_send "$@"
;;
--send)
shift
[ $# -ge 1 ] || usage
text="$1"
shift
parse_mode=""
while [ $# -gt 0 ]; do
case "$1" in
--token) TELEGRAM_BOT_TOKEN="$2"; shift 2 ;;
--chat-id) TELEGRAM_CHAT_ID="$2"; shift 2 ;;
--parse-mode)
case "$2" in
plain|markdown|html) ;;
*) err "Unknown parse mode '$2' (allowed: plain, markdown, html)" ;;
esac
parse_mode="$2"; shift 2 ;;
*) err "Unknown option '$1'" ;;
esac
done
[ "$parse_mode" = "plain" ] && parse_mode=""
load_config
cmd_send "$text" "$parse_mode"
cmd_send "$@"
;;
test)
load_config
cmd_send "Test message from pos $(date '+%Y-%m-%d %H:%M:%S')"
send_message "Test message from pos $(date '+%Y-%m-%d %H:%M:%S')"
echo "[+] test message sent to chat ${TELEGRAM_CHAT_ID}"
;;
config)
shift
+31 -10
View File
@@ -5,6 +5,25 @@ set -euo pipefail
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
source "$(dirname "$0")/../lib/entertainment-lib.sh" 2>/dev/null || source "$(dirname "$0")/entertainment-lib.sh"
keys_section() {
# Renders the Keys reference: ENABLED + every key declared by installed
# plugins (via their # POS_KEYS: headers), aligned by key name.
local d="$1" maxw=8 line key plugin desc req
while IFS= read -r line; do
IFS='|' read -r key _desc _req <<<"$line"
[ ${#key} -gt "$maxw" ] && maxw=${#key}
done <<< "$(config_keys "$d")"
printf ' %-*s %s\n' "$maxw" "ENABLED" "Auto-trigger list: comma-separated 'plugin, interval' pairs,"
printf ' %-*s %s\n' "$maxw" "" "e.g. ENABLED=\"weather, 5m gold, 1h\""
while IFS= read -r line; do
[ -n "$line" ] || continue
IFS='|' read -r plugin key desc req <<<"$line"
printf ' %-*s %s (%s) [%s]\n' "$maxw" "$key" "$desc" "$req" "$plugin"
done <<< "$(config_keys "$d")"
printf ' %-*s %s\n' "$maxw" "<any>" "UPPER_SNAKE key settable freely; a warning is shown if no installed plugin declares it"
}
usage() {
cat <<EOF
Usage: pos entertainment config [set KEY=VALUE ...]
@@ -14,12 +33,8 @@ After 'set', auto-trigger timers are re-synced from ENABLED.
Config file: $CONFIG_FILE
Keys:
ENABLED Auto-trigger list: comma-separated 'plugin, interval' pairs,
e.g. ENABLED="weather, 5m gold, 1h"
WEATHER_LAT Weather plugin latitude (required for weather)
WEATHER_LON Weather plugin longitude (required for weather)
WEATHER_CITY Optional city label
Keys (declared by the installed plugins' # POS_KEYS: headers):
$(keys_section "$(plugin_dir)")
Intervals: 5m 10m 15m 30m 45m hourly 2h 6h 12h daily weekly, or OnCalendar=...
@@ -32,6 +47,10 @@ EOF
exit 0
}
key_known() {
config_key_known "$(plugin_dir)" "$1"
}
case "${1:-}" in
-h|--help) usage ;;
set)
@@ -40,11 +59,10 @@ case "${1:-}" in
for pair in "$@"; do
key="${pair%%=*}"
val="${pair#*=}"
case "$key" in
ENABLED|WEATHER_LAT|WEATHER_LON|WEATHER_CITY) ;;
*) err "Unknown key '$key' (allowed: ENABLED, WEATHER_LAT, WEATHER_LON, WEATHER_CITY)" ;;
esac
[[ "$key" =~ ^[A-Z][A-Z0-9_]*$ ]] || \
err "Invalid key '$key' (expected UPPER_SNAKE, e.g. WEATHER_LAT)"
[ -z "$val" ] && [ "$key" != "ENABLED" ] && err "No value given (expected $key=...)"
key_known "$key" || warn "Key '$key' not referenced by any installed plugin — saving anyway"
write_config_key "$key" "$val"
ok "$key saved to $CONFIG_FILE"
done
@@ -60,5 +78,8 @@ case "${1:-}" in
echo
echo "ENABLED: $(config_value ENABLED)"
echo "Intervals: 5m 10m 15m 30m 45m hourly 2h 6h 12h daily weekly, or OnCalendar=..."
echo
echo "Plugin keys (declared in each plugin's # POS_KEYS: header):"
keys_section "$(plugin_dir)"
;;
esac
+1 -1
View File
@@ -11,7 +11,7 @@ Usage: pos entertainment disable <plugin>
Remove a plugin from the ENABLED list in
$CONFIG_FILE
and disable/remove its systemd user timer.
and remove its scheduled job (systemd user timer or cron line).
Examples:
pos entertainment disable weather
+4 -3
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
# POS: entertainment enable — Enable an auto-trigger for a plugin (systemd user timer)
# POS: entertainment enable — Enable an auto-trigger for a plugin on a schedule
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
source "$(dirname "$0")/../lib/entertainment-lib.sh" 2>/dev/null || source "$(dirname "$0")/entertainment-lib.sh"
@@ -11,8 +11,9 @@ Usage: pos entertainment enable <plugin> [interval]
Enable an auto-trigger: writes '<plugin>, <interval>' into the ENABLED list in
$CONFIG_FILE
and creates/enables a systemd user timer that runs 'pos entertainment send <plugin>'
on the schedule. Re-running without an interval keeps the plugin's current one.
and schedules 'pos entertainment send <plugin>' to run (systemd user timer, or
cron when systemd is unavailable). Re-running without an interval keeps the
plugin's current one.
Available plugins:
$(list_plugins "$(plugin_dir)" | sed 's/^/ /')
+3 -3
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
# POS: entertainment status — Show enabled plugins and their timer state
# POS: entertainment status — Show enabled plugins and scheduler state
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
source "$(dirname "$0")/../lib/entertainment-lib.sh" 2>/dev/null || source "$(dirname "$0")/entertainment-lib.sh"
@@ -9,8 +9,8 @@ usage() {
cat <<EOF
Usage: pos entertainment status
Show the enabled plugins (from ENABLED in $CONFIG_FILE)
and each one's systemd user timer state + next fire time.
Show the enabled plugins (from ENABLED in $CONFIG_FILE),
the detected scheduler, and the schedule for each plugin.
Enable plugins with: pos entertainment enable <plugin> [interval]
EOF
+20 -1
View File
@@ -3,7 +3,7 @@
# Install: source this file in ~/.bashrc or place in /etc/bash_completion.d/
# GEN:START posflags
declare -A _pos_flags
_pos_flags[communication-telegram]="--send"
_pos_flags[communication-telegram]="--send --type --caption --parse-mode --no-preview --token --chat-id"
_pos_flags[entertainment-send]="--print --markdown"
_pos_flags[network-hotspot]="--foreground"
_pos_flags[system-backup]="--service"
@@ -161,6 +161,25 @@ _pos() {
;;
esac
;;
communication-telegram)
case "${words[3]}" in
send|--send) _pos_complete_flags communication-telegram ;;
esac
;;
esac
;;
6)
case "${words[1]}-${words[2]}" in
communication-telegram)
case "${words[3]}:${words[4]}" in
send:--type|--send:--type)
COMPREPLY=($(compgen -W "message file link sticker photo video audio voice animation" -- "$cur"))
;;
send:--parse-mode|--send:--parse-mode)
COMPREPLY=($(compgen -W "plain markdown html" -- "$cur"))
;;
esac
;;
esac
;;
esac
+3
View File
@@ -3,6 +3,9 @@ set -euo pipefail
# Entertainment plugin: current weather via Open-Meteo (no API key).
# POS_PLUGIN: weather
# POS_KEYS: WEATHER_LAT <latitude> (required)
# POS_KEYS: WEATHER_LON <longitude> (required)
# POS_KEYS: WEATHER_CITY <city label> (optional)
# Contract: stdout is the message sent by 'pos entertainment send weather'.
err() { echo "ERROR: $*" >&2; exit 1; }
+48
View File
@@ -80,6 +80,54 @@ resolve_plugin() {
err "Plugin '$name' not found in $dir — try one of: $(list_plugins "$dir" | tr '\n' ' ')"
}
# ── Config keys declared by plugins ────────────────────────────────
# Pattern: each plugin documents the config keys it reads with
# # POS_KEYS: <KEY> <description> (required|optional)
# on one or more lines (right after # POS_PLUGIN:). 'pos entertainment
# config' prints them in its Keys section; config set warns when a key is
# not declared by any installed plugin.
plugin_keys() {
# Usage: plugin_keys <plugin-file> → "KEY|description|required|optional"
local file="$1" line key desc req
grep '^# POS_KEYS:' "$file" 2>/dev/null | sed 's/^# POS_KEYS:[[:space:]]*//' | while IFS= read -r line; do
key="${line%% *}"
desc="${line#* }"
case "$desc" in
*\(required\)) req=required; desc="${desc% (required)}" ;;
*\(optional\)) req=optional; desc="${desc% (optional)}" ;;
*) req="" ;;
esac
printf '%s|%s|%s\n' "$key" "$desc" "$req"
done
}
config_keys() {
# Usage: config_keys <plugin-dir> → "KEY|plugin|description|required|optional"
local dir="$1" f name line
for f in "$dir"/*.sh; do
[ -f "$f" ] || continue
name="$(plugin_marker "$f")"
[ -n "$name" ] || continue
while IFS= read -r line; do
[ -n "$line" ] || continue
printf '%s|%s\n' "$name" "$line"
done <<< "$(plugin_keys "$f")"
done
}
config_key_known() {
# Usage: config_key_known <plugin-dir> <KEY> → true if ENABLED or declared by a plugin
local dir="$1" key="$2"
[ "$key" = "ENABLED" ] && return 0
local line _plugin _key _rest
while IFS= read -r line; do
IFS='|' read -r _plugin _key _rest <<<"$line"
[ "$_key" = "$key" ] && return 0
done <<< "$(config_keys "$dir")"
return 1
}
# ── ENABLED list parsing ──────────────────────────────────────────
# Format: comma/space-separated 'plugin[, interval]' pairs, e.g.
# 'weather, 5m gold, 1h'. Parsed name-aware: a token that resolves to a