Demote puppet users created by Matrix Bridges

  • 3 min read
  • Tags: 
  • dev
  • matrix

I am currently a member of the AG Administration in the Hackerspace Bremen. We manage our own federated Matrix homeserver as part of the services we offer for our members. That service is relatively new, and we use mautrix-discord with double puppeting to bridge a few legacy rooms from Discord to corresponding Matrix rooms, so users that have not yet moved to Matrix are not left out. Messages written by these users in Discord are visible in Matrix rooms using puppet users automatically created by the bridge.

Due to some carelessness on my part I accidentally gave one of the puppet users a power level of 100 in a bridged room. In Matrix, once another user has the same power level as you, you reach a ‘stalemate’ state in which it becomes impossible to demote that user to a lower power level again. Only the user itself is able to lower their own power level.

While that had no immediate negative consequences for puppet users, which can (probably?) not do much with that power level anyway, it bugged me and I looked for ways to fix my mistake. Fortunately, Tulir was very helpful in #discord:maunium.net by pointing out that the bridge uses the Matrix Application Service API and the access token (as_token) used by the bridge can be used to send events on behalf of all puppet users managed by the bridge.

It is possible to craft the required curl calls by hand, but I decided to give mautrix-python (also by Tulir) a try. Below are the commands that finally worked.

After installing the package, open an asyncio shell (python -m asyncio) and run these commands to create a ClientAPI object. Adapt the values as necessary:

>>> from mautrix.client import ClientAPI
>>> puppet_user = "@discord_...:hackerspace-bremen.de"
>>> base_url = "https://matrix.hackerspace-bremen.de"
>>> as_token = "..."  # Taken from the bridges registration.yaml
>>> client = ClientAPI(puppet_user, base_url=base_url, token=as_token, as_user_id=puppet_user)

Verify that the client works correctly by attempting to find the rooms currently joined by the puppet user:

>>> await client.get_joined_rooms()
['!room_id_1:hackerspace-bremen.de', '!room_id_2:hackerspace-bremen.de', ...]

Ensure those look correct. To change power levels in a room, a complete m.room.power_levels event must be sent to the room. It is easiest to get the current event and just change its current content.

>>> room_id = "!room_id_1:hackerspace-bremen.de"
>>> states = await client.get_state(room_id)
>>> len(states)
22

Filter out the event describing the current power levels:

>>> power_state = [s for s in states if s.type.t == "m.room.power_levels"][0]

Modify the event and set the power level of the puppet user to 0:

>>> power_state.content.users[puppet_user] = 0

Finally, send the event back to the server:

>>> from mautrix.types import EventType, EventContent
>>> await client.send_state_event(room_id, EventType.ROOM_POWER_LEVELS, power_state.content)
'$...'

If everything worked correctly, the puppet user should be demoted back to power level 0.