Compare commits

...

No commits in common. "master" and "v1.1" have entirely different histories.
master ... v1.1

12 changed files with 387 additions and 673 deletions

41
.gitignore vendored
View file

@ -1,41 +0,0 @@
# Compiled Lua sources
luac.out
matrix-send
# luarocks build files
*.src.rock
*.zip
*.tar.gz
# Object files
*.o
*.os
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
*.def
*.exp
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

35
COMPILE
View file

@ -1,35 +0,0 @@
___________________
< Manually building >
-------------------
Since matrix-send uses Lua, an interpreted programming language,
building is not necessary; you could just move matrix-send.lua to
/usr/local/bin and be done with it. However, for additional speed,
the Makefile precompiles it to Lua bytecode using
luac <https://www.lua.org/manual/5.4/luac.html> which is included
in the Lua distribution. To do this yourself, follow these steps:
=> Create the shebang
In order to be run by a shell, the compiled bytecode needs a
shebang at the start. To do this, run this command:
printf '#!/usr/bin/env lua\n' > matrix-send
=> Precompile
Now, let's append the bytecode to the file:
luac -o - matrix-send.lua >> matrix-send
=> Mark as an executable
Finally, mark the file as executable:
chmod +x matrix-send
The file `matrix-send` is now ready to be run by a shell.
# vi: ft=txt

View file

@ -1,21 +1,14 @@
VERSION = 2.1
VERSION = 1.1
PREFIX = /usr/local
MANPREFIX = ${PREFIX}/man
config:
mkdir -p ~/.config/matrix-send
cp config.lua ~/.config/matrix-send/config.lua
cp matrix-send.conf ~/.config/matrix-send.conf
install:
@# How I wish luarocks was better than this
luarocks install luaposix
luarocks install luasocket
luarocks install lua-cjson
printf '#!/usr/bin/env lua\n' > matrix-send
luac -o - matrix-send.lua >> matrix-send
chmod +x matrix-send
cp -f matrix-send ${DESTDIR}${PREFIX}/bin
chmod +x ${DESTDIR}${PREFIX}/bin/matrix-send
mkdir -p ${DESTDIR}${MANPREFIX}/man1
cp -f matrix-send.1 ${DESTDIR}${MANPREFIX}/man1
mkdir -p ${DESTDIR}${MANPREFIX}/man5
cp -f matrix-send-config.5 ${DESTDIR}${MANPREFIX}/man5
cp -f matrix-send.conf.5 ${DESTDIR}${MANPREFIX}/man5

View file

