blob: 3c2d3e70b49025d2547c6be7320b23d62725ae4b [file] [log] [blame]
#!/bin/bash
# Default values
NC_URL="${NC_TALK_URL:-https://cloud.ids-mannheim.de}"
CONVERSATION="${NC_TALK_CONVERSATION:-o6toyqx7}"
SECRET="${NC_TALK_SECRET}"
NC_USER="${NC_TALK_USER}"
NC_APP_PASSWORD="${NC_TALK_APP_PASSWORD}"
ATTACHMENT_FOLDER="${NC_TALK_ATTACHMENT_FOLDER:-/Talk}"
ATTACHMENTS=()
MESSAGE=""
DEBUG=false
SILENT=false
# Function to display help
show_help() {
cat << EOF
Usage: $(basename "$0") [OPTIONS] [message]
Sends <message> to a Nextcloud Talk room. Use - for reading message from stdin
Options:
-h, --help Show this help message
-d, --debug Output extra debugging
-c, --conversation <token> Send message to conversation (default: $CONVERSATION)
-s, --secret <secret> Bot secret
-u, --url <url> Nextcloud URL (default: $NC_URL)
-a, --attachment <file> Attach a file (PDF, image, ...); may be given
multiple times. Requires a Nextcloud user
account (see -U/-P), since the bot API cannot
upload files. Attachments are uploaded to the
user's attachment folder and shared into the
conversation as that user (not as the bot).
-U, --user <user> Nextcloud user for attachment upload
-P, --password <password> App password for attachment upload
--silent Send message silently (no notification)
Environment variables:
NC_TALK_URL Nextcloud URL
NC_TALK_CONVERSATION Conversation token
NC_TALK_SECRET Bot secret
NC_TALK_USER Nextcloud user for attachment upload
NC_TALK_APP_PASSWORD App password for attachment upload
NC_TALK_ATTACHMENT_FOLDER Remote folder for uploads (default: /Talk)
EOF
exit 0
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
;;
-d|--debug)
DEBUG=true
shift
;;
-c|--conversation)
CONVERSATION="$2"
shift 2
;;
-s|--secret)
SECRET="$2"
shift 2
;;
-u|--url)
NC_URL="$2"
shift 2
;;
-a|--attachment)
ATTACHMENTS+=("$2")
shift 2
;;
-U|--user)
NC_USER="$2"
shift 2
;;
-P|--password)
NC_APP_PASSWORD="$2"
shift 2
;;
--silent)
SILENT=true
shift
;;
-*)
echo "Unknown option: $1" >&2
show_help
;;
*)
MESSAGE="$1"
shift
;;
esac
done
# Check if we should read from stdin (before consuming stdin).
# With attachments, an empty message is allowed and does not trigger stdin reading.
if [[ "$MESSAGE" == "-" ]] || { [[ -z "$MESSAGE" ]] && [[ ${#ATTACHMENTS[@]} -eq 0 ]]; }; then
if [[ "$DEBUG" == true ]]; then
echo "Reading from stdin" >&2
fi
# Read from stdin and wrap in code blocks like slack.js
MESSAGE=$'```\n'"$(cat)"$'\n```'
fi
# Check required parameters
if [[ -z "$CONVERSATION" ]]; then
echo "Error: Conversation token is required. Use -c/--conversation option or set NC_TALK_CONVERSATION environment variable." >&2
exit 1
fi
if [[ -z "$SECRET" ]]; then
echo "Error: Bot secret is required. Use -s/--secret option or set NC_TALK_SECRET environment variable." >&2
exit 1
fi
if [[ -z "$MESSAGE" ]] && [[ ${#ATTACHMENTS[@]} -eq 0 ]]; then
echo "Error: Message is required." >&2
exit 1
fi
# Check attachment prerequisites
if [[ ${#ATTACHMENTS[@]} -gt 0 ]]; then
if [[ -z "$NC_USER" ]] || [[ -z "$NC_APP_PASSWORD" ]]; then
echo "Error: Attachments require a Nextcloud user and app password (the bot API cannot upload files)." >&2
echo "Use -U/--user and -P/--password or set NC_TALK_USER and NC_TALK_APP_PASSWORD." >&2
exit 1
fi
for FILE in "${ATTACHMENTS[@]}"; do
if [[ ! -f "$FILE" ]]; then
echo "Error: Attachment not found: $FILE" >&2
exit 1
fi
done
fi
if [[ "$DEBUG" == true ]]; then
echo "URL: $NC_URL" >&2
echo "Conversation: $CONVERSATION" >&2
echo "Message: $MESSAGE" >&2
echo "Silent: $SILENT" >&2
if [[ ${#ATTACHMENTS[@]} -gt 0 ]]; then
echo "Attachments: ${ATTACHMENTS[*]}" >&2
echo "Upload user: $NC_USER" >&2
echo "Attachment folder: $ATTACHMENT_FOLDER" >&2
fi
fi
if [[ -n "$MESSAGE" ]]; then
# Generate a random header and signature
RANDOM_HEADER=$(openssl rand -hex 32)
MESSAGE_TO_SIGN="${RANDOM_HEADER}${MESSAGE}"
SIGNATURE=$(echo -n "${MESSAGE_TO_SIGN}" | openssl dgst -sha256 -hmac "${SECRET}" | cut -d' ' -f2)
# Escape the message for JSON (replace backslashes, quotes, and newlines)
MESSAGE_ESCAPED=$(echo -n "$MESSAGE" | jq -Rs .)
# Send the message
curl -X POST \
"${NC_URL}/ocs/v2.php/apps/spreed/api/v1/bot/${CONVERSATION}/message" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "OCS-APIRequest: true" \
-H "X-Nextcloud-Talk-Bot-Random: ${RANDOM_HEADER}" \
-H "X-Nextcloud-Talk-Bot-Signature: ${SIGNATURE}" \
-d '{"message":'"${MESSAGE_ESCAPED}"',"silent":'"$SILENT"'}'
fi
# Upload and share attachments. The bot API cannot upload files, so this uses
# the WebDAV and OCS share APIs with a regular user account: upload the file
# to the user's attachment folder, then share it into the conversation
# (shareType 10 = Talk room).
if [[ ${#ATTACHMENTS[@]} -gt 0 ]]; then
# The WebDAV path needs the internal user ID, which can differ from the
# login name (e.g. a UUID for LDAP accounts), so resolve it via OCS.
DAV_USER=$(curl -s -u "${NC_USER}:${NC_APP_PASSWORD}" \
-H "OCS-APIRequest: true" \
"${NC_URL}/ocs/v2.php/cloud/user?format=json" | jq -r '.ocs.data.id // empty')
if [[ -z "$DAV_USER" ]]; then
echo "Error: Could not resolve user ID for $NC_USER. Check user name and app password." >&2
exit 1
fi
if [[ "$DEBUG" == true ]]; then
echo "DAV user ID: $DAV_USER" >&2
fi
# Make sure the attachment folder exists (405 = already exists, ignored)
curl -s -o /dev/null -X MKCOL -u "${NC_USER}:${NC_APP_PASSWORD}" \
"${NC_URL}/remote.php/dav/files/${DAV_USER}${ATTACHMENT_FOLDER}"
for FILE in "${ATTACHMENTS[@]}"; do
BASENAME=$(basename "$FILE")
# Prefix with a timestamp to avoid overwriting or re-share conflicts
REMOTE_NAME="$(date +%Y%m%d-%H%M%S)_${BASENAME}"
REMOTE_NAME_ENCODED=$(jq -rn --arg v "$REMOTE_NAME" '$v|@uri')
if [[ "$DEBUG" == true ]]; then
echo "Uploading $FILE as ${ATTACHMENT_FOLDER}/${REMOTE_NAME}" >&2
fi
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-u "${NC_USER}:${NC_APP_PASSWORD}" \
-T "$FILE" \
"${NC_URL}/remote.php/dav/files/${DAV_USER}${ATTACHMENT_FOLDER}/${REMOTE_NAME_ENCODED}")
if [[ "$HTTP_CODE" != 2* ]]; then
echo "Error: Upload of $FILE failed (HTTP $HTTP_CODE)." >&2
exit 1
fi
SHARE_RESPONSE=$(curl -s -X POST \
-u "${NC_USER}:${NC_APP_PASSWORD}" \
-H "OCS-APIRequest: true" \
-H "Accept: application/json" \
"${NC_URL}/ocs/v2.php/apps/files_sharing/api/v1/shares" \
-d "shareType=10" \
-d "shareWith=${CONVERSATION}" \
--data-urlencode "path=${ATTACHMENT_FOLDER}/${REMOTE_NAME}")
SHARE_STATUS=$(echo "$SHARE_RESPONSE" | jq -r '.ocs.meta.statuscode // empty')
if [[ "$SHARE_STATUS" != "200" ]]; then
echo "Error: Sharing $FILE into conversation failed:" >&2
echo "$SHARE_RESPONSE" | jq -r '.ocs.meta.message // .' >&2
exit 1
fi
if [[ "$DEBUG" == true ]]; then
echo "Shared ${REMOTE_NAME} into conversation ${CONVERSATION}" >&2
fi
done
fi