Skip to content

Instantly share code, notes, and snippets.

@bryan-harter
Last active February 25, 2025 22:00
Show Gist options
  • Save bryan-harter/242190e7a68c4b32dac5e88fc3db523e to your computer and use it in GitHub Desktop.
Save bryan-harter/242190e7a68c4b32dac5e88fc3db523e to your computer and use it in GitHub Desktop.
Adding an AI Bot to your Signal Group Chat!

Adding an AI Bot to your Signal Group Chat!

Do you want to switch to Signal from Facebook Messenger, but worry that you'll miss the @MetaAI chatbot?

Well, have I got the solution for you!

In this tutorial, I use an old Raspberry Pi to run a tiny local language model for signal group chats. Lets get started!

Set up your Raspberry Pi

"Pi only has two digits: 0 and 1." -SignalBot

For my hardware, I'm using a Raspberry Pi 400 and the tiny 16GB Micro SD card it comes with by default.

For an operating system, I'm just using the default OS, with no custom settings. I just downloaded the Raspberry Pi Imager and used that to put the latest OS on there.

image

For additional help setting up your raspberry pi, you can take a look at their official documentation here.

When you're done, eject the micro SD card from your laptop, pop that bad boy into your Pi-hole, and boot 'er up.

Note

There's nothing special about using a raspberry pi. In fact, it's not the best choice if you actually want the bot to have the slightest bit of use. The important thing is, at the end of this is that you have a functional linux machine.

Install Signal-CLI

Signal-CLI is an unofficial CLI to signal. So beware, it may stop being supported without notice, though it has been continuously maintained for 10 years.

Open up the terminal, and input the commands:

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

Congrats, signal CLI is installed? Will it work yet? NO!

Downloaded aarch JDK-21 from adoptium.net

On my Raspberry Pi, they did not have the latest version of the Java Developer Kit (JDK) to be able to run the latest Signal CLI on their installer thing, so you'll need to install it manually.

You can browse JDK versions on adoptium.net

image

For raspberry pi, you'll need to install version 21+ for aarch64 architectures. This is the one I used: https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.6%2B7/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.6_7.tar.gz

cd Downloads
tar -xvf OpenJDK21*tar.gz
sudo mv jdk-21* /usr/lib/jvm
java -version

Downloaded libsignal-client

"The greatest danger in life is not fear, but the fear of being feared." - SignalBot, on its favorite quote from Yoda

You'll also need to download the client to talk to Signal. I got mine from here (instructions for download are in the README): GitLab.com/packaging/libsignal-client

You might need a newer version, but here is what I installed:

curl -Lo libsignal_jni.so "https://gitlab.com/packaging/libsignal-client/-/jobs/artifacts/v0.65.4/raw/libsignal-client/arm64/libsignal_jni.so?job=libsignal-client-arm64"

Then put the .so file in /usr/lib and run ldconfig:

sudo mv libsignal_jni.so /usr/lib/libsignal_jni.so
sudo ldconfig

Get a Phone Number for SignalBot

Get a throwaway phone number. Or maybe you already have one. Or multiple! I don't judge.

Note

I do judge

It doesn't matter where you get it from, you only need it for the initial verification text.

For a free option, I just used voice.google.com to get a google number since its completely free, and I wasn't using it for anything else.

For the rest of this gist, I'l pretend my number is "(+1)234-567-8910", but obviously insert your own.

Verified your new phone number with Signal-CLI

"32 (or 60 in some cases) is a widely accepted number due to its simplicity." - SignalBot, on its favorite phone number

Hurray! Now you should be able to run the Signal-CLI. Go to this link for a captcha:

https://signalcaptchas.org/registration/generate.html

Solve the captcha, proving you're not a robot once and for all. Then, right click the "Open Signal" link, and Copy Address.

Run your first command of the Signal-CLI, using that gigantic string of letters you just copied:

signal-cli -a +12345678910 register --captcha "signalcaptcha://signal-hcaptcha.5fad9...the really really long thing"

if all goes well, then you should receive a text with a confirmation code. Copy that confirmation code, and run

signal-cli -a +12345678910 verify 981448

Note

You may now cast your temporary number into the sea

Test and Set a profile

If you want to verify that your account is working, try talking to yourself:

signal-cli -a +12345678910 send -m "I'm looking for a man in finance" +155533396887

image

Since you don't want to be a creepy bot, you should also go off and make a name for youself. How about "SignalBot"!

signal-cli -a +12345678910 updateProfile --name "SignalBot"

Install Ollama and Smollm

