diff --git a/bot.py b/bot.py index 147a4c4..d02a860 100644 --- a/bot.py +++ b/bot.py @@ -2,6 +2,7 @@ import asyncio import logging import re +import signal import sys from pathlib import Path @@ -260,15 +261,36 @@ class Bot: watcher = asyncio.get_running_loop().create_task(self._watch_config(alerter)) log.info("机器人就绪,等待 OneBot 实现端连接...") try: - await asyncio.Event().wait() - except (KeyboardInterrupt, asyncio.CancelledError): - pass + await self._wait_for_stop() finally: + log.info("正在关闭...") watcher.cancel() await updater.stop() await reporter.stop() await alerter.stop() await self.ob.stop() + log.info("已退出") + + async def _wait_for_stop(self): + """阻塞直到收到 Ctrl-C / SIGTERM。""" + stop = asyncio.Event() + loop = asyncio.get_running_loop() + + def request_stop(): + loop.call_soon_threadsafe(stop.set) + + sigs = [signal.SIGINT, signal.SIGTERM] + if sys.platform == "win32" and hasattr(signal, "SIGBREAK"): + sigs.append(signal.SIGBREAK) # CTRL_BREAK / 任务管理器停止服务 + for sig in sigs: + try: + loop.add_signal_handler(sig, request_stop) + except NotImplementedError: # Windows 回退 + signal.signal(sig, lambda s, f: request_stop()) + try: + await stop.wait() + except KeyboardInterrupt: # Windows 上 Ctrl-C 可能直接中断 await + pass async def _watch_config(self, alerter: Alerter, interval: float = 5.0): """监测 config.toml 修改并热重载(端口/token 等需重启的项不生效)。""" @@ -294,7 +316,11 @@ def main(): if sys.platform == "win32": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) cfg = load_config() - asyncio.run(Bot(cfg).run()) + try: + asyncio.run(Bot(cfg).run()) + except KeyboardInterrupt: + print("已退出") + sys.exit(0) if __name__ == "__main__": diff --git a/monitor.py b/monitor.py index 6f309d7..c729946 100644 --- a/monitor.py +++ b/monitor.py @@ -110,22 +110,16 @@ def get_memory() -> dict: def _wmi_temps() -> dict: """Windows: 通过 WMI 热区读取温度(部分主板支持,可能需要管理员权限)。""" - import subprocess try: - out = subprocess.run( - ["powershell", "-NoProfile", "-Command", - "Get-CimInstance -Namespace root/wmi -ClassName MSAcpi_ThermalZoneTemperature " - "| Select-Object -ExpandProperty CurrentTemperature"], - capture_output=True, text=True, timeout=10, - encoding="utf-8", errors="replace", - ) - values = [float(x) for x in out.stdout.split() if x.replace(".", "", 1).isdigit()] - except (OSError, subprocess.SubprocessError, ValueError): + import wmi # 仅 Windows 安装 + zones = wmi.WMI(namespace="root\\wmi").MSAcpi_ThermalZoneTemperature() + values = [z.CurrentTemperature for z in zones] + except Exception: # 类不存在 / 权限不足 / 非 Windows return {} # WMI 单位是 0.1 开尔文 return {"ThermalZone": [ {"label": f"zone{i}", "current": round(v / 10 - 273.15, 1)} - for i, v in enumerate(values) + for i, v in enumerate(values) if v ]} @@ -249,11 +243,7 @@ def get_top_memory(n: int = 10) -> list: def get_uptime() -> float: - try: - with open("/proc/uptime") as f: - return float(f.read().split()[0]) - except OSError: - return time.time() - psutil.boot_time() + return time.time() - psutil.boot_time() def get_status(top_n: int | None = None) -> dict: diff --git a/pyproject.toml b/pyproject.toml index 004c1e5..de596e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "aiohttp>=3.14.3", "gitpython>=3.1.62", "psutil>=7.2.2", + "wmi>=1.5 ; sys_platform == 'win32'", ] [dependency-groups] diff --git a/uv.lock b/uv.lock index fd45ee9..d61aa33 100644 --- a/uv.lock +++ b/uv.lock @@ -281,6 +281,7 @@ dependencies = [ { name = "aiohttp" }, { name = "gitpython" }, { name = "psutil" }, + { name = "wmi", marker = "sys_platform == 'win32'" }, ] [package.dev-dependencies] @@ -294,6 +295,7 @@ requires-dist = [ { name = "aiohttp", specifier = ">=3.14.3" }, { name = "gitpython", specifier = ">=3.1.62" }, { name = "psutil", specifier = ">=7.2.2" }, + { name = "wmi", marker = "sys_platform == 'win32'", specifier = ">=1.5" }, ] [package.metadata.requires-dev] @@ -618,6 +620,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, ] +[[package]] +name = "pywin32" +version = "312" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" }, + { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" }, + { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, +] + [[package]] name = "smmap" version = "5.0.3" @@ -636,6 +657,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] +[[package]] +name = "wmi" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/66/6364deb0a03415f96c66803d8c4379f808f2401da3bdb183348487b10510/WMI-1.5.1.tar.gz", hash = "sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6", size = 26254, upload-time = "2020-04-28T08:22:58.096Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/b9/a80d1ed4d115dac8e2ac08d16af046a77ab58e3d186e22395bf2add24090/WMI-1.5.1-py2.py3-none-any.whl", hash = "sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942", size = 28912, upload-time = "2020-04-28T08:22:56.055Z" }, +] + [[package]] name = "yarl" version = "1.24.5"