nonebot-plugin-shiro-web-console 0.1.13__tar.gz → 0.1.15__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-shiro-web-console
3
- Version: 0.1.13
3
+ Version: 0.1.15
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,20 @@ 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.15
65
+ - **修复**:修复插件商店操作时的 500 错误(缺少异步锁定义)。
66
+ - **修复**:修复 WebSocket 连接断开时的异常报错。
67
+ - **新增**:正式实装完整日志记录与下载功能。
68
+
69
+ ### v0.1.14
70
+ - **新增**:完整运行日志记录功能,支持在网页端直接下载。
71
+ - **优化**:添加插件商店操作锁,解决多插件同时更新导致的冲突问题。
72
+ - **优化**:改进插件版本获取逻辑,准确对比版本号并过滤非更新项。
73
+ - **优化**:优化移动端适配及部分 UI 交互。
74
+
75
+ ## 许可证
76
+
77
+ MIT
@@ -27,3 +27,20 @@ pip install nonebot-plugin-shiro-web-console
27
27
  ```env
28
28
  web_console_password=your_password # 设置固定登录密码
29
29
  ```
30
+
31
+ ## 更新日志
32
+
33
+ ### v0.1.15
34
+ - **修复**:修复插件商店操作时的 500 错误(缺少异步锁定义)。
35
+ - **修复**:修复 WebSocket 连接断开时的异常报错。
36
+ - **新增**:正式实装完整日志记录与下载功能。
37
+
38
+ ### v0.1.14
39
+ - **新增**:完整运行日志记录功能,支持在网页端直接下载。
40
+ - **优化**:添加插件商店操作锁,解决多插件同时更新导致的冲突问题。
41
+ - **优化**:改进插件版本获取逻辑,准确对比版本号并过滤非更新项。
42
+ - **优化**:优化移动端适配及部分 UI 交互。
43
+
44
+ ## 许可证
45
+
46
+ MIT
@@ -35,7 +35,7 @@ __plugin_meta__ = PluginMetadata(
35
35
  supported_adapters={"~onebot.v11"},
36
36
  extra={
37
37
  "author": "luojisama",
38
- "version": "0.1.13",
38
+ "version": "0.1.15",
39
39
  "pypi_test": "nonebot-plugin-shiro-web-console",
40
40
  },
41
41
  )
@@ -77,6 +77,15 @@ async def log_sink(message):
77
77
  # 注册 loguru sink
78
78
  logger.add(log_sink, format="{time} {level} {message}", level="INFO")
79
79
 