@ -1,51 +1,52 @@
# matrix-send
A script that sends messages to Matrix rooms.
A script that sends a message to a Matrix room.
[Read the manual page](https://jtbx.codeberg.page/man/matrix-send.1)
---
matrix-send is a simple script that sends messages to Matrix rooms.
**Syntax:** `matrix-send message room`
It does this by directly making requests to the API of your Matrix server.
The supported event types are `m.text` and `m.notice`.
**Example:** `matrix-send "Hello world\!" \!aBcDeFgHiJkLmNoP:example.org`
It is mainly designed for automation.
---
matrix-send is a simple script that sends a message to a Matrix room.
It does this by sending a JSON message to your Matrix server. The message types that are supported are *m.text*, and *m.notice*.
It is mainly designed for automation. I use it on a private Matrix room of mine.
There aren't any plans for matrix-send to support encryption.
## Get started
In order to start using matrix-send, you need a config file. To copy the default configuration to .config, clone this repository and type `make config` as your user (not root).
In order to start using matrix-send, you need a config file. To copy the default configuration to .config, clone this repository and type `make config` as the desired user (not root).
Now edit the file ~/.config/matrix-send/config.lua. You will first see the `login` table.
Now edit the file ~/.config/matrix-send.conf. You will see three (uncommented) lines.
```lua
-- This is a comment
login = {
server = "matrix.org",
username = "user",
password = "password"
}
```
Server ...
Username ...
Password ...
```
Delete the example values and enter in your credentials.
For more configuration options, see [matrix-send-config(5)](https://jtbx.codeberg.page/man/matrix-send-config.5).
If you don't want to put your password in plain text, as matrix-send.conf is a shell script, you could do something like this:
```shell
Password $(gpg -d /path/to/passwd.gpg)
```
/path/to/passwd.gpg is a GPG-encrypted file containing only your password, once decrypted.
For more configuration options, see [matrix-send.conf(5)](https://jtbx.codeberg.page/man/matrix-send.conf.5).
---
## Installation
Before you install, you will need a few dependencies:
* lua: >= 5.3
* [luarocks](https://luarocks.org)
* plus some luarocks packages...
To install matrix-send, run `make install` as root, in the cloned repository's directory.
This will install all required packages, and copy matrix-send and its documentation to your system.
In order to send messages, you will need the Room ID of your choice. Find the room you want to send to, and find its Room ID in the settings or somewhere else. It will likely be under Advanced or something similar.
@ -55,33 +56,6 @@ To send a message, type `matrix-send` followed by your message and then your Roo
matrix-send 'Hi!' \!asdfasdfasdfasdf:matrix.org
```
That will send the message "Hi!" to the Matrix room `!asdfasdfasdfasdf:matrix.org.`
That will send the message "Hi!" to the Matrix room !asdfasdfasdfasdf:matrix.org.
If you don't want to type the long room ID every time, you can add an alias.
Open your configuration file, and create a `rooms` table like so:
```lua
rooms = {
}
```
Now, you can add aliases. Say you wanted to add an alias called `lounge` for a room with room ID `!AbCdEfGhIjKlMn:matrix.org`.
You would make it this way:
```lua
rooms = {
lounge = "!AbCdEfGhIjKlMn:matrix.org"
}
```
Multiple aliases are separated by commas (as with any Lua table):
```lua
rooms = {
lounge = "!AbCdEfGhIjKlMn:matrix.org", -- <-- notice the comma
bathroom = "!OpQrStUvWxYz:matrix.org"
}
```
For more information, see [matrix-send(1)](https://jtbx.codeberg.page/man/matrix-send.1) and [matrix-send-config(5)](https://jtbx.codeberg.page/man/matrix-send-config.5).
For more information, see [matrix-send(1)](https://jtbx.codeberg.page/man/matrix-send.1).

View file

@ -1,31 +0,0 @@
login = {
-- The Matrix server to use.
server = "matrix.org",
-- The user to log in to.
username = "user",
-- The password for the user.
password = "password",
-- The access token to use (instead of credentials).
-- If token equals nil, credentials are used.
-- If token is not nil, credentials are ignored.
token = nil
-- The server value needs to be provided.
-- You can choose to login with user credentials or a
-- token. One of them needs to be provided.
}
rooms = {
-- Room aliases.
-- Here you can add aliases for rooms,
-- instead of having to type the confusing
-- Room ID every single time you send a message.
-- Examples:
--my_alias = "!AbCdEfGhIjKl:burger.land",
--lounge = "!MnOpQrSTuVWxYz:gaming.bruvs"
-- When you want to send to a Matrix room, you
-- can just type the alias instead of the long Room ID.
}

View file

@ -1,49 +0,0 @@
-- The configuration file for matrix-send is written in Lua format.
-- Here is a list of all possible options in the configuration file.
-- Default values appear after the '=' sign.
login = {
-- The Matrix server to use.
server = nil,
-- The user to log in to.
username = nil,
-- The password for the user.
password = nil,
-- The access token to use (instead of credentials).
-- If token equals nil, credentials are used.
-- If token is not nil, credentials are ignored.
token = nil
-- The server value needs to be provided.
-- You can choose to login with user credentials or a
-- token. One of them needs to be provided.
}
cache = {
-- The path to cache access tokens at.
location = "~/.cache/matrix-send",
-- Disable caching access tokens?
disable = false
}
rooms = {
-- Room aliases.
-- Here you can add aliases for rooms,
-- instead of having to type the confusing
-- Room ID every single time you send a message.
-- Examples:
--my_alias = "!AbCdEfGhIjKl:burger.land",
--lounge = "!MnOpQrSTuVWxYz:gaming.bruvs"
-- When you want to send to a Matrix room, you
-- can just type the alias instead of the long Room ID.
}
advanced = {
-- The default event type.
-- Can be either m.text or m.notice.
event = "m.text"
}

220
matrix-send Executable file
View file

@ -0,0 +1,220 @@
#!/usr/bin/env sh
# matrix-send: send a message to a Matrix room
version="1.1"
###########################
#### Generic Functions ####
###########################
error () {
printf "\033[31;1merror:\033[0m $1\n"
exit 1
}
conf_error () {
printf "\033[31;1mconfiguration error:\033[0m $1\n"
exit 2
}
warning () {
printf "\033[93;1mwarning:\033[0m $1\n"
}
vargrep () {
printf "$2\n" | grep "$1" $3
}
usage () {
printf "usage: matrix-send [-t type] [-C config] [-chV] message room\n"
exit 1
}
help () {
cat <<EOF
matrix-send: send a message to a Matrix room
Options:
-t type: change default event type
-C config: use a custom configuration file
-c: clear cached access tokens
-h: show this help menu
-V: show version and program information
For more information, type 'man matrix-send'.
https://codeberg.org/jtbx/matrix-send
EOF
exit 0
}
version () {
cat <<EOF
matrix-send: send a message to a Matrix room
Version $version
matrix-send is licensed under the GNU General Public License v2.
Made in New Zealand
https://codeberg.org/jtbx/matrix-send
EOF
exit 0
}
##################################
#### Configuration Directives ####
##################################
Server () {
[ -z $1 ] && conf_error "No argument for directive Server"
server="$1"
}
Username () {
[ -z $1 ] && conf_error "No argument for directive Username"
username="$1"
}
Password () {
[ -z $1 ] && conf_error "No argument(s) for directive Password"
password="$@"
}
AccessToken () {
[ -z $1 ] && conf_error "No argument for directive AccessToken"
manualAuth="true"
token="$1"
}
CacheLocation () {
[ -z $1 ] && conf_error "No argument for directive CacheLocation"
if vargrep "^/.+$" "$1" -Eq || \
vargrep "^~.+$" "$1" -Eq;
then cacheloc="$1";
else conf_error "Cache location is not valid (does not begin with / or ~)"; fi
}
DefaultEvent () {
[ -z $1 ] && conf_error "No argument for directive DefaultEvent"
if vargrep "m\.(text|notice)" "$1" -Eq
then defaultevent="$1"
else conf_error "Invalid default event type"; fi
}
##################################
#### Configuration Statements ####
##################################
NoCache () {
nocache="true"
}
PrettyPrint () {
prettyprint="true"
}
############################
#### Specific Functions ####
############################
GetAccessToken () {
[ -z "$manualAuth" ] && printf "Getting access token...\n"
if [ "$manualAuth" = "true" ];
then printf "";
else token=$(curl -s -XPOST -d "{"'"'"type"'"'":"'"'"m.login.password"'"'", "'"'"user"'"'":"'"'"$username"'"'", "'"'"password"'"'":"'"'"$password"'"'"}" "https://$server/_matrix/client/r0/login" | grep -oE 'syt_.+_...................._......');
fi
}
CacheAccessToken () {
if [ "$nocache" = "true" ];
then printf "";
else
mkdir -p "$cacheloc/matrix-send"
printf "$token\n" > "$cacheloc/matrix-send/access-token";
fi
}
ClearCache () {
[ -e "$cacheloc/matrix-send/access-token" ] || printf "There is no cache to be cleared.\n"
[ -e "$cacheloc/matrix-send/access-token" ] && rm -rf "$cacheloc/matrix-send/" && printf "Cleared cache\n"
exit 0
}
Send () {
if [ -z "$prettyprint" ]
then curl -s -XPOST -d "{"'"'"msgtype"'"'":"'"'"$mtype"'"'", "'"'"body"'"'":"'"'"$message"'"'"}" "https://$server/_matrix/client/r0/rooms/%21$roomid/send/m.room.message?access_token=$token"
else curl -s -XPOST -d "{"'"'"msgtype"'"'":"'"'"$mtype"'"'", "'"'"body"'"'":"'"'"$message"'"'"}" "https://$server/_matrix/client/r0/rooms/%21$roomid/send/m.room.message?access_token=$token" | jq
fi
}
########################
#### Initial checks ####
########################
[ -e /usr/local/bin/curl ] || [ -e /usr/bin/curl ] || error "curl not found"
[ -z "$1" ] && usage
CacheLocation "$HOME/.cache"
while getopts :t:C:chV opt
do
case $opt in
t)
if vargrep "m\.(text|notice)" "$OPTARG" -Eq
then
mtype="$OPTARG"
optind="$OPTIND"
else error "Type not valid (-t)"
fi
;;
C)
if vargrep "^~" "$OPTARG" -Eq
then config="$OPTARG"
elif vargrep "^/" "$OPTARG" -Eq
then config="$OPTARG"
else error "Configuration file location not valid (-C)"
fi
;;
c) ClearCache ;;
h) help ;;
V) version ;;
esac
done
unset OPTARG
unset OPTIND
###############################
#### Configuration loading ####
###############################
# Load configuration
if [ -e "$config" ];
then . "$config"
else
warning "~/.config/matrix-send.conf doesn't exist; creating it"
mkdir -p $HOME/.config
touch $HOME/.config/matrix-send.conf
fi
# Run checks for essential directives
[ -z $server ] && conf_error "Server directive is not present"
[ -z $username ] && conf_error "Username directive is not present"
[ -z "$password" ] && conf_error "Password directive is not present"
##############
#### Main ####
##############
# Get token and cache it (unless NoCache is set)
[ -e "$cacheloc/matrix-send/access-token" ] && token=$(cat $cacheloc/matrix-send/access-token)
[ -e "$cacheloc/matrix-send/access-token" ] || GetAccessToken
CacheAccessToken
if [ -z "$2" ];
then error "Room ID not specified.";
else
shift $((OPTIND-1))
message="$1"
roomid_input="$2"
if [ -z "$mtype" ]; then mtype="$defaultevent"; fi;
if vargrep '!' "$roomid_input" -qo
then roomid="$(printf "$roomid_input" | sed 's/!//g')"
else roomid="$roomid_input"; fi
Send
fi

