Bridges into Matrix

Bridges into Matrix

Last time I wrote about how to set up your very own personal Matrix stack with everything you need - the Synapse servers, Element web client, and Synapse Admin. This time I will show how to set up a few bridges - that is, components that connect Matrix to other chat platforms and services. They will generally let you send and receive messages from other services directly to and from Matrix.

This time the "technical stuff" will be a bit less complex. While there are a few gotchas, we don't need to expose anything to the internets, so we needn't configure any webservers, domains, or anything else.

When I first decided to give Matrix a spin, I only expected to use it little, since none of my friends or communities use it. But after I added bridges for Whatsapp, Facebook Messenger, and Discord, I ended up using it daily - while also being able to ditch the apps for those three. Those are the bridges we will set up today.

An added benefit: Not only can I now just open one window and chat with my contacts on any of the four platforms [i.e. WA, FB, DC, and Matrix (MX) itself] - I also can do it from a web app simultaneously on multiple computers. Which is something I just couldn't do before, because Whatsapp doesn't let you log into multiple browsers (or computers) at once.  

How does this work?

I won't go into the details, but the gist of it is: Matrix servers (in this case, Synapse) allow registrations of "Application Services" as a way of extending the server's functionality. They're not plugins in the usual sense of the word - they live alongside the MX server and communicate with it via HTTP.

Unlike standard clients though, they have special powers - they can reserve namespaces in which to create rooms or puppet users, monitor room events, etc.

There are various kinds of bridges, depending on what they can do. I won't go into any of those, either; I'll just say that all the bridges mentioned here can import your contacts into Matrix, initiate and sync direct messages and group chats, download avatars and images, forward reactions, and other fancy features. (Side note: Facebook introduced reactions with arbitrary emoji. They haven't been rolled out for me yet, but the bridge did support them - so I could use them from Matrix, but not the official Messenger app.)

On the "client" side of the bridges - WA, FB, or DC often don't really expose an API, so the bridges impersonate a web app when communicating with the service they're bridging into. Authenticating with them is sometimes more complicated than just logging into a service, but it's nothing more difficult than finding and copying cookie values.

Setting up with docker-compose

You don't need anything besides Docker (and -compose). The Whatsapp bridge's readme shows you how to run an emulated Whatsapp on your server so you don't need to have your phone connected to WA at all (remember WA's web and desktop apps only work as long as your phone has an active internet connection). I chose not to do this and only run Whatsapp on my phone.

Just as last time, I've always gone with the default SQLite database instead of additional dependencies. After a few months of usage, no bridge's DB is bigger than 10 megabytes.

The compose file is pretty straightforward. I'm not exposing ports, since I won't be hosting these separately - instead, I'll add this to the compose file from the previous post, which also holds the entire Matrix stack. Since the only component that needs to see the bridges' ports is the Synapse server, it will do so over the default network and there's no need to expose them.

These are the new bits to add:

version: "3"

services:
  messenger_bridge:
    image: dock.mau.dev/tulir/mautrix-facebook:latest
    restart: unless-stopped
    volumes:
      - messenger_bridge_files:/data

  whatsapp_bridge:
    image: dock.mau.dev/tulir/mautrix-whatsapp:latest
    restart: unless-stopped
    volumes:
      - whatsapp_bridge_files:/data

  discord_bridge:
    image: sorunome/mx-puppet-discord:latest
    restart: unless-stopped
    environment:
      - CONFIG_PATH=/data/config.yaml
      - REGISTRATION_PATH=/data/discord-registration.yaml
    volumes:
      - discord_bridge_files:/data
      
volumes:
 discord_bridge_files:
 whatsapp_bridge_files:
 messenger_bridge_files:

By default, Docker exposes each service on its default network under the service's specified name. Turns out my snake_case names are unpalatable when it comes to the network. (I got stuck on this for a while until I realized what the problem was.) It's solved easily enough, by adding aliases for the bridge services to the Synapse section. Let's modify the Synapse config, add the links section:

services:
  synapse:
    image: matrixdotorg/synapse:latest
    restart: unless-stopped
    environment:
      - SYNAPSE_CONFIG_PATH=/data/homeserver.yaml
    volumes:
      - synapse_files:/data
    ports:
      - 33333:8008
    links:
      - "messenger_bridge:messenger-bridge"
      - "whatsapp_bridge:whatsapp-bridge"
      - "discord_bridge:discord-bridge"

This basically says that the messenger_bridge service should also be available at the messenger-bridge network address.

Bridge configuration

All the bridges are set up with the same basic idea: you create the container, modify the configs, and run the bridge container. It generates the Application Registration file you need to register with Synapse and exits.

My docker-compose.yml file is in a folder called matrix. So to create all the containers and edit the config for the Messenger bridge, I run:

docker-compose up --no-start
docker volume inspect matrix_messenger_bridge_data

Check where the container's Mountpoint is and edit the config.yaml file as needed. This is where you set things like the homeserver's address/domain, the bridge's local port, etc.