80
+ # Async lock for plugin actions
81
+ store_lock = asyncio.Lock()
82
+
83
+ # Persistent log configuration
84
+ log_dir = nonebot_plugin_localstore.get_plugin_data_dir() / "logs"
85
+ log_dir.mkdir(parents=True, exist_ok=True)
86
+ full_log_path = log_dir / "web_console_full.log"
87
+ logger.add(full_log_path, rotation="10 MB", encoding="utf-8", level="INFO", format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {module}:{line} | {message}")
88
+
80
89
  # 验证码管理
81
90
  class AuthManager:
82
91
  def __init__(self):
@@ -608,6 +617,12 @@ if app:
608
617
  async def get_logs():
609
618
  return list(log_buffer)
610
619
 
620
+ @app.get("/web_console/api/logs/download", dependencies=[Depends(check_auth)])
621
+ async def download_logs():
622
+ if not full_log_path.exists():
623
+ return Response("暂无日志记录", status_code=404)
624
+ return FileResponse(full_log_path, filename="nonebot_full.log", media_type="text/plain")
625
+
611
626
  @app.get("/web_console/api/plugins", dependencies=[Depends(check_auth)])
612
627
  async def get_plugins():
613
628
  from nonebot import get_loaded_plugins
@@ -767,84 +782,90 @@ if app:
767
782
 
768
783
  @app.post("/web_console/api/store/action", dependencies=[Depends(check_auth)])
769
784
  async def store_action(request: Request):
770
- data = await request.json()
771
- action = data.get("action") # install, update, uninstall
772
- plugin_name = data.get("plugin")
785
+ # 尝试获取锁,如果已被占用则立即返回错误,或等待
786
+ # 这里选择等待,确保操作按顺序执行
787
+ if store_lock.locked():
788
+ logger.warning("插件操作正在进行中,请求已排队...")
773
789
 
774
- if not action or not plugin_name:
775
- return {"error": "参数错误"}
790
+ async with store_lock:
791
+ data = await request.json()
792
+ action = data.get("action") # install, update, uninstall
793
+ plugin_name = data.get("plugin")
776
794
 
777
- if not re.match(r'^[a-zA-Z0-9_-]+$', plugin_name):
778
- return {"error": "非法插件名称"}
779
-
780
- # 执行命令
781
- import asyncio
782
- import sys
783
-
784
- # 构建命令
785
- cmd = []
786
- # 尝试定位 nb 命令
787
- import shutil
788
- nb_path = shutil.which("nb")
789
-
790
- if not nb_path:
791
- # 如果系统 PATH 中找不到,再尝试在 Python 脚本目录下找
792
- script_dir = os.path.dirname(sys.executable)
793
- possible_nb = os.path.join(script_dir, "nb.exe" if sys.platform == "win32" else "nb")
794
- if os.path.exists(possible_nb):
795
- nb_path = possible_nb
796
- else:
797
- nb_path = "nb" # 最后的保底,尝试直接运行 nb
798
-
799
- # 获取项目根目录 (通常是当前工作目录)
800
- root_dir = Path.cwd()
795
+ if not action or not plugin_name:
796
+ return {"error": "参数错误"}
797
+
798
+ if not re.match(r'^[a-zA-Z0-9_-]+$', plugin_name):
799
+ return {"error": "非法插件名称"}
801
800
 
802
- if action == "install":
803
- cmd = [nb_path, "plugin", "install", plugin_name]
804
- elif action == "update":
805
- cmd = [nb_path, "plugin", "update", plugin_name]
806
- elif action == "uninstall":
807
- cmd = [nb_path, "plugin", "uninstall", plugin_name]
808
- else:
809
- return {"error": "无效操作"}
810
-
811
- logger.info(f"开始执行插件操作: {' '.join(cmd)} (工作目录: {root_dir})")
812
-
813
- try:
814
- process = await asyncio.create_subprocess_exec(
815
- *cmd,
816
- stdout=asyncio.subprocess.PIPE,
817
- stderr=asyncio.subprocess.PIPE,
818
- cwd=str(root_dir)
819
- )
801
+ # 执行命令
802
+ import asyncio
803
+ import sys
820
804
 
821
- stdout_bytes, stderr_bytes = await process.communicate()
805
+ # 构建命令
806
+ cmd = []
807
+ # 尝试定位 nb 命令
808
+ import shutil
809
+ nb_path = shutil.which("nb")
822
810
 
823
- def safe_decode(data: bytes) -> str:
824
- if not data:
825
- return ""
826
- for encoding in ["utf-8", "gbk", "cp936"]:
827
- try:
828
- return data.decode(encoding).strip()
829
- except UnicodeDecodeError:
830
- continue
831
- return data.decode("utf-8", errors="replace").strip()
811
+ if not nb_path:
812
+ # 如果系统 PATH 中找不到,再尝试在 Python 脚本目录下找
813
+ script_dir = os.path.dirname(sys.executable)
814
+ possible_nb = os.path.join(script_dir, "nb.exe" if sys.platform == "win32" else "nb")
815
+ if os.path.exists(possible_nb):
816
+ nb_path = possible_nb
817
+ else:
818
+ nb_path = "nb" # 最后的保底,尝试直接运行 nb
832
819
 
833
- stdout = safe_decode(stdout_bytes)
834
- stderr = safe_decode(stderr_bytes)
835
-
836
- if process.returncode == 0:
837
- msg = f"插件 {plugin_name} {action} 成功"
838
- logger.info(msg)
839
- return {"msg": msg, "output": stdout}
820
+ # 获取项目根目录 (通常是当前工作目录)
821
+ root_dir = Path.cwd()
822
+
823
+ if action == "install":
824
+ cmd = [nb_path, "plugin", "install", plugin_name]
825
+ elif action == "update":
826
+ cmd = [nb_path, "plugin", "update", plugin_name]
827
+ elif action == "uninstall":
828
+ cmd = [nb_path, "plugin", "uninstall", plugin_name]
840
829
  else:
841
- error_msg = stderr or stdout
842
- logger.error(f"插件操作失败: {error_msg}")
843
- return {"error": error_msg}
830
+ return {"error": "无效操作"}
844
831
 
845
- except Exception as e:
846
- logger.error(f"执行插件命令时发生异常: {e}")
847
- return {"error": str(e)}
832
+ logger.info(f"开始执行插件操作: {' '.join(cmd)} (工作目录: {root_dir})")
833
+
834
+ try:
835
+ process = await asyncio.create_subprocess_exec(
836
+ *cmd,
837
+ stdout=asyncio.subprocess.PIPE,
838
+ stderr=asyncio.subprocess.PIPE,
839
+ cwd=str(root_dir)
840
+ )
841
+
842
+ stdout_bytes, stderr_bytes = await process.communicate()
843
+
844
+ def safe_decode(data: bytes) -> str:
845
+ if not data:
846
+ return ""
847
+ for encoding in ["utf-8", "gbk", "cp936"]:
848
+ try:
849
+ return data.decode(encoding).strip()
850
+ except UnicodeDecodeError:
851
+ continue
852
+ return data.decode("utf-8", errors="replace").strip()
853
+
854
+ stdout = safe_decode(stdout_bytes)
855
+ stderr = safe_decode(stderr_bytes)
856
+
857
+ if process.returncode == 0:
858
+ msg = f"插件 {plugin_name} {action} 成功"
859
+ logger.info(msg)
860
+ return {"msg": msg, "output": stdout}
861
+ else:
862
+ error_msg = stderr or stdout
863
+ logger.error(f"插件操作失败: {error_msg}")
864
+ return {"error": error_msg}
865
+
866
+ except Exception as e:
867
+ logger.error(f"执行插件命令时发生异常: {e}")
868
+ return {"error": str(e)}
848
869
 
849
870
  @app.post("/web_console/api/plugins/{plugin_id}/config", dependencies=[Depends(check_auth)])
850
871
  async def update_plugin_config(plugin_id: str, new_config: dict):
@@ -1074,7 +1095,6 @@ if app:
1074
1095
  # 保持连接,接收心跳或其他
1075
1096
  await websocket.receive_text()
1076
1097
  except WebSocketDisconnect:
1077
- active_connections.remove(websocket)
1098
+ active_connections.discard(websocket)
1078
1099
  except Exception:
1079
- if websocket in active_connections:
1080
- active_connections.remove(websocket)
1100
+ active_connections.discard(websocket)
@@ -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>
@@ -1697,6 +1698,36 @@
1697
1698
  sendBtn.onclick = sendMessage;
1698
1699
  msgInput.onkeypress = (e) => { if (e.key === 'Enter') sendMessage(); };
1699
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
+
1700
1731
  // 初始化
1701
1732
  fetchChats();
1702
1733
  initWS();
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-shiro-web-console"
3
- version = "0.1.13"
3
+ version = "0.1.15"
4
4
  description = "一个用于 NoneBot2 的网页控制台插件,支持通过浏览器查看日志和发送消息"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"