#!/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} + [+ ...]" 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 "$@"