Files
Linux_post_install/bin/pos-communication-matrix-sender
T
Your Name 99085adc76 feat: add web dashboard with slide-in drawer navigation
- Flask backend with 23 API routes (entertainment, telegram, docker, system)
- Alpine.js + Tailwind CSS dark-mode SPA with 4 tabs
- pos-dashboard CLI tool with port/config management
- Mobile slide-in drawer with swipe-to-close
- Sticky header stays pinned on scroll
- Tab completion fixes for category-less tools
- POST /api/telegram/commands endpoint for adding commands
2026-08-18 11:54:41 -04:00

224 lines
7.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# POS: communication matrix-sender — Send messages to a Matrix room via the client-server API (send, test, login)
# POS_SUBCMDS: send test login
# POS_CONFIG: matrix | matrix.env | MATRIX_HOMESERVER=:Homeserver URL (https://matrix.example.org)::https://matrix.example.org | MATRIX_ACCESS_TOKEN=secret:Access token (from 'pos communication matrix sender login' or a Matrix client) | MATRIX_USER_ID=:Your Matrix user id (set by login)::@you:example.org | MATRIX_ROOM_ID=:Room id or alias::#pos:example.org
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
CONFIG_DIR="${CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/linux_post_install}"
CONFIG_FILE="$CONFIG_DIR/matrix.env"
usage() {
cat <<EOF
Usage: pos communication matrix sender [command] [args]
Send messages to a Matrix room via the client-server API (v3).
Commands:
send <value> [options] Send a text message to the configured room
test Send a test message using the current config
login --user <@id> Get an access token (password prompt) and save it
Options (send):
--markdown Send with org.matrix.custom.html formatting
(best-effort markdown → HTML conversion)
--room <id|alias> Override the room for one send
Config: $CONFIG_FILE (MATRIX_HOMESERVER, MATRIX_ACCESS_TOKEN,
MATRIX_USER_ID, MATRIX_ROOM_ID) — edit it with 'pos config matrix'
Precedence: CLI flags > environment > config file.
Examples:
pos communication matrix sender send "Backup finished"
pos communication matrix sender send "**disk full**" --markdown
pos communication matrix sender send "hello" --room '#ops:example.org'
pos communication matrix sender login --user @you:example.org
pos communication matrix sender test
EOF
exit 0
}
load_config() {
[ -f "$CONFIG_FILE" ] || return 0
local k v
while IFS='=' read -r k v; do
[ -n "$k" ] || continue
case "$k" in
\#*) continue ;;
esac
v="${v%\"}"; v="${v#\"}"; v="${v%\'}"; v="${v#\'}"
v="${v//$'\r'/}"
if [ -z "${!k:-}" ]; then
export "$k"="$v"
fi
done < <(grep -E '^[A-Z_]+=' "$CONFIG_FILE" || true)
}
save_config() {
mkdir -p "$CONFIG_DIR"
local key="$1" val="$2" tmp
tmp="$(mktemp)"
grep -v "^${key}=" "$CONFIG_FILE" 2>/dev/null >"$tmp" || true
printf '%s="%s"\n' "$key" "$val" >>"$tmp"
mv "$tmp" "$CONFIG_FILE"
chmod 600 "$CONFIG_FILE"
}
urlencode() {
local s="$1"
s="${s//#/%23}"
s="${s//\"/%22}"
s="${s// /%20}"
printf '%s' "$s"
}
# Best-effort markdown → HTML for org.matrix.custom.html. Handles code
# fences, inline code, bold/italic/strike, links, headers, and lists. The
# conversion is deliberately simple — it must never fail the send.
md_to_html() {
local t="$1"
t="$(printf '%s' "$t" | sed -E \
-e 's/&/\&amp;/g' \
-e 's/</\&lt;/g' \
-e 's/>/\&gt;/g' \
-e 's/```([^`]*)```/\n<pre><code>\1<\/code><\/pre>\n/g' \
-e 's/`([^`]*)`/<code>\1<\/code>/g' \
-e 's/\*\*([^*]*)\*\*/<strong>\1<\/strong>/g' \
-e 's/__([^_]*)__/<strong>\1<\/strong>/g' \
-e 's/\*([^*]*)\*/<em>\1<\/em>/g' \
-e 's/_([^_]*)_/<em>\1<\/em>/g' \
-e 's/~~([^~]*)~~/<del>\1<\/del>/g' \
-e 's/\[([^]]*)\]\(([^)]*)\)/<a href="\2">\1<\/a>/g' \
-e 's/^[[:space:]]*#{1,6}[[:space:]]+/<h3>/' \
-e 's/^[[:space:]]*([-*+]|[0-9]+\.)[[:space:]]+/<li>/' \
-e 's/$/<br\/>/')"
printf '%s' "$t"
}
api_base() {
local base="${MATRIX_HOMESERVER:-}"
[ -n "$base" ] || err "No homeserver — run 'pos config matrix' (MATRIX_HOMESERVER)"
printf '%s' "${base%/}"
}
send_room_message() {
local room="$1" text="$2" markdown="$3"
[ -n "${MATRIX_ACCESS_TOKEN:-}" ] || err "No access token — run 'pos communication matrix sender login'"
[ -n "$room" ] || err "No room — run 'pos config matrix' (MATRIX_ROOM_ID) or pass --room"
local base txn body fmt
base="$(api_base)"
txn="$(date +%s%N)"
room="$(urlencode "$room")"
if [ "$markdown" -eq 1 ]; then
fmt="$(md_to_html "$text")"
body="$(jq -n --arg body "$text" --arg html "$fmt" \
'{msgtype:"m.text", body:$body, format:"org.matrix.custom.html", formatted_body:$html}')"
else
body="$(jq -n --arg body "$text" '{msgtype:"m.text", body:$body}')"
fi
curl -fsS -m 60 -X PUT \
-H "Authorization: Bearer ${MATRIX_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
--data "$body" \
"${base}/_matrix/client/v3/rooms/${room}/send/m.room.message/${txn}" >/dev/null
}
cmd_send() {
local value="" markdown=0 room="${MATRIX_ROOM_ID:-}"
while [ $# -gt 0 ]; do
case "$1" in
--markdown) markdown=1; shift ;;
--room)
[ $# -ge 2 ] || err "--room needs a value"
room="$2"; shift 2 ;;
-h|--help) usage ;;
--) shift; [ $# -ge 1 ] || err "No value given for send"; value="$1"; shift ;;
-*) err "Unknown option '$1'" ;;
*) [ -z "$value" ] || err "Unexpected extra argument '$1'"; value="$1"; shift ;;
esac
done
[ -n "$value" ] || err "No value given for send"
load_config
[ -n "${room:-}" ] || room="${MATRIX_ROOM_ID:-}"
send_room_message "$room" "$value" "$markdown"
log "m.text sent to room ${room}"
}
cmd_login() {
local user="" password
while [ $# -gt 0 ]; do
case "$1" in
--user)
[ $# -ge 2 ] || err "--user needs a value"
user="$2"; shift 2 ;;
-h|--help) usage ;;
-*) err "Unknown option '$1'" ;;
*) err "Unexpected argument '$1' (use --user @you:server)" ;;
esac
done
[ -n "$user" ] || err "--user is required (e.g. --user @you:example.org)"
[[ "$user" == @* ]] || user="@$user"
load_config
local base
base="$(api_base)"
read -rsp "Password for ${user}: " password </dev/tty || true
echo >&2
[ -n "$password" ] || err "empty password"
local body code resp token uid
body="$(mktemp)"
code="$(curl -sS -o "$body" -w '%{http_code}' -m 60 -X POST \
-H "Content-Type: application/json" \
--data "$(jq -n --arg user "$user" --arg pw "$password" \
'{type:"m.login.password", identifier:{type:"m.id.user", user:$user}, password:$pw, initial_device_display_name:"pos"}')" \
"${base}/_matrix/client/v3/login")" || { rm -f "$body"; err "login request failed (homeserver unreachable?)"; }
if [ "$code" != "200" ]; then
local reason
reason="$(jq -r 'if .errcode then .errcode + (if .error then ": " + .error else "" end) else "HTTP '"$code"'" end' "$body" 2>/dev/null || echo "HTTP $code")"
rm -f "$body"
err "login failed — $reason"
fi
resp="$(cat "$body")"
rm -f "$body"
token="$(printf '%s' "$resp" | jq -r '.access_token // empty')"
uid="$(printf '%s' "$resp" | jq -r '.user_id // empty')"
[ -n "$token" ] || err "login succeeded but no access_token in response"
[ -n "$uid" ] || uid="$user"
save_config MATRIX_ACCESS_TOKEN "$token"
save_config MATRIX_USER_ID "$uid"
log "logged in as $uid — token saved to $CONFIG_FILE"
}
cmd_test() {
load_config
send_room_message "${MATRIX_ROOM_ID:-}" "Test message from pos $(date '+%Y-%m-%d %H:%M:%S')" 0
log "test message sent to room ${MATRIX_ROOM_ID:-}"
}
cmd="${1:-}"
case "$cmd" in
-h|--help) usage ;;
send)
shift
cmd_send "$@"
;;
test)
shift
cmd_test "$@"
;;
login)
shift
cmd_login "$@"
;;
"")
usage
;;
*)
echo "ERROR: Unknown matrix command '$cmd'"
echo "Run 'pos communication matrix-sender --help' for usage."
exit 1
;;
esac