Gediminas Lelešius

Signal bot in Python

2021-10-14, U: 2022-10-07

Install Java 17

Ubuntu:

sudo add-apt-repository ppa:linuxuprising/java -y
sudo apt update
sudo apt-get install oracle-java17-installer oracle-java17-set-default

Install Signal-cli

Download latest version directly from releases: https://github.com/AsamK/signal-cli/releases.
Extract it and try to run signal-cli -u [PHONE NUMBER] receive.
If libsignal fails to load because architectures don't match, go to https://github.com/exquo/signal-libs-build/releases or https://gitlab.com/packaging/libsignal-client/.

Short version of Signal-cli guide https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal:
Choose your architecture and download latest version (dpkg --print-architecture).
Go to signal-cli/lib, seecheck the exact version, remove included library and add downloaded one:

zip -d libsignal-client-*.jar libsignal_jni.so

zip -u libsignal-client-*.jar libsignal_jni.so

Log in to Signal

As main device (not tested!)
signal-cli --config /var/lib/signal-cli -u [PHONE NUMBER] register
signal-cli --config /var/lib/signal-cli -u [PHONE NUMBER] verify [CODE FROM SMS]
As secondary device
signal-cli --config /var/lib/signal-cli link -n "[DEVICE NAME]"

Copy printed link, paste it into QR generator and scan using Signal app on mobile.

import asyncio
import json

SIGNAL_COMMAND = "signal-cli -u +xxxYOURPHONENUMBER --output=json jsonRpc"

mid = 1

async def send_msg(method, data):
    global mid, proc
    mid = mid + 1
    req = {"jsonrpc":"2.0", "method": method, "params": data, id: mid}
    proc.stdin.write((json.dumps(req) + "\n").encode("utf-8"))
    await proc.stdin.drain()


async def main():
    global proc
    proc = await asyncio.create_subprocess_exec(
        *SIGNAL_COMMAND.split(" "),
        stdin=asyncio.subprocess.PIPE,
        stdout=asyncio.subprocess.PIPE,
    )

    while True:
        line = await proc.stdout.readline()
        if not line:
            continue
        msg = json.loads(line)
        if msg.get("method", None) == "receive":
            data = msg["params"].get("envelope", None)
            if not data or data.get("source", None) == "+xxxYOURPHONENUMBER":
                continue
            msgData = data["dataMessage"]
            source = data.get("source", None)
            groupID = msgData.get("groupInfo", {}).get("groupId", None)
            timestamp = msgData.get("timestamp", None)
            text = msgData.get("message", "")
            if "dataMessage" in data:
                # handle message
                await send_msg(
                    "sendReceipt", {"targetTimestamp": timestamp, "recipient": source}
                )
                await send_msg(
                    "send",
                    {
                        "recipient": None if groupID else source,
                        "groupId": groupID,
                        "message": f"you said {text}",
                        "attachments": [],
                    },
                )

asyncio.run(main())

Create DBus service (OLD way)

https://github.com/AsamK/signal-cli/wiki/DBus-service

Write bot on DBus

Install python dependencies:

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

Simple echo bot:

from pydbus import SystemBus
from gi.repository import GLib
import base64

bus = SystemBus()
loop = GLib.MainLoop()

signal = bus.get('org.asamk.Signal')

def send_message(source, groupID, text, attachments=[]):
    if len(groupID) == 0:
        signal.sendMessage(text, attachments, [source])
    else:
        signal.sendGroupMessage(text, attachments, groupID)


def msgRcv (timestamp, source, groupID, message, attachments):
    send_message(source, groupID, 'text', ['attachment_file_path'])


signal.onMessageReceived = msgRcv
print("START")
loop.run()