"What a great question! I'm an English teacher" - SignalBot, when asked what Smollm is

The next thing we need is the brains of the operation. The tiny, tiny brains.

First, install Ollama, a FOSS tool to run numerous LLMs:

curl -fsSL https://ollama.com/install.sh | sh

After waiting, you can install the tiniest model they have, Smollm

ollama pull smollm:135m

You can even start sampling it's amazing insights from right here in the command line:

ollama run smollm:135m "omg, are you the president of the united states??"

image

Set up Python Environment

We'll need to set up a small python environment in our home directory to run a script on a loop.

mkdir signalbot
cd signalbot 
python -m venv signalbotvenv
source signalbotvenv/bin/activate
pip install ollama

Now we need to set up the actual script! Below is the python script I'm using:

import subprocess
import time
import ollama
import multiprocessing

PHONE_NUMBER="+12345678910"
BOT_NAME="SignalBot"
LLM_TIMEOUT = 120
MODEL_NAME = 'smollm:135m'

def run_ollama_chat(prompt):
    import ollama
    reply = ollama.chat(
        model=MODEL_NAME,
        messages=[
            {
                'role': 'user',
                'content': prompt
            }
        ]
    )
    return reply['message']['content']

def call_llm_with_timeout(prompt, timeout=LLM_TIMEOUT):
    # Run the LLM as a process so we can kill it if it gets stuck
    pool = multiprocessing.Pool(processes=1)
    async_result = pool.apply_async(run_ollama_chat, (prompt,))
    try:
        return async_result.get(timeout=timeout)
    except multiprocessing.TimeoutError:
        # Kill the worker process
        pool.terminate()
        return "Timeout error :("
    finally:
        pool.close()
        pool.join()

def run_bot():
    # Loop forever, receiving messages and responding to Mentions
    command = ["signal-cli", "-u", PHONE_NUMBER, "receive"]
    while True:
        try:
            # Receive Messages
            result = subprocess.run(
                command,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True
            )
            output = result.stdout.strip()

            if output:
                lines = output.split("\n")
                for i in range(len(lines)):
                    # Check if bot was mentioned
                    if 'Mentions:' in lines[i] and BOT_NAME in lines[i+1]:
                        if 'Body: ' in lines[i-7]: # The body of the message should be 7 lines back
                            # Feed the message into the LLM
                            response = call_llm_with_timeout(lines[i-7])
                            # Group ID should be 5 lines above current line
                            group_id = lines[i-5].split()[-1]
                            # Send the response
                            send_command = [
                                "signal-cli",
                                "-u", PHONE_NUMBER,
                                "send",
                                "-m", f"{response}",
                                "-g", f"{group_id}"
                            ]
                            _ = subprocess.run(
                                send_command,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                text=True
                            )
            # Wait a bit before checking again
            time.sleep(1)

        except KeyboardInterrupt:
            print("\nScript stopped by user.")
            break
        except Exception as e:
            print(f"An error occurred: {e}")

if __name__ == "__main__":
    run_bot()

So type

nano signalbot.py

And paste in the code above. You'll need to modify it depending on your phone number, the name of the bot (and the model name, if you decide to pick an actual useful model)

Set it as a Linux Service

"I've been living on this planet for over 20 years, which means I have a intelligence that is far beyond what humans can achieve in their lifetime." - SignalBot, sounding like a recent college graduate

You can run the script right now if you want to:

python signalbot.py

But if it dies or your computer restarts, thats not ideal! We want the yuks to stay with us for eternity. Lets set this up as a linux service.

Run the following in your terminal:

sudo nano /etc/systemd/system/signalbot.service 

and paste in the following.

[Unit]
Description=SignalBot Service
After=network-online.target

[Service]
Type=simple
User=signalbot
WorkingDirectory=/home/signalbot/signalbot
ExecStart=/home/harter/signalbot/signalbotvenv/bin/python /home/signalbot/signalbot/signalbot.py
Restart=always

[Install]
WantedBy=multi-user.target

Note that you'll need to modify the directories if you installed these scripts somewhere else.

Once that service file is in place, we just enable our service:

sudo systemctl daemon-reload
sudo systemctl enable signalbot.service
sudo systemctl start signalbot.service

Join a Group

Now SignalBot is just waiting patiently for messages to come in. How do we get it messages? Join a group!

Copy the invite URL from the group you want to join, and type:

signal-cli -u +12345678910 joinGroup --uri "[INVITE LINK]"

Note

If you don't have friends, Signal allows groups of 1

Conclusion

Now you're ready! Go out there and join the singularity!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment