nonebot-plugin-shiro-web-console 0.1.12__py3-none-any.whl → 0.1.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nonebot-plugin-shiro-web-console might be problematic. Click here for more details.
- nonebot_plugin_shiro_web_console/__init__.py +94 -72
- nonebot_plugin_shiro_web_console/static/index.html +46 -2
- {nonebot_plugin_shiro_web_console-0.1.12.dist-info → nonebot_plugin_shiro_web_console-0.1.14.dist-info}/METADATA +13 -1
- nonebot_plugin_shiro_web_console-0.1.14.dist-info/RECORD +7 -0
- nonebot_plugin_shiro_web_console-0.1.12.dist-info/RECORD +0 -7
- {nonebot_plugin_shiro_web_console-0.1.12.dist-info → nonebot_plugin_shiro_web_console-0.1.14.dist-info}/WHEEL +0 -0
- {nonebot_plugin_shiro_web_console-0.1.12.dist-info → nonebot_plugin_shiro_web_console-0.1.14.dist-info}/licenses/LICENSE +0 -0
|
@@ -35,7 +35,7 @@ __plugin_meta__ = PluginMetadata(
|
|
|
35
35
|
supported_adapters={"~onebot.v11"},
|
|
36
36
|
extra={
|
|
37
37
|
"author": "luojisama",
|
|
38
|
-
"version": "0.1.
|
|
38
|
+
"version": "0.1.13",
|
|
39
39
|
"pypi_test": "nonebot-plugin-shiro-web-console",
|
|
40
40
|
},
|
|
41
41
|
)
|
|
@@ -611,6 +611,7 @@ if app:
|
|
|
611
611
|
@app.get("/web_console/api/plugins", dependencies=[Depends(check_auth)])
|
|
612
612
|
async def get_plugins():
|
|
613
613
|
from nonebot import get_loaded_plugins
|
|
614
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
614
615
|
import os
|
|
615
616
|
plugins = []
|
|
616
617
|
for p in get_loaded_plugins():
|
|
@@ -627,11 +628,26 @@ if app:
|
|
|
627
628
|
elif module_name.startswith("nonebot_plugin_"):
|
|
628
629
|
plugin_type = "store"
|
|
629
630
|
|
|
631
|
+
# 获取版本号
|
|
632
|
+
ver = "0.0.0"
|
|
633
|
+
# 优先尝试从 importlib 获取真实安装版本
|
|
634
|
+
try:
|
|
635
|
+
# 尝试将包名中的下划线替换为连字符(常见 PyPI 命名规范)
|
|
636
|
+
pkg_name = module_name.replace("_", "-")
|
|
637
|
+
ver = version(pkg_name)
|
|
638
|
+
except PackageNotFoundError:
|
|
639
|
+
try:
|
|
640
|
+
ver = version(module_name)
|
|
641
|
+
except PackageNotFoundError:
|
|
642
|
+
# 如果获取失败,回退到元数据中的版本
|
|
643
|
+
if metadata and metadata.extra and "version" in metadata.extra:
|
|
644
|
+
ver = metadata.extra.get("version", "0.0.0")
|
|
645
|
+
|
|
630
646
|
plugins.append({
|
|
631
647
|
"id": p.name,
|
|
632
648
|
"name": metadata.name if metadata else p.name,
|
|
633
649
|
"description": metadata.description if metadata else "暂无描述",
|
|
634
|
-
"version":
|
|
650
|
+
"version": ver,
|
|
635
651
|
"type": plugin_type,
|
|
636
652
|
"module": module_name,
|
|
637
653
|
"homepage": metadata.homepage if metadata else None
|
|
@@ -751,84 +767,90 @@ if app:
|
|
|
751
767
|
|
|
752
768
|
@app.post("/web_console/api/store/action", dependencies=[Depends(check_auth)])
|
|
753
769
|
async def store_action(request: Request):
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
770
|
+
# 尝试获取锁,如果已被占用则立即返回错误,或等待
|
|
771
|
+
# 这里选择等待,确保操作按顺序执行
|
|
772
|
+
if store_lock.locked():
|
|
773
|
+
logger.warning("插件操作正在进行中,请求已排队...")
|
|
757
774
|
|
|
758
|
-
|
|
759
|
-
|
|
775
|
+
async with store_lock:
|
|
776
|
+
data = await request.json()
|
|
777
|
+
action = data.get("action") # install, update, uninstall
|
|
778
|
+
plugin_name = data.get("plugin")
|
|
760
779
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
import sys
|
|
767
|
-
|
|
768
|
-
# 构建命令
|
|
769
|
-
cmd = []
|
|
770
|
-
# 尝试定位 nb 命令
|
|
771
|
-
import shutil
|
|
772
|
-
nb_path = shutil.which("nb")
|
|
773
|
-
|
|
774
|
-
if not nb_path:
|
|
775
|
-
# 如果系统 PATH 中找不到,再尝试在 Python 脚本目录下找
|
|
776
|
-
script_dir = os.path.dirname(sys.executable)
|
|
777
|
-
possible_nb = os.path.join(script_dir, "nb.exe" if sys.platform == "win32" else "nb")
|
|
778
|
-
if os.path.exists(possible_nb):
|
|
779
|
-
nb_path = possible_nb
|
|
780
|
-
else:
|
|
781
|
-
nb_path = "nb" # 最后的保底,尝试直接运行 nb
|
|
782
|
-
|
|
783
|
-
# 获取项目根目录 (通常是当前工作目录)
|
|
784
|
-
root_dir = Path.cwd()
|
|
780
|
+
if not action or not plugin_name:
|
|
781
|
+
return {"error": "参数错误"}
|
|
782
|
+
|
|
783
|
+
if not re.match(r'^[a-zA-Z0-9_-]+$', plugin_name):
|
|
784
|
+
return {"error": "非法插件名称"}
|
|
785
785
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
cmd = [nb_path, "plugin", "update", plugin_name]
|
|
790
|
-
elif action == "uninstall":
|
|
791
|
-
cmd = [nb_path, "plugin", "uninstall", plugin_name]
|
|
792
|
-
else:
|
|
793
|
-
return {"error": "无效操作"}
|
|
794
|
-
|
|
795
|
-
logger.info(f"开始执行插件操作: {' '.join(cmd)} (工作目录: {root_dir})")
|
|
796
|
-
|
|
797
|
-
try:
|
|
798
|
-
process = await asyncio.create_subprocess_exec(
|
|
799
|
-
*cmd,
|
|
800
|
-
stdout=asyncio.subprocess.PIPE,
|
|
801
|
-
stderr=asyncio.subprocess.PIPE,
|
|
802
|
-
cwd=str(root_dir)
|
|
803
|
-
)
|
|
786
|
+
# 执行命令
|
|
787
|
+
import asyncio
|
|
788
|
+
import sys
|
|
804
789
|
|
|
805
|
-
|
|
790
|
+
# 构建命令
|
|
791
|
+
cmd = []
|
|
792
|
+
# 尝试定位 nb 命令
|
|
793
|
+
import shutil
|
|
794
|
+
nb_path = shutil.which("nb")
|
|
806
795
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
return data.decode("utf-8", errors="replace").strip()
|
|
796
|
+
if not nb_path:
|
|
797
|
+
# 如果系统 PATH 中找不到,再尝试在 Python 脚本目录下找
|
|
798
|
+
script_dir = os.path.dirname(sys.executable)
|
|
799
|
+
possible_nb = os.path.join(script_dir, "nb.exe" if sys.platform == "win32" else "nb")
|
|
800
|
+
if os.path.exists(possible_nb):
|
|
801
|
+
nb_path = possible_nb
|
|
802
|
+
else:
|
|
803
|
+
nb_path = "nb" # 最后的保底,尝试直接运行 nb
|
|
816
804
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
if
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
805
|
+
# 获取项目根目录 (通常是当前工作目录)
|
|
806
|
+
root_dir = Path.cwd()
|
|
807
|
+
|
|
808
|
+
if action == "install":
|
|
809
|
+
cmd = [nb_path, "plugin", "install", plugin_name]
|
|
810
|
+
elif action == "update":
|
|
811
|
+
cmd = [nb_path, "plugin", "update", plugin_name]
|
|
812
|
+
elif action == "uninstall":
|
|
813
|
+
cmd = [nb_path, "plugin", "uninstall", plugin_name]
|
|
824
814
|
else:
|
|
825
|
-
|
|
826
|
-
logger.error(f"插件操作失败: {error_msg}")
|
|
827
|
-
return {"error": error_msg}
|
|
815
|
+
return {"error": "无效操作"}
|
|
828
816
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
817
|
+
logger.info(f"开始执行插件操作: {' '.join(cmd)} (工作目录: {root_dir})")
|
|
818
|
+
|
|
819
|
+
try:
|
|
820
|
+
process = await asyncio.create_subprocess_exec(
|
|
821
|
+
*cmd,
|
|
822
|
+
stdout=asyncio.subprocess.PIPE,
|
|
823
|
+
stderr=asyncio.subprocess.PIPE,
|
|
824
|
+
cwd=str(root_dir)
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
stdout_bytes, stderr_bytes = await process.communicate()
|
|
828
|
+
|
|
829
|
+
def safe_decode(data: bytes) -> str:
|
|
830
|
+
if not data:
|
|
831
|
+
return ""
|
|
832
|
+
for encoding in ["utf-8", "gbk", "cp936"]:
|
|
833
|
+
try:
|
|
834
|
+
return data.decode(encoding).strip()
|
|
835
|
+
except UnicodeDecodeError:
|
|
836
|
+
continue
|
|
837
|
+
return data.decode("utf-8", errors="replace").strip()
|
|
838
|
+
|
|
839
|
+
stdout = safe_decode(stdout_bytes)
|
|
840
|
+
stderr = safe_decode(stderr_bytes)
|
|
841
|
+
|
|
842
|
+
if process.returncode == 0:
|
|
843
|
+
msg = f"插件 {plugin_name} {action} 成功"
|
|
844
|
+
logger.info(msg)
|
|
845
|
+
return {"msg": msg, "output": stdout}
|
|
846
|
+
else:
|
|
847
|
+
error_msg = stderr or stdout
|
|
848
|
+
logger.error(f"插件操作失败: {error_msg}")
|
|
849
|
+
return {"error": error_msg}
|
|
850
|
+
|
|
851
|
+
except Exception as e:
|
|
852
|
+
logger.error(f"执行插件命令时发生异常: {e}")
|
|
853
|
+
return {"error": str(e)}
|
|
832
854
|
|
|
833
855
|
@app.post("/web_console/api/plugins/{plugin_id}/config", dependencies=[Depends(check_auth)])
|
|
834
856
|
async def update_plugin_config(plugin_id: str, new_config: dict):
|
|
@@ -554,6 +554,7 @@
|
|
|
554
554
|
<div style="font-weight: bold; font-size: 1.2em;">NoneBot 日志</div>
|
|
555
555
|
<div style="display: flex; gap: 10px;">
|
|
556
556
|
<button onclick="fetchLogs()" style="padding: 5px 12px; background: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9em;">刷新</button>
|
|
557
|
+
<button id="btn-download-log" onclick="downloadFullLog()" style="padding: 5px 12px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9em;" title="下载完整运行日志">下载日志</button>
|
|
557
558
|
<button onclick="document.getElementById('log-viewer').innerHTML = ''" style="padding: 5px 12px; background: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9em;">清屏</button>
|
|
558
559
|
</div>
|
|
559
560
|
</div>
|
|
@@ -1092,6 +1093,19 @@
|
|
|
1092
1093
|
}
|
|
1093
1094
|
}
|
|
1094
1095
|
|
|
1096
|
+
function compareVersions(v1, v2) {
|
|
1097
|
+
const parts1 = v1.split('.').map(p => parseInt(p, 10) || 0);
|
|
1098
|
+
const parts2 = v2.split('.').map(p => parseInt(p, 10) || 0);
|
|
1099
|
+
|
|
1100
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
1101
|
+
const n1 = parts1[i] || 0;
|
|
1102
|
+
const n2 = parts2[i] || 0;
|
|
1103
|
+
if (n1 > n2) return 1;
|
|
1104
|
+
if (n1 < n2) return -1;
|
|
1105
|
+
}
|
|
1106
|
+
return 0;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1095
1109
|
async function openUpdates() {
|
|
1096
1110
|
const listEl = document.getElementById('updates-list');
|
|
1097
1111
|
const countEl = document.getElementById('updates-count');
|
|
@@ -1122,12 +1136,12 @@
|
|
|
1122
1136
|
const storePlugin = storeData.find(s => s.module_name === local.module || s.project_link === local.id);
|
|
1123
1137
|
|
|
1124
1138
|
if (storePlugin) {
|
|
1125
|
-
// 简单版本比较: 如果字符串不相等,且 store 版本通常较新
|
|
1126
1139
|
// 移除 'v' 前缀进行比较
|
|
1127
1140
|
const v1 = (local.version || '0.0.0').replace(/^v/, '');
|
|
1128
1141
|
const v2 = (storePlugin.version || '0.0.0').replace(/^v/, '');
|
|
1129
1142
|
|
|
1130
|
-
|
|
1143
|
+
// 只显示商店版本大于本地版本的情况
|
|
1144
|
+
if (compareVersions(v2, v1) > 0) {
|
|
1131
1145
|
updates.push({
|
|
1132
1146
|
local: local,
|
|
1133
1147
|
store: storePlugin
|
|
@@ -1684,6 +1698,36 @@
|
|
|
1684
1698
|
sendBtn.onclick = sendMessage;
|
|
1685
1699
|
msgInput.onkeypress = (e) => { if (e.key === 'Enter') sendMessage(); };
|
|
1686
1700
|
|
|
1701
|
+
// 下载完整日志
|
|
1702
|
+
async function downloadFullLog() {
|
|
1703
|
+
const btn = document.getElementById('btn-download-log');
|
|
1704
|
+
const originalText = btn.textContent;
|
|
1705
|
+
btn.textContent = '下载中...';
|
|
1706
|
+
btn.disabled = true;
|
|
1707
|
+
try {
|
|
1708
|
+
const res = await authorizedFetch('/web_console/api/logs/download');
|
|
1709
|
+
if (res.status === 404) {
|
|
1710
|
+
alert('暂无完整日志记录');
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
if (!res.ok) throw new Error('下载失败');
|
|
1714
|
+
const blob = await res.blob();
|
|
1715
|
+
const url = window.URL.createObjectURL(blob);
|
|
1716
|
+
const a = document.createElement('a');
|
|
1717
|
+
a.href = url;
|
|
1718
|
+
a.download = `nonebot_full_${new Date().toISOString().slice(0,10)}.log`;
|
|
1719
|
+
document.body.appendChild(a);
|
|
1720
|
+
a.click();
|
|
1721
|
+
window.URL.revokeObjectURL(url);
|
|
1722
|
+
document.body.removeChild(a);
|
|
1723
|
+
} catch (e) {
|
|
1724
|
+
alert('下载日志失败: ' + e.message);
|
|
1725
|
+
} finally {
|
|
1726
|
+
btn.textContent = originalText;
|
|
1727
|
+
btn.disabled = false;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1687
1731
|
// 初始化
|
|
1688
1732
|
fetchChats();
|
|
1689
1733
|
initWS();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nonebot-plugin-shiro-web-console
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: 一个用于 NoneBot2 的网页控制台插件,支持通过浏览器查看日志和发送消息
|
|
5
5
|
Project-URL: Homepage, https://github.com/luojisama/nonebot-plugin-shiro-web-console
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/luojisama/nonebot-plugin-shiro-web-console/issues
|
|
@@ -58,3 +58,15 @@ pip install nonebot-plugin-shiro-web-console
|
|
|
58
58
|
```env
|
|
59
59
|
web_console_password=your_password # 设置固定登录密码
|
|
60
60
|
```
|
|
61
|
+
|
|
62
|
+
## 更新日志
|
|
63
|
+
|
|
64
|
+
### v0.1.14
|
|
65
|
+
- **新增**:完整运行日志记录功能,支持在网页端直接下载。
|
|
66
|
+
- **优化**:添加插件商店操作锁,解决多插件同时更新导致的冲突问题。
|
|
67
|
+
- **优化**:改进插件版本获取逻辑,准确对比版本号并过滤非更新项。
|
|
68
|
+
- **优化**:优化移动端适配及部分 UI 交互。
|
|
69
|
+
|
|
70
|
+
## 许可证
|
|
71
|
+
|
|
72
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
nonebot_plugin_shiro_web_console/__init__.py,sha256=ff9EWyPSOx4xqc0d-BMCf6ivYTgOjAIC9hNCLtrOIm8,42518
|
|
2
|
+
nonebot_plugin_shiro_web_console/config.py,sha256=LqHht8N5CuSMyBbEjzyJgfZtg05cvBlpGn_2MgTjt-g,202
|
|
3
|
+
nonebot_plugin_shiro_web_console/static/index.html,sha256=er7iyIdX8Z4vD3yjRC5noAUvpnLszztOycsB6D5Y4fg,90118
|
|
4
|
+
nonebot_plugin_shiro_web_console-0.1.14.dist-info/METADATA,sha256=rpcuqIcQ4iY0MpRjuS6MLXCwmJf8a-weon4eJ68Ioi8,2361
|
|
5
|
+
nonebot_plugin_shiro_web_console-0.1.14.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
nonebot_plugin_shiro_web_console-0.1.14.dist-info/licenses/LICENSE,sha256=WedEfsrQQfoZsmbJHIAPn4UY_IcpavYULMV5in_ZPbg,1066
|
|
7
|
+
nonebot_plugin_shiro_web_console-0.1.14.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
nonebot_plugin_shiro_web_console/__init__.py,sha256=hAkxmqi7wSe8xDOlmWKgHNBmbjwfCu5iTNVl52x3hMQ,41226
|
|
2
|
-
nonebot_plugin_shiro_web_console/config.py,sha256=LqHht8N5CuSMyBbEjzyJgfZtg05cvBlpGn_2MgTjt-g,202
|
|
3
|
-
nonebot_plugin_shiro_web_console/static/index.html,sha256=rpCJlGm4f0PWvBB4uVn-t_g2r8DWiywd02fG6qNvXl0,88112
|
|
4
|
-
nonebot_plugin_shiro_web_console-0.1.12.dist-info/METADATA,sha256=6t1xGleTrByc2AIFV6JbR3L8g_QeHSMWXWzMAjmTMis,1980
|
|
5
|
-
nonebot_plugin_shiro_web_console-0.1.12.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
-
nonebot_plugin_shiro_web_console-0.1.12.dist-info/licenses/LICENSE,sha256=WedEfsrQQfoZsmbJHIAPn4UY_IcpavYULMV5in_ZPbg,1066
|
|
7
|
-
nonebot_plugin_shiro_web_console-0.1.12.dist-info/RECORD,,
|
|
File without changes
|