🤖 How to make a Signal bot in Python

Building a Signal bot with signal-cli.

Fabio Barbero published on

5 min, 970 words

Categories: Guide

Making a Signal bot in Python (Linux)

Note: This guide was updated on 29/07/2022 as it was previously outdated. If anything doesn't work, feel free to contact me.

What's signal?

Signal is a cross-platform, centralized (😞), encrypted messaging service that I've been using for a few years now. My friends and I decided to switch to Signal as an alternative to Whatsapp, which unlike Signal collects a lot of metadata of your messages and is owned by a big tech company (Facebook), and Telegram, which stores all your (encrypted) data on their servers.

Signal has evolved a lot in the past few years, and now has all the major features a messaging app needs: group chats, stickers, reactions, phone/video calls, ... Unlike Telegram, however, it does not have support for bots. In fact, it does not provide a public API for sending and receiving messages, and the only valid tool I've found that can be used for Signal apart from their official apps is signal-cli.

Step 1: Installing and configuring signal-cli

This is probably the easiest step. You can follow the installation guide, or just paste the instructions below to install it for version 0.10.9.

export VERSION=0.10.9
wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}"-Linux.tar.gz
sudo tar xf signal-cli-"${VERSION}"-Linux.tar.gz -C /opt
sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/

You then need to register your phone number (even if you already registered it on another device). This can be done by running

signal-cli --config /var/lib/signal-cli -u +4915151111111 register

(replace +4915151111111 with your phone number).

If you get a

Captcha invalid or required for verification

you need to

  • go to https://signalcaptchas.org/registration/generate.html
  • open Web Developer Tool
  • complete Captcha
  • you'll then either get redirected to a link which looks like signalcaptcha://... or (on Firefox) you'll get a message on the console which says Prevented navigation to “signalcaptcha://...
  • run signal-cli --config /var/lib/signal-cli -u +4915151111111 register --captcha TOKEN (replace TOKEN with whatever comes after signalcaptcha://)

to register with Captcha. Guide can be found here Further instructions can be found here.

Step 2: Enable DBus service

This is the most crucial part. In order to be able to make a Python script that uses signal-cli, you need to start it over dbus. For that, you need to add these three files:

These files may change for future versions, so make sure to get the latest files from the "data/" folder of the github repository.

/etc/dbus-1/system.d/org.asamk.Signal.conf:

<?xml version="1.0"?> <!--*-nxml-*-->
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
        "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">

<busconfig>
        <policy user="YOUR_USERNAME">
                <allow own="org.asamk.Signal"/>
                <allow send_destination="org.asamk.Signal"/>
                <allow receive_sender="org.asamk.Signal"/>
        </policy>

        <policy context="default">
                <allow send_destination="org.asamk.Signal"/>
                <allow receive_sender="org.asamk.Signal"/>
        </policy>
</busconfig>

Where YOUR_USERNAME is your linux username (e.g. "root" if you're running as root). It is recommended in the signal-cli dbus documentation to create a new user called signal-cli

/usr/share/dbus-1/system-services/org.asamk.signal-cli.service:

[D-BUS Service]
Name=org.asamk.Signal
Exec=/bin/false
SystemdService=dbus-org.asamk.signal-cli.service

and

/etc/systemd/system/signal-cli.service:

[Unit]
Description=Send secure messages to Signal clients
Requires=dbus.socket
After=dbus.socket
Wants=network-online.target
After=network-online.target

[Service]
Type=dbus
Environment="SIGNAL_CLI_OPTS=-Xms2m"
ExecStart=%dir%/bin/signal-cli --config /var/lib/signal-cli daemon --system
User=YOUR_USERNAME
BusName=org.asamk.Signal
# JVM always exits with 143 in reaction to SIGTERM signal
SuccessExitStatus=143

[Install]
Alias=dbus-org.asamk.Signal.service

where again YOUR_USERNAME is your linux username. If you have installed signal-cli anywhere else than in /usr/local/bin you should change that file.

You can then run these commands to run the service:

systemctl daemon-reload
systemctl enable signal-cli.service
systemctl reload dbus.service

Step 3: Configuring Python

To use the dbus interface in Python, you need to install pydbus

pip install git+https://github.com/LEW21/pydbus.git

You might also need to install the following packages (in Ubuntu):

sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-3.0
sudo apt install libgirepository1.0-dev gcc libcairo2-dev pkg-config python3-dev gir1.2-gtk-3.0
pip install pycairo
pip install PyGObject

(as with every project, I recommend making a separate virtual environment for this.)

Step 4: Python script!

The original script I used to make my Python bot can be found here.

The main skeleton for the program is the following:

from pydbus import SystemBus
from gi.repository import GLib
bus = SystemBus()
loop = GLib.MainLoop()

signal = bus.get('org.asamk.Signal', object_path='/org/asamk/Signal')

def reply_ping(timestamp, source, groupID, message, attachments):
    signal.sendMessage(message, [], [source])

signal.onMessageReceived = reply_ping
loop.run()

A list of all methods available can be found here.

Tips: debugging

If something goes wrong after starting the dbus interface, you might want to kill the process manually as it is running in the background with

ps aux | grep signal-cli
kill PROCESS_ID

where PROCESS_ID is the PID of the process that you can see from running the first command.

Joining a v2 group

I remember having trouble joining a version 2 Signal group. I ended up succeeding only by updating the profile and then running the command from the terminal with the join link. See here for further instructions.