#!/usr/bin/env bash
#
# set-gif-picker.sh
#
# Registers the GIF picker widget on a Matrix account by writing the
# `m.widgets` account data event via the client-server API.
#
# This is per-user account data stored on the homeserver, so it follows the
# user to every device and every Element instance they log into. It only needs
# to be run once per person.
#
# Usage:  ./set-gif-picker.sh
#         ./set-gif-picker.sh --remove     (unregisters the picker)

set -euo pipefail

# ---------------------------------------------------------------------------
# Site configuration -- edit these to match your deployment
# ---------------------------------------------------------------------------

HOMESERVER="https://matrix.hylianux.com"   # client-server API base URL
SERVER_NAME="hylianux.com"                 # the domain in your MXIDs
WIDGET_NAME="GIFs"                         # label shown in Element

# NOTE: single quotes are required. $theme is a literal placeholder that
# Element substitutes at load time -- it must NOT be expanded by the shell.
PICKER_URL='https://gifs.hylianux.com/?theme=$theme'

# ---------------------------------------------------------------------------

RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[0;33m'
BOLD=$'\033[1m'; DIM=$'\033[2m'; RESET=$'\033[0m'

info()  { printf '%s\n' "$*"; }
ok()    { printf '%s✓%s %s\n' "$GREEN" "$RESET" "$*"; }
warn()  { printf '%s!%s %s\n' "$YELLOW" "$RESET" "$*" >&2; }
die()   { printf '%s✗%s %s\n' "$RED" "$RESET" "$*" >&2; exit 1; }

command -v curl >/dev/null 2>&1 || die "curl is required but not installed."

REMOVE=false
[[ "${1:-}" == "--remove" ]] && REMOVE=true

# ---------------------------------------------------------------------------
# Gather credentials
# ---------------------------------------------------------------------------

cat <<BANNER

${BOLD}Matrix GIF picker setup${RESET}

This registers the GIF picker widget on your Matrix account.
You will need an access token.

${BOLD}Where to find your access token in Element:${RESET}

  Element Web / Desktop
    Settings -> Help & About -> Advanced -> Access Token
    (click "<click to reveal>", then copy it)

  Element mobile
    Settings -> Help & About -> Advanced -> Access Token

${YELLOW}Treat this token like a password.${RESET} It grants full access to your
account. Close this terminal when you are done, and never paste it into
a chat or an issue tracker.

BANNER

read -rp "Username (just the local part, e.g. 'alice'): " USERNAME
USERNAME="${USERNAME#@}"          # tolerate a leading @
USERNAME="${USERNAME%%:*}"        # tolerate a pasted full MXID
[[ -n "$USERNAME" ]] || die "No username entered."

read -rsp "Access token: " TOKEN
echo
[[ -n "$TOKEN" ]] || die "No token entered."

MXID="@${USERNAME}:${SERVER_NAME}"

# Percent-encode the MXID for use in the URL path.
MXID_ENC="${MXID//@/%40}"
MXID_ENC="${MXID_ENC//:/%3A}"

ENDPOINT="${HOMESERVER}/_matrix/client/v3/user/${MXID_ENC}/account_data/m.widgets"

# ---------------------------------------------------------------------------
# Verify the token actually belongs to this account
# ---------------------------------------------------------------------------

info ""
info "Verifying token against ${HOMESERVER} ..."

WHOAMI="$(curl -sS -H "Authorization: Bearer ${TOKEN}" \
  "${HOMESERVER}/_matrix/client/v3/account/whoami")" \
  || die "Could not reach the homeserver. Check HOMESERVER in this script."

case "$WHOAMI" in
  *M_UNKNOWN_TOKEN*)
    die "Homeserver rejected the token. It may be expired -- grab a fresh one from Element."
    ;;
  *'"user_id"'*)
    : # good
    ;;
  *)
    die "Unexpected response from whoami: ${WHOAMI}"
    ;;
esac

# Extract user_id without requiring jq.
ACTUAL_MXID="${WHOAMI##*\"user_id\":\"}"
ACTUAL_MXID="${ACTUAL_MXID%%\"*}"

if [[ "$ACTUAL_MXID" != "$MXID" ]]; then
  warn "Token belongs to ${ACTUAL_MXID}, but you entered ${MXID}."
  warn "A token can only write its own account data, so ${MXID} would fail."
  read -rp "Use ${ACTUAL_MXID} instead? [y/N] " REPLY
  [[ "$REPLY" =~ ^[Yy]$ ]] || die "Aborted. Re-run with the correct username."
  MXID="$ACTUAL_MXID"
  MXID_ENC="${MXID//@/%40}"
  MXID_ENC="${MXID_ENC//:/%3A}"
  ENDPOINT="${HOMESERVER}/_matrix/client/v3/user/${MXID_ENC}/account_data/m.widgets"
fi

ok "Authenticated as ${MXID}"

# ---------------------------------------------------------------------------
# Build the payload
# ---------------------------------------------------------------------------

if [[ "$REMOVE" == true ]]; then
  PAYLOAD='{}'
  ACTION="Removing"
else
  # printf keeps $theme inside PICKER_URL literal -- no heredoc expansion.
  PAYLOAD="$(printf '{"stickerpicker":{"content":{"type":"m.stickerpicker","url":"%s","name":"%s","creatorUserId":"%s","data":{}},"sender":"%s","state_key":"stickerpicker","type":"m.widget","id":"stickerpicker"}}' \
    "$PICKER_URL" "$WIDGET_NAME" "$MXID" "$MXID")"
  ACTION="Registering"
fi

info "${ACTION} the picker for ${MXID} ..."

RESPONSE="$(curl -sS -X PUT \
  -w $'\n%{http_code}' \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d "$PAYLOAD" \
  "$ENDPOINT")"

HTTP_CODE="${RESPONSE##*$'\n'}"
BODY="${RESPONSE%$'\n'*}"

if [[ "$HTTP_CODE" != "200" ]]; then
  info ""
  die "Homeserver returned HTTP ${HTTP_CODE}: ${BODY}"
fi

ok "Account data written."

# ---------------------------------------------------------------------------
# Read it back
# ---------------------------------------------------------------------------

VERIFY="$(curl -sS -H "Authorization: Bearer ${TOKEN}" "$ENDPOINT")"

if [[ "$REMOVE" == true ]]; then
  ok "Picker removed."
elif [[ "$VERIFY" == *"m.stickerpicker"* ]]; then
  ok "Verified: the widget is present on the account."
else
  warn "Write succeeded but read-back looks wrong:"
  info "  ${VERIFY}"
fi

cat <<DONE

${BOLD}Next step:${RESET} hard-reload Element (Ctrl-Shift-R, or fully quit and
reopen the desktop/mobile app). A ${BOLD}${WIDGET_NAME}${RESET} button should appear in the
message composer.

${DIM}If the button does not appear, the account data is set correctly but
Element has cached the old state -- log out and back in as a last resort.
To undo this entirely, re-run with:  $0 --remove${RESET}

DONE

unset TOKEN
