import logging logging.basicConfig( level=logging.INFO, # Or INFO, WARNING, etc. format="%(asctime)s [%(levelname)s] %(message)s" ) import asyncio import json import time import datetime import traceback import uuid import re import importlib from aiohttp import web from pymetaverse import login from pymetaverse.bot import SimpleBot from pymetaverse.viewer import messages from pymetaverse.const import * def loadDictIntoMessage(msg, data): for name, block in data.items(): if type(data[name]) == list: for i, varblock in enumerate(data[name]): for key, value in data[name][i].items(): if type(value) == dict: if value["type"] == "bytestring": value = value["data"].encode() + b"\0" elif value["type"] == "bytes": value = bytes(value["data"]) msg.blocks[name][i].values[key] = value msg.blocks[name].count = len(data[name]) elif type(data[name]) == dict: for key, value in data[name].items(): if type(value) == dict: if value["type"] == "bytestring": value = value["data"].encode() + b"\0" elif value["type"] == "bytes": value = bytes(value["data"]) msg.blocks[name].values[key] = value class BotInstance: def __init__(self, username, password, features = None): self.username = username self.password = password self.features = features or [] self.routes = [] self.bot = None def route(self, pattern): def decorator(func): regex = re.compile(f"^{pattern}$") self.routes.append((regex, func)) return func return decorator async def handle_request(self, request, path): for regex, handler in self.routes: match = regex.match(path) if match: return await handler(request, **match.groupdict()) logging.warning("[{}] No handler for path: {}".format( ".".join(self.bot.agent.username), path )) return web.Response(status=404, text="No handler for path") async def run(self): while True: try: bot = SimpleBot() self.bot = bot self.routes = [] for feature in self.features: feature(self, bot) await bot.login(self.username, self.password) logging.info("[{}] Logged in.".format( ".".join(bot.agent.username) )) await bot.run() logging.info("[{}] Logged out.".format( ".".join(bot.agent.username) )) self.bot = None except asyncio.exceptions.CancelledError as e: break def load_callable(path: str): """Loads a function or object from a string like 'package.module:func'.""" if ':' not in path: raise ValueError(f"Invalid path '{path}', expected format 'module.submodule:function'") module_path, func_name = path.split(':', 1) module = importlib.import_module(module_path) try: return getattr(module, func_name) except AttributeError: raise ImportError(f"Function '{func_name}' not found in module '{module_path}'") async def main(): with open("bots.json", "r") as f: bots = json.load(f) instances = [] for bot in bots: if bot.get("disabled", False) == True: continue functions = [] for function_path in bot.get("functions", []): functions.append(load_callable(function_path)) instance = BotInstance(bot["username"], bot["password"], functions) instances.append(instance) async def handle_bot_request(request): uuid_str = request.match_info['uuid'] path = request.match_info.get('path', "") bot = None try: bot_uuid = str(uuid.UUID(uuid_str)) for instance in instances: try: if instance.bot.agent.agentId == uuid_str: bot = instance break except Exception as e: raise e except ValueError: for instance in instances: try: if ".".join(instance.bot.agent.username).lower() == uuid_str.lower(): bot = instance break except Exception as e: raise e if bot: return await bot.handle_request(request, path) logging.warning("[WEB] No bot found: {}".format( uuid_str )) return web.Response(status=404, text="Bot not found") async def handle_bot_index(request): response = {} for instance in instances: response[instance.bot.agent.agentId] = { "username": list(instance.bot.agent.username) if instance.bot.agent.username != (None, None) else [] } return web.Response(status=200, text=json.dumps(response)) async def run_web_server(): app = web.Application() app.router.add_get('/bot/{uuid}/{path:.*}', handle_bot_request) app.router.add_get('/bot/{uuid}', handle_bot_request) app.router.add_get('/bot', handle_bot_index) runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, 'localhost', 26875) await site.start() # Launch both the web server and bots await asyncio.gather( run_web_server(), *[instance.run() for instance in instances] ) # Run everything asyncio.run(main())