feat: init matrix bot code

This commit is contained in:
草师傅 2025-08-24 19:13:25 +08:00
parent 1ef4efbc68
commit b041aa0de8
5 changed files with 262 additions and 2 deletions

View file

@ -0,0 +1,154 @@
import asyncio
import json
import logging
import os
from nio import AsyncClient, MatrixRoom, RoomMessageText
from nio.events.room_events import RoomMessageText
from typing import Dict, Callable
import config
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class MatrixAdapter:
def __init__(self, homeserver: str, user_id: str, token: str, device_name: str = "MatrixBot"):
"""
Initialize the Matrix bot.
Args:
homeserver: The Matrix homeserver URL
user_id: The bot's Matrix user ID
token: The bot's token
device_name: Device name for the bot
"""
self.homeserver = homeserver
self.user_id = user_id
self.token = token
self.device_name = device_name
self.client = AsyncClient(homeserver, user_id)
self.commands: Dict[str, Callable] = {}
# Register event handlers
self.client.add_event_callback(self.message_callback, RoomMessageText)
def add_command(self, command: str, handler: Callable):
"""Add a command handler."""
self.commands[command.lower()] = handler
async def message_callback(self, room: MatrixRoom, event: RoomMessageText):
"""Handle incoming messages."""
# Ignore messages from the bot itself
if event.sender == self.user_id:
return
# Check if message starts with command prefix
message = event.body.strip()
if not message.startswith('!'):
return
# Parse command and arguments
parts = message[1:].split(' ', 1)
command = parts[0].lower()
args = parts[1] if len(parts) > 1 else ""
# Execute command if it exists
if command in self.commands:
try:
response = await self.commands[command](room, event, args)
if response:
await self.send_message(room.room_id, response)
except Exception as e:
logger.error(f"Error executing command {command}: {e}")
await self.send_message(room.room_id, f"Error executing command: {str(e)}")
else:
await self.send_message(room.room_id, f"Unknown command: {command}")
async def send_message(self, room_id: str, message: str):
"""Send a message to a room."""
await self.client.room_send(
room_id=room_id,
message_type="m.room.message",
content={
"msgtype": "m.text",
"body": message
}
)
async def start(self):
"""Start the bot."""
logger.info("Starting Matrix bot...")
# Login
#response = await self.client.login(token=self.token, device_name=self.device_name)
self.client.access_token = self.token
self.client.device_id = "REALBOT"
logger.info(f"Logged in as {self.user_id}")
# Sync and listen for events
await self.client.sync_forever(timeout=30000)
async def stop(self):
"""Stop the bot."""
logger.info("Stopping Matrix bot...")
await self.client.logout()
await self.client.close()
# Example command handlers
async def hello_command(room: MatrixRoom, event: RoomMessageText, args: str) -> str:
"""Handle !hello command."""
return f"Hello {event.sender}!"
async def echo_command(room: MatrixRoom, event: RoomMessageText, args: str) -> str:
"""Handle !echo command."""
if not args:
return "Usage: !echo <message>"
return f"Echo: {args}"
async def help_command(room: MatrixRoom, event: RoomMessageText, args: str) -> str:
"""Handle !help command."""
help_text = """
Available commands:
- !hello - Say hello
- !echo <message> - Echo a message
- !help - Show this help message
"""
return help_text.strip()
async def main():
"""Main function to run the bot."""
# 从环境变量或配置文件中获取值
matrix_config = config.Config().get_config_value('matrix', {})
homeserver = matrix_config.get('homeserver', "https://matrix.org")
user_id = matrix_config.get('user_id')
token = os.getenv("MATRIX_BOT_TOKEN")
# Create bot instance
bot = MatrixAdapter(homeserver, user_id, token)
# Register commands
bot.add_command("hello", hello_command)
bot.add_command("echo", echo_command)
bot.add_command("help", help_command)
try:
print("Starting Matrix bot...")
await bot.start()
except KeyboardInterrupt:
logger.info("Bot interrupted by user")
except Exception as e:
logger.error(f"Bot error: {e}")
finally:
await bot.stop()
if __name__ == "__main__":
asyncio.run(main())

View file

