diff --git a/.woodpecker/check.yml b/.woodpecker/check.yml new file mode 100644 index 0000000..baa5da1 --- /dev/null +++ b/.woodpecker/check.yml @@ -0,0 +1,31 @@ +labels: + type: exec + +steps: +- name: flake check + image: bash + commands: + - nix flake check + +- name: package check + image: bash + commands: + - nix build + +- name: notifiy + image: bash + secrets: + - source: matrix_password + target: pass + - source: matrix_homeserver + target: address + - source: matrix_roomid + target: room + - source: matrix_username + target: user + commands: + - nix run . + when: + status: + - failure + - success diff --git a/README.md b/README.md new file mode 100644 index 0000000..420c711 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# matrix-notifier + +This is a simple to send a message to a Matrix room. It automatically logs in +and sends the message when invoked. This was written to be used a notification +script in my CI/CD pipelines. + +## How to use + +You need to define the following environment variables for the script to be +executed correctly: + +* `USER`: the user to login as. +* `PASS`: the password to login with. +* `ADDRESS`: the address of the homeserver to connect to. +* `ROOM`: the room id, as can be found in the room parameters. +* `MESSAGE`: the message you wish to send to the room. + +### Example + +```sh +export ADDRESS='https://matrix.org' +export USER='username' +export PASS='password' +export ROOM='!aaaaaaaaaaaaaaaaaa:matrix.org' +export MESSAGE='This is my test message' +./matrix-notifier +``` + +## How to run/install + +This script is packaged with `Nix`, you can just use `nix run .` to run it. + +The only dependencies are `bash`, `curl`, and `jq`, install those and you should +be ready to go! Format is needed when using formatting (enabled by default). diff --git a/flake.nix b/flake.nix index 5388bdc..e02328a 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "Ambroisie's blog"; + description = "A simple Matrix notifier for CI purposes"; inputs = { futils = { @@ -36,6 +36,8 @@ in rec { apps = { + default = apps.matrix-notifier; + matrix-notifier = futils.lib.mkApp { drv = packages.matrix-notifier; }; }; @@ -56,26 +58,24 @@ }; }; - defaultApp = apps.matrix-notifier; + devShells = { + default = pkgs.mkShell { + name = "matrix-notifier"; - defaultPackage = packages.matrix-notifier; + inputsFrom = with self.packages.${system}; [ + matrix-notifier + ]; - devShell = pkgs.mkShell { - name = "matrix-notifier"; - - buildInputs = with pkgs; [ - curl - jq - shellcheck - ]; - - inherit (self.checks.${system}.pre-commit) shellHook; + inherit (self.checks.${system}.pre-commit) shellHook; + }; }; packages = { + default = packages.matrix-notifier; + matrix-notifier = pkgs.stdenvNoCC.mkDerivation rec { pname = "matrix-notifier"; - version = "0.1.0"; + version = "0.4.0"; src = ./matrix-notifier; @@ -99,12 +99,24 @@ wrapperPath = with pkgs; lib.makeBinPath [ curl jq + pandoc ]; fixupPhase = '' patchShebangs $out/bin/${pname} wrapProgram $out/bin/${pname} --prefix PATH : "${wrapperPath}" ''; + + meta = with pkgs.lib; { + description = '' + A very simple bash script that can be used to send a message to + a Matrix room + ''; + homepage = "https://gitea.belanyi.fr/ambroisie/${pname}"; + license = licenses.mit; + platforms = platforms.unix; + maintainers = with maintainers; [ ambroisie ]; + }; }; }; } diff --git a/matrix-notifier b/matrix-notifier index 9bf6b4c..838396a 100755 --- a/matrix-notifier +++ b/matrix-notifier @@ -4,6 +4,21 @@ print_err() { printf "%s\n" "$1" >&2 } +usage() { + print_err "Usage: $0 [option]..." + print_err "" + print_err " -h, --help" + print_err " print this usage screen and exit" + print_err " -f, --format" + print_err " should the message be formatted using 'pandoc'." + print_err " Must be either 'true' or 'false'." + print_err " -t, --type" + print_err " which message type should be sent must be one of" + print_err " 'text' or 'notice', or 'auto'." + print_err " The special value 'auto' defaults to 'notice', unless" + print_err " 'CI_PIPELINE_STATUS'/'DRONE_BUILD_STATUS' indicates a failure." +} + # Blessed Stack Overflow rawurlencode() { (set +x @@ -23,37 +38,150 @@ rawurlencode() { echo "$encoded") } -if [ -z "$USER" ] || [ -z "$PASS" ]; then - print_err "You must provide USER and PASS" - exit 1 -elif [ -z "$ADDRESS" ] || [ -z "$ROOM" ]; then - print_err "You must provide ADDRESS and ROOM" - exit 1 -elif [ -z "$MESSAGE" ]; then - print_err "You must provide MESSAGE" - exit 1 -fi +default_woodpecker_message() { + local msg="Build ${CI_PIPELINE_STATUS}" + local woodpecker_url="${CI_PIPELINE_URL}" + if [ "$FORMAT" == "true" ]; then + msg="$msg [${CI_REPO}#${CI_COMMIT_SHA:0:8}]($woodpecker_url)" + else + msg="$msg $woodpecker_url" + fi + msg="$msg (${CI_COMMIT_TAG:-$CI_COMMIT_BRANCH})" + printf '%s' "$msg" +} -if [ "$(curl -XGET "$ADDRESS/_matrix/client/r0/login" 2>/dev/null | - jq 'any(.flows[].type; .== "m.login.password")')" != "true" ]; then - print_err "Login method not supported" - exit 1 -fi +default_drone_message() { + local msg="Build ${DRONE_BUILD_STATUS}" + local drone_url="${DRONE_SYSTEM_PROTO}://${DRONE_SYSTEM_HOST}/${DRONE_REPO}/${DRONE_BUILD_NUMBER}" + if [ "$FORMAT" == "true" ]; then + msg="$msg [${DRONE_REPO_OWNER}/${DRONE_REPO_NAME}#${DRONE_COMMIT:0:8}]($drone_url)" + else + msg="$msg $drone_url" + fi + msg="$msg (${DRONE_TAG:-$DRONE_BRANCH})" + printf '%s' "$msg" +} + +ensure_variables() { + if [ "$CI" == "woodpecker" ] && [ -z "$MESSAGE" ]; then + MESSAGE="$(default_woodpecker_message)" + elif [ "$DRONE" == "true" ] && [ -z "$MESSAGE" ]; then + MESSAGE="$(default_drone_message)" + fi + + if [ -z "$USER" ] || [ -z "$PASS" ]; then + print_err "You must provide USER and PASS" + exit 1 + elif [ -z "$ADDRESS" ] || [ -z "$ROOM" ]; then + print_err "You must provide ADDRESS and ROOM" + exit 1 + elif [ -z "$MESSAGE" ]; then + print_err "You must provide MESSAGE" + exit 1 + fi +} + +get_message_type() { + if [ "$MSG_TYPE" == "auto" ]; then + if [ "$CI_PIPELINE_STATUS" == "failure" ]; then + MSG_TYPE="text" + elif [ "$DRONE_BUILD_STATUS" == "failure" ]; then + MSG_TYPE="text" + else + MSG_TYPE="notice" + fi + fi + + printf '%s' "m.$MSG_TYPE" +} + +make_message_json() { + { + printf '%s' "$1" | + jq --raw-input --slurp "{msgtype: \"$(get_message_type)\", body: .}" + [ "$FORMAT" == "true" ] && printf '%s' "$1" | + pandoc | + jq --raw-input --slurp "{format: \"org.matrix.custom.html\", formatted_body: .}" + } | jq -s 'add' +} + +send_message() { + local login_json + local token + local message_json + + if [ "$(curl -XGET "$ADDRESS/_matrix/client/r0/login" 2>/dev/null | + jq 'any(.flows[].type; .== "m.login.password")')" != "true" ]; then + print_err "Login method not supported" + exit 1 + fi -LOGIN_JSON="$(printf '%s\n%s' "$USER" "$PASS" | - jq -Rn '[inputs] | {type: "m.login.password", user: .[0], password: .[1]}')" &>/dev/null -TOKEN="$(curl -XPOST \ - -d "$LOGIN_JSON" \ - "$ADDRESS/_matrix/client/r0/login" 2>/dev/null| jq .access_token --raw-output)" + login_json="$(printf '%s\n%s' "$USER" "$PASS" | + jq -Rn '[inputs] | {type: "m.login.password", user: .[0], password: .[1]}')" &>/dev/null + token="$(curl -XPOST \ + -d "$login_json" \ + "$ADDRESS/_matrix/client/r0/login" 2>/dev/null| jq .access_token --raw-output)" -if [ -z "$TOKEN" ] || [ "$TOKEN" == "null" ]; then - print_err "Error during login" - exit 1 -fi + if [ -z "$token" ] || [ "$token" == "null" ]; then + print_err "Error during login" + exit 1 + fi -MESSAGE_JSON="$(printf '%s' "$MESSAGE" | - jq --raw-input --slurp '{msgtype: "m.text", body: .}')" -curl -XPOST \ - -d "$MESSAGE_JSON" \ - "$ADDRESS/_matrix/client/r0/rooms/$(rawurlencode "$ROOM")/send/m.room.message?access_token=$(rawurlencode "$TOKEN")" 2>/dev/null + message_json="$(make_message_json "$MESSAGE")" + curl -XPOST \ + -d "$message_json" \ + "$ADDRESS/_matrix/client/r0/rooms/$(rawurlencode "$ROOM")/send/m.room.message?access_token=$(rawurlencode "$token")" 2>/dev/null +} + +MSG_TYPE='auto' +FORMAT='true' + +while [ $# -gt 0 ]; do + opt="$1" + shift + + case "$opt" in + -f|--format) + arg="$1" + shift + + if [ "$arg" == "true" ] || [ "$arg" == "false" ]; then + FORMAT="$arg" + continue + fi + + print_err "Must give value 'true' or 'false' with '-t|--type'" + exit 1 + ;; + -t|--type) + arg="$1" + shift + + if [ -z "$arg" ]; then + print_err "Must give a message type when using '-t|--type'" + exit 1 + fi + + for type in text notice auto; do + if [ "$arg" == "$type" ]; then + continue 2 # Go to next argument + fi + done + print_err "Invalid message type '$arg'" + exit 1 + ;; + -h|--help) + usage + exit + ;; + *) + print_err "Unknown argument '$opt'" + usage + exit 1 + ;; + esac +done + +ensure_variables +send_message