Teamspeak 3 Observer

Recently we had issues with one of our team members not having time due to stuff going on in real life. Not being aware of it, one of the higher-ups just decided to do all the given tasks by himself.

Now my team is in a position where he did not tell us that he made changes. Also, we can not properly check what changes were made unless we read a bunch of logs.

Being one of the higher-ups he also does not have in-depth knowledge about the extensive Teamspeak3 permission system. Meaning you can easily miss something that can cause you a headache later.

The plan

We’re running a Mattermost server for all our internal communications. Mattermost, for those of you who don’t know, is basically a clone of Slack that you can host yourself. Like Slack, you have a whole set of features like Bots, Webhooks, etc.

I want to be able to get a message if anything changes on the Teamspeak3 server. The message should contain all relevant information:

  • When?
  • What?
  • Who?

Creating a Webhook

If you wand to have a separate channel for the Bot to post in, now it’s a good time to create it. I’ve created a private Channel called TS3-Reports for this.

In your Mattermost Team, click on the burger menu next to your username and in this menu on Integrations.

In that screen, we want to create am Incomming Webhook, as we’re sending messates to Mattermost.

After clicking Save, Mattermost should show you an URL like this:

Writing the Script

I’m running Teamspeak3 in a Docker container setup which makes it convenient for reading the logs. If you’re running a different setup, please adjust your script accordingly for reading the logs.

Also, I do separate changes to group permissions and the creation of groups. That’s why there are two different calls.


I’m using some files to temporarily store the payload to send to the Webhook as I had a lot of trouble getting a valid JSON otherwise.


# Files for changes in groups (added, deleted, duplicated)

# Files for changes in group permissions

Reading the log and filtering

As my script checks every 5 minutes for new entries, I conveniently use one of dockers functions to only give me the log entries from the last 5 minutes.
docker logs --since '5m' ts3server_teamspeak 2>&1

Then we filter for the entries we’d like to catch. Teamspeak does help us out a lot there by just having a | in front of everything we want to get.

Also if you have multiple virtual server instances running, now would be a good time to filter it as well. In my case, this is the virtual server by the ID 2

docker logs --since '5m' ts3server_teamspeak 2>&1 | grep '|servergroup' | grep '|2'
docker logs --since '5m' ts3server_teamspeak 2>&1 | grep '|permission' | grep '|2' 

Now we have all relevant information. Next we just cut out everything we don’t want to be in there using the cut command. It’s also convenient to use the | as a delimiter again.

And once that’s done, we just store it within a file.

ocker logs --since '5m' ts3server_teamspeak 2>&1 | grep '|servergroup' | grep '|2' | cut -d "|" -f1,2,5 > "${GROUPS_FILE}"
docker logs --since '5m' ts3server_teamspeak 2>&1 | grep '|permission' | grep '|2' | cut -d "|" -f1,2,5 > "${PERMISSIONS_FILE}"

Processing the saved file(s)

Basically what we have to do next is to escape all \n (newline) characters with \\n in order to generate a valid JSON.

To achieve this, I’m just looping through the whole file and temporarily store this in a variable.

# Variables to store the escaped text in

# reading file line by line
while IFS= read -r line
done < "$GROUPS_FILE"

# reading file line by line
while IFS= read -r line

Now that we have our escaped file contents in an variable, I decided it’d be best to write all of the contents back to another file and let cURL handle the rest later on.

echo "{\"username\":\"TS3-Observer\",\"text\":\"**[Ă„nderungen]** Server Groups:\\n\`\`\`\\n${OUTPUT_GROUPS}\\n\`\`\`\"}" > "${GROUPS_SEND}"
echo "{\"username\":\"TS3-Observer\",\"text\":\"**[Ă„nderungen]** Group Permissions:\\n\`\`\`\\n${OUTPUT_PERMISSIONS}\\n\`\`\`\"}" > "${PERMISSIONS_SEND}"

If you want some special formatting, different from the one I use, please take a look at Mattermosts official documentation who the payload for Incomming Webhooks should look like.

Sending to Mattermost

Now we have a file with our JSON payload and a Webhook URL stored in a variable. Now it’s time to get it to our Mattermost server.

But instead of just sending it, we should check if there where even any changes on our Teamspeak3 server as we don’t want to send an empty message.

To check this, we can simply use the variables where the escaped log entries are stored in. If it’s empty, then we know there have not been any changes. The -n within the if-condition simply checks if the variable is not empty.

if [ -n "${OUTPUT_GROUPS}" ]; then
    curl -i -X POST -H 'Content-Type: application/json' -d @$GROUPS_SEND $WEBHOOK
    echo ""

if [ -n "${OUTPUT_PERMISSIONS}" ]; then
    curl -i -X POST -H 'Content-Type: application/json' -d @$PERMISSIONS_SEND $WEBHOOK
    echo ""

You should now see the message in your Mattermost channel.


This step is optional.

Now we’ve created a lot of probably unnecessary files as I found no other way to achieve this and I just want to get rid of them for now.




It’s far from perfect but hey, it works.

See the full script on Gitlab:

Leave a Comment