mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-10 20:27:17 +01:00
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
136 lines
4.4 KiB
Bash
Executable File
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 "$@"
|