commit 6eb2cf380e5d5e293e53a9c839d45cf6f139b519 Author: Fabian Schlenz Date: Tue Apr 30 12:32:45 2019 +0200 Initial commit. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6cad469 --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# matrix.sh + +matrix.sh is a bash script to send messages to a matrix chat. + +## Features +* Interactively log in to a server. +* Select a default chat to use. +* Send text messages. +* Optionally enable parsing of HTML tags. +* Directly pipe command output to the script and get it automatically +wrapped in <pre> tags. +* Send files, optionally as audio, image or video. + +## Installation +* Download matrix.sh, either by using `git clone` or something like `wget + ...` and put it somewhere in your path or whatever. +* Install dependencies: + * `curl` + * `jq` + + Something like `sudo apt-get install curl jq`. +* Use it to log in. See Usage. + +## Usage +### Logging in +Use `-l `. The script will try to resolve delegation via the +`/.well-known/matrix/server` path. If that doesn't work, you'll get an error +message. + +``` +$ ./matrix.sh -l matrix.org +Username on the server (just the local part, so e.g. 'bob'): bob +bob's password: + +Success. Access token saved to ~/.matrix.sh +You should now use ./matrix.sh -s to select a default room. +``` + +### Selecting a default room +You can select a default room which will be used if you don't provide a +room_id at runtime. +``` +$ ./matrix.sh -s +Getting Rooms... +Joined rooms: + !GCHxYlasvdh778dsOx:matrix.org - Me and my server + !OEassajhhkasLULVAa:matrix.org - + +Rooms I'm invited to: + !2o587thjlgjHUIUHni:matrix.org - + +Which room do you want to use? +Enter the room_id (the thing at the beginning of the line): +!2o587thjlgjHUIUHni:matrix.org + +Saved default room to ~/.matrix.sh +``` + +### Sending messages +#### Sending a normal text message: +``` +$ ./matrix.sh "Hello World" +``` + +#### Sending a text message with markup: +``` +$ ./matrix.sh -H "This is very important." +``` + +#### Piping command output: +``` +$ echo "Hello" | ./matrix.sh +``` + +#### Code formatting: +You can use `-P` to send messages formatted as code. This will also escape +HTML tags. +``` +$ ls -l | ./matrix.sh -P +``` + +#### Sending files: +``` +$ ./matrix.sh -f upload.zip +``` +Use `-a`, `-i`, `-v` instead of `-f` to send files as audio, images or +video, respectively. + +#### Providing a room: +You can use `-r` to provide a room_id. This supersedes the default room. +``` +$ ./matrix.sh -r '!OEassajhhkasLULVAa:matrix.org' "Hello World" +``` +(Note: bash doesn't like exclamation marks in double quoted strings. So we +use single quotes for the room id.) diff --git a/matrix.sh b/matrix.sh new file mode 100755 index 0000000..d60f19b --- /dev/null +++ b/matrix.sh @@ -0,0 +1,296 @@ +#!/usr/bin/env bash +#set -x +shopt -s extglob +VERSION="0.3" +LOG="true" + +AUTHORIZATION="X-Dummy: 1" + +version() { + echo "matrix.sh $VERSION" + echo "by Fabian Schlenz" +} + +help() { + version + echo + echo "Usage:" + echo "$0 " + echo "ACTIONS" + echo " -l Login to a server." + echo " -L List rooms the matrix user joined or is invited to." + echo " -s Select a default room." + echo " -h This help." + echo + echo "OPTIONS" + echo " -r Which room to send the message to." + echo " -H Enable HTML tags in message." + echo " -P Wraps the given message into
 and escapes all other HTML special chars."
