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:
commit
7eba27ea9e
7 changed files with 162 additions and 0 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
.venv/
|
||||
config.yaml
|
||||
registration.yaml
|
||||
__pycache__
|
||||
*.pyc
|
||||
dist/
|
||||
venv/
|
19
LICENSE
Normal file
19
LICENSE
Normal 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
17
README.md
Normal 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
28
pyproject.toml
Normal 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"
|
64
src/matrix_double_puppeting_bridge/__main__.py
Normal file
64
src/matrix_double_puppeting_bridge/__main__.py
Normal 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}")
|
0
src/matrix_double_puppeting_bridge/classes/__init__.py
Normal file
0
src/matrix_double_puppeting_bridge/classes/__init__.py
Normal file
27
src/matrix_double_puppeting_bridge/classes/bridge.py
Normal file
27
src/matrix_double_puppeting_bridge/classes/bridge.py
Normal 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
|
||||
})
|
Loading…
Reference in a new issue