commit 7eba27ea9ea66b2319bb2bdd4cc2fd3494feeddb Author: Kumi Date: Thu May 16 10:10:19 2024 +0200 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. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d592891 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.venv/ +config.yaml +registration.yaml +__pycache__ +*.pyc +dist/ +venv/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fe42c31 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 Kumi Mitterer + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1411def --- /dev/null +++ b/README.md @@ -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. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0a3ed64 --- /dev/null +++ b/pyproject.toml @@ -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" \ No newline at end of file diff --git a/src/matrix_double_puppeting_bridge/__main__.py b/src/matrix_double_puppeting_bridge/__main__.py new file mode 100644 index 0000000..17a5a29 --- /dev/null +++ b/src/matrix_double_puppeting_bridge/__main__.py @@ -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}") \ No newline at end of file diff --git a/src/matrix_double_puppeting_bridge/classes/__init__.py b/src/matrix_double_puppeting_bridge/classes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/matrix_double_puppeting_bridge/classes/bridge.py b/src/matrix_double_puppeting_bridge/classes/bridge.py new file mode 100644 index 0000000..598b1e9 --- /dev/null +++ b/src/matrix_double_puppeting_bridge/classes/bridge.py @@ -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 + }) \ No newline at end of file