+	echo
+	echo "FILES (message will be ignored)"
+	echo "  -f     Send ."
+	echo "  -a     Send  as audio."
+	echo "  -i     Send  as image."
+	echo "  -v     Send  as video."
+	echo
+	echo "If  is \"-\", stdin is used."
+	echo "See https://matrix.org/docs/spec/client_server/latest.html#m-room-message-msgtypes for a list of valid HTML tags for use with -H."
+	echo
+}
+
+_curl() {
+	curl -s --fail -H "$AUTHORIZATION" -H "User-Agent: matrix.sh/$VERSION" $*
+}
+
+die() {
+	>&2 echo "$1"
+	exit 1
+}
+
+log() {
+	"$LOG" && echo $1
+}
+
+get() {
+	url="$1"
+	log "GET $url"
+	response=`_curl "${MATRIX_HOMESERVER}${url}"`
+}
+
+query() {
+	url="$1"
+	data="$2"
+	type="$3"
+	log "POST $url"
+	response=$( _curl -X$type -H "Content-Type: application/json" --data "$data" "${MATRIX_HOMESERVER}${url}" )
+	if [ `jq -r .errcode <<<"$response"` != "null" ]; then
+		echo
+		>&2 echo "An error occurred. The matrix server responded with:"
+		>&2 echo "`jq -r .errcode <<<"$response"` `jq -r .error <<<"$response"`"
+		>&2 echo "Following request was sent to ${url}:"
+		>&2 jq . <<<"$data"
+		exit 1
+	fi
+}
+
+post() {
+	query "$1" "$2" "POST"
+}
+
+put() {
+	query "$1" "$2" "PUT"
+}
+
+upload_file() {
+	file="$1"
+	content_type="$2"
+	filename="$3"
+	response=$( _curl -XPOST --data-binary "@$file" -H "Content-Type: $content_type" "${MATRIX_HOMESERVER}/_matrix/media/r0/upload?filename=${filename}" )
+}
+
+escape() {
+	jq -s -R . <<<"$1"
+}
+
+############## Check for dependencies
+hash jq >/dev/null 2>&1 || die "jq is required, but not installed."
+hash curl >/dev/null 2>&1 || die "curl is required, but not installed."
+
+
+
+############## Logic
+login() {
+	MATRIX_HOMESERVER="https://${MATRIX_HOMESERVER#https://}"
+	identifier="`whoami`@`hostname` using matrix.sh"
+	identifier=`escape "$identifier"`
+	log "Trying homeserver: $MATRIX_HOMESERVER"
+	if ! get "/_matrix/client/versions"; then
+		if ! get "/.well-known/matrix/server"; then
+			die "$MATRIX_HOMESERVER does not appear to be a matrix homeserver. Trying /.well-known/matrix/server failed. Please ask your homeserver's administrator for the correct address of the homeserver."
+		fi
+		MATRIX_HOMESERVER=`jq -r '.["m.server"]' <<<"$response"`
+		MATRIX_HOMESERVER="https://${MATRIX_HOMESERVER#https://}"
+		log "Delegated to home server $MATRIX_HOMESERVER."
+		if ! get "/_matrix/client/versions"; then
+			die "Delegation led us to $MATRIX_HOMESERVER, but it does not appear to be a matrix homeserver. Please ask your homeserver's administrator for the correct address of the server."
+		fi
+	fi
+	
+	read -p "Username on the server (just the local part, so e.g. 'bob'): " username
+	read -sp "${username}'s password: " password
+	echo
+	post "/_matrix/client/r0/login" "{\"type\":\"m.login.password\", \"identifier\":{\"type\":\"m.id.user\",\"user\":\"${username}\"},\"password\":\"${password}\",\"initial_device_display_name\":$identifier}"
+	
+	data="MATRIX_TOKEN=\"`jq -r .access_token <<<"$response"`\"\nMATRIX_HOMESERVER=\"$SERVER\"\nMATRIX_USER=\"`jq -r .user_id <<<"$response"`\"\n"
+	echo -e "$data" > ~/.matrix.sh
+	chmod 600 ~/.matrix.sh
+	source ~/.matrix.sh
+	
+	echo
+	echo "Success. Access token saved to ~/.matrix.sh."
+	echo "You should now use $0 -s to select a default room."
+}	
+
+list_rooms() {
+	echo "Getting Rooms..."
+	get '/_matrix/client/r0/sync'
+	
+	echo "Joined rooms:"
+	jq -r '.rooms.join | (to_entries[] | "  \(.key) - \(((.value.state.events + .value.timeline.events)[] | select(.type=="m.room.name") | .content.name) // "")") // "  NONE"' <<<"$response"
+	echo
+	echo "Rooms I'm invited to:"
+	jq -r '.rooms.invite | (to_entries[] | "  \(.key) - \((.value.invite_state.events[] | select(.type=="m.room.name") | .content.name) // "Unnamed")") // "  NONE"' <<<"$response"
+}	
+
+select_room() {
+	list_rooms
+	echo "Which room do you want to use?"
+	read -p "Enter the room_id (the thing at the beginning of the line): " room
+	
+	# The chosen could be a room we are only invited to. So we send a join command.
+	# If we already are a member of this room, nothing will happen.
+	post "/_matrix/client/r0/rooms/$room/join"
+	
+	echo -e "MATRIX_ROOM_ID=\"$room\"\n" >> ~/.matrix.sh
+	echo
+	echo "Saved default room to ~/.matrix.sh"
+}
+
+_send_message() {
+	data="$1"
+	txn=`date +%s%N`
+	put "/_matrix/client/r0/rooms/$MATRIX_ROOM_ID/send/m.room.message/$txn" "$data"
+}
+
+send_message() {
+	# Get the text. Try the last variable
+	text="$1"
+	[ "$text" = "-" ] || [ "$text" = "" ] && text=$(/}"
+		clean_body=`escape "$clean_body"`
+		data="{\"body\": $clean_body, \"msgtype\":\"m.text\",\"formatted_body\":$text,\"format\":\"org.matrix.custom.html\"}"
+	else
+		data="{\"body\": $text, \"msgtype\":\"m.text\"}"
+	fi
+	_send_message "$data"
+}
+
+send_file() {
+	[ ! -e "$FILE" ] && die "File $FILE does not exist."
+	
+	# Query max filesize from server
+	get "/_matrix/media/r0/config"
+	max_size=`jq -r ".[\"m.upload.size\"]" <<<"$response"`
+	size=$(stat -c%s "$FILE")
+	if (( size > max_size )); then
+		die "File is too big. Size is $size, max_size is $max_size."
+	fi
+	filename=`basename "$FILE"`
+	log "filename: $filename"
+	filename=`jq -s -R . <<<"$filename"`
+	content_type=`file --brief --mime-type "$FILE"`
+	log "content-type: $content_type"
+	upload_file "$FILE" "$content_type" "$filename"
+	uri=`jq -r .content_uri <<<"$response"`
+	
+	data="{\"body\":$filename, \"msgtype\":\"$FILETYPE\", \"filename\":$filename, \"url\":\"$uri\"}"
+	_send_message "$data"
+}
+
+
+######## Program flow stuff
+[ -r ~/.matrix.sh ] && source ~/.matrix.sh
+
+ACTION="send_message"
+HTML="false"
+PRE="false"
+while getopts "l:shr:a:f:i:v:HPL" opt; do
+	case $opt in
+		l)
+			ACTION="login"
+			MATRIX_HOMESERVER="$OPTARG"
+			;;
+		L)
+			ACTION="list_rooms"
+			;;
+		s)
+			ACTION="select_room"
+			;;
+		h)
+			ACTION="help"
+			;;
+		H)
+			HTML="true"
+			;;
+		P)
+			PRE="true"
+			;;
+		r)
+			MATRIX_ROOM_ID="$OPTARG"
+			;;
+		f)
+			ACTION="send_file"
+			FILETYPE="m.file"
+			FILE="$OPTARG"
+			;;
+		v)
+			ACTION="send_file"
+			FILETYPE="m.video"
+			FILE="$OPTARG"
+			;;
+		i)
+			ACTION="send_file"
+			FILETYPE="m.image"
+			FILE="$OPTARG"
+			;;
+		a)
+			ACTION="send_file"
+			FILETYPE="m.audio"
+			FILE="$OPTARG"
+			;;
+		\?)
+			die "Invalid option -$OPTARG"
+			;;
+		:)
+			die "Option -$OPTARG requires an argument"
+			;;
+	esac
+done
+
+shift $((OPTIND - 1))
+
+[ -z $MATRIX_HOMESERVER ] && die "No homeserver set. Use -l  to log into an account on the given server and persist those settings."
+
+if [ "$ACTION" = "login" ]; then
+	login
+	# Do not exit here. We want select_room to run as well.
+elif [ "$ACTION" = "help" ]; then
+	help
+	exit 1
+fi
+
+[ -z $MATRIX_TOKEN ] && die "No matrix token set. Use -l to login."
+
+AUTHORIZATION="Authorization: Bearer $MATRIX_TOKEN"
+
+if [ "$ACTION" = "select_room" ]; then
+	select_room
+elif [ "$ACTION" = "list_rooms" ]; then
+	list_rooms
+elif [ "$ACTION" = "send_file" ]; then
+	send_file
+elif [ "$ACTION" = "send_message" ]; then
+	send_message "$1"
+fi
+    
+