Files
Linux_post_install/bin/pos-communication-telegram-sender
T
Your Name d817c37652
gates / consistency-and-conventions (push) Successful in 26s
fix: stabilization pass — fail-closed auth, ai flag validation, lint/config/security hardening, regression tests
17-point code-level audit executed via Explorer->Architect->Builder->Tester->Reviewer;
Reviewer accepted (APPROVE_WITH_NOTES; 3 block-list items resolved):

- security: telegram sender-owner AND-gate + TELEGRAM_OWNER_ID, matrix
  MATRIX_ROOM_ID fail-closed, gpg --passphrase-fd 3 (no argv secret),
  /dev/tcp positional-arg form (checkport/smb-client/share-lib/NET_PROBE),
  eval deny-by-default + --no-command-execution carried by both chat bridges,
  tty-gated --trust; config/{telegram,matrix}.env reference templates
- ai: all ExecStart flags validated against installed llama.cpp
  (requested->error, default->omit+warn, CONFIG_REQUESTED_FLAGS); single-file
  hf download failure rc=1 + no .hf-meta; LLAMACPP_HOST coherent;
  POS_SUBCMDS + metadata gaps closed
- tooling: lint-conventions Bash-native rewrite (~24-30x faster, rules and
  output byte-identical, :num restored); pos system uninstall covers all 12
  libs + scale-tail + flags dir + systemd user units (|| true) + plugin
  markers; anchored .bash_completion/.bashrc removal replaces sed -i '/pos/d'
- config: canonical load_env_file in lib/config-ui.sh (CRLF strip, env-wins,
  XDG, LOADED_ENV_KEYS); 9 tools migrated; entertainment-lib collapsed to
  wrappers; docker-compose deliberately unmigrated (source semantics)
- tests: first committed regression suite — tests/run-tests.sh zero-dep
  runner + make test; 12 files / 179 checks / 0 skip / ~52s; hard skip
  contract; systemd-analyze verify on generated unit PASS

Verified: make gen idempotent; make check green; make lint 0 FAIL, 0 WARN;
make test green; bash -n clean; git diff --check clean. Audit deliverables +
agent reports + AGENT_TODO Done entry included.
2026-09-06 07:25:44 -04:00

213 lines
8.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# POS: communication telegram-sender — Send Telegram messages/files/links/stickers via Bot API (send, test)
# POS_FLAGS: --type --caption --parse-mode --no-preview --token --chat-id --markdown
# POS_SUBCMDS: send test
# POS_CONFIG: telegram | telegram.env | TELEGRAM_BOT_TOKEN=secret:Bot token from @BotFather | TELEGRAM_CHAT_ID=digits:Numeric chat id from @userinfobot | TELEGRAM_OWNER_ID=digits:Numeric Telegram user id (your account) allowed to run chat commands | TELEGRAM_AI_PREFIX=:AI-bridge trigger word in the telegram listener (default ai)::ai
CONFIG_DIR="${CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/linux_post_install}"
CONFIG_FILE="$CONFIG_DIR/telegram.env"
API="https://api.telegram.org"
# Shared config loader (canonical KEY=VALUE parser, env-wins precedence)
source "$(dirname "$0")/../lib/config-ui.sh" 2>/dev/null || source "$(dirname "$0")/config-ui.sh"
usage() {
cat <<EOF
Usage: pos communication telegram sender [command] [args]
Send Telegram messages via the Bot API.
Commands:
send <value> [options] Send a message, link, or media (auto-detects the type)
test Send a test message using the current config
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)
--markdown Alias for --parse-mode markdown (uniform notify_send contract)
--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: $CONFIG_FILE (TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID,
TELEGRAM_OWNER_ID — edit it with 'pos config telegram')
Precedence: CLI flags > environment > config file.
Examples:
pos communication telegram sender send "Backup finished"
pos communication telegram sender send "/path/to/report.pdf" --caption "Daily report"
pos communication telegram sender send "/path/to/photo.jpg" --caption "Sunset"
pos communication telegram sender send "/path/to/sticker.webp"
pos communication telegram sender send "https://example.com" --no-preview
pos communication telegram sender send "/path/to/video.mp4" --type video
pos communication telegram sender test
EOF
exit 0
}
err() { echo "ERROR: $*" >&2; exit 1; }
load_config() {
load_env_file "$CONFIG_FILE"
}
send_request() {
local endpoint="$1"; shift
[ -z "${TELEGRAM_BOT_TOKEN:-}" ] && err "No bot token — run 'pos config telegram'"
[ -z "${TELEGRAM_CHAT_ID:-}" ] && err "No chat id — run 'pos config telegram'"
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}")
[ "$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 ;;
--markdown)
# Alias for --parse-mode markdown (uniform notify_send contract)
parse_mode="markdown"; shift ;;
--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="${1:-}"
case "$cmd" in
-h|--help) usage ;;
send)
shift
cmd_send "$@"
;;
test)
load_config
send_message "Test message from pos $(date '+%Y-%m-%d %H:%M:%S')"
echo "[+] test message sent to chat ${TELEGRAM_CHAT_ID}"
;;
*)
echo "ERROR: Unknown telegram command '$cmd'"
echo "Run 'pos communication telegram-sender --help' for usage."
exit 1
;;
esac