View file

@ -1,65 +0,0 @@
.Dd $Mdocdate: January 1 2023 $
.Dt MATRIX-SEND-CONFIG 5
.Os
.Sh NAME
.Nm config.lua
.Nd configuration file for
.Xr matrix-send 1
.Sh DESCRIPTION
.Xr matrix-send 1
sends messages to Matrix rooms. In order to send a message however, it needs
credentials for the account sending the message. These credentials are provided
in the
.Nm
configuration file, as well as other options if you need them.
.Pp
The configuration file is in Lua format, meaning
you can write whatever Lua code you wish inside.
.Pp
The following configuration options are available:
.Bl -tag -width 11n
.Bl -tag -width login.server
.It Ic login.server Ar server
The address of the Matrix server being used.
.It Ic login.username Ar username
The username of the account being used.
.It Ic login.password Ar password
The password for the account being used.
.It Ic login.token Ar token
Instead of authenticating via username and password to obtain an access token, use
.Ar token
as the access token.
.It Ic cache.location Ar location
Use
.Ar location
as the cache location.
.It Ic cache.disable
Don't cache anything.
.It Ic advanced.event Ar type
Modify the default event type from the default (m.text) to
.Ar type .
Supported types are m.text, and m.notice.
.El
.Sh FILES
.Bl -tag -width ~/.config/matrix-send/config.lua -compact
.It Pa ~/.config/matrix-send/config.lua
.Xr matrix-send 1
configuration file
.It Pa ~/.cache/matrix-send
default cache directory
.El
.Sh EXAMPLES
The following example logs into the user 'john' of the server
matrix.org, and the password 'supersecretpassword',
and sets the default event type to m.notice.
.Bd -literal -offset indent
login = {
server = "matrix.org",
username = "john",
password = "supersecretpassword"
}
advanced.event = "m.notice"
.Ed
.Sh SEE ALSO
.Xr matrix-send 1

