๐Ÿ”€ๆ‰‹ๅŠจMerge่ฝป้›ชไธปไป“ๅบ“a77f97f

This commit is contained in:
2024-10-06 02:39:10 +08:00
parent 4cc2ae61db
commit f8b57bfe9a
108 changed files with 3131 additions and 3574 deletions

View File

@ -1,103 +1,102 @@
import asyncio
import atexit
import os
import platform
import signal
import sys
import threading
import time
from typing import Any, Optional
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
from liteyuki.bot.lifespan import LIFESPAN_FUNC, Lifespan
from liteyuki.core import IS_MAIN_PROCESS
from liteyuki.bot.lifespan import LIFESPAN_FUNC, Lifespan, PROCESS_LIFESPAN_FUNC
from liteyuki.comm.channel import get_channel
from liteyuki.core.manager import ProcessManager
from liteyuki.core.spawn_process import mb_run, nb_run
from liteyuki.log import init_log, logger
from liteyuki.plugin import load_plugins
from liteyuki.plugin import load_plugin
from liteyuki.utils import IS_MAIN_PROCESS
__all__ = ["LiteyukiBot", "get_bot"]
__all__ = [
"LiteyukiBot",
"get_bot",
"get_config",
"get_config_with_compat",
]
class LiteyukiBot:
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs) -> None:
"""
ๅˆๅง‹ๅŒ–่ฝป้›ชๅฎžไพ‹
Args:
**kwargs: ้…็ฝฎ
"""
"""ๅธธ่ง„ๆ“ไฝœ"""
print_logo()
global _BOT_INSTANCE
_BOT_INSTANCE = self # ๅผ•็”จ
"""้…็ฝฎ"""
self.config: dict[str, Any] = kwargs
"""ๅˆๅง‹ๅŒ–"""
self.init(**self.config) # ๅˆๅง‹ๅŒ–
logger.info("ๅฐน็ตๆธฉ ๆญฃๅœจๅˆๅง‹ๅŒ–โ€ฆ")
self.lifespan: Lifespan = Lifespan()
"""็”Ÿๅ‘ฝๅ‘จๆœŸ็ฎก็†"""
self.lifespan = Lifespan()
self.process_manager: ProcessManager = ProcessManager(lifespan=self.lifespan)
self.process_manager: ProcessManager = ProcessManager(bot=self)
"""ไบ‹ไปถๅพช็Žฏ"""
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
self.loop_thread = threading.Thread(target=self.loop.run_forever, daemon=True)
self.stop_event = threading.Event()
self.call_restart_count = 0
"""ๅŠ ่ฝฝๆ’ไปถๅŠ ่ฝฝๅ™จ"""
load_plugin("liteyuki.plugins.plugin_loader") # ๅŠ ่ฝฝ่ฝป้›ชๆ’ไปถ
async def _run(self):
"""
ๅฏๅŠจ้€ป่พ‘
"""
await self.lifespan.before_start() # ๅฏๅŠจๅ‰้’ฉๅญ
await self.lifespan.after_start() # ๅฏๅŠจๅŽ้’ฉๅญ
await self.keep_alive()
def run(self):
load_plugins("liteyuki/plugins") # ๅŠ ่ฝฝ่ฝป้›ชๆ’ไปถ
"""
ๅค–้ƒจๅฏๅŠจๆŽฅๅฃ
"""
self.process_manager.start_all()
try:
asyncio.run(self._run())
except KeyboardInterrupt:
logger.opt(colors=True).info("<y>ๅฐน็ตๆธฉ ๅ…ณ้—ญไธญโ€ฆ</y>")
self.stop()
logger.opt(colors=True).info("<y>ๅฐน็ตๆธฉ ๅทฒๅ…ณๅœ</y>")
self.loop_thread.start() # ๅฏๅŠจไบ‹ไปถๅพช็Žฏ
asyncio.run(self.lifespan.before_start()) # ๅฏๅŠจๅ‰้’ฉๅญ
self.process_manager.add_target("nonebot", nb_run, **self.config)
self.process_manager.start("nonebot")
self.process_manager.add_target("melobot", mb_run, **self.config)
self.process_manager.start("melobot")
asyncio.run(self.lifespan.after_start()) # ๅฏๅŠจๅŽ้’ฉๅญ
self.start_watcher() # ๅฏๅŠจๆ–‡ไปถ็›‘่ง†ๅ™จ
def start_watcher(self):
if self.config.get("debug", False):
src_directories = (
"liteyuki",
"src/liteyuki_main",
"src/liteyuki_plugins",
"src/nonebot_plugins",
"src/utils",
)
src_excludes_extensions = ("pyc",)
logger.debug("่ฝป้›ช้‡่ฝฝ ๅทฒๅฏ็”จ๏ผŒๆญฃๅœจๅŠ ่ฝฝๆ–‡ไปถไฟฎๆ”น็›‘ๆต‹โ€ฆโ€ฆ")
restart = self.restart_process
class CodeModifiedHandler(FileSystemEventHandler):
"""
Handler for code file changes
"""
def on_modified(self, event):
if (
event.src_path.endswith(src_excludes_extensions)
or event.is_directory
or "__pycache__" in event.src_path
):
return
logger.info(f"ๆ–‡ไปถ {event.src_path} ๅทฒไฟฎๆ”น๏ผŒๆœบๅ™จไบบ่‡ชๅŠจ้‡ๅฏโ€ฆโ€ฆ")
restart()
code_modified_handler = CodeModifiedHandler()
observer = Observer()
for directory in src_directories:
observer.schedule(code_modified_handler, directory, recursive=True)
observer.start()
async def keep_alive(self):
"""
ไฟๆŒ่ฝป้›ช่ฟ่กŒ
"""
logger.info("ๅฐน็ตๆธฉ ๆŒ็ปญ่ฟ่กŒไธญโ€ฆ")
try:
while not self.stop_event.is_set():
await asyncio.sleep(0.1)
except Exception:
logger.info("ๅฐน็ตๆธฉ ็Žฐ้€€ๅœโ€ฆ")
self.stop()
def restart(self, delay: int = 0):
"""
้‡ๅฏ่ฝป้›ชๆœฌไฝ“
Returns:
Args:
delay ([`int`](https%3A//docs.python.org/3/library/functions.html#int), optional): ๅปถ่ฟŸ้‡ๅฏๆ—ถ้—ด. Defaults to 0.
"""
if self.call_restart_count < 1:
executable = sys.executable
args = sys.argv
logger.info("ๆญฃๅœจ้‡ๅฏ ๅฐน็ตๆธฉ...")
logger.info("ๆญฃๅœจ้‡ๅฏ ๅฐน็ตๆธฉๆœบๅ™จไบบๆก†ๆžถ")
time.sleep(delay)
if platform.system() == "Windows":
cmd = "start"
@ -110,7 +109,9 @@ class LiteyukiBot:
self.process_manager.terminate_all()
# ่ฟ›็จ‹้€€ๅ‡บๅŽ้‡ๅฏ
threading.Thread(
target=os.system, args=(f"{cmd} {executable} {' '.join(args)}",)
target=os.system,
args=(f"{cmd} {executable} {' '.join(args)}",),
daemon=True,
).start()
sys.exit(0)
self.call_restart_count += 1
@ -119,44 +120,46 @@ class LiteyukiBot:
"""
ๅœๆญข่ฝป้›ช
Args:
name: ่ฟ›็จ‹ๅ็งฐ, ้ป˜่ฎคไธบNone, ๆ‰€ๆœ‰่ฟ›็จ‹
name ([`Optional`](https%3A//docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https%3A//docs.python.org/3/library/stdtypes.html#str)]): ่ฟ›็จ‹ๅ. Defaults to None.
Returns:
"""
logger.info("Stopping LiteyukiBot...")
self.loop.create_task(self.lifespan.before_shutdown()) # ้‡ๅฏๅ‰้’ฉๅญ
self.loop.create_task(self.lifespan.before_shutdown()) # ๅœๆญขๅ‰้’ฉๅญ
if name:
self.process_manager.terminate(name)
if name is not None:
chan_active = get_channel(f"{name}-active")
chan_active.send(1)
else:
self.process_manager.terminate_all()
for process_name in self.process_manager.processes:
chan_active = get_channel(f"{process_name}-active")
chan_active.send(1)
def init(self, *args, **kwargs):
"""
ๅˆๅง‹ๅŒ–่ฝป้›ช, ่‡ชๅŠจ่ฐƒ็”จ
Returns:
Args:
*args: ๅ‚ๆ•ฐ
**kwargs: ๅ…ณ้”ฎๅญ—ๅ‚ๆ•ฐ
"""
self.init_config()
self.init_logger()
def init_logger(self):
# ไฟฎๆ”นnonebot็š„ๆ—ฅๅฟ—้…็ฝฎ
"""
ๅˆๅง‹ๅŒ–ๆ—ฅๅฟ—
"""
init_log(config=self.config)
def init_config(self):
pass
def stop(self):
"""
ๅœๆญข่ฝป้›ช
"""
self.process_manager.terminate_all()
self.stop_event.set()
def on_before_start(self, func: LIFESPAN_FUNC):
def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
"""
ๆณจๅ†ŒๅฏๅŠจๅ‰็š„ๅ‡ฝๆ•ฐ
Args:
func:
func ([`LIFESPAN_FUNC`](./lifespan#var-lifespan-func)): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
Returns:
[`LIFESPAN_FUNC`](./lifespan#var-lifespan-func): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
"""
return self.lifespan.on_before_start(func)
@ -164,81 +167,128 @@ class LiteyukiBot:
"""
ๆณจๅ†ŒๅฏๅŠจๅŽ็š„ๅ‡ฝๆ•ฐ
Args:
func:
func ([`LIFESPAN_FUNC`](./lifespan#var-lifespan-func)): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
Returns:
[`LIFESPAN_FUNC`](./lifespan#var-lifespan-func): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
"""
return self.lifespan.on_after_start(func)
def on_before_shutdown(self, func: LIFESPAN_FUNC):
"""
ๆณจๅ†Œๅœๆญขๅ‰็š„ๅ‡ฝๆ•ฐ๏ผŒไธบๅญ่ฟ›็จ‹ๅœๆญขๆ—ถ่ฐƒ็”จ
Args:
func:
Returns:
"""
return self.lifespan.on_before_shutdown(func)
def on_after_shutdown(self, func: LIFESPAN_FUNC):
"""
ๆณจๅ†ŒๅœๆญขๅŽ็š„ๅ‡ฝๆ•ฐ๏ผšๆœชๅฎž็Žฐ
Args:
func:
func ([`LIFESPAN_FUNC`](./lifespan#var-lifespan-func)): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
Returns:
[`LIFESPAN_FUNC`](./lifespan#var-lifespan-func): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
"""
return self.lifespan.on_after_shutdown(func)
def on_before_restart(self, func: LIFESPAN_FUNC):
def on_before_process_shutdown(self, func: PROCESS_LIFESPAN_FUNC):
"""
ๆณจๅ†Œ้‡ๅฏๅ‰็š„ๅ‡ฝๆ•ฐ๏ผŒไธบๅญ่ฟ›็จ‹้‡ๅฏๆ—ถ่ฐƒ็”จ
ๆณจๅ†Œ่ฟ›็จ‹ๅœๆญขๅ‰็š„ๅ‡ฝๆ•ฐ๏ผŒไธบๅญ่ฟ›็จ‹ๅœๆญขๆ—ถ่ฐƒ็”จ
Args:
func:
func ([`PROCESS_LIFESPAN_FUNC`](./lifespan#var-process-lifespan-func)): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
Returns:
[`PROCESS_LIFESPAN_FUNC`](./lifespan#var-process-lifespan-func): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
"""
return self.lifespan.on_before_process_shutdown(func)
def on_before_process_restart(
self, func: PROCESS_LIFESPAN_FUNC
) -> PROCESS_LIFESPAN_FUNC:
"""
ๆณจๅ†Œ่ฟ›็จ‹้‡ๅฏๅ‰็š„ๅ‡ฝๆ•ฐ๏ผŒไธบๅญ่ฟ›็จ‹้‡ๅฏๆ—ถ่ฐƒ็”จ
Args:
func ([`PROCESS_LIFESPAN_FUNC`](./lifespan#var-process-lifespan-func)): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
Returns:
[`PROCESS_LIFESPAN_FUNC`](./lifespan#var-process-lifespan-func): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
"""
return self.lifespan.on_before_restart(func)
return self.lifespan.on_before_process_restart(func)
def on_after_restart(self, func: LIFESPAN_FUNC):
"""
ๆณจๅ†Œ้‡ๅฏๅŽ็š„ๅ‡ฝๆ•ฐ๏ผšๆœชๅฎž็Žฐ
Args:
func:
func ([`LIFESPAN_FUNC`](./lifespan#var-lifespan-func)): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
Returns:
[`LIFESPAN_FUNC`](./lifespan#var-lifespan-func): ็”Ÿๅ‘ฝๅ‘จๆœŸๅ‡ฝๆ•ฐ
"""
return self.lifespan.on_after_restart(func)
def on_after_nonebot_init(self, func: LIFESPAN_FUNC):
"""
ๆณจๅ†Œnonebotๅˆๅง‹ๅŒ–ๅŽ็š„ๅ‡ฝๆ•ฐ
Args:
func:
Returns:
"""
return self.lifespan.on_after_nonebot_init(func)
_BOT_INSTANCE: LiteyukiBot
_BOT_INSTANCE: Optional[LiteyukiBot] = None
def get_bot() -> Optional[LiteyukiBot]:
def get_bot() -> LiteyukiBot:
"""
่Žทๅ–่ฝป้›ชๅฎžไพ‹
Returns:
LiteyukiBot: ๅฝ“ๅ‰็š„่ฝป้›ชๅฎžไพ‹
[`LiteyukiBot`](#class-liteyukibot): ่ฝป้›ชๅฎžไพ‹
"""
if IS_MAIN_PROCESS:
if _BOT_INSTANCE is None:
raise RuntimeError("ๅฐน็ตๆธฉ ๅฎžไพ‹ๆœชๅˆๅง‹ๅŒ–")
return _BOT_INSTANCE
else:
# ไปŽๅคš่ฟ›็จ‹ไธŠไธ‹ๆ–‡ไธญ่Žทๅ–
pass
raise RuntimeError("ๆ— ๆณ•ๅœจๅญ่ฟ›็จ‹ไธญ่Žทๅ–ๆœบๅ™จไบบๅฎžไพ‹")
def get_config(key: str, default: Any = None) -> Any:
"""
่Žทๅ–้…็ฝฎ
Args:
key ([`str`](https%3A//docs.python.org/3/library/stdtypes.html#str)): ้…็ฝฎ้”ฎ
default ([`Any`](https%3A//docs.python.org/3/library/functions.html#any), optional): ้ป˜่ฎคๅ€ผ. Defaults to None.
Returns:
[`Any`](https%3A//docs.python.org/3/library/functions.html#any): ้…็ฝฎๅ€ผ
"""
return get_bot().config.get(key, default)
def get_config_with_compat(
key: str, compat_keys: tuple[str], default: Any = None
) -> Any:
"""
่Žทๅ–้…็ฝฎ๏ผŒๅ…ผๅฎนๆ—ง็‰ˆๆœฌ
Args:
key ([`str`](https%3A//docs.python.org/3/library/stdtypes.html#str)): ้…็ฝฎ้”ฎ
compat_keys ([`tuple`](https%3A//docs.python.org/3/library/stdtypes.html#tuple)[`str`](https%3A//docs.python.org/3/library/stdtypes.html#str)): ๅ…ผๅฎน้”ฎ
default ([`Any`](https%3A//docs.python.org/3/library/functions.html#any), optional): ้ป˜่ฎคๅ€ผ. Defaults to None.
Returns:
[`Any`](https%3A//docs.python.org/3/library/functions.html#any): ้…็ฝฎๅ€ผ
"""
if key in get_bot().config:
return get_bot().config[key]
for compat_key in compat_keys:
if compat_key in get_bot().config:
logger.warning(f'้…็ฝฎ้”ฎ "{compat_key}" ๅณๅฐ†่ขซ "{key}" ๅ–ไปฃ๏ผŒ่ฏทๅŠๆ—ถๆ›ดๆ–ฐ')
return get_bot().config[compat_key]
return default
def print_logo():
"""@litedoc-hide"""
print(
"\033[34m"
+ r"""
โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–ˆโ–ˆ โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–ˆโ–ˆ โ–ˆโ–ˆ โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–ˆโ„ข
โ–› โ–ˆโ–ˆ โ–ˆโ–ˆ โ–› โ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ
โ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–… โ–ˆโ–ˆ
โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ
โ–ˆโ–ˆ โ–ˆโ–ˆ โ–…โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–› โ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ
โ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆ
โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–… โ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆ โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–…โ–ˆโ–ˆ
โ–ˆโ–ˆโ–ˆ โ–ˆ โ–œโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ
โ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–› โ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ
โ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆ โ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ โ–ˆโ–ˆ
โ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ
ๅ•†ไธšๆ ‡่ฎฐ ็‰ˆๆƒๆ‰€ๆœ‰ ยฉ 2024 ้‡‘็พฟEilles
ๆœบๅ™จ่ฝฏไปถ ็‰ˆๆƒๆ‰€ๆœ‰ ยฉ 2020-2024 ็ฅž็พฝSnowyKami & ้‡‘็พฟEilles\\
ไผšๅŒ LiteyukiStudio & ็ฟไน็ป„็ป‡
ไฟ็•™ๆ‰€ๆœ‰ๆƒๅˆฉ
"""
+ "\033[0m"
)