Once  that's done, start the bridge:

docker-compose up messenger_bridge

You should see something like this:

Starting matrix_messenger_bridge_1 ... done
Attaching to matrix_messenger_bridge_1
messenger_bridge_1  | INFO  [alembic.runtime.migration] Context impl SQLiteImpl.

(some parts omitted for brevity)

messenger_bridge_1  | INFO  [alembic.runtime.migration] Running upgrade 905a91536747 -> 2162f7ae5365, Store encryption state event in db
messenger_bridge_1  | Registration generated and saved to /data/registration.yaml
messenger_bridge_1  | Didn't find a registration file.
messenger_bridge_1  | Generated one for you.
messenger_bridge_1  | Copy that over to synapses app service directory.
matrix_messenger_bridge_1 exited with code 0

This generated the registration file we needed: registration.yml. Copy it somewhere handy, perhaps also rename it to something more descriptive (like messenger_bridge_registration.yaml), since there's going to be more of them.

id: messenger
as_token: x923secretstuff
hs_token: g33secretstuff
namespaces:
    users:
    - exclusive: true
      regex: '@facebook_.+:zble.sk'
      group_id: messenger
    - exclusive: true
      regex: '@messenger:zble\.sk'
url: http://messenger_bridge:32020
sender_localpart: gfu3secretstuff
rate_limited: false

The contents of this file have been generated based on the config.yaml file - that's where the port in URL comes from, for example. Also notice the URL says "messenger_bridge" - this won't work. It took me a while to figure out where the problem is, but the Docker network doesn't like the underscore in that name. That's why we've added the links section to the Synapse server, above. (Yes, of course you could just name the container differently, but that would break my naming scheme and I don't wanna.) After the links are set up, you only need to change it to "messenger-bridge" instead.

Do this analogously for all the bridges, then continue with:

Bridge registration

The bridge containers are configured and ready, and all of the files generated. The only remaining step is registering them with Synapse.

So copy the config files to where the Synapse's config file (homeserver.yaml) is and add or edit the app_service_config_files section:

# A list of application service config files to use
#
app_service_config_files:
   - /data/messenger_bridge_registration.yaml
   - /data/whatsapp_bridge_registration.yaml
   - /data/discord-registration.yaml

(Just a reminder, if you can't find where the files are on your drive, inspect the synapse_files volume and look for the Mountpoint.)

When all that is done, restart the Synapse container and start all the bridges.

docker-compose stop synapse
docker-compose up -d

If everything worked, the bot users should now be responding to messages from within Matrix. You will still need to log in with them etc, but that's best described in their own docs, linked above.

This is the final compose file in its entirety:

```
version: "3"

services:
  synapse:
    image: matrixdotorg/synapse:latest
    restart: unless-stopped
    environment:
      - SYNAPSE_CONFIG_PATH=/data/homeserver.yaml
    volumes:
      - synapse_files:/data
    ports:
      - 32000:8008
    links:
      - "messenger_bridge:messenger-bridge"
      - "whatsapp_bridge:whatsapp-bridge"
      - "discord_bridge:discord-bridge"


  synapse_admin:
    image: awesometechnologies/synapse-admin:latest
    restart: unless-stopped
    ports:
      - 32001:80

  riot:
    image: vectorim/riot-web:latest
    restart: unless-stopped
    volumes:
      - riot_files:/app
    ports:
      - 32002:80


  messenger_bridge:
    image: dock.mau.dev/tulir/mautrix-facebook:latest
    restart: unless-stopped
    volumes:
      - messenger_bridge_files:/data

  whatsapp_bridge:
    image: dock.mau.dev/tulir/mautrix-whatsapp:latest
    restart: unless-stopped
    volumes:
      - whatsapp_bridge_files:/data

  discord_bridge:
    image: sorunome/mx-puppet-discord:latest
    restart: unless-stopped
    environment:
      - CONFIG_PATH=/data/config.yaml
      - REGISTRATION_PATH=/data/discord-registration.yaml
    volumes:
      - discord_bridge_files:/data
      
volumes:
 synapse_files:
 riot_files:
 discord_bridge_files:
 whatsapp_bridge_files:
 messenger_bridge_files:

Closing remarks

I found these bridges to be really useful. I don't really need any of the apps on my phone anymore, and only need one app on the PC.

It isn't always a smooth ride, and isn't totally feature-complete: for instance, if you want to make actual voicecalls via Discord, you still need to do it via the official client. (But I don't really do that often, so it's not a problem.)

As usual, the biggest issues are with Facebook: since my bridge is hosted on a VM in a different country, I get locked out of my account (because "SoMEoNe Is HAcKiNg ME fRoM NeThErLanDs aGaIn hurr durr") quite often. That could probably be remedied by hosting the bridge in my home, but frankly, I use Messenger less and less these days, so I'm not sure I'll ever bother.

And all of the bridges have great communities - when I got stuck, I always found help in their Matrix chatrooms or on Github.

Enjoy.