View file

@ -1,4 +1,4 @@
.Dd $Mdocdate: January 1 2023 $
.Dd $Mdocdate: August 4 2022 $
.Dt MATRIX-SEND 1
.Os
.Sh NAME
@ -8,24 +8,19 @@
.Nm matrix-send
.Bk -words
.Op Fl t Ar type
.Op Fl c Ar config
.Op Fl CV
.Op Fl C Ar config
.Op Fl chV
.Ar message room
.Ek
.Sh DESCRIPTION
.Nm
sends messages to Matrix rooms.
The default event type is m.text (standard text message),
but you can specify a custom type with
is a script that sends a message to a Matrix room.
It works by sending a JSON message to your Matrix server. The default message
type is m.text, but you can specify a custom type with
.Em -t .
.Pp
The options are as follows:
.Bl -tag -width keyword
.It Fl C
Clear cached access tokens and transaction IDs then exit.
.It Fl c Ar config
Instead of reading the default configuration file (~/.config/matrix-send.conf), read
.Ar config .
.It Fl t Ar type
Change the event type to
.Ar type .
@ -35,37 +30,59 @@ and
.Em m.notice .
Default is
.Em m.text .
.It Fl C Ar config
Instead of reading the default configuration file (~/.config/matrix-send.conf), read
.Ar config .
.It Fl c
Clear cached access tokens.
.It Fl h
Show the help menu, then exit.
.It Fl V
Print version information and exit.
Print version and program information, then exit.
.El
.Pp
To begin, start by making a configuration file. This always has the location of
.Em ~/.config/matrix-send/config.lua .
.Em ~/.config/matrix-send.conf .
At the very least, your configuration file has to contain three values:
.Em login.server ,
.Em login.username ,
At the very least, your configuration file has to contain 3 directives:
.Em Server ,
.Em Username ,
and
.Em login.password .
Let's say your Matrix server is envs.net, your username is john, and your password
.Em Password .
Let's say your Matrix server is envs.net. Your username is john, and your password
is supersecretpassword. Your configuration could look like this:
.Bd -literal -offset indent
login = {
server = "envs.net",
username = "john",
password = "supersecretpassword"
}
Server matrix.envs.net
Username john
Password supersecretpassword
.Ed
.Pp
It could also look like this:
.Bd -literal -offset indent
login.server = "envs.net"
login.username = "john"
login.password = "supersecretpassword"
.Ed
In that example, we used matrix.envs.net instead of envs.net. This is because
matrix.envs.net is the address in which the Client-Server API is listening on.
You can find your server's Client-Server API URL in Element by entering in
your server into the Homeserver dialogue in the sign-in screen, then hovering
over it once entered. Your server's Client-Server API URL should appear once
you hover over it. If it doesn't appear, you've already got it.
.Pp
You may have noticed that with this configuration, your password
is visible in plain sight.
At its most basic, matrix-send.conf is a shell script. This means you could
replace the
.Em Password
line with something similar to this
.Pp
.Dl Password $(gpg -d /path/to/passwd.gpg)
.Pp
where
.Em /path/to/passwd.gpg
is a GPG-encrypted plain text file containing only your password.
This line would mean that you would be asked for the passphrase required to
decrypt your password file before authenticating. This way, your password
is not in plain text.
More information on configuration options can be found in
.Xr matrix-send-config 5 .
.Xr matrix-send.conf 5 .
.Pp
To use
.Nm ,
@ -74,9 +91,9 @@ This will be something like
.Em !zyxwvutsrq:example.org .
Type the Room ID as the argument after the message you wish to send.
You may have to write the Room ID in quotes to avoid shell patterns.
You may have to escape the exclamation mark with a backslash. For example:
.Pp
.Dl matrix-send 'Hello world!' '!zyxwvutsrq:example.org'
.Dl matrix-send "Hello world!" \\!zyxwvutsrq:example.org
.Pp
That would send a message saying
.Em Hello world!
@ -85,13 +102,9 @@ to the room with the Room ID !zyxwvutsrq:example.org.
By default,
.Nm
caches access tokens in
.Em $HOME/.cache/matrix-send/token .
If you would like to disable caching of access tokens, add
.Pp
.Dl cache.disable = true
.Pp
to your
.Em config.lua .
.Em $HOME/.cache/matrix-send/access-token .
If you would like to disable caching of access tokens, add NoCache to your
.Em matrix-send.conf .
.Pp
Encryption is not supported. All messages will be sent unencrypted.
There are currently no plans to add encryption to
@ -99,19 +112,19 @@ There are currently no plans to add encryption to
.El
.Sh FILES
.Bl -tag -width ~/.config/matrix-send/config.lua
.It Pa ~/.config/matrix-send/config.lua
.Bl -tag -width ~/.config/matrix-send.conf
.It Pa ~/.config/matrix-send.conf
configuration file for
.Nm
.It Pa ~/.cache/matrix-send
cache directory
.It Pa ~/.cache/matrix-send/access-token
access token cache file
.El
.Sh EXAMPLES
Send a message saying "Hello world!" to the Matrix room !TfbDbeqp:example.org:
Send a message saying "How are you?" to the Matrix room !TfbDbeqp:example.org:
.Pp
.Dl matrix-send 'Hello world!' '!TfbDbeqp:example.org'
.Dl matrix-send 'How are you?' !TfbDbeqp:example.org
.Pp
.Sh SEE ALSO
.Xr matrix-send-config 5
.Xr matrix-send.conf 5

