class AppCore(object): """ 应用核心,用于管理所有相关组件 """ __instance = None __first_init: bool = False __app: Ariadne = None __loop: AbstractEventLoop = None __bcc = None __saya = None __thread_pool = None __config: GlobalConfig = None __launched: bool = False __exception_resender: ExceptionReSender = None __frequency_limit_instance: GlobalFrequencyLimitDict = None necessary_parameters = ["miraiHost", "verify_key", "BotQQ"] def __new__(cls, config: GlobalConfig): if not cls.__instance: cls.__instance = object.__new__(cls) return cls.__instance def __init__(self, config: GlobalConfig): if not self.__first_init: logger.info("Initializing") self.__loop = asyncio.get_event_loop() self.__bcc = Broadcast(loop=self.__loop) self.__app = Ariadne(broadcast=self.__bcc, chat_log_config=False, connect_info=DefaultAdapter( broadcast=self.__bcc, ping=False, mirai_session=MiraiSession( host=config.mirai_host, verify_key=str(config.verify_key), account=config.bot_qq), )) self.__saya = Saya(self.__bcc) self.__saya.install_behaviours(BroadcastBehaviour(self.__bcc)) if _install_scheduler: self.__sche = GraiaScheduler(loop=self.__loop, broadcast=self.__bcc) self.__saya.install_behaviours( GraiaSchedulerBehaviour(self.__sche)) self.__app.debug = False self.__config = config AppCore.__first_init = True logger.info("Initialize end") else: raise AppCoreAlreadyInitialized() @classmethod def get_core_instance(cls) -> "AppCore": """ 获取 AppCore 实例 """ if cls.__instance: return cls.__instance else: raise AppCoreNotInitialized() def get_bcc(self) -> Broadcast: """ 获取 Broadcast 实例 """ if self.__bcc: return self.__bcc else: raise AppCoreNotInitialized() def get_loop(self) -> AbstractEventLoop: """ 获取 loop """ if self.__loop: return self.__loop else: raise AppCoreNotInitialized() def get_app(self) -> Ariadne: """ 获取 Ariadne 实例 """ if self.__app: return self.__app else: raise AppCoreNotInitialized() def get_saya(self) -> Saya: """ 获取 Saya 实例 """ return self.__saya def get_config(self) -> GlobalConfig: """ 获取配置 """ return self.__config def launch(self) -> None: """ 启动 Ariadne 实例 """ if not self.__launched: self.__app.launch_blocking() self.__launched = True else: raise AriadneAlreadyLaunched() def get_frequency_limit_instance(self) -> GlobalFrequencyLimitDict: """ 返回频率限制模块实例 """ return self.__frequency_limit_instance def get_exception_resender(self) -> ExceptionReSender: """ 返回错误重发模块实例 """ return self.__exception_resender async def bot_launch_init(self) -> None: """ 机器人启动初始化 """ self.config_check() try: try: await orm.init_check() except (AttributeError, InternalError, ProgrammingError): await orm.create_all() if not os.path.exists(f"{os.getcwd()}/alembic"): logger.info("未检测到alembic目录,进行初始化") os.system("alembic init alembic") with open(f"{os.getcwd()}/statics/alembic_env_py_content.txt", "r") as r: alembic_env_py_content = r.read() with open(f"{os.getcwd()}/alembic/env.py", "w") as w: w.write(alembic_env_py_content) logger.warning( f"请前往更改 {os.getcwd()}/alembic.ini 文件,将其中的 sqlalchemy.url 替换为自己的数据库url(不需注明引擎)后重启机器人" ) exit(0) if not os.path.exists(f"{os.getcwd()}/alembic/versions"): os.mkdir(f"{os.getcwd()}/alembic/versions") os.system("alembic revision --autogenerate -m 'update'") os.system("alembic upgrade head") await orm.update(Setting, [], {"active": False}) group_list = await self.__app.getGroupList() frequency_limit_dict = {} for group in group_list: frequency_limit_dict[group.id] = 0 await orm.insert_or_update(Setting, [Setting.group_id == group.id], { "group_id": group.id, "group_name": group.name, "active": True }) results = await orm.fetchall( select(Setting.group_id, Setting.group_name).where(Setting.active == True)) self.load_required_saya_modules() logger.info("本次启动活动群组如下:") for result in results: logger.info( f"群ID: {str(result.group_id).ljust(14)}群名: {result.group_name}" ) for result in results: await orm.insert_or_update( UserPermission, [ UserPermission.member_id == self.__config.host_qq, UserPermission.group_id == result[0] ], { "member_id": self.__config.host_qq, "group_id": result[0], "level": 4 }) self.__frequency_limit_instance = GlobalFrequencyLimitDict( frequency_limit_dict) threading.Thread(target=frequency_limit, args=(self.__frequency_limit_instance, )).start() exception_resender_instance = ExceptionReSender(self.__app) listener = threading.Thread(target=exception_resender_listener, args=(self.__app, exception_resender_instance, self.__loop)) listener.start() await group_setting.data_init() except: logger.error(traceback.format_exc()) exit(0) @staticmethod def dict_check(dictionary: dict, indent: int = 4) -> None: for key in dictionary.keys(): if isinstance(dictionary[key], dict): logger.success(f"{' ' * indent}{key}:") AppCore.dict_check(dictionary[key], indent + 4) elif dictionary[key] == key: logger.warning( f"{' ' * indent}Unchanged initial value detected: {key} - {dictionary[key]}" ) else: logger.success(f"{' ' * indent}{key} - {dictionary[key]}") def config_check(self) -> None: """ 配置检查 """ required_key = ("bot_qq", "host_qq", "mirai_host", "verify_key") logger.info("Start checking configuration") father_properties = tuple(dir(BaseModel)) properties = [ _ for _ in dir(self.__config) if _ not in father_properties and not _.startswith("_") ] for key in properties: value = self.__config.__getattribute__(key) if key in required_key and key == value: logger.error( f"Required initial value not changed detected: {key} - {value}" ) exit(0) elif isinstance(value, dict): logger.success(f"{key}:") self.dict_check(value) elif key == value: logger.warning( f"Unchanged initial value detected: {key} - {value}") else: logger.success(f"{key} - {value}") logger.info("Configuration check completed") def load_saya_modules(self) -> None: """ 加载自定义 saya 模块 """ ignore = ["__init__.py", "__pycache__"] with self.__saya.module_context(): for module in os.listdir(f"modules"): if module in ignore: continue try: if os.path.isdir(module): self.__saya.require(f"modules.{module}") else: self.__saya.require(f"modules.{module.split('.')[0]}") except ModuleNotFoundError as e: logger.error(f"saya模块:{module} - {e}") def load_required_saya_modules(self) -> None: """ 加载必要 saya 模块 """ ignore = ["__init__.py", "__pycache__"] with self.__saya.module_context(): for module in os.listdir(f"sagiri_bot/handler/required_module"): if module in ignore: continue try: if os.path.isdir(module): self.__saya.require( f"sagiri_bot.handler.required_module.{module}") else: self.__saya.require( f"sagiri_bot.handler.required_module.{module.split('.')[0]}" ) except ModuleNotFoundError as e: logger.error(f"saya模块:{module} - {e}") def get_saya_channels(self) -> Dict[str, Channel]: """ 获取 saya channels """ return self.__saya.channels def load_schedulers(self): pass
class AppCore: __instance = None __first_init: bool = False __app: GraiaMiraiApplication = None __loop: AbstractEventLoop = None __bcc = None __saya = None __thread_pool = None __config: dict = None __launched: bool = False __group_handler_chain = {} __exception_resender: ExceptionReSender = None __frequency_limit_instance: GlobalFrequencyLimitDict = None necessary_parameters = ["miraiHost", "authKey", "BotQQ"] def __new__(cls, config: dict): if not cls.__instance: cls.__instance = object.__new__(cls) return cls.__instance def __init__(self, config: dict): if not self.__first_init: logger.info("Initializing") if any(parameter not in config for parameter in self.necessary_parameters): raise ValueError( f"Missing necessary parameters! (miraiHost, authKey, BotQQ)" ) self.__loop = asyncio.get_event_loop() self.__bcc = Broadcast(loop=self.__loop) self.__app = GraiaMiraiApplication(broadcast=self.__bcc, connect_info=Session( host=config["miraiHost"], authKey=config["authKey"], account=config["BotQQ"], websocket=True), enable_chat_log=False) self.__saya = Saya(self.__bcc) self.__saya.install_behaviours(BroadcastBehaviour(self.__bcc)) self.__app.debug = False self.__config = config AppCore.__first_init = True logger.info("Initialize end") else: raise AppCoreAlreadyInitialized() @classmethod def get_core_instance(cls): if cls.__instance: return cls.__instance else: raise AppCoreNotInitialized() def get_bcc(self) -> Broadcast: if self.__bcc: return self.__bcc else: raise AppCoreNotInitialized() def get_loop(self) -> AbstractEventLoop: if self.__loop: return self.__loop else: raise AppCoreNotInitialized() def get_app(self) -> GraiaMiraiApplication: if self.__app: return self.__app else: raise AppCoreNotInitialized() def get_config(self): return self.__config def launch(self): if not self.__launched: self.__app.launch_blocking() self.__launched = True else: raise GraiaMiraiApplicationAlreadyLaunched() def set_group_chain(self, chains: list): for chain in chains: self.__group_handler_chain[chain.__name__] = chain def get_group_chains(self): return self.__group_handler_chain def get_group_chain(self, chain_name: str): return self.__group_handler_chain[ chain_name] if chain_name in self.__group_handler_chain else None def get_frequency_limit_instance(self): return self.__frequency_limit_instance def get_exception_resender(self): return self.__exception_resender async def bot_launch_init(self): self.config_check() try: await orm.create_all() await orm.update(Setting, [], {"active": False}) group_list = await self.__app.groupList() frequency_limit_dict = {} for group in group_list: frequency_limit_dict[group.id] = 0 await orm.insert_or_update(Setting, [Setting.group_id == group.id], { "group_id": group.id, "group_name": group.name, "active": True }) results = await orm.fetchall( select(Setting.group_id, Setting.group_name).where(Setting.active == True)) logger.info("本次启动活动群组如下:") for result in results: logger.info( f"群ID: {str(result.group_id).ljust(14)}群名: {result.group_name}" ) for result in results: await orm.insert_or_update( UserPermission, [ UserPermission.member_id == self.__config["HostQQ"], UserPermission.group_id == result[0] ], { "member_id": self.__config["HostQQ"], "group_id": result[0], "level": 4 }) self.__frequency_limit_instance = GlobalFrequencyLimitDict( frequency_limit_dict) threading.Thread(target=frequency_limit, args=(self.__frequency_limit_instance, )).start() exception_resender_instance = ExceptionReSender(self.__app) listener = threading.Thread(target=exception_resender_listener, args=(self.__app, exception_resender_instance, self.__loop)) listener.start() except: logger.error(traceback.format_exc()) exit() def config_check(self): logger.info("checking config") pic_paths = [ "setuPath", "setu18Path", "realPath", "realHighqPath", "wallpaperPath", "sketchPath" ] if self.__config["HostQQ"] == 123: logger.warning(f"HostQQ无效,请检查配置!") for path in pic_paths: if not os.path.exists(self.__config[path]): logger.warning(f"{path}无效,请检查配置!") if self.__config["saucenaoApiKey"] == "balabala": logger.warning("saucenaoApiKey无效,请检查配置!") if self.__config["txAppId"] == "1234567890": logger.warning("txAppId无效,请检查配置!") if self.__config["txAppKey"] == "ABCDEFGHIJKLMN": logger.warning("txAppKey无效,请检查配置!") if self.__config["loliconApiKey"] == "loliconApiKey": logger.warning("loliconApiKey无效,请检查配置!") if self.__config["wolframAlphaKey"] == "wolframAlphaKey": logger.warning("wolframAlphaKey无效,请检查配置!") logger.info("check done") def load_saya_modules(self): ignore = ["__init__.py", "__pycache__"] with self.__saya.module_context(): for module in os.listdir(f"modules"): if module in ignore: continue try: if os.path.isdir(module): self.__saya.require(f"modules.{module}") else: self.__saya.require(f"modules.{module.split('.')[0]}") except ModuleNotFoundError as e: logger.error(f"saya模块:{module} - {e}") def get_saya_channels(self): return self.__saya.channels def get_saya(self): return self.__saya
loop = asyncio.get_event_loop() bcc = Broadcast(loop=loop) saya = Saya(bcc) config_path = Path("config.yaml") if not config_path.is_file(): config_path.write_text(Path("config.yaml.sample").read_text()) sys.exit(1) config = yaml.load(config_path.read_text(), Loader=SafeLoader) saya.install_behaviours(BroadcastBehaviour(bcc)) app = GraiaMiraiApplication( broadcast=bcc, connect_info=Session( host=config["mirai"]["host"], authKey=config["mirai"]["authKey"], account=config["mirai"]["account"], websocket=config["mirai"]["websocket"], ), ) with saya.module_context(): for i in get_all_package_name("functions/"): saya.require("functions.{}".format(i), config) try: app.launch_blocking() except KeyboardInterrupt: sys.exit()
def load(cls, saya: Saya): modules = "modules.{}" with saya.module_context(): for module_name in cls._get_module_str(): saya.require(modules.format(module_name))