signal-bot/scripts/prime_profile.sh
2026-08-29 15:09:21 +02:00

136 lines
4.4 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# Hand the bot's profile key to somebody who is about to add it to a group.
#
# Signal only adds an account to a group outright when whoever adds it
# already holds that account's profile key. Otherwise the add silently
# becomes a pending invitation -- and signal-cli cannot accept an invite
# addressed to a phone number, so the bot sits in the group forever,
# looking healthy and receiving nothing.
#
# Sending one direct message from the bot to that person hands their client
# the profile key, so the add that follows lands as a real join. Run this
# BEFORE asking anyone to add the bot to an existing group.
#
# Usage: scripts/prime_profile.sh +48123456789 [+48987654321 ...]
# ============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_DIR
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
readonly REPO_ROOT
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
readonly PORT="${PORT:-9922}"
readonly API="${API:-http://localhost:${PORT}}"
readonly BOT_NAME="${BOT_NAME:-signal-bot}"
readonly MESSAGE="${MESSAGE:-Hi, I am ${BOT_NAME}. Adding me to a group now works: this message gave your phone my profile, so the add becomes a real join instead of an invite I cannot accept.}"
RECIPIENTS=()
# shellcheck source-path=SCRIPTDIR source=scripts/lib_signal.sh
. "${SCRIPT_DIR}/lib_signal.sh"
# Prints only. The caller decides the exit code, so a usage *error* cannot
# exit 0 and be read as success by whatever ran this.
usage() {
echo "Usage: ${SCRIPT_NAME} +<E164 number> [+<E164 number> ...]"
echo "Sends one DM from the bot so the recipient's client learns its"
echo "profile key, which makes a later group add land as a real join."
}
validate_requirements() {
command -v curl >/dev/null || { echo "Error: curl is required" >&2; exit 1; }
command -v jq >/dev/null || { echo "Error: jq is required" >&2; exit 1; }
if [[ ${#RECIPIENTS[@]} -eq 0 ]]; then
echo "Error: at least one recipient number is required" >&2
usage >&2
exit 1
fi
local number
for number in "${RECIPIENTS[@]}"; do
if [[ ! "$number" =~ ^\+[0-9]{6,15}$ ]]; then
echo "Error: '${number}' is not an E.164 number like +48123456789" >&2
exit 1
fi
done
if [[ ! -f "${REPO_ROOT}/.env" ]]; then
echo "Error: no ${REPO_ROOT}/.env -- run scripts/register_bot.sh first" >&2
exit 1
fi
}
# Read the bot's own number out of .env rather than asking for it: a number
# typed twice is a number that can be typed differently the second time.
bot_number() {
local number
# `|| true`: under `set -e` a grep that matches nothing kills the whole
# subshell, so the error message below would never print and the script
# would exit 1 silently -- the failure mode this repo exists to gate.
number="$(grep -E '^PHONE_NUMBER=' "${REPO_ROOT}/.env" || true)"
number="$(printf '%s' "$number" | head -1 | cut -d= -f2-)"
if [[ -z "$number" ]]; then
echo "Error: PHONE_NUMBER is not set in ${REPO_ROOT}/.env" >&2
return 1
fi
printf '%s' "$number"
}
send_dm() {
local from="$1" to="$2" status
status="$(curl -sS -o /dev/null -w '%{http_code}' -X POST "${API}/v2/send" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg msg "$MESSAGE" --arg from "$from" --arg to "$to" \
'{message: $msg, number: $from, recipients: [$to]}')")"
if [[ "$status" != "2"* ]]; then
echo "Error: could not message ${to} (HTTP ${status})." >&2
return 1
fi
echo "== ${to} now has the bot's profile key"
}
main() {
validate_requirements
local number
number="$(bot_number)"
signal_wait_for_url "${API}/v1/about" "signal-cli-rest-api" 25
signal_verify_mode
# The profile is re-asserted rather than assumed: an account with no
# profile hands over nothing, so the DM would leave the recipient in
# exactly the state this script exists to prevent.
signal_set_profile "$number" "$BOT_NAME"
local recipient
for recipient in "${RECIPIENTS[@]}"; do
send_dm "$number" "$recipient"
done
echo "== done. Ask them to add ${number} to the group now."
echo " Then add the group id to GROUP_IDS in .env and restart:"
echo " systemctl --user restart signal-bot.service"
}
while [[ $# -gt 0 ]]; do
case $1 in
-h | --help)
usage
exit 0
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*)
RECIPIENTS+=("$1")
shift
;;
esac
done
main "$@"