17
matrix-send.conf Normal file
View file

@ -0,0 +1,17 @@
## Example basic configuration for matrix-send
# A line with an option (e.g. 'Username jeremy') is a "directive".
# A line with only one word (e.g. 'NoCache') is a "statement".
# Comments start with a pound (#)
# Additional options can be found in matrix-send.conf(5)
# The Server directive is for your server's Matrix Client-Server API URL.
# matrix-send assumes that this is valid.
# matrix.org's CS API URL is matrix-client.matrix.org.
# You can find it by typing it into Element's sign in page and hovering your mouse over it.
Server matrix.envs.net
# The Username directive is your username for that server.
Username john
# The Password directive is your password for your account.
Password supersecretpassword

60
matrix-send.conf.5 Normal file
View file

@ -0,0 +1,60 @@
.Dd $Mdocdate: August 4 2022 $
.Dt MATRIX-SEND.CONF 5
.Os
.Sh NAME
.Nm matrix-send.conf
.Nd configuration file for
.Xr matrix-send 1
.Sh DESCRIPTION
.Xr matrix-send 1
sends messages to Matrix rooms. In order to send a message however, it needs
credentials for the account sending the message. These credentials are provided
in the
.Nm
configuration file, as well as other options if you need them.
.Pp
The following configuration options are available:
.Bl -tag -width 11n
.Bl -tag -width keepenv
.It Ic Server Ar server
The Client-Server API address of the Matrix server being used.
.It Ic Username Ar username
The username of the account being used.
.It Ic Password Ar password
The password for the account being used.
.It Ic AccessToken Ar token
Instead of authenticating via username and password to obtain an access token, use
.Ar token
as the access token.
.It Ic CacheLocation Ar location
Modify the cache location from the default (~/.cache) to
.Ar location .
.It Ic NoCache
Don't cache access tokens.
.It Ic DefaultEvent Ar type
Modify the default event type from the default (m.text) to
.Ar type .
Supported types are m.text, and m.notice.
.It Ic PrettyPrint
Pretty-print the JSON server messages.
.El
.Sh FILES
.Bl -tag -width ~/.config/matrix-send.conf -compact
.It Pa ~/.config/matrix-send.conf
.Xr matrix-send 1
configuration file.
.It Pa ~/.cache
Default cache location.
.El
.Sh EXAMPLES
The following example logs into the user johndoe of the server with the
Client-Server API address of matrix-client.matrix.org with the password
supersecretpassword, and sets the default event type to m.notice.
.Bd -literal -offset indent
Server matrix-client.matrix.org
Username johndoe
Password supersecretpassword
DefaultEvent m.notice
.Ed
.Sh SEE ALSO
.Xr matrix-send 1