@ -6,6 +6,14 @@ admin: 123456789
# 这部分仅限于汇报链接跟踪参数去除的问题
dev: 616760897
start_telegram_bot: true
also_start_matrix_bot: false
matrix:
homeserver: "https://matrix.org"
user_id: "@your_bot:matrix.org"
# Token 请使用登录后生成的 Access Token一般可以在客户端的设置页面找到然后设置到 MATRIX_BOT_TOKEN 环境变量中
# 如果你想使用一个新的 token可以使用 helpers/matrix_login.py 脚本生成一个新的 token
# global features settings

View file

@ -27,6 +27,28 @@ class Config:
"""Get developer user ID"""
return self.config_data.get('dev')
def get_config_value(self, key: str, default: Any = None) -> Any:
"""
Get a configuration value by key
Args:
key: Configuration key to retrieve
default: Default value if key is not found
Returns:
The configuration value or default if not found
"""
keys = key.split('.')
value = self.config_data
for k in keys:
if isinstance(value, dict) and k in value:
value = value[k]
else:
return default
return value
def is_feature_enabled(self, feature_name: str, chat_id: Optional[int] = None) -> bool:
"""
Check if a feature is enabled for a specific chat or globally

65
helpers/matrix_login.py Normal file
View file

@ -0,0 +1,65 @@
#!/usr/bin/env python3
import asyncio
import getpass
import json
import os
import sys
import aiofiles
from nio import AsyncClient, LoginResponse
CONFIG_FILE = "credentials.json"
# Check out main() below to see how it's done.
def write_details_to_disk(resp: LoginResponse, homeserver) -> None:
"""Writes the required login details to disk so we can log in later without
using a password.
Arguments:
resp {LoginResponse} -- the successful client login response.
homeserver -- URL of homeserver, e.g. "https://matrix.example.org"
"""
# open the config file in write-mode
with open(CONFIG_FILE, "w") as f:
# write the login details to disk
json.dump(
{
"homeserver": homeserver, # e.g. "https://matrix.example.org"
"user_id": resp.user_id, # e.g. "@user:example.org"
"device_id": resp.device_id, # device ID, 10 uppercase letters
"access_token": resp.access_token, # cryptogr. access token
},
f,
)
async def main() -> None:
# If there are no previously-saved credentials, we'll use the password
if not os.path.exists(CONFIG_FILE):
print(
"First time use. Did not find credential file. Asking for "
"homeserver, user, and password to create credential file."
)
homeserver = "https://matrix.example.org"
homeserver = input(f"Enter your homeserver URL: [{homeserver}] ")
if not (homeserver.startswith("https://") or homeserver.startswith("http://")):
homeserver = "https://" + homeserver
user_id = "@user:example.org"
user_id = input(f"Enter your full user ID: [{user_id}] ")
device_name = "matrix-nio"
device_name = input(f"Choose a name for this device: [{device_name}] ")
client = AsyncClient(homeserver, user_id)
pw = getpass.getpass()
resp = await client.login(pw, device_name=device_name)
# check that we logged in successfully
if isinstance(resp, LoginResponse):
write_details_to_disk(resp, homeserver)
else:
print(f'homeserver = "{homeserver}"; user = "{user_id}"')
print(f"Failed to log in: {resp}")
sys.exit(1)
print(
"登录成功,凭据已经保存到 " + CONFIG_FILE + " 文件中"
)
# Otherwise the config file exists, so we'll use the stored credentials
else:
print("你已经登录过了")
asyncio.run(main())

15
main.py
View file

@ -2,6 +2,7 @@ import asyncio
import logging
import sys
import config
from adapters.tg import TelegramAdapter
@ -9,9 +10,19 @@ async def main():
"""Main entry point"""
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
tasks = []
# Initialize and start Telegram adapter
tg_adapter = TelegramAdapter()
await tg_adapter.start()
if config.Config().get_config_value('start_telegram_bot', True):
tg_adapter = TelegramAdapter()
tasks.append(tg_adapter.start())
if config.Config().get_config_value('also_start_matrix_bot', False):
import adapters.matrix as matrix_bot
# Initialize and start Matrix bot if configured
tasks.append(matrix_bot.main())
if tasks:
await asyncio.gather(*tasks, return_exceptions=True)
else:
logging.error("No bot is configured to start. Please check your configuration.")
if __name__ == "__main__":