feat: Initialize Matrix double puppeting bridge project

Introduced the scaffolding for a Matrix double puppeting bridge, including essential configuration and basic operational logic. The setup encompasses the .gitignore additions to exclude environment and build files, inclusion of LICENSE and README for legal and project outlining, respectively, and foundational project structure in pyproject.toml for package management. Core functionality to relay messages between Matrix and an external service is established, with stubs for extending the bridge's capabilities. This groundwork lays the foundation for further development and experimentation with the Matrix API and double puppeting mechanisms, illustrating a preliminary model for bi-directional communication bridges.
This commit is contained in:
Kumi 2024-05-16 10:10:19 +02:00
commit 7eba27ea9e
Signed by: kumi
GPG key ID: ECBCC9082395383F
7 changed files with 162 additions and 0 deletions

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
.venv/
config.yaml
registration.yaml
__pycache__
*.pyc
dist/
venv/

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2023 Kumi Mitterer <matrix-double-puppeting-bridge@kumi.email>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17
README.md Normal file
View file

@ -0,0 +1,17 @@
# Matrix Double Puppeting Bridge
This is a simple demonstration of how to write a Matrix double puppeting bridge using the [mautrix-python](https://docs.mau.fi/python/latest/) library.
At this point, it is not complete. It is a work in progress I am using to learn how to write a Matrix bridge, and one I hope I will be able to fork and use as a starting point for future projects.
## What is a double puppeting bridge?
A double puppeting bridge handles multiple users on both sides of the bridge. Each user on Matrix is represented by an individual account on the remote service, and each user on the remote service is represented by an individual puppet account on Matrix.
## How does it work?
The bridge listens for messages on the Matrix side and sends them to the remote service. It also listens for messages on the remote service and sends them to the Matrix side.
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

28
pyproject.toml Normal file
View file

@ -0,0 +1,28 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "matrix-double-puppeting-bridge"
version = "0.0.1"
authors = [
{ name="Kumi Mitterer", email="matrix-double-puppeting-bridge@kumi.email" },
]
description = "A simple demo bridge that demonstrates how to use the mautrix library to create a double-puppeting bridge."
readme = "README.md"
license = { file="LICENSE" }
requires-python = ">=3.10"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dependencies = [
"mautrix",
"pyyaml",
]
[project.urls]
"Homepage" = "https://git.private.coffee/kumi/matrix-double-puppeting-bridge"
"Bug Tracker" = "https://git.private.coffee/kumi/matrix-double-puppeting-bridge/issues"
"Source Code" = "https://git.private.coffee/kumi/matrix-double-puppeting-bridge"

View file

@ -0,0 +1,64 @@
import asyncio
import yaml
import logging
from mautrix.appservice import AppService
from mautrix.types import EventType
from matrix_double_puppeting_bridge.classes.bridge import ExampleBridge
logging.basicConfig(level=logging.DEBUG)
async def main():
logging.debug("Starting main function")
try:
with open("config.yaml") as f:
config = yaml.safe_load(f)
logging.debug("Config loaded successfully")
except Exception as e:
logging.error(f"Failed to load config: {e}")
return
try:
appservice = AppService(
server=config["homeserver"]["address"],
domain=config["homeserver"]["domain"],
as_token=config["appservice"]["as_token"],
hs_token=config["appservice"]["hs_token"],
bot_localpart=config["appservice"]["bot_username"],
id=config["appservice"]["id"]
)
logging.debug("AppService initialized")
except KeyError as e:
logging.error(f"Missing configuration key: {e}")
return
bridge = ExampleBridge(config, appservice)
logging.debug("Bridge instance created")
@appservice.matrix_event_handler
async def on_matrix_event(event):
logging.debug(f"Received event: {event}")
if event.type == EventType.ROOM_MESSAGE:
await bridge.handle_matrix_message(event.room_id, event.sender, event.content)
try:
logging.debug("Starting AppService on port 9101")
await appservice.start(port=9101)
logging.debug("AppService started")
# Keep the application running
while True:
await asyncio.sleep(3600) # Sleep for an hour
except Exception as e:
logging.error(f"Failed to start the appservice: {e}")
finally:
await appservice.stop()
logging.debug("AppService closed")
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
logging.info("KeyboardInterrupt received, exiting")
except Exception as e:
logging.error(f"Unexpected error: {e}")

View file

@ -0,0 +1,27 @@
from mautrix.appservice import AppService
from mautrix.types import EventType, MessageType, RoomID, UserID
class ExampleBridge:
def __init__(self, config: dict, appservice: AppService) -> None:
self.config = config
self.appservice = appservice
self.matrix_client = appservice._intent
self.external_service_url = config["bridge"]["external_service_url"]
self.external_service_token = config["bridge"]["external_service_token"]
async def handle_matrix_message(self, room_id: RoomID, sender: UserID, content: dict) -> None:
# Handle incoming messages from Matrix and send them to the external service
message = content.get("body", "")
# Send message to the external service
await self.send_to_external_service(sender, message)
async def send_to_external_service(self, sender: UserID, message: str) -> None:
# Implement the logic to send the message to the external service
print(f"Sending message to external service: {message}")
async def handle_external_service_message(self, user_id: UserID, message: str) -> None:
# Handle incoming messages from the external service and send them to Matrix
await self.matrix_client.send_message(room_id=user_id, content={
"msgtype": MessageType.TEXT,
"body": message
})