View file

@ -1,342 +0,0 @@
#!/usr/bin/env lua
--[[
The GPLv2 License (GPLv2)
Copyright (c) 2022 Jeremy Baxter
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
]]--
require "luarocks.loader"
getopt = require "posix.unistd".getopt
mkdir = require "posix.sys.stat".mkdir
bname = require "posix.libgen".basename
http = require "socket.http"
cjson = require "cjson"
ltn12 = require "ltn12"
version = "2.1"
confdir = string.gsub("~/.config/matrix-send", '~', os.getenv("HOME"), 1)
confpath = confdir .. "/config.lua"
confvarv = os.getenv("MATRIXSEND_CONFIG")
hostname = io.input("/etc/hostname"):read("l")
matrix = {}
json = {}
uri = {}
sh = os.execute
-----------------
--- Functions ---
-----------------
-- Returns true if the given file name file exists, otherwise returns false.
function fileexists(file)
local f, err = io.open(file, 'r')
if f then
f:close()
return true
else
return false, err
end
end
-- Prints a message to stdout in the following format:
-- matrix-send: s
function msg(s)
io.stdout:write(string.format("%s: %s\n", bname(arg[0]), s))
end
-- Prints an error message to stderr in the following format:
-- matrix-send: s
-- After that, exits and returns 1 to the OS.
function panic(s)
io.stderr:write(string.format("%s: %s\n", bname(arg[0]), s))
os.exit(1)
end
-- Prints an error message to stderr in the following format:
-- matrix-send: confpath: s
-- After that, exits and returns 1 to the OS.
function confpanic(s)
panic(string.format("%s: %s", bname(confpath), s))
os.exit(1)
end
------------------------
--- Matrix functions ---
------------------------
function matrix.login(uri, json)
local respbody = {}
local result, respcode, respheaders, respstatus = http.request {
method = "POST",
url = uri,
source = ltn12.source.string(json),
headers = {
["content-type"] = "application/json",
["content-length"] = tostring(#json)
},
sink = ltn12.sink.table(respbody)
}
local body = table.concat(respbody)
local t = cjson.decode(body)
if t.error then
panic("server: " .. t.error)
else
return t.access_token
end
end
function matrix.send(uri, json, login_uri, login_json)
local respbody = {}
local result, respcode, respheaders, respstatus = http.request {
method = "PUT",
url = uri,
source = ltn12.source.string(json),
headers = {
["content-type"] = "application/json",
["content-length"] = tostring(#json)
},
sink = ltn12.sink.table(respbody)
}
local body = table.concat(respbody)
local t = cjson.decode(body)
if t.error then
if t.errcode == "M_UNKNOWN_TOKEN" then
msg("token expired/invalidated; re-authenticating")
-- I mean, it works
t = setmetatable({}, {
__concat = function(left, right)
if type(left) == "string" then
return left .. tostring(right)
end
return tostring(left) .. tostring(right)
end
})
t.token = matrix.login(login_uri, login_json)
if not cache.disable then
mkdir(cache.location)
cf = io.open(cache.location .. "/token", 'w+')
cf:write(t.token)
cf:close()
end
matrix.send(string.gsub(uri, "access_token=.+", "access_token=" .. t.token), json, login_uri, login_json)
else
panic("server: " .. t.error)
end
else
return t
end
end
-- Default configuration file
default_config = [[
-- Default configuration file for matrix-send
-- In comments, here is a simple example configuration.
--login = {
-- server = "envs.net",
-- username = "john",
-- password = "examplepassword
--}
-- You could also do it this way:
--login.server = "envs.net"
--login.username = "john"
--login.password = "examplepassword"
-- Both configurations do the same thing.
-- See matrix-send.conf(5) for more information
-- on configuration files.
]]
------------------------------------
--- Default configuration values ---
------------------------------------
-- Initialise tables
login = {}
cache = {}
rooms = {}
advanced = {}
-- Cache location
cache.location = "~/.cache/matrix-send"
-- Disable caching access tokens?
cache.disable = false
-- Message type
advanced.event = "m.text"
----------------------
--- Initial checks ---
----------------------
if arg[1] == nil then
io.stderr:write("usage: " .. bname(arg[0]) .. " [-c config] [-t type] [-CV] message room\n")
os.exit(1)
end
if confvarv then
if fileexists(confvarv) then
confpath = os.getenv("MATRIXSEND_CONFIG")
else
local _, err = fileexists(os.getenv("MATRIXSEND_CONFIG"))
panic("error opening " .. err)
end
end
if not fileexists(confpath) then
mkdir(confdir)
f = io.open(confpath, 'w')
f:write(default_config)
f:close()
end
dofile(confpath)
cache.location = string.gsub(cache.location, '~', os.getenv("HOME"), 1)
-- Make sure all required values are set
if not login.server then
confpanic("required value 'login.server' left unset")
end
-- login.token always supersedes login.username and password
if not login.token then
if not login.username then
confpanic("required value 'login.username' left unset")
elseif not login.password then
confpanic("required value 'login.password' left unset")
end
end
-- Parse options
for opt, optarg, ind in getopt(arg, ':Cc:t:V') do
optind = ind
if opt == 'C' then
msg("clearing cache")
os.execute("rm -rf " .. cache.location) -- TODO: replace this os.execute line
os.exit(0)
elseif opt == 'c' then
confpath = optarg
elseif opt == 't' then
if optarg:match("m%.text")
or optarg:match("m%.notice") then
advanced.event = optarg
else panic("unknown message type '" .. optarg .. "'")
end
elseif opt == 'V' then
io.stdout:write("matrix-send version " .. version .. '\n')
io.stdout:write(_VERSION .. '\n')
os.exit(0)
elseif opt == '?' then
panic("unknown option " .. arg[optind-1])
os.exit(1)
elseif opt == ':' then
panic("missing argument for option " .. arg[optind-1])
os.exit(1)
end
end
if optind == nil then
message = arg[1]
room = arg[2]
else
message = arg[optind+1]
room = arg[optind+2]
end
if room:match("^!") then
room_id = room
else
if rooms[room] ~= nil then
room_id = rooms[room]
else
panic("alias '" .. room .. "' invalid")
end
end
if fileexists(cache.location .. "/txnid") then
txnidf = io.open(cache.location .. "/txnid", 'r')
txnid = txnidf:read('l')
txnidf:close()
else
txnid = 0
end
-- Get .well-known
uri.well_known = string.format("https://%s/.well-known/matrix/client", login.server)
wellknown_body, errno = http.request(uri.well_known)
if errno ~= 200 then
panic("server: Error " .. errno .. " while getting /.well-known/matrix/client")
end
wellknown_t = cjson.decode(wellknown_body)
login.server = wellknown_t["m.homeserver"].base_url
-- Assign URIs and JSON bodies
uri.login = string.format("%s/_matrix/client/v3/login", login.server)
json.login = string.format([[
{
"identifier": {
"type": "m.id.user",
"user": "%s"
},
"initial_device_display_name": "matrix-send@%s",
"password": "%s",
"type": "m.login.password"
}
]], login.username, hostname, login.password)
json.message = string.format([[
{
"body": "%s",
"msgtype": "%s"
}
]], message, advanced.event)
-- Get the access token if credentials are used
if not login.token then
if fileexists(cache.location .. "/token") then
cf = io.open(cache.location .. "/token", 'r')
login.token = cf:read('l')
cf:close()
else
-- Login
login.token = matrix.login(uri.login, json.login)
-- Cache the access token
if not cache.disable then
mkdir(cache.location)
cf = io.open(cache.location .. "/token", 'w+')
cf:write(login.token)
cf:close()
end
end
end
uri.message = string.format("%s/_matrix/client/v3/rooms/%s/send/m.room.message/%d?access_token=%s",
login.server, room_id, txnid, login.token)
-- Send the message!
matrix.send(uri.message, json.message, uri.login, json.login)
-- Increment txnid and cache
if not cache.disable then
txnidf = io.open(cache.location .. "/txnid", 'w+')
txnidf:write(txnid + 1)
txnidf:close()
end