WhatsApp, fucking WhatsApp. The app that everybody uses without thinking twice, that makes no sense being used in places that I have seen it being used. Fucking police officers share police-work-related stuff via WhatsApp groups for the entire user-data department of Meta ( and their data-brokers ) to see. This has to stop! WhatsApp has to be defeated!

# The Background

At work, for about a year ( I work in an Icream Shop ) I avoided the need to use the work-based WhatsApp group by simply being a person with a dumb-phone. I would flash my Nokia, and they would understand why I haven't seen the last update or something. The boss adopted to it rather quickly and was calling me or sending me regular SMS, when I specifically was required to know something. When a digital file was needed, she would send me an Email.

Then the bosses have changed, and the new boss immediately made me one of the chefs. Now I no longer sell icecream, I make it. I operate the Cattabriga Multifreeze 12 Pro machine and do all the various preparations and stuff to make the icecream tasty and beautiful. 

The problem is, the boss ( or more like the boss's boss ) really wanted me to be in the WhatsApp groups. Specifically so I could photograph and send them things, if something goes wrong with the machines. My new boss even brought one of his own old phones to work, bought for it a new SIM-card and made an account for me. Thankfully he understood some of my concerns and didn't call my account in my name. It has a generic "The Icecream Man" account. [Fuck](/articles/why_i_m_not_going_to_watch_ice_cream_man___2026____).

In any case, working with WhatsApp directly didn't feel right. And the boss of the boss demanded me to take the store-phone home as if it is mine. But I'm not going to sit with an active Meta spy in my house. Are you fucking kidding me?

So I decided to make a Matrix bridge.

# Official Matrix-to-Whatsapp bridge?

I installed F-Droid on the store-phone ( [it still works](https://keepandroidopen.org/) ), and through it installed a few apps that I might need: Termux ( because I thought to host the bridge on the phone itself ), Libre Camera and Libre Gallery, because I'm not using the standard Android ones, are you kidding me, Element X, to make an account for this phone, and a few other things including SuperTuxKart, because why not.

Then I started trying to piece together how the [Matrix Official WhatsApp bridge](https://matrixdocs.github.io/docs/bridges/whatsapp) works. And to my horror this required me to have admin access to a matrix server. I use a x.madiator.org matrix server and could ask Madiator to implement it, but unfortunately at the time I needed it the most he was out of his base and couldn't do that. ( Now as he returned he says he could, will see what I decide ).

Basically the official Matrix-to-WhatsApp bridge is a plugin for the synapse server, which allows it to create a bunch of accounts ( as I understand ) to represent various people on the WhatsApp side. This is useful to make the bridge seamless.

Two issues with that:

- It is not very beginner friendly. As in those people that most need a WhatsApp bridge might not have any way of using it. I needed something I could run in a terminal, at least. And use 1 standard matrix account.

- It perhaps makes the bridging a bit too seamless. As in, it won't force people to move away from WhatsApp. Which is the entire goal of this operation.

I need to defeat WhatsApp, not work with it.

# My own bridge

So I thought, how hard it would be to make my own bridge? There is the official one, so there is a method to connect to WhatsApp and somehow scrape it periodically. And send messages to it, periodically. And I know of at least one descent Matrix bot modules for python that I've used.

I thought I'd do something like this: I make a matrix bot that listens to new messages in some matrix group related to the work. And when there is a new message it forwards it to the WhatsApp side of things using some WhatsApp API call.

Say I write:

> Hello there.

It would post on WhatsApp this:

> @blenderdumbass:x.madiator.org at Matrix has sent: Hello there.

And do a similar thing in the other direction. 

This would constantly remind people of the Matrix group. And would give me more and more opportunity to rescue them out of WhatsApp.

I've implemented a simple text-based bridge in 3 days:

## Day 1: Looking for a WhatsApp API thingie

On day one I took the damned phone home ( as they initially wanted me to do ) in order to hack on it. I needed a way to be able to access WhatsApp messages and send WhatsApp messages back.

At first, looking at the official Matrix Bridge I found a link to [Beeper](https://github.com/beeper). Which apparently is a Matrix based app that can also connect to various other things like WhatsApp. It was not found in F-Droid ( red flag ).

But looking though their Github repos, and seeing that they posted a lot of Free Software I decided that it is at least *better* that using WhatsApp directly. So I installed it ( on the phone ), hooked it to WhatsApp, and guess what... it worked. And I could also invite this Beeper account into the matrix group. Which means Beeper is 100% matrix as well.

Now, is Beeper itself Free Software? I don't really know. I can't find the app source code anywhere. So I'm not sure. But it feels mildly better to use this than to use the Meta's app.

I started looking further into it. I found [nchat](https://github.com/d99kris/nchat). A little free software terminal client app that supports WhatsApp. I installed it, hooked it up to the WhatsApp account. And wow, now I can not take the phone home. I have access to all the whatsapp shenanigans from my terminal. 

But that was not all. If I just use `nchat` I will not rescue people on the WhatsApp side out of WhatsApp. So I needed to look further.

`nchat` had a way to export messages ( which I could use for the bridge ), but I couldn't write messages. I mean I could have hacked into it a little bit to make it work. It is a cli app anyway, so I can `STDIN` into it. But come on.

I ended up finding [wacli](https://github.com/openclaw/wacli). This is not so much a cli, but more of a `bash` able interface for WhatsApp. Which is amazing. 

This is exactly what I need. And not just that I can use `bash` syntax to send messages, it can also output the messages in `JSON`. Like can it be any better?

I had this work by the end of Day 1. It was something like 1 AM and I had to run to bed.

## Day 2 and 3: Fighting with the Matrix bot

I started the [codeberg repository](https://codeberg.org/blenderdumbass/WhatsApp_to_Matrix) for my WhatsApp to Matrix bridge on day 2. I knew how I want to make it.

2 loops. One handling incoming messages from Matrix and sending them over to WhatsApp and another handing incoming messages from WhatsApp and sending the to Matrix.

I was going to use the [simplematrixbotlib](https://pypi.org/project/simplematrixbotlib/) for the Matrix side of things, and some manually made python code to `bash`interact with `wacli` for the WhatsApp side of things.

Fuck `simplematrixbotlib`!

Or more like:

Fuck `asyncio`!

Making it send from Matrix to WhatsApp - easy.
Making it send from WhatsApp to Matrix - almost easy.

Making it do both - a fucking nightmare.

Let me attempt to explain the fucking nightmare.

```
import simplematrixbotlib as botlib

creds = botlib.Creds("https://home.server", "echo_bot", "pass")
bot = botlib.Bot(creds)
PREFIX = '!'

@bot.listener.on_message_event
async def echo(room, message):
    match = botlib.MessageMatch(room, message, bot, PREFIX)

    if match.is_not_from_this_bot() and match.prefix() and match.command("echo"):

        await bot.api.send_text_message(
            room.room_id, " ".join(arg for arg in match.args())
            )

bot.run()

```

This is the example code of `simplematrixbotlib` on their PyPi page. Notice the `@bot.listener.on_message_event` part followed by `async def` function.

Well ladies and gentleman, we found the bitch.

In my mind python's `asyncio` was kind of like a new, fancy version of threading. I thought that there is a loop somewhere started by `bot.run()` which executes this `async` function in a separate thread when a certain condition is met. I was wrong.

For the whole of day 2 I was fighting with the code under this assumption. And nothing I did would allow both sides to work coherently together.

See I can't just use the `bot.api.send_text_message()` function in a separate thread or something, because it is an awaitable function. You have to run it inside of an `async` function. Or more like you have to `await` it inside of the `async` function. And yeah, technically I could create an `async` function in a separate thread and do that this way. Perhaps. But it then looses all its context and cannot send messages. Because apparently it needs to run within one of the `@bot.listener` event calls to make it work. To have the context necessary to send the message properly, or something.

They do have the `@bot.listener.on_startup` call to bind some `async` code to the startup of the bot. Which gives me an option to start running something. And I can run a while loop that listens to WhatsApp inputs and sends messages over to Matrix. And it would work.

But this while loop would completely block all other events. 

`asyncio` is not threading.

I had to look into it and understand what the fuck `asyncio` even is. And thankfully I did something similar for [Dani's Race](/games/Dani's_Race) the other day.

In Dani's Race due to the `depsgraph` problem I have some stuff that happens in the game scheduled to be executed once there is enough `depsgraph` to execute the function. Instead of running the function, I pass it over to the scheduler that periodically checks if the conditions are favorable, and only if they are, it executes the function.

`asyncio` does that for each line of `async` function code, or something. All awaiting `async` functions are scheduled to be executed at once. But they are not executing until one of the functions gives away the execution privileges to the other functions. If I run a while loop, it will never return nothing. All the other scheduled functions will just wait for their turn forever.

By the end of Day 3 I figured out this code:

```
@bot.listener.on_startup
async def on_startup_setuper(stuff):

    if not config.get("swl"):
        config["swl"] = True

        print("\n\nStarting WhatsApp to Matrix thread!!!\n\n")
        
        # THIS IS THE MAGIC SAUSE!!!
        asyncio.ensure_future(whatsapp_loop())

```

Where the `whatsapp_loop()` actually contains the while loop. And periodically it runs `asyncio.sleep()` ( a version of `time.sleep()` that returns privileges for some time ), and so now it sort of works.

But there is another problem.

Notice the print:

```
print("\n\nStarting WhatsApp to Matrix thread!!!\n\n")
```

Two new lines in front. Two new lines in the back! Why? Well... because I don't know why, but the `@bot.listener.on_startup` thing doesn't work always.

If I stop the bot and then immediately restart it, chances are, the `@bot.listener.on_startup` will not fire. And therefor I wont see the text. So the text is needed for me to know that everything started as needed. 

# Conclusion

This is the state of my [WhatsApp to Matrix](https://codeberg.org/blenderdumbass/WhatsApp_to_Matrix) bridge as of now.

I think I have to do a few things to make it better:

 - Make sure that it knows to restart itself if the `@bot.listener.on_startup` doesn't fire in say a minute after starting the bot. 

 - Implement some sort of auto-restart on failure. So if there is a bug somewhere it won't require me to manually restart it all the time.

 - Implement forwarding media files.

Please take a look at what I did. If there will be people struggling with it, maybe it might develop into something rather useful.

**Happy Hacking!!!**