├── .gitignore
├── LICENSE
├── README.md
├── nonebot_plugin_reboot
├── __init__.py
├── command.py
├── config.py
└── reloader.py
└── pyproject.toml
/.gitignore:
--------------------------------------------------------------------------------
1 | test.py
2 | poetry.lock
3 | # Created by https://www.toptal.com/developers/gitignore/api/python
4 | # Edit at https://www.toptal.com/developers/gitignore?templates=python
5 |
6 | ### Python ###
7 | # Byte-compiled / optimized / DLL files
8 | __pycache__/
9 | *.py[cod]
10 | *$py.class
11 |
12 | # C extensions
13 | *.so
14 |
15 | # Distribution / packaging
16 | .Python
17 | build/
18 | develop-eggs/
19 | dist/
20 | downloads/
21 | eggs/
22 | .eggs/
23 | lib/
24 | lib64/
25 | parts/
26 | sdist/
27 | var/
28 | wheels/
29 | pip-wheel-metadata/
30 | share/python-wheels/
31 | *.egg-info/
32 | .installed.cfg
33 | *.egg
34 | MANIFEST
35 |
36 | # PyInstaller
37 | # Usually these files are written by a python script from a template
38 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
39 | *.manifest
40 | *.spec
41 |
42 | # Installer logs
43 | pip-log.txt
44 | pip-delete-this-directory.txt
45 |
46 | # Unit test / coverage reports
47 | htmlcov/
48 | .tox/
49 | .nox/
50 | .coverage
51 | .coverage.*
52 | .cache
53 | nosetests.xml
54 | coverage.xml
55 | *.cover
56 | *.py,cover
57 | .hypothesis/
58 | .pytest_cache/
59 | pytestdebug.log
60 |
61 | # Translations
62 | *.mo
63 | *.pot
64 |
65 | # Django stuff:
66 | *.log
67 | local_settings.py
68 | db.sqlite3
69 | db.sqlite3-journal
70 |
71 | # Flask stuff:
72 | instance/
73 | .webassets-cache
74 |
75 | # Scrapy stuff:
76 | .scrapy
77 |
78 | # Sphinx documentation
79 | docs/_build/
80 | doc/_build/
81 |
82 | # PyBuilder
83 | target/
84 |
85 | # Jupyter Notebook
86 | .ipynb_checkpoints
87 |
88 | # IPython
89 | profile_default/
90 | ipython_config.py
91 |
92 | # pyenv
93 | .python-version
94 |
95 | # pipenv
96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
99 | # install all needed dependencies.
100 | #Pipfile.lock
101 |
102 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
103 | __pypackages__/
104 |
105 | # Celery stuff
106 | celerybeat-schedule
107 | celerybeat.pid
108 |
109 | # SageMath parsed files
110 | *.sage.py
111 |
112 | # Environments
113 | .env
114 | .venv
115 | env/
116 | venv/
117 | ENV/
118 | env.bak/
119 | venv.bak/
120 |
121 | # Spyder project settings
122 | .spyderproject
123 | .spyproject
124 |
125 | # Rope project settings
126 | .ropeproject
127 |
128 | # mkdocs documentation
129 | /site
130 |
131 | # mypy
132 | .mypy_cache/
133 | .dmypy.json
134 | dmypy.json
135 |
136 | # Pyre type checker
137 | .pyre/
138 |
139 | # pytype static type analyzer
140 | .pytype/
141 |
142 | # End of https://www.toptal.com/developers/gitignore/api/python
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 18870
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nonebot-plugin-reboot
2 |  
3 |
4 | 用命令重启 bot
5 |
6 |
7 |
8 | ## :warning:注意事项
9 | **必须要有** `bot.py`
10 | 新版 nb-cli 默认不生成 bot.py,需要在 nb 菜单里选择 `生成机器人的入口文件` / `Generate entry file of your bot.` 生成一个,不需要修改。 ~~不要再去群里问bot.py在哪了~~
11 |
12 | **不兼容** `fastapi_reload`,见 [#1](https://github.com/18870/nonebot-plugin-reboot/issues/1)、[#2](https://github.com/18870/nonebot-plugin-reboot/issues/2)。
13 | 不推荐使用 nb-cli 的 `--reload` 参数,这个插件是 `--reload` 在生产环境中的替代品。
14 |
15 | 重启时直接对子进程使用 `process.terminate()`,如果你的其他插件启动了子进程,请确保它们能在设定的等待时间内正确关闭子进程,否则子进程会变成孤立进程。
16 | :warning: Windows 下因系统 API 的限制进程会直接被杀死, **没有** 等待时间。
17 |
18 |
19 |
20 | 插件依赖于 `multiprocessing` `spawn` 生成子进程方式工作,支持由 nb-cli 生成的 bot.py,以及其他在加载插件后调用 `nonebot.run()` 的启动方式。
21 |
22 |
23 | ## 安装
24 | 通过 nb-cli 安装:
25 | `nb plugin install nonebot-plugin-reboot`
26 |
27 | 新版 nb-cli 默认不生成 bot.py,需要在 nb 菜单里选择 `生成机器人的入口文件` / `Generate entry file of your bot.` 生成一个,不需要修改。
28 |
29 |
30 | ## 使用
31 | **超级用户**向机器人**私聊**发送**命令** `重启`, `reboot` 或 `restart`
32 | > :warning: 注意命令的 `COMMAND_START`.
33 | > 例如 /重启 、 /reboot 、 /restart
34 |
35 |
36 | ## 配置项
37 | `reboot_load_command`: `bool`
38 | - 加载内置的 `onebot v11` 重启命令
39 | - 可以通过命令 `重启` `reboot` `restart` 触发重启
40 | - 默认值: `True`
41 |
42 | `reboot_grace_time_limit`: `int`
43 | - 收到重启命令后等待进程退出的最长时间,超时会强制杀进程
44 | - 在 Windows 下没有等待时间,会直接杀进程
45 | - ~~真寻从ctrl+c到彻底退出居然要六秒~~
46 | - 默认值: `20`
47 |
48 |
49 | ## `bot.py`
50 | 因为使用了 `spawn` 方式启动子进程,默认情况下会加载两次插件,如果你觉得这不是问题可以忽略,也不建议你在不懂的情况下修改 `bot.py`。
51 |
52 | 推荐的写法是将 插件加载部分 和 启动部分 分开,以避免插件在主进程和子进程都加载一遍,见 [#6](https://github.com/18870/nonebot-plugin-reboot/issues/6)
53 |
54 | ~~真寻启动居然要20秒~~
55 |
56 |
57 |
58 |
59 | ## API
60 | ```python
61 | require("nonebot_plugin_reboot")
62 | from nonebot_plugin_reboot import Reloader
63 | Reloader.reload(delay=5) # 可选参数 5秒后触发重启
64 | ```
65 |
66 |
67 | ## 依赖
68 | - `nonebot2 >= 2.0.0beta.2`
69 |
70 | 启用 `reboot_load_command` 时需要以下依赖
71 | - `nonebot-adapter-onebot`
--------------------------------------------------------------------------------
/nonebot_plugin_reboot/__init__.py:
--------------------------------------------------------------------------------
1 | from .config import plugin_config, Config
2 | __plugin_name__ = "nonebot_plugin_reboot [重启 bot]"
3 | __plugin_des__ = "用命令重启机器人"
4 | __plugin_usage__ = ""
5 | __plugin_author__ = "18870 "
6 | __plugin_homepage__ = "https://github.com/18870/nonebot-plugin-reboot"
7 |
8 | try:
9 | from nonebot.plugin import PluginMetadata
10 | __plugin_meta__ = PluginMetadata(
11 | name=__plugin_name__,
12 | description=__plugin_des__,
13 | usage=__plugin_usage__,
14 | type="application",
15 | homepage=__plugin_homepage__,
16 | config=Config,
17 | supported_adapters={"~onebot.v11"},
18 | extra={
19 | "author": __plugin_author__,
20 | }
21 | )
22 | except ImportError:
23 | pass
24 |
25 | from .reloader import Reloader
26 |
27 | if plugin_config.reboot_load_command:
28 | from .command import reboot_matcher
--------------------------------------------------------------------------------
/nonebot_plugin_reboot/command.py:
--------------------------------------------------------------------------------
1 | from nonebot import on_command
2 | from nonebot.adapters.onebot.v11 import PrivateMessageEvent
3 | from nonebot.permission import SUPERUSER
4 | from .reloader import Reloader
5 |
6 | reboot_matcher = on_command(
7 | cmd="重启",
8 | aliases={"reboot", "restart"},
9 | permission=SUPERUSER,
10 | priority=1,
11 | block=True
12 | )
13 |
14 | @reboot_matcher.handle()
15 | async def _(event: PrivateMessageEvent):
16 | Reloader.reload()
17 |
--------------------------------------------------------------------------------
/nonebot_plugin_reboot/config.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional
2 |
3 | from nonebot import get_driver
4 | from pydantic import BaseSettings
5 |
6 |
7 | class Config(BaseSettings):
8 | reboot_load_command: bool = True
9 | reboot_grace_time_limit: int = 20
10 |
11 | class Config:
12 | extra = "ignore"
13 |
14 |
15 | global_config = get_driver().config
16 | plugin_config = Config(**global_config.dict())
17 |
--------------------------------------------------------------------------------
/nonebot_plugin_reboot/reloader.py:
--------------------------------------------------------------------------------
1 | import threading
2 | from multiprocessing import get_context
3 |
4 | import nonebot
5 | from nonebot import logger
6 |
7 | from .config import plugin_config
8 |
9 | _nb_run = nonebot.run
10 |
11 |
12 | class Reloader:
13 | event: threading.Event = None
14 |
15 | @classmethod
16 | def reload(cls, delay: int = 0):
17 | if cls.event is None:
18 | raise RuntimeError()
19 | if delay > 0:
20 | threading.Timer(delay, function=cls.event.set).start()
21 | return
22 | cls.event.set()
23 |
24 |
25 | def _run(ev: threading.Event, *args, **kwargs):
26 | Reloader.event = ev
27 | _nb_run(*args, **kwargs)
28 |
29 |
30 | def run(*args, **kwargs):
31 | should_exit = False
32 | ctx = get_context("spawn")
33 | while not should_exit:
34 | event = ctx.Event()
35 | process = ctx.Process(
36 | target=_run,
37 | args=(
38 | event,
39 | *args,
40 | ),
41 | kwargs=kwargs,
42 | )
43 | process.start()
44 | while not should_exit:
45 | if event.wait(1):
46 | logger.info("Receive reboot event")
47 | process.terminate()
48 | process.join(plugin_config.reboot_grace_time_limit)
49 | if process.is_alive():
50 | logger.warning(
51 | f"Cannot shutdown gracefully in {plugin_config.reboot_grace_time_limit} second, force kill process."
52 | )
53 | process.kill()
54 | break
55 | elif process.is_alive():
56 | continue
57 | else:
58 | # Process stoped without setting event
59 | should_exit = True
60 |
61 |
62 | nonebot.run = run
63 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "nonebot_plugin_reboot"
3 | version = "0.1.4"
4 | description = "Reboot your bot by using command"
5 | license = "MIT"
6 | authors = ["18870 "]
7 | readme = "README.md"
8 | repository = "https://github.com/18870/nonebot-plugin-reboot"
9 | keywords = ["nonebot"]
10 |
11 | [tool.poetry.dependencies]
12 | python = "^3.7.3"
13 | nonebot2 = "^2.0.0-beta.2"
14 | nonebot-adapter-onebot = { version = "^2.1.0", optional = true }
15 |
16 | [tool.poetry.dev-dependencies]
17 | pytest = "^5.2"
18 |
19 | [build-system]
20 | requires = ["poetry-core>=1.0.0"]
21 | build-backend = "poetry.core.masonry.api"
22 |
--------------------------------------------------------------------------------