1
0
forked from bot/app

560 Commits
v6.3.2 ... v7

Author SHA1 Message Date
5566e9294f 🔧 更新 Dockerfile 和示例 docker-compose 文件中的镜像地址,修正 README.md 和相关配置文件中的链接 2025-06-15 23:40:05 +08:00
ae464ed3ea Merge branch 'v7' of https://github.com/LiteyukiStudio/LiteyukiBot into v7 2025-05-16 18:58:06 +08:00
add5174e93 🔧 更新依赖项,添加 hypercorn 及其相关依赖,修改 README.md 中的图标链接,新增测试文件 test_hypercorn.py 2025-05-16 18:58:03 +08:00
Nanaloveyuki
fe85f1e612 ✏️ 改善自述文件部分显示内容, 替换许可证内的命名空间 2025-05-02 02:11:00 +08:00
efb13340f0 🔧 添加获取 FastAPI 实例的函数,更新配置文件路径以支持新的源文件 2025-04-29 02:55:38 +08:00
56996ef082 🔧 重构配置管理,添加配置文件支持,更新日志系统,优化守护进程,完善测试用例 2025-04-29 02:51:37 +08:00
fec96e694d 🔧 更新 pyproject.toml 和 uv.lock,添加 yukilog 依赖及相关配置 2025-04-28 21:48:25 +08:00
a4e423168c 🔧 更新 Dockerfile 和 docker-compose 文件,添加配置目录并引入新依赖,重构配置加载功能 2025-04-28 15:03:56 +08:00
20eb1809f1 📝 更新 README.md,修改轻雪版本号至 7 2025-04-28 01:00:36 +08:00
66c88d8a30 🔧 更新 pytest 工作流,将上传工件的 GitHub Actions 版本升级至 v4 2025-04-28 00:59:54 +08:00
7aaa589a51 🔧 更新 pytest 工作流,升级上传工件的 GitHub Actions 版本至 v4 2025-04-28 00:58:49 +08:00
84133b2f27 🔧 更新 GitHub Actions 工作流,升级 uv 版本至 v6 2025-04-28 00:57:16 +08:00
1de7d81693 ️ Refactor LiteyukiBot structure and add configuration loading
- Updated .gitignore to include .pytest_cache
- Replaced FastAPI with Daemon in main.py for bot execution
- Enhanced pyproject.toml with new dependencies and dev group
- Added iniconfig package for pytest configuration
- Created initial structure for liteyukibot with context management
- Implemented configuration loading functions for YAML, JSON, and TOML
- Added tests for configuration loading with temporary files
- Set up GitHub Actions for pytest testing on push and pull requests
2025-04-28 00:56:07 +08:00
4cbf043547 📝 更新 .gitignore 文件以包含开发相关的忽略项;更新 Dockerfile 以支持 uvicorn 和虚拟环境;删除不再使用的 docker-compose 文件;重构 main.py 以使用 FastAPI;更新 pyproject.toml 和 uv.lock 文件以添加 uvicorn 依赖;创建 Makefile 和 docker-compose-dev.yaml 示例文件。 2025-04-27 23:30:39 +08:00
7d755581cb 📝 更新 .gitignore 文件以包含 Python 相关的忽略项;更新 README.md,添加开发状态说明并移除参考及鸣谢部分 2025-04-27 22:13:54 +08:00
b634352c95 📝 更新 README.md,添加项目特点、服务支持及鸣谢信息;更新 pyproject.toml,添加依赖项;创建 uv.lock 文件以管理依赖版本。 2025-04-27 22:11:35 +08:00
Nanaloveyuki
1d35ab21f8 📄 [许可证] 添加 LSO v1.3(Common) 许可证
添加:
- LISENCE
> 许可证文件
2025-04-27 18:30:37 +08:00
7a006bc6fd ⬆️ v7 2025-04-27 18:24:59 +08:00
26c9cb7e35 🚀 v7 2025-04-27 18:24:46 +08:00
Nanaloveyuki
fc017c8255 📝 文档与代码注释信息微调 2025-04-04 11:44:50 +08:00
Nanaloveyuki
908812a3d9 📝 重置了deploy下的文档
新添加了adapter.md用来转移配置器相关内容; 修改了部分文件的版权归属日期;
2025-04-03 21:54:03 +08:00
6c7d073cb1 📝 添加文档uptime 2025-03-19 02:12:28 +08:00
8677286bd4 📝 更新pages部署地址 2025-03-10 23:07:21 +08:00
232ffcf714 📝 修正文档中的LSO LICENSE拼写错误 2025-03-09 20:01:36 +08:00
6871bc0d22 更新配置,重新引入 liteyuki_footer.js 脚本 2025-03-08 23:26:54 +08:00
641c16ee7a 📝 文档引入神秘js 2025-03-08 23:21:14 +08:00
75c67bdffc 更新配置,禁用清理 URL 功能 2025-03-07 21:35:21 +08:00
3a60450358 📝 更新文档页脚信息,修正链接文本 2025-03-07 17:43:43 +08:00
7b97210a31 📝 更新文档页脚信息,添加网站部署链接 2025-03-07 17:21:01 +08:00
d97145ee5e 📝 更新文档部署工作流程,添加对状态的写入权限并恢复 GITHUB_TOKEN 环境变量 2025-03-07 17:09:01 +08:00
bc3a4355c3 🐛 移除文档部署工作流程中的 GITHUB_TOKEN 环境变量 2025-03-07 17:00:27 +08:00
d835a32683 🗑️ 删除文档部署工作流程文件 2025-03-07 16:49:25 +08:00
aedea1acb9 Merge branch 'main' of https://github.com/LiteyukiStudio/LiteyukiBot 2025-03-07 16:37:18 +08:00
3bce8325c1 📝 新增 Liteyuki PaaS 部署工作流程,优化文档构建和发布步骤 2025-03-07 16:37:15 +08:00
d1c1320963 📝 Create .domain 2025-03-07 00:12:44 +08:00
317e07eb71 🐛 更新文档页脚信息,添加Liteyukiflare CDN加速说明 2025-03-01 03:05:04 +08:00
37749ae15e 📝 使用Liteyukiflare对GitHub Page进行亚太地区加速 2025-03-01 03:04:34 +08:00
f94c10de61 🐛 修正 Docker 镜像标签的大小写,更新相关文档中的镜像拉取命令 2025-02-18 09:08:50 +08:00
5ccef735be 🐛 更新 Docker 镜像拉取地址,修正文档中的镜像源 2025-02-18 09:07:37 +08:00
262002b49a 🐛 更新 GitHub 容器注册表登录配置,修正 Docker 镜像构建和推送步骤 2025-02-18 09:06:52 +08:00
40c6ba6d9e 🐛 更新管道依赖,修复管道句柄错误 2025-02-18 06:47:31 +08:00
60093b562b 🐛 移除 Gotify 插件及相关配置,更新依赖项版本 2025-02-18 06:42:42 +08:00
30880ec13b 在 README 中添加 LiteyukiLab 的徽章链接 2025-01-13 00:57:50 +08:00
cc1d82312a 添加新的图标配置,更新链接以指向新的资源 2025-01-13 00:49:01 +08:00
efca13d397 🐛 移除 requirements.txt 中的 pip 依赖项 2024-12-14 02:59:55 +08:00
3a8c09d6db 🐛 删除 __pypackages__ 目录的 .gitignore 文件,并更新主 .gitignore 文件以忽略 pdm 相关文件 2024-12-14 02:57:51 +08:00
cc1bb8e5e4 添加 pre-commit 配置和工作流,集成代码格式化和静态检查工具 2024-12-13 19:35:07 +08:00
93c17b6026 添加新的配置加载器,支持从YAML、TOML、JSON和环境变量加载配置,并修改默认主机地址 2024-12-02 21:46:29 +08:00
fd3f6272f1 🐛 修复获取系统语言代码的逻辑,处理None值的情况 2024-11-29 00:04:34 +08:00
4d87a3c0b7 Merge branch 'main' of https://git.liteyuki.icu/bot/app 2024-11-29 00:03:14 +08:00
86f47ee411 🐛 修复获取系统语言代码的逻辑,添加对None值的处理 2024-11-29 00:01:49 +08:00
1d6b8d60f3 Merge pull request #89 from Asankilp/main
添加uninfo依赖项
2024-11-27 01:47:23 +08:00
3890704045 添加uninfo依赖项 2024-11-27 01:44:49 +08:00
b0761e9873 🐛 更新docker-compose.yml,修改时区设置为Asia/Chongqing 2024-11-23 22:31:51 +08:00
291314de93 新增docker-compose.yml文件,定义应用服务及其配置 2024-11-23 22:31:06 +08:00
fd835e9406 更新.gitignore,新增Python工具链缓存目录排除项;更新README.md,调整标题并添加monorepo说明 2024-11-22 20:15:04 +08:00
d681c5645a 🐛 更新Dockerfile,新增libpango-1.0-0和libcairo2依赖 2024-11-21 19:53:49 +08:00
d0619f1fe8 优化Dockerfile,移除不必要的sources.list复制步骤,简化pip安装命令 2024-11-16 02:24:47 +08:00
b022a364e3 更新GitHub Actions工作流,修改并发组名称为“docker-build” 2024-11-16 02:20:06 +08:00
df00c61dd8 新增GitHub Actions工作流以构建和推送Docker镜像,更新.gitignore以排除.github目录 2024-11-16 02:19:31 +08:00
94a021bab0 更新安装文档,替换Docker构建步骤为拉取最新夜间版镜像 2024-11-16 02:17:06 +08:00
6b20e9eae0 更新README.md,将“参考及鸣”更正为“参考及鸣谢” 2024-11-10 01:20:14 +08:00
0a35a3c6f8 修正README.md中的链接错误,将“lightyuki-link”更正为“liteyuki-link” 2024-11-10 01:19:58 +08:00
2e75c7bc65 更新README.md,新增GitHub和官方仓库链接,优化内容结构 2024-11-10 01:15:34 +08:00
3341505715 更新LiteyukiBot克隆命令中的镜像链接 2024-11-09 23:52:17 +08:00
bdde9c45fd 优化配置文件格式和清理无用调试信息,修改gitea仓库路径 2024-11-09 23:51:35 +08:00
7bf94a15c8 📝 文档新增插件通信部分内容 2024-10-26 02:34:58 +08:00
4510477026 Merge branch 'main' of https://github.com/LiteyukiStudio/LiteyukiBot 2024-10-23 01:06:47 +08:00
86e50e369b 初步对Uninfo的支持 2024-10-23 01:04:42 +08:00
796fc6f233 Merge pull request #88 from EillesWan/main
🌠也许,大家的测试环境都是*nix?
2024-10-22 13:36:57 +08:00
金羿ELS
80c6875567 🌠也许,大家的测试环境都是*nix?
Update file.py
2024-10-22 13:34:09 +08:00
ab89cd1c72 性能提升200倍 2024-10-20 23:37:40 +08:00
5e454bc971 性能提升200倍 2024-10-20 23:36:49 +08:00
70bfb0fcee 性能提升200倍 2024-10-20 23:35:44 +08:00
13b95c2732 🐛 修复一些细节小问题 2024-10-20 20:59:30 +08:00
ef5866343d add: nonebot-plugin-gotify 2024-10-20 04:12:17 +08:00
d5ccd105a2 add: nonebot-plugin-gotify 2024-10-20 03:11:26 +08:00
20ad8dc53f 🐛 hotfix: from mypy import 2024-10-19 21:15:37 +08:00
de9c91d8bd 🐛 hotfix: from mypy import 2024-10-19 21:12:55 +08:00
6b64a0c379 🐛 hotfix: from mypy import 2024-10-19 21:10:18 +08:00
f117da7ff3 🎨 更新轻雪依赖版本 2024-10-14 20:57:30 +08:00
f548a07230 📝 文档删除常规部署,强制使用虚拟环境 2024-10-14 20:51:37 +08:00
e2e53c21fa 📝 文档删除常规部署,强制使用虚拟环境 2024-10-14 01:03:06 +08:00
3eaf23a56b 📝 文档删除常规部署,强制使用虚拟环境 2024-10-14 01:02:57 +08:00
4a5dd1f727 🐛 修复一些细节小问题 2024-10-14 00:57:33 +08:00
c2cb416b4e 🐛 hotfix: ubl 2024-10-13 17:44:24 +08:00
5cd528d5e9 🐛 hotfix: ubl 2024-10-13 17:44:17 +08:00
980fca650b 🐛 hotfix: ubl 2024-10-13 13:44:07 +08:00
9c525141f6 分离magicocacroterline 2024-10-13 02:56:29 +08:00
3d218a0e8d Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/liteyuki_plugins/nonebot/__init__.py
#	src/liteyuki_plugins/nonebot/nb_utils/adapter_manager/__init__.py
#	src/liteyuki_plugins/nonebot/nb_utils/adapter_manager/onebot.py
#	src/liteyuki_plugins/nonebot/nb_utils/adapter_manager/satori.py
#	src/liteyuki_plugins/nonebot/nb_utils/driver_manager/__init__.py
#	src/liteyuki_plugins/nonebot/nb_utils/driver_manager/auto_set_env.py
#	src/liteyuki_plugins/nonebot/nb_utils/driver_manager/defines.py
2024-10-13 02:55:04 +08:00
db385f597b 分离magicocacroterline 2024-10-13 02:54:47 +08:00
98a9d6413a 分离magicocacroterline 2024-10-13 02:51:33 +08:00
a77f97fd4b 🐛 hotfix: 移除状态卡片Linux下非储存分区 2024-09-29 22:57:04 +08:00
e6ea1b700f 🐛 hotfix: 天气拉取异常 2024-09-29 22:52:34 +08:00
596f4d06ea 🐛 bug: 修复状态卡片显示问题 2024-09-29 22:35:24 +08:00
8e3d3b3b5d Merge remote-tracking branch 'origin/main' 2024-09-27 00:50:05 +08:00
a34ad87e01 todo: add todo file 2024-09-27 00:49:54 +08:00
6c4c7f34cd 📝 Update resource_publish_en.yml 2024-09-22 01:56:00 +08:00
0c859957b4 🐛 docs: 添加发布插件表单 2024-09-22 01:48:53 +08:00
fbb9ed82ee 🐛 docs: 添加发布插件表单 2024-09-22 01:42:03 +08:00
b469c9420e 🐛 docs: 添加发布插件表单 2024-09-22 01:38:44 +08:00
aa4d930cc4 🐛 docs: 添加发布插件表单 2024-09-21 23:16:37 +08:00
76be748160 :docs: Update resource_publish_zh.yml 2024-09-21 22:36:04 +08:00
a9dd37b8a5 📝 Update resource_publish_zh.yml 2024-09-21 22:32:47 +08:00
5900d621f2 Create resource_publish_zh.yml 2024-09-21 22:28:58 +08:00
7442a3651b Create config.yml 2024-09-21 22:28:13 +08:00
413f438689 Rename resource_publish.md to resource_publish.yml 2024-09-21 22:21:20 +08:00
1fc4999b09 :docs: add resource_publish to resource_publish.md 2024-09-21 22:20:43 +08:00
975446a096 Create resource_publish 2024-09-21 22:19:34 +08:00
98cdd2f4b8 🐛 docs: 商店点击主页改为新窗口,修复插件商店仅轻雪不显示的问题 2024-09-21 03:22:40 +08:00
c0beec0429 🐛 docs: 商店点击主页改为新窗口,修复插件商店仅轻雪不显示的问题 2024-09-21 03:16:32 +08:00
614d78b3fa 🐛 docs: 商店点击主页改为新窗口,修复插件商店仅轻雪不显示的问题 2024-09-21 03:12:32 +08:00
24b0f345e4 📦 docs: 主页添加开发按钮 2024-09-21 02:51:59 +08:00
0ae10aa1b2 Merge remote-tracking branch 'origin/main' 2024-09-21 02:49:11 +08:00
9fe7478840 📦 docs: 修复文档404 2024-09-21 02:49:01 +08:00
liteyuki-flow[bot]
efca0bc7b3 📦 发布资源: 测试资源包2 2024-09-18 01:41:24 +08:00
50c5e99b98 📦 docs: 修改发布按钮样式 2024-09-18 01:38:13 +08:00
7415efcc90 📦 docs: 修改发布按钮样式 2024-09-18 01:32:12 +08:00
5bb4584e6a Merge remote-tracking branch 'origin/main' 2024-09-18 01:31:13 +08:00
795a6f3f76 📦 docs: 修改发布按钮样式 2024-09-18 01:31:03 +08:00
liteyuki-flow[bot]
fa74e08514 📦 发布资源: 测试资源包2 2024-09-18 01:30:26 +08:00
e6cf6e0c68 📦 docs: 修改发布按钮样式 2024-09-18 01:25:43 +08:00
6789c16773 📦 docs: 修改发布按钮样式 2024-09-18 01:24:32 +08:00
cdea0f8563 📦 docs: 修改发布按钮样式 2024-09-18 01:23:21 +08:00
9df55671ac 📦 docs: 修改发布按钮样式 2024-09-18 01:22:08 +08:00
d96c6f13c1 📦 docs: 修改发布按钮样式 2024-09-18 01:05:23 +08:00
bce1bf8704 Merge remote-tracking branch 'origin/main' 2024-09-18 01:04:47 +08:00
8eb626b8da 📦 docs: 修改发布按钮样式 2024-09-18 01:04:37 +08:00
liteyuki-flow[bot]
e6505d335b 📦 发布资源: 测试资源包2 2024-09-18 00:59:47 +08:00
c8cb341afb Merge remote-tracking branch 'origin/main' 2024-09-17 16:30:30 +08:00
e99cb88b13 📦 docs: 修改发布按钮样式 2024-09-17 16:30:19 +08:00
liteyuki-flow[bot]
78c3e299d0 📦 发布资源: 轻雪Kakyo语言包 托管版 2024-09-17 16:27:24 +08:00
23338437e9 📦 docs: 资源商店新增发布资源功能 2024-09-17 16:23:07 +08:00
f95899aebd 📦 docs: 资源商店新增发布资源功能 2024-09-17 16:21:45 +08:00
5df10c66b6 📦 docs: 资源商店新增发布资源功能 2024-09-17 16:21:18 +08:00
811d1594cd 📦 docs: 资源商店新增发布资源功能 2024-09-17 16:19:47 +08:00
c162208638 📦 docs: 资源商店新增发布资源功能 2024-09-17 16:16:29 +08:00
679d6597d8 Merge remote-tracking branch 'origin/main' 2024-09-17 16:14:18 +08:00
f402799f28 📦 docs: 资源商店新增发布资源功能 2024-09-17 16:14:08 +08:00
liteyuki-flow[bot]
60542d7426 📦 发布资源: 轻雪Kakyo语言包 稳定版 2024-09-17 16:06:05 +08:00
db1fb58717 📦 docs: 资源商店新增发布资源功能 2024-09-17 16:01:19 +08:00
7d5675ec97 Merge remote-tracking branch 'origin/main' 2024-09-17 15:58:56 +08:00
d8c50752f7 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:58:45 +08:00
liteyuki-flow[bot]
c674b837bb 📦 发布资源: 测试资源包 2024-09-17 15:53:47 +08:00
d867996072 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:53:17 +08:00
7ef36c6933 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:52:32 +08:00
982aae4dbf 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:50:10 +08:00
b5d3c6aaa8 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:48:02 +08:00
5537bc32df 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:45:43 +08:00
5c0c723c5d 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:33:32 +08:00
0ed3b307d9 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:31:59 +08:00
53a603d4ee 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:26:21 +08:00
fbf906bea7 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:16:54 +08:00
a87e8bc3e8 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:04:19 +08:00
a16a67dbc9 📦 docs: 资源商店新增发布资源功能 2024-09-17 15:03:48 +08:00
4c2231feb5 📦 docs: 资源商店新增发布资源功能 2024-09-17 14:59:49 +08:00
3932dd60da 📦 docs: 资源商店新增发布资源功能 2024-09-17 14:57:27 +08:00
3c6380cb82 📦 docs: 资源商店新增发布资源功能 2024-09-17 14:55:05 +08:00
2612f99f35 📦 docs: 资源商店新增发布资源功能 2024-09-17 14:52:26 +08:00
0b4b9a6241 📦 docs: 资源商店新增发布资源功能 2024-09-17 14:48:48 +08:00
2d100885ee 📦 docs: 资源商店新增发布资源功能 2024-09-17 14:46:06 +08:00
cb335720b7 📦 docs: 资源商店新增发布资源功能 2024-09-17 14:44:43 +08:00
dc8ad30b84 📦 docs: 资源商店新增发布资源功能 2024-09-17 14:42:22 +08:00
09e00652c3 📦 docs: 资源商店新增发布资源功能 2024-09-17 14:40:54 +08:00
b5b15c82f8 📦 docs: 资源商店新增发布资源功能 2024-09-17 14:37:45 +08:00
72e71124b8 📝 docs: 资源商店新增发布资源功能 2024-09-17 01:35:26 +08:00
d2be2acc95 📝 docs: add egg 2024-09-15 00:20:22 +08:00
d95614e960 📝 使用复数代替单数visitor 2024-09-10 08:17:24 +08:00
dad9482d7a 📝 update readme 2024-09-07 04:42:45 +08:00
fff5d09ad9 📝 docs: remove docstring from src of docs 2024-09-06 20:05:29 +08:00
e6ffd1fcc0 📝 docs: remove docstring from src of docs 2024-09-06 19:57:53 +08:00
a97747b7c4 Merge remote-tracking branch 'origin/main' 2024-09-06 19:50:15 +08:00
1921dcd023 📝 docs: remove docstring from src of docs 2024-09-06 19:50:01 +08:00
18af1d00bd 📝 update i18n.ts 2024-09-05 11:45:12 +08:00
30cdc1da23 Merge pull request #73 from ElapsingDreams/main
📝更新轻雪文档url
2024-09-04 20:45:32 +08:00
ElapsingDreams
bb84958ce4 📝更新轻雪文档url
https://bot.liteyuki.icu/usage/  -->  https://bot.liteyuki.icu/
2024-09-04 20:40:31 +08:00
ElapsingDreams
44de3fd00a 📝更新轻雪文档url
https://bot.liteyuki.icu/usage/  -->  https://bot.liteyuki.icu/usage/basic.html
2024-09-04 20:34:13 +08:00
39b1920532 📝 docs: 修改侧边栏选项 2024-09-04 19:15:11 +08:00
aa591ec29e 📝 更新开发规范 2024-09-04 19:10:32 +08:00
310c3f065d 📝 更新开发规范 2024-09-04 03:23:18 +08:00
2311ef82c3 🐛 fix: logo display error 2024-09-04 02:57:21 +08:00
b4b931fc95 📝 更新开发规范 2024-09-04 02:43:44 +08:00
d1b887fcaa 📝https://github.com/jooy2/vitepress-sidebar/issues/170#event-14113011263一起更新 2024-09-03 22:26:58 +08:00
5a2990770c 📝 新增gitea数据 2024-09-02 21:28:08 +08:00
1d0f0a2539 📝 新增gitea数据 2024-09-02 21:27:41 +08:00
dbc4d83b08 📝 新增gitea数据 2024-09-02 21:27:28 +08:00
da905d21bd 📝 新增gitea数据 2024-09-02 21:26:11 +08:00
7d91079500 🚀 workflows: 新增gitea工作流 2024-09-02 17:08:29 +08:00
81a006a308 🐛 [docs]: 增加访客记录 2024-09-02 11:20:53 +08:00
be59e241c6 🐛 [docs]: 增加访客记录 2024-09-02 00:13:37 +08:00
e493139d85 🐛 [docs]: 英文文档未适配暗色模式的问题 2024-09-01 22:58:24 +08:00
1f59ec2ef9 📝 [docs]: 新增统计信息 2024-09-01 22:52:00 +08:00
20d05f609d 📝 [docs]: 新增统计信息 2024-09-01 22:42:20 +08:00
0f9683de89 📝 [docs]: 新增统计信息 2024-09-01 22:30:46 +08:00
c805db3371 📝 [docs]: 新增在线展示 2024-09-01 22:14:09 +08:00
58d0d12c1f 📝 [docs]: 新增在线展示 2024-09-01 22:00:17 +08:00
359683dbae 📝 [docs]: 新增在线展示 2024-09-01 21:21:04 +08:00
94cab8b743 📝 [docs]: 新增在线展示 2024-09-01 21:16:45 +08:00
837447b6e4 📝 [docs]: 新增在线展示 2024-09-01 20:45:06 +08:00
ad52eade07 📝 [docs]: 新增在线展示 2024-09-01 20:44:50 +08:00
9cae3edb6b 📝 [docs]: 新增在线展示 2024-09-01 20:39:51 +08:00
0860b61ccd 📝 [docs]: 新增在线展示 2024-09-01 18:29:00 +08:00
d125c52b50 📝 [docs]: 新增在线展示 2024-09-01 18:27:45 +08:00
d485e095ae 📝 [docs]: 新增在线展示 2024-09-01 18:25:37 +08:00
499caca7e3 📝 [docs]: 新增在线展示 2024-09-01 17:57:21 +08:00
83a2d36209 📝 [docs]: 新增在线展示 2024-09-01 17:20:33 +08:00
8b77ced05e 📝 [docs]: 新增在线展示 2024-09-01 16:52:10 +08:00
49a9617f08 📝 [docs]: 修改开源协议 2024-09-01 15:45:03 +08:00
06aa919d9b 📝 [docs]: 修改开源协议 2024-09-01 15:34:52 +08:00
77b77c285b 📝 [docs]: 重新更改开发文档侧边栏顺序 2024-09-01 15:31:22 +08:00
bdc32b26fe 🐛 [plugin]: 暂时关闭轻雪推送功能 2024-09-01 13:22:12 +08:00
736125f4ee 🐛 [plugin]: remove crt-util 2024-09-01 11:27:39 +08:00
89cb75f105 🐛 [plugin]: 修复htmlredner造成的错误 2024-09-01 11:21:45 +08:00
34a6261f27 🐛 [plugin]: 移除sign状态插件 2024-09-01 11:06:30 +08:00
ae18bfaee1 🐛 [channel]: 暂停通道接收功能 2024-09-01 11:05:01 +08:00
8510b0ed3f 🐛 [utils]: htmlrender load error 2024-09-01 11:02:58 +08:00
967f1a0e5b 🐛 [utils]: htmlrender load error 2024-09-01 10:59:52 +08:00
6c1fc62ef1 🔥 [plugin]: 移除liteyuki_docs 2024-09-01 10:53:42 +08:00
433c6b3b85 📝 新增开发规范 2024-09-01 10:51:31 +08:00
ee1ae5a071 📝 新增开发规范 2024-09-01 10:35:54 +08:00
49a15d512e 📝 新增开发规范 2024-09-01 10:35:26 +08:00
fd1d73cc32 📝 添加项目结构说明 | add project structure description 2024-09-01 10:23:06 +08:00
29c2aa9404 📝 fix docs index usage 404 2024-09-01 09:46:47 +08:00
0ec1195930 📝 修复中文文档 主页 使用手册404的问题 2024-09-01 09:45:50 +08:00
d7a625bedb 📝 修正英文文档中的liteyuki docstring 跳转路径 2024-09-01 08:27:50 +08:00
3854376210 📝 修正中文文档中的Liteyuki docstring跳转路径 2024-09-01 08:26:47 +08:00
5253d0e515 Merge pull request #71 from ElapsingDreams/main
暂时使用nonebot_plugin_htmlrender以解决issue#68
2024-09-01 08:14:05 +08:00
Envision
3bc7fa82b1 暂时使用nonebot_plugin_htmlrender以解决issue#68 2024-09-01 01:11:24 +08:00
7d98d5819d 🐛 点击 编辑这页 跳转后404 2024-09-01 00:16:34 +08:00
8316c0ff63 📝 修改函数头 2024-09-01 00:03:20 +08:00
315b8c91e5 Merge pull request #70 from ElapsingDreams/main
🐛“__version__”
2024-08-31 23:52:58 +08:00
ElapsingDreams
405eb10a8a 🐛“__version__” 2024-08-31 22:53:08 +08:00
c3072e93c7 📝 修复暗黑模式下商店物品显示对比度不高的问题 2024-08-31 22:06:04 +08:00
ae34ff622d 📝 插件商店支持 2024-08-31 21:50:58 +08:00
d2704818d9 📝 新增开发指南 2024-08-31 20:23:45 +08:00
2ab4184314 📝 新增开发指南 2024-08-31 20:21:26 +08:00
9aade6599c 📝 文档初步大迁移 vuepress -> vitepress 2024-08-31 19:51:34 +08:00
2f87b06c83 📝 文档初步大迁移 vuepress -> vitepress 2024-08-31 19:07:34 +08:00
8bb3f15bd9 📝 文档初步大迁移 vuepress -> vitepress 2024-08-31 19:05:37 +08:00
7f198c83b5 📝 文档初步大迁移 vuepress -> vitepress 2024-08-31 18:57:48 +08:00
f70c75e9c4 ⬇️ 更新文档样式 2024-08-31 16:17:27 +08:00
be5a4b270d ⬇️ 更新文档样式 2024-08-29 15:50:26 +08:00
50c0216435 ⬇️ 更新文档样式 2024-08-29 14:23:13 +08:00
4910de74fd Merge remote-tracking branch 'origin/main'
# Conflicts:
#	.github/workflows/deploy-docs.yml
#	docs/dev/api/README.md
#	docs/dev/api/bot/README.md
#	docs/dev/api/bot/lifespan.md
#	docs/dev/api/comm/README.md
#	docs/dev/api/comm/channel.md
#	docs/dev/api/comm/event.md
#	docs/dev/api/comm/storage.md
#	docs/dev/api/config.md
#	docs/dev/api/core/README.md
#	docs/dev/api/core/manager.md
#	docs/dev/api/dev/README.md
#	docs/dev/api/dev/observer.md
#	docs/dev/api/dev/plugin.md
#	docs/dev/api/exception.md
#	docs/dev/api/log.md
#	docs/dev/api/message/README.md
#	docs/dev/api/message/event.md
#	docs/dev/api/message/matcher.md
#	docs/dev/api/message/on.md
#	docs/dev/api/message/rule.md
#	docs/dev/api/message/session.md
#	docs/dev/api/mkdoc.md
#	docs/dev/api/plugin/README.md
#	docs/dev/api/plugin/load.md
#	docs/dev/api/plugin/manager.md
#	docs/dev/api/plugin/model.md
#	docs/dev/api/utils.md
#	docs/en/dev/api/README.md
#	docs/en/dev/api/bot/README.md
#	docs/en/dev/api/bot/lifespan.md
#	docs/en/dev/api/comm/README.md
#	docs/en/dev/api/comm/channel.md
#	docs/en/dev/api/comm/event.md
#	docs/en/dev/api/comm/storage.md
#	docs/en/dev/api/config.md
#	docs/en/dev/api/core/README.md
#	docs/en/dev/api/core/manager.md
#	docs/en/dev/api/dev/README.md
#	docs/en/dev/api/dev/observer.md
#	docs/en/dev/api/dev/plugin.md
#	docs/en/dev/api/exception.md
#	docs/en/dev/api/log.md
#	docs/en/dev/api/message/README.md
#	docs/en/dev/api/message/event.md
#	docs/en/dev/api/message/matcher.md
#	docs/en/dev/api/message/on.md
#	docs/en/dev/api/message/rule.md
#	docs/en/dev/api/message/session.md
#	docs/en/dev/api/mkdoc.md
#	docs/en/dev/api/plugin/README.md
#	docs/en/dev/api/plugin/load.md
#	docs/en/dev/api/plugin/manager.md
#	docs/en/dev/api/plugin/model.md
#	docs/en/dev/api/utils.md
#	litedoc/__main__.py
#	litedoc/docstring/docstring.py
#	litedoc/output.py
#	litedoc/style/markdown.py
#	litedoc/syntax/astparser.py
2024-08-29 14:20:19 +08:00
f12b6854b7 ⬇️ 更新文档样式 2024-08-29 14:19:39 +08:00
b0b61fbaf7 ⬇️ 2024-08-29 13:54:24 +08:00
7c0b0df6ed ⬇️ 2024-08-29 13:52:19 +08:00
cb3ee4b72f ⬇️ 2024-08-29 13:50:12 +08:00
3a3ef4d6ae Merge pull request #69 from ElapsingDreams/patch-1
🌐 翻译修正
2024-08-25 14:26:08 +08:00
ElapsingDreams
9b3dea840e 🌐 翻译修正
🌐 翻译修正
Weather service by QWeather
来源:和风天气官方文档
2024-08-25 13:01:22 +08:00
ca89fa7efd ⬇️ 修复部分依赖问题,目前请勿使用清华镜像源安装 2024-08-24 21:19:51 +08:00
4705eac79c 📦 插件商店上新:liteyukibot-plugin-htmlrender及liteyukibot-plugin-lagrange 2024-08-24 11:57:53 +08:00
ebe0c5bcbb 📦 插件商店上新:liteyukibot-plugin-htmlrender及liteyukibot-plugin-lagrange 2024-08-24 11:39:10 +08:00
93c287bbd9 Merge pull request #67 from EillesWan/main
__liteyuki_plugin_meta__支持
2024-08-24 01:46:39 +08:00
EillesWan
0defb00ede 🧨新增未指定metadata的空判断 2024-08-24 01:43:35 +08:00
EillesWan
81c1d0286d 🎇新增依赖 liteyukibot-plugin-htmlrender 以便兼容其他需要 nonebot-plugin-htmlrender 渲染的插件 2024-08-24 01:14:46 +08:00
EillesWan
fb25005bd5 __liteyuki_plugin_meta__支持 2024-08-24 01:09:54 +08:00
2eb5aae23f [nonebot-plugin]状态提供更多品牌的cpu支持 2024-08-23 23:47:01 +08:00
a7d0560932 Merge pull request #66 from EillesWan/main
👻新增内置的页面渲染组件
2024-08-23 20:50:57 +08:00
EillesWan
391f112bb3 😅少改个东西.给改过去 2024-08-23 18:10:09 +08:00
EillesWan
bc8d13ba8a 😅不小心多改了个东西,给改回来 2024-08-23 18:08:11 +08:00
EillesWan
e8ec2ee28a Merge branch 'main' of https://github.com/LiteyukiStudio/LiteyukiBot 2024-08-23 17:58:49 +08:00
EillesWan
b6c8fcbccd 🧐内置页面渲染组件,弃用nonebot-plugin-htmlrender,避免playwright对多平台的不友好绑架 2024-08-23 17:58:36 +08:00
71476560e4 📝 商店新增anti-dislink插件和tag展示 2024-08-22 10:41:41 +08:00
aa2d182840 🐛 修复轻雪与NoneBot对接回复的错误 2024-08-22 10:10:03 +08:00
4bf8512a7d 新增on_keywords 2024-08-22 09:35:02 +08:00
a3a31a2c94 🔥 移除通道的部分特性 2024-08-22 07:23:44 +08:00
9ed4c1abb1 📝 文档新增源代码展示 2024-08-21 18:05:04 +08:00
a9c6ea0452 🐛 修复npm无法显示的问题 2024-08-21 17:59:21 +08:00
9e2bbe2e5c 🐛 修复npm无法显示的问题 2024-08-20 21:39:01 +08:00
598bff8c49 擴展event字段 2024-08-20 20:38:10 +08:00
eb7c8300fa 擴展event字段 2024-08-20 20:30:50 +08:00
287ab63091 移除测试插件 2024-08-20 06:24:00 +08:00
0c942d9806 添加对主流框架的消息io支持 2024-08-20 06:20:41 +08:00
237789e0d4 🚀 测试文档工作流 2024-08-19 23:50:15 +08:00
e656fa6a48 🚀 测试文档工作流 2024-08-19 23:49:21 +08:00
6dcb085b53 🚀 测试文档工作流 2024-08-19 23:47:39 +08:00
55a427e344 📝 修复生成文档中self多出类型注解的问题,修复__init__丢失的问题 2024-08-19 10:24:13 +08:00
43eef20b71 📝 修复生成文档中self多出类型注解的问题,修复__init__丢失的问题 2024-08-19 10:22:24 +08:00
b8fdb4146e 📝 修复生成文档中self多出类型注解的问题,修复__init__丢失的问题 2024-08-19 10:09:38 +08:00
cdbede7135 📝 修复生成文档中self多出类型注解的问题,修复__init__丢失的问题 2024-08-19 10:04:24 +08:00
85a3a9ad52 📝 生成了api文档 2024-08-19 09:55:47 +08:00
943e0c2665 修正工作流 2024-08-19 09:43:46 +08:00
fd4d680e87 修正工作流 2024-08-19 00:14:34 +08:00
0b763135c9 Merge remote-tracking branch 'origin/main' 2024-08-19 00:12:12 +08:00
832cc2ec44 修正工作流 2024-08-19 00:11:54 +08:00
3160b4be69 添加依赖版本检测 2024-08-19 00:06:24 +08:00
775596b5bf 🔥 remove melobot 2024-08-19 00:00:12 +08:00
84782a92d8 ⬆️ 更新nonebot依赖版本 2024-08-18 23:54:35 +08:00
6e817111cb 🐛 修复依赖pdm错误的问题 2024-08-18 23:52:59 +08:00
cd8d631348 📝 添加快速启动说明 2024-08-18 23:52:26 +08:00
af37e61d05 📝 修复中文文档 快速部署 路由错误 2024-08-18 23:43:48 +08:00
803b65e08e add color plugin name 2024-08-18 23:39:19 +08:00
aa9abde63a 🔖 liteyuki 6.3.6 2024-08-18 21:45:59 +08:00
9c35abc6e2 🔖 liteyuki 6.3.6 2024-08-18 21:45:11 +08:00
1af95a15aa 🔖 liteyuki 6.3.6 2024-08-18 21:29:19 +08:00
d2b693b1e0 Merge remote-tracking branch 'origin/main' 2024-08-18 21:26:29 +08:00
b05bbf2f19 🐛 Linux下创建子进程出错的问题 fork -> spawn 2024-08-18 21:26:19 +08:00
37ed3b0824 🐛 修复注册失败的问题 2024-08-18 19:58:36 +08:00
b56ec5ce38 Merge remote-tracking branch 'origin/main' 2024-08-18 11:51:18 +08:00
1fb3f6cd58 🐛 修复pdm依赖缺失 2024-08-18 11:51:07 +08:00
1dfe1a5819 🔖 发布NoneBot插件 liteyukibot-plugin-nonebot 2024-08-18 08:12:29 +08:00
5d194b8ebe 新增插件类型枚举 2024-08-18 08:06:25 +08:00
78810d2ca8 新增开发者模式,快速运行插件 2024-08-18 05:19:52 +08:00
87d4202ed3 新增开发者模式,快速运行插件 2024-08-18 05:11:10 +08:00
1d0b18291e 新增开发者模式,快速运行插件 2024-08-18 05:10:57 +08:00
16df5706ff 🚸 添加发布工作流 2024-08-18 04:40:02 +08:00
1b24157f08 🚸 添加发布工作流 2024-08-18 04:37:58 +08:00
8ba50b7bd6 🚸 添加发布工作流 2024-08-18 04:34:54 +08:00
502ccb46bb 🚸 添加发布工作流 2024-08-18 04:29:57 +08:00
fcbc410f1a 🚸 添加发布工作流 2024-08-18 04:26:50 +08:00
85a7c28a3a 🚸 添加发布工作流 2024-08-18 04:24:38 +08:00
7bd0da9316 🚸 添加发布工作流 2024-08-18 04:24:32 +08:00
212c338fcd 🚸 添加发布工作流 2024-08-18 04:22:55 +08:00
137270b886 🚸 添加发布工作流 2024-08-18 04:22:03 +08:00
079b940d8e 🚸 添加发布工作流 2024-08-18 04:18:20 +08:00
e396db67ce 🚸 添加发布工作流 2024-08-18 04:15:09 +08:00
f37b469ab9 🚸 添加发布工作流 2024-08-18 04:05:33 +08:00
0a3363ebce 🚸 添加发布工作流 2024-08-18 03:58:13 +08:00
53291822c0 🚸 添加发布工作流 2024-08-18 03:57:15 +08:00
5f5dcc7f99 🚸 添加发布工作流 2024-08-18 03:49:00 +08:00
9d27abfe04 🚸 添加发布工作流 2024-08-18 03:40:06 +08:00
4b4f030fe3 🚸 添加发布工作流 2024-08-18 03:31:29 +08:00
d5b0f947e0 🚸 添加发布工作流 2024-08-18 02:24:12 +08:00
9e6372185f Merge remote-tracking branch 'origin/main' 2024-08-18 02:20:21 +08:00
0e125f7c81 🚸 添加发布工作流 2024-08-18 02:20:09 +08:00
a3ea422ec3 支持通道通过通道在进程中传递 2024-08-18 01:25:11 +08:00
8d78e643e0 支持通道通过通道在进程中传递 2024-08-17 23:46:43 +08:00
aa9cae7008 Merge remote-tracking branch 'origin/main' 2024-08-17 19:12:22 +08:00
48085a946d 对通道类添加类型检查和泛型 2024-08-17 19:12:11 +08:00
8e27f6b9b0 对通道类添加类型检查和泛型 2024-08-17 19:10:03 +08:00
f980e77a4a 对通道类添加类型检查和泛型 2024-08-17 17:41:56 +08:00
01798f7b11 📝 fix docs router error 2024-08-17 04:50:27 +08:00
03057c8ef9 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	docs/.vuepress/public/js/zh/get_data.js
#	docs/.vuepress/theme.ts
#	docs/README.md
#	docs/en/README.md
2024-08-17 02:38:58 +08:00
ee851116d8 🐛 文档构建失败的问题 2024-08-17 02:38:24 +08:00
d367903946 📝 添加英文文档框架 2024-08-17 02:24:25 +08:00
66ade9efc6 Merge remote-tracking branch 'origin/main' 2024-08-17 01:28:08 +08:00
ff41e72378 📝 添加英文文档框架 2024-08-17 01:27:57 +08:00
169f1645a4 📝 Update dev_comm.md
📝
2024-08-17 00:39:49 +08:00
c3914b2b15 📝 添加shared_memory文档 2024-08-17 00:31:05 +08:00
b356524a9e 📝 添加shared_memory文档 2024-08-17 00:18:06 +08:00
85a13251a5 🐛 Ctrl+C 无法终止Channel接收线程的问题 2024-08-16 23:43:43 +08:00
0417805e46 🚚 移除通信测试插件 2024-08-16 22:53:54 +08:00
d3f1e35a12 📝 新增NoneBot插件 2024-08-16 21:53:12 +08:00
ff585ac7c2 新增线程安全共享内存储存器(注释) 2024-08-16 21:44:27 +08:00
1b692dd13f 新增线程安全共享内存储存器 2024-08-16 21:43:29 +08:00
dd00e6ecec Merge remote-tracking branch 'origin/main' 2024-08-16 21:38:34 +08:00
222250bc41 新增线程安全共享内存储存器 2024-08-16 21:38:22 +08:00
c36e706731 Merge pull request #64 from ElapsingDreams/main
🐛 继续修复liteyuki_status目录名过长布局问题, 💄 更新其显示布局
2024-08-16 03:00:01 +08:00
adc9b76688 🐛 修复无法重启进程的问题(未启动生命监视器) 2024-08-16 02:56:50 +08:00
Envision
2e3ea96972 🐛 继续修复liteyuki_status目录名过长布局问题, 💄 更新其显示布局 2024-08-15 23:22:25 +08:00
9b07d41f86 在结束进程时无法杀死进程的问题 2024-08-15 17:32:02 +08:00
65ad377099 Merge remote-tracking branch 'origin/main' 2024-08-15 16:40:58 +08:00
a61357f4e2 🐛 在结束进程时无法杀死进程的问题 2024-08-15 16:40:46 +08:00
60403b2a4f Merge pull request #63 from ElapsingDreams/main
🐛 尝试修复liteyuki_status插件目录过长的布局问题
2024-08-15 03:06:16 +08:00
Envision
624afa57ba 🐛 尝试修复liteyuki_status插件目录过长的布局问题 2024-08-15 02:59:03 +08:00
36a39e1ed7 Merge pull request #62 from ElapsingDreams/main
🌐 liteyuki_weather i18n (虽然仅支持三种语言)
2024-08-13 02:35:03 +08:00
ElapsingDreams
4a872c3435 Merge branch 'LiteyukiStudio:main' into main 2024-08-12 22:14:25 +08:00
Envision
90b9d1af1e 🌐 liteyuki_weather i18n (虽然仅支持三种语言) 2024-08-12 22:13:59 +08:00
551ca06ea7 Merge pull request #61 from ElapsingDreams/main
🩹非关键问题的简单修复:插件服务声明
2024-08-12 20:40:22 +08:00
ElapsingDreams
61c0cf2c2d Update weather_now.js 2024-08-12 20:01:29 +08:00
Envision
e801a99f67 🩹非关键问题的简单修复:插件服务声明 2024-08-12 19:13:03 +08:00
ElapsingDreams
beebfe7deb Merge branch 'LiteyukiStudio:main' into main 2024-08-12 16:48:24 +08:00
Envision
32e1963d5a 🎨热修复前端显示 🙈更新.gitignore 2024-08-12 16:47:16 +08:00
facf5bedb1 Merge pull request #59 from ElapsingDreams/main
🎨🐛 修复了一个潜在的前端显示bug,优化代码结构
2024-08-12 16:37:38 +08:00
Envision
035d43fb18 🎨🐛 修复了一个潜在的前端显示bug,优化代码结构 2024-08-12 16:04:27 +08:00
0d5f9fee52 📝 尝试修复文档有些段落显示两次的问题 2024-08-12 15:47:59 +08:00
3cb03fa4dc 📝 添加轻雪插件编写的一些注释 2024-08-12 06:07:01 +08:00
47ef3f2a49 📝 添加轻雪插件编写简易文档和轻雪进程通信说明 2024-08-12 05:50:12 +08:00
8568c7bb99 新增observer类和开发调试器 2024-08-12 05:26:36 +08:00
02cf058552 Merge remote-tracking branch 'origin/main' 2024-08-12 04:46:33 +08:00
c9157f0e2c 新增observer类和开发调试器 2024-08-12 04:45:59 +08:00
83325e63ea 添加默认配置文件,支持多种及多个配置文件 2024-08-12 02:47:53 +08:00
37b8d969b1 🐛 fix 通道无法在进程内传递消息的问题 2024-08-12 02:41:53 +08:00
298bdc7b8c Merge pull request #58 from ElapsingDreams/main
Add New function and layout for liteyuki_weather
2024-08-11 21:45:30 +08:00
Envision
81a191f8ba Add New function and layout for liteyuki_weather 2024-08-11 01:40:26 +08:00
c3fc5d429b 🐛 fix 多线程占用数据库的问题 2024-08-10 23:35:32 +08:00
b08c934c78 🐛 fix 多线程占用数据库的问题 2024-08-10 23:34:34 +08:00
1b1ddbdd8d 🐛 fix 多线程占用数据库的问题 2024-08-10 22:54:00 +08:00
0d16d53cb7 🐛 fix 通道类回调函数在进程间传递时无法序列号的问题 2024-08-10 22:42:01 +08:00
6c39ed8ab5 🐛 fix 通道类回调函数在进程间传递时无法序列号的问题 2024-08-10 22:36:30 +08:00
2f8999b5ad 🐛 fix 通道类回调函数在进程间传递时无法序列号的问题 2024-08-10 22:27:42 +08:00
7107d03b72 🐛 fix 通道类回调函数在进程间传递时无法序列号的问题 2024-08-10 22:25:41 +08:00
3bd40e7271 🐛 修复生命周期钩子函数的问题 2024-08-08 18:10:18 +08:00
8ace3e68f4 Merge remote-tracking branch 'origin/main' 2024-08-08 18:06:14 +08:00
f69feb1def 🐛 修复生命周期钩子函数的问题 2024-08-08 18:06:03 +08:00
9fb423d5e0 Merge pull request #55 from ElapsingDreams/main
📝Hot Fixed Dev judgment
2024-08-08 16:57:16 +08:00
ElapsingDreams
e9df67a661 📝Hot Fixed Dev judgment 2024-08-08 16:55:58 +08:00
b6871ea13a Merge pull request #54 from ElapsingDreams/main
Add Dev judgement
2024-08-08 16:49:00 +08:00
ElapsingDreams
cb84a7d0d9 Add Dev judgment 2024-08-08 16:41:23 +08:00
ElapsingDreams
25f7540f86 Add Dev judgment 2024-08-08 16:40:34 +08:00
c29a3fd6d4 🐛 数据库未进行迁移前初始化 2024-08-05 07:23:04 +08:00
ab48396db9 🐛 数据库未进行迁移前初始化 2024-08-05 07:13:41 +08:00
51982b63c3 📝 新增全球统计 2024-08-05 06:05:37 +08:00
2b537d27ec 📝 新增全球统计 2024-08-05 06:02:53 +08:00
16930e96aa 📝 新增全球统计 2024-08-05 06:00:07 +08:00
d63ba4943a 📝 新增全球统计 2024-08-01 13:17:48 +08:00
5d22f20ce3 📝 新增全球统计 2024-08-01 13:11:05 +08:00
2451849fd6 📝 新增全球统计 2024-08-01 12:33:36 +08:00
61680d9e87 📝 新增全球统计 2024-08-01 12:28:44 +08:00
850dd75822 📝 新增全球统计 2024-08-01 12:23:56 +08:00
6ba983fae3 🐛 fix: Channel的接收者过滤器的问题,优化重启部分 2024-07-31 02:35:05 +08:00
ca34f9c2a1 🐛 fix: Channel的接收者过滤器的问题,优化重启部分 2024-07-31 02:28:25 +08:00
0fb5b84392 🐛 移除liteyuki.channel.on_receive() wrapper对async和sync function的判断冗余代码 2024-07-27 10:21:31 +08:00
39a9c39924 添加liteyuki.channel.Channel通道,可安全跨进程通信 2024-07-27 10:12:45 +08:00
13692228c6 添加进程及生命周期管理器,添加轻雪框架支持 2024-07-26 14:35:47 +08:00
f22f4d229d 添加进程及生命周期管理器,添加轻雪框架支持 2024-07-24 02:58:16 +08:00
97dbf42a4d 添加进程及生命周期管理器,添加轻雪框架支持 2024-07-24 02:57:35 +08:00
041a219151 添加进程及生命周期管理器,添加轻雪框架支持 2024-07-24 02:54:59 +08:00
c137f2f916 添加进程及生命周期管理器,添加轻雪框架支持 2024-07-24 02:36:46 +08:00
6ef3b09ec9 📝 恢复pyproject.toml 2024-07-21 16:06:50 +08:00
263b78e995 📝 修改文档语言风格 2024-07-21 00:40:55 +08:00
0d87848a7e 📝 修改文档语言风格 2024-07-20 18:32:40 +08:00
44ad0832ba 📝 修改文档语言风格 2024-07-20 18:31:51 +08:00
93ced26e07 Merge pull request #52 from EillesWan/main
🎇修复liteyuki_pacman在执行disable时因session_id导致的反馈不当 & 🀄优化文言文翻译,修复翻译漏洞
2024-07-15 02:40:30 +08:00
EillesWan
363daf6251 🎇修复liteyuki_pacman在执行disable时因session_id导致的反馈不当
🀄优化文言文翻译,修复翻译漏洞
2024-07-15 02:31:47 +08:00
a6b1d1c9e0 Merge pull request #51 from EillesWan/main
🥠修复状态缓存出错的问题 & 📃规范化文言文本地化文本
2024-07-15 02:26:07 +08:00
EillesWan
576d8c23b3 🥠修复状态缓存出错的问题
🧾规范化文言文本地化文本
2024-07-15 02:07:15 +08:00
c232c6e5f6 插件商店及资源商店新增搜索功能 2024-07-15 00:22:52 +08:00
5d6ae52157 插件商店及资源商店新增搜索功能 2024-07-15 00:03:22 +08:00
01e6256ed4 插件商店及资源商店新增搜索功能 2024-07-14 23:50:31 +08:00
dbc114a529 插件商店及资源商店新增搜索功能 2024-07-14 13:46:54 +08:00
4d77af8f0c 插件商店及资源商店新增搜索功能 2024-07-14 13:43:47 +08:00
c36a925bb5 插件商店及资源商店新增搜索功能 2024-07-14 13:38:25 +08:00
605dd035d4 Merge pull request #49 from EillesWan/main
完善翻译文本 & 优化状态缓存
2024-07-14 02:38:51 +08:00
EillesWan
7526ae13d7 补一个错 2024-07-14 02:30:04 +08:00
EillesWan
0eb41f70d2 Merge branch 'main' of https://github.com/LiteyukiStudio/LiteyukiBot 2024-07-14 02:21:10 +08:00
EillesWan
a4c7ee738c 更改内容:
1. 更新文言文翻译
2. 删除重复的翻译键
3. 重写status缓存机制并black格式化
请注意:未经过本地测试,需要观察者辅助测试,谢谢!
2024-07-14 02:19:57 +08:00
8d4602c40d 📝 add more background rp 2024-07-13 01:26:32 +08:00
bb20d9623d 🐛 fix typo 2024-07-06 12:01:09 +08:00
73cc28d1cf 📝 update README.md 2024-07-06 11:29:05 +08:00
ae54cd923c 📝 update README.md 2024-07-06 11:25:36 +08:00
9d3c9a7d70 📝 update and rename Lisence to LICENSE 2024-07-06 10:59:22 +08:00
92a4274be7 Merge pull request #47 from Nanaloveyuki/main
📄 更新许可证 MIT -> LSO & LSO-AGC
2024-07-06 10:54:07 +08:00
Nanaloveyuki
4800b3f46c 📄 更新许可证文件
完成MIT->LSO的更迭工作
2024-07-06 10:35:47 +08:00
Nanaloveyuki
cce593b2f4 📄 移除原MIT许可 2024-07-06 10:32:08 +08:00
Nanaloveyuki
c491642713 📄 移除AGC非许可形式协议 2024-07-06 10:31:50 +08:00
Nanaloveyuki
5b4dd638a4 📄 移除原LSO许可 2024-07-06 10:31:31 +08:00
8440952167 📝 update repo url in src 2024-07-06 02:26:33 +08:00
4e4227e204 📝 update repo url 2024-07-06 02:25:12 +08:00
effb01d43c 📝 add 2nd license 2024-07-04 02:08:07 +08:00
28d730a2ca Merge pull request #46 from Nanaloveyuki/main
🌐 📝 日常优化文档, 提供多语言支持
2024-07-01 17:30:16 +08:00
Nanaloveyuki
0bd135a5c9 🌐 为statistics提供基础多语言支持 2024-07-01 16:08:15 +08:00
Nanaloveyuki
5522391942 Add files via upload 2024-07-01 16:00:46 +08:00
7576355e95 📝 商店新增轻雪Kakyo语言包 2024-06-30 03:28:28 +08:00
c9e518f2ed Merge pull request #45 from Nanaloveyuki/main
📄 更新反克隆文件`Anti-Gitcode-Clone.md`
2024-06-29 23:09:22 +08:00
Nanaloveyuki
593cf2407b Merge branch 'main' into main 2024-06-29 00:53:23 +08:00
Nanaloveyuki
bdb4c76d70 📄 Add Anti-Gitcode-Clone md-file 2024-06-29 00:40:14 +08:00
Nanaloveyuki
7cce805d39 📄 Delete Dis-CSDN-Gitcode.md 2024-06-29 00:25:59 +08:00
b9d3ecc15d Merge pull request #44 from Nanaloveyuki/main
📄 Add Anti-Gitcode-Clone md-file
2024-06-29 00:20:19 +08:00
Nanaloveyuki
ec5eae08f7 📄 Add Anti-Gitcode-Clone md-file 2024-06-29 00:15:44 +08:00
c1edf31577 🔥 create dependabot.yml 2024-06-27 04:17:39 +08:00
5262c04e46 🔥 小型重构 2024-06-26 13:53:15 +08:00
8b01943d14 🔥 小型重构 2024-06-26 13:52:04 +08:00
35823be13e 新增 stat rank 功能 2024-06-25 20:06:49 +08:00
4162ea33ff orm框架新增@db.on_save回调函数,用于检测数据库更新时的变动 2024-06-22 14:17:14 +08:00
1787ef4db7 📝 更新夜间logo 2024-06-19 23:38:53 +08:00
38b13611c9 📝 更新夜间logo 2024-06-19 23:31:11 +08:00
52fa143e75 📝 更新夜间logo 2024-06-19 22:30:27 +08:00
89047a0c8a 📝 更新小logo 2024-06-19 17:59:57 +08:00
9fbded7d6a 📝 部署部分文档优化 2024-06-19 12:34:38 +08:00
c657781599 🔥 移除测试工作流 2024-06-19 12:16:42 +08:00
ecbe1ff79e 新logo 2024-06-19 12:15:22 +08:00
d6811ab9b3 新logo 2024-06-18 23:57:15 +08:00
d45170db3e 新logo 2024-06-18 23:36:04 +08:00
6e63768c71 新logo 2024-06-18 23:32:58 +08:00
1424bc2cf6 Merge remote-tracking branch 'origin/main' 2024-06-18 23:25:33 +08:00
051fe3d15d 新logo 2024-06-18 23:25:23 +08:00
fcae485071 新logo 2024-06-18 23:23:44 +08:00
2e9a7fdf94 Merge pull request #42 from Nanaloveyuki/main
📝  主要更新06-17
2024-06-17 18:46:12 +08:00
Nanaloveyuki
570d7e18a4 Docs 主要更新: 优化表格
主要改动:
- 表格
- 标点符号
- 愚人节主页
2024-06-16 23:33:58 +08:00
8ac53970a3 Merge remote-tracking branch 'origin/main' 2024-06-16 02:02:25 +08:00
dd3e108e10 新logo 2024-06-16 02:01:53 +08:00
Snowykami
c29bd81ffb Merge pull request #41 from MiaoMioLint/patch-1
详细化 lyfunc.md
2024-06-13 09:54:15 +08:00
Nanaloveyuki
a1173e4d84 详细化 lyfunc.md 2024-06-13 09:47:13 +08:00
Snowykami
d877e30a05 📝 添加2次quote的解释 2024-06-10 07:33:44 +08:00
33ad54090d 新版本npm 2024-06-09 22:36:58 +08:00
83ee6cfdbd 新版本npm 2024-06-04 18:04:25 +08:00
f48971a0c4 🐛 orm框架在解析字段值时遇到None报错的问题 2024-06-04 18:00:38 +08:00
a4b71aa73c 🐛 orm框架在解析字段值时遇到None报错的问题 2024-06-03 17:58:46 +08:00
4b7df662e8 Merge remote-tracking branch 'origin/main' 2024-06-03 17:54:32 +08:00
4cd7b6718b 🐛 orm框架在解析字段值时遇到None报错的问题 2024-06-03 17:53:44 +08:00
Snowykami
ac2a94dda0 Merge pull request #40 from yuhan2680/patch-1
添加词库w
2024-06-03 13:54:18 +08:00
神楽坂小涵🍥
de7e65b32a 添加词库w 2024-06-03 13:52:34 +08:00
1b283261c3 词库性格区分 2024-06-02 23:21:14 +08:00
39cbfc1baa 词库性格区分 2024-06-02 23:18:00 +08:00
e563f18d31 词库性格区分 2024-06-02 23:15:38 +08:00
1d03b3f28f 词库性格区分 2024-06-02 23:12:24 +08:00
ae0025a203 词库性格区分 2024-06-02 23:09:59 +08:00
b5bd7acb7f 词库性格区分 2024-06-02 22:56:37 +08:00
657e7e52ac 📝 空字符串 2024-06-02 15:03:50 +08:00
d6341c88cd 📝 函数指引 2024-06-02 13:58:41 +08:00
bdb1191f9e 🐛 智障回复分词bug 2024-06-02 02:32:54 +08:00
3b29b67c0b 📝 函数指引 2024-06-02 02:23:00 +08:00
cc43e53c4b npm 会话开关功能新增群号可选项 2024-06-02 02:22:16 +08:00
a25c900d49 智障回复功能支持切断回复 2024-06-02 01:41:46 +08:00
206651da94 智障回复功能 2024-06-02 01:32:52 +08:00
be28116a98 优化状态卡片速度 2024-06-01 15:32:41 +08:00
4cdf29557c 新增启动时对本地仓库的检测 2024-06-01 15:26:13 +08:00
62928e47eb 新增启动时对本地仓库的检测 2024-06-01 15:25:54 +08:00
9b50b719d9 🚀 对lyfunction命令添加鉴权 2024-06-01 15:05:45 +08:00
def60bf298 对lyfunction命令添加鉴权 2024-06-01 15:01:14 +08:00
6496b6e463 对lyfunction命令添加鉴权 2024-06-01 15:01:04 +08:00
6ce4c972a0 对lyfunction命令添加鉴权 2024-06-01 14:16:04 +08:00
70e3c9968a 添加对lyfunction的支持 2024-05-31 23:15:40 +08:00
074882f092 添加对lyfunction的支持 2024-05-31 22:59:02 +08:00
c2b3018908 添加对lyfunction的支持 2024-05-31 22:42:04 +08:00
96c85d9dca Merge remote-tracking branch 'origin/main' 2024-05-31 19:17:31 +08:00
e15aafd781 添加对zip格式的资源包的支持,对function的支持 2024-05-31 19:17:25 +08:00
Snowykami
9e17b84a5d 🐛 一些单词复数形式 2024-05-28 18:28:20 +08:00
c66d470166 🐛 mdts反转义错误的问题 2024-05-27 18:32:58 +08:00
4deb7d11a1 新增开发者选项是否允许更新 2024-05-26 18:15:57 +08:00
6f069f83d4 📝 文档新增特性 2024-05-26 18:11:18 +08:00
fa53df1e8a 📝 文档新增特性 2024-05-26 17:25:20 +08:00
ba17f9d159 📝 文档新增特性 2024-05-26 17:19:32 +08:00
b558b51601 📝 文档新增特性 2024-05-26 17:12:06 +08:00
3ea0acd48b 修改启动逻辑和插件加载逻辑 2024-05-26 16:38:38 +08:00
c171873fa6 优化圆角样式 2024-05-25 12:09:54 +08:00
1ccf94883a 消息统计新增指定用户选项 2024-05-25 00:13:29 +08:00
b26f8e0d24 消息统计新增指定用户选项 2024-05-24 23:46:24 +08:00
5bc2725d1b 消息统计新增指定用户选项 2024-05-24 23:41:33 +08:00
8e06244311 🐛 修复了消息模型储存时类型不兼容的问题 2024-05-23 23:37:24 +08:00
0f35613e50 🐛 修复了消息模型储存时类型不兼容的问题 2024-05-23 23:32:02 +08:00
4cfad0b5ca 添加了外部资源热重载 2024-05-21 13:13:16 +08:00
c2593e71c0 Merge remote-tracking branch 'origin/main' 2024-05-20 23:31:45 +08:00
bb331232ca 添加了外部资源热重载 2024-05-20 23:31:11 +08:00
Snowykami
96e8293bf4 Merge pull request #39 from expliyh/qweather_on_satori
🐛 在使用 satori 适配器时不能响应例如 武汉天气 的指令
2024-05-20 13:54:04 +08:00
Expliyh
e13464cb7c 拼写错了(尴尬) 2024-05-20 08:51:21 +08:00
Expliyh
c5f8fbe86d 🐛 在使用 satori 适配器时不能响应例如 武汉天气 的指令 2024-05-20 08:22:06 +08:00
Expliyh
8667706377 使用 alc 发送例如 天气武汉 的回复 2024-05-20 08:20:20 +08:00
Snowykami
86e47ab226 Merge pull request #38 from expliyh/satori
对昨晚 PR 的一些修正
2024-05-19 23:27:43 +08:00
Expliyh
10c383d66a 删除无用的 print 2024-05-17 18:16:57 +08:00
Expliyh
4c65a308d6 🐛 当没有 .env 文件时意外加载错误的环境变量 2024-05-17 18:00:07 +08:00
Expliyh
246e43317f 📝 添加 onebot_v12_event_monitor 2024-05-17 17:53:38 +08:00
Expliyh
974b97b744 📝 初次启动生成默认配置文件时添加 satori 相关配置
📝 将适配器配置初始化和注册移动到 utils.adapter_manager
2024-05-17 17:51:42 +08:00
Expliyh
c914ddc0ee 📝 NapCat.Onebot 显示头像 2024-05-17 17:48:37 +08:00
Expliyh
6509b293db 📝 使用 driver_manager 自动管理启用的驱动器,无需手动配置环境变量 (当配置了环境变量时环境变量优先) 2024-05-17 16:33:48 +08:00
Expliyh
a72eeb4c3f 📝 使用 driver_manager自动管理启用的驱动器,无需手动配置环境变量 (当配置了环境变量时环境变量优先) 2024-05-17 16:26:30 +08:00
Expliyh
309397b72c 📝 将从事件中获取信息的工具函数移动到单独的 utils.event 2024-05-17 15:00:01 +08:00
Expliyh
077658c68d 📝 将 Satori 用户缓存更新日志的输出时机由原来的每次调用函数调整为每次用户信息发生变更 2024-05-17 14:42:37 +08:00
Expliyh
322ad19889 🐛 在使用 satori 时部分指令无响应
🐛 使用 onebot 时部分事件在 postprocessor 阶段报错
2024-05-17 14:18:55 +08:00
ab9d3d3d3e 🐛 satori.Adapter TypeError: 'HeartbeatMetaEvent' object is not subscriptable 2024-05-17 00:25:27 +08:00
Snowykami
06a109d2b5 Merge pull request #37 from expliyh/satori
试着添加了对于 NoneBot-Adapter-Satori 的支持
2024-05-16 23:31:54 +08:00
Expliyh
351743068a 判断配置文件是否启用satori 2024-05-16 22:15:04 +08:00
Expliyh
4e6532ff0d pacman和profile适配satori 2024-05-16 21:28:18 +08:00
Expliyh
eaf57f2c33 status适配satori 2024-05-16 21:17:10 +08:00
Expliyh
7abdac7c9c statistic适配satori 2024-05-16 20:28:46 +08:00
Expliyh
002df66878 weather适配satori(bind city未找到) 2024-05-16 20:22:07 +08:00
Expliyh
251bfaf410 Core适配satori 2024-05-16 20:09:20 +08:00
Expliyh
24722447da 使用satori时维护一个有昵称的用户列表
get_plugin_session_enable 判断当前使用的适配器
2024-05-16 19:20:54 +08:00
Expliyh
90e7a90bcf 添加 nonebot-adapter-satori~=0.11.5 2024-05-16 17:32:06 +08:00
6d3d3fc52c output to latest 2024-05-14 10:37:32 +08:00
e843d790b1 message 统计 2024-05-13 19:37:10 +08:00
c90ac1d21a message 统计 2024-05-12 03:04:26 +08:00
041ceb81d8 message 统计 2024-05-12 02:47:14 +08:00
c6f2a29320 使用webp背景图压缩资源包大小 2024-05-12 00:25:51 +08:00
0532d7592e Merge remote-tracking branch 'origin/main' 2024-05-12 00:23:36 +08:00
f9fe1922d4 使用webp背景图压缩资源包大小 2024-05-12 00:18:53 +08:00
Snowykami
88b5b55062 Update issue templates 2024-05-11 10:57:36 +08:00
afe501a06d 使用webp背景图压缩资源包大小 2024-05-11 10:05:01 +08:00
262 changed files with 1206 additions and 25742 deletions

View File

@@ -1,33 +0,0 @@
---
name: BUG 反馈
about: 使用轻雪时遇到了问题?
---
## 问题反馈
### **描述**
请详细描述一下你所遇到的bug
### **确保**
- [ ] 我已查阅所有issues没有相似或已被证实的内容
- [ ] 我已按照文档指引进行正确的操作,仍会复现该问题
### **预期效果**
你想要什么样的预期效果
### **实际效果**
实际上是怎么样的
### **运行环境**
- 系统及版本:
- Python环境
- commit哈希或版本
- 硬件信息(可选)
### **运行日志**
```
将相关日志粘贴到此处
```
### **严重等级**
致命|严重|警告|轻微

View File

@@ -1,49 +0,0 @@
name: 部署文档
on:
push:
branches:
# 确保这是你正在使用的分支名称
- main
permissions:
contents: write
jobs:
deploy-gh-pages:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
# 如果你文档需要 Git 子模块,取消注释下一行
# submodules: true
- name: 安装 pnpm
uses: pnpm/action-setup@v2
with:
run_install: true
version: 8
- name: 设置 Node.js
run: |-
cd docs
pnpm install
- name: 构建文档
env:
NODE_OPTIONS: --max_old_space_size=8192
run: |-
cd docs
pnpm run docs:build
> .vuepress/dist/.nojekyll
- name: 部署文档
uses: JamesIves/github-pages-deploy-action@v4
with:
# 这是文档部署到的分支名称
branch: gh-pages
folder: docs/.vuepress/dist

View File

@@ -0,0 +1,21 @@
name: Liteyuki PyPI Publish
on:
push:
tags:
- 'v*'
jobs:
liteyuki-pypi-publish:
name: upload release to PyPI (Nightly)
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v3
- uses: pdm-project/setup-pdm@v3
- name: Publish package distributions to PyPI
run: pdm publish

38
.github/workflows/pytest.yaml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Pytest API Testing
on:
push:
branches: [ "v7" ]
pull_request:
branches: [ "v7" ]
permissions:
contents: read
jobs:
Pytes-API-Testing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup uv
uses: astral-sh/setup-uv@v6
with:
version: "latest"
- name: Test with pytest
run: |
uv run pytest --junitxml=report/report.xml
- name: Archive Pytest test report
uses: actions/upload-artifact@v4
with:
name: SuperTest-test-report
path: report
- name: Upload Pytest report to GitHub
uses: actions/upload-artifact@v4
with:
name: Pytest-test-report
path: report

56
.gitignore vendored
View File

@@ -1,37 +1,27 @@
.venv/ # python and toolchains
.idea/ .mypy_cache/
.cache/
node_modules/
data/
db/
/resources/
__pycache__/ __pycache__/
*.pyc .pytest_cache/
*.pyo
*.pyd
*.pyw
/plugins/
_config.yml
config.yml
config.example.yml
compile.bat
liteyuki/resources/templates/latest-debug.html
# vuepress
.github
pyproject.toml
test.py # idea
line_count.py .idea/
.vscode/
.venv/
venv/
# nuitka # platform
main.build/ # macOS
main.dist/ **/.DS_Store
main.exe # windows
main.cmd Thumbs.db
docs/.vuepress/.cache/ # linux
docs/.vuepress/.temp/
docs/.vuepress/dist/
prompt.txt
# js # development
**/echarts.js .env
.env.*
plugins/
data/
configs/
config.yaml
config-dev.yaml
config-prod.yaml

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.12

View File

@@ -1,19 +1,16 @@
FROM python:3.11-bullseye FROM python:3.12-alpine
ENV TZ Asia/Shanghai
COPY docker/sources.list /etc/apt/sources.list
RUN apt-get update && apt-get install -y git
WORKDIR /liteyukibot WORKDIR /liteyukibot
COPY . /liteyukibot COPY main.py .
COPY pyproject.toml .
COPY liteyukibot/ .
COPY uv.lock .
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple RUN pip install uv
RUN apt-get install -y libnss3 libnspr4 libdbus-1-3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libxkbcommon0 libasound2 ENV UV_COMPILE_BYTECODE=1
EXPOSE 20216 RUN uv venv --python 3.12 && uv sync
CMD ["python", "main.py"] CMD [".venv/bin/python3", "main.py"]

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Snowykami
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

42
LISENCE Normal file
View File

@@ -0,0 +1,42 @@
LSO License
LiteyukiStudio Open Source License
---
Copyright © 2025 Liteyuki Studio & Snowykami
---
Any individual or organization that obtains a copy of this software is hereby granted, free of charge, the relevant rights under this license agreement.
These rights include, but are not limited to, the right to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the software.
This software and its related documentation files (hereinafter referred to as "this software". The "software" includes user manuals, technical documents, API documentation, sample code, etc.) are released in an open - source form on the Internet or other media platforms under this license agreement.
Anyone has the right to obtain a copy of this software through proper channels and distribute and/or use it in accordance with this license agreement.
In case of a conflict with other open - source or non - open - source licenses,
unless otherwise specifically stated, all conflicting parts shall be subject to this open - source license agreement.
The conflicting parts mainly include:
1. Principles of commerciality or profitability
2. Legal liability
3. Licensed ways of publication and distribution
During the process of software distribution and dissemination through media or their media exchanges,
this license agreement shall be retained by default and distributed and redistributed in the same way. If the distributed project does not include this license agreement, the project can still continue to use this license agreement without additional addition.
When processing or re - processing the software and its copies for profit purposes,
if this license agreement is used, the individual or organization to which the re - processed software belongs can decide on its own to change, add, or delete non - essential license terms.
The essential license terms include:
1. Distribution of rights and their scope of application
2. Disclaimer clause and its final interpretation
3. Copyright statement and its legal handling
However, when obtaining a copy of the software, the following points should still be noted:
- The above copyright notice and this license notice must be included in the software copy, and the software and its copies must be used in the same form as the original.
- When using the software, the copy must be presented publicly under the same license agreement. The software copy shall not be used for external profit under a non - original license agreement without the permission of the original author.
---
The software is provided "as is" without any warranty of any kind, either express or implied,
including but not limited to the warranty of merchantability and non - infringement for specific purposes.
In any case, the author or copyright owner shall not be liable for any claims, damages, or other liabilities arising from the use of the software, whether in contract litigation, infringement litigation, or other forms of litigation. The author and its copyright owner have the right to refuse compensation for any losses caused by the user for personal reasons.

View File

@@ -1,52 +1,80 @@
<div align="center"> <div align="center">
[//]: # (<img src="https://cdn.liteyuki.icu/static/img/logo.png" style="align-content: center; width: 50%; margin-top:10%;" alt="a">) [//]: # (<img src="https://cdn.liteyuki.org/logos/bot.svg" style="align-content: center; width: 50%; margin-top:10%;" alt="a">)
[![][banner]][lightyuki-link] [![][banner]][liteyuki-link]
<h2><a href="https://bot.liteyuki.icu"> <span style="color: #a2d8f4">轻雪</span> <span style="color: #d0e9ff">6</span></a></h2> <h2><a href="https://bot.liteyuki.org"> <span style="color: #a2d8f4">轻雪</span> <span style="color: #d0e9ff">7</span></a></h2>
<h4> <span style="color: #a2d8f4">✨ 轻量,高效,易于扩展✨</span></h4> <h4> <span style="color: #a2d8f4">✨ 轻量,高效,易于扩展✨</span></h4>
[![][OneBot]][onebot-link] [![][Liteyuki7.0]][liteyuki-link]
[![][Nonebot2]][nonebot-link] [![][Python3.12+]][python-link]
[![][Liteyuki6.0]][lightyuki-link]
[![][Python3.10+]][python-link]
[![][Usage]][usage-link] [![][Usage]][usage-link]
[![][Repo]][repo-link]
[![][Github]][github-link]
[![][LiteyukiLab]][liteyukilab-link]
![docs uptime](https://uptime.liteyuki.org/api/badge/8/uptime?labelPrefix=Docs+&style=for-the-badge)
- 基于[Nonebot2](https://github.com/nonebot/nonebot2),有良好的生态支持 **👇所有内容请访问👇**
- 开箱即用,无需复杂配置 [bot.liteyuki.org](https://bot.liteyuki.org)
- 新的点击交互模式,拒绝手打指令
- 可视化插件管理包管理,支持一键安装插件
- 支持OneBot标准通信但不限于此
- 自定义主题支持,满足审美需求
- 国际化支持,支持多种语言
<h3>👇所有内容已迁移至👇</h3>
<h2><a href="https://bot.liteyuki.icu">轻雪主页</a></h2>
</div> </div>
### 感谢 > 受限的自由才是真正的自由
- [Nonebot2](https://nonebot.dev)提供的框架支持
- [nonebot-plugin-htmlrender](https://github.com/kexue-z/nonebot-plugin-htmlrender)提供的渲染功能 ## 关于
- [nonebot-plugin-alconna](https://github.com/ArcletProject/nonebot-plugin-alconna)提供的命令解析功能 开发中
访问[轻雪7.0](https://bot.liteyuki.org)主页获取更多信息
## 特点及优势
- 化繁为简, 加速开发
- 轻量级,快速启动
- 模块化设计,易于扩展
## 服务及支持(敬请期待)
- 提供Liteyuki Cloud官方的容器化托管服务(SaaS),无需担心服务器问题
[OneBot]: https://img.shields.io/badge/OneBot-11/12-blue?style=for-the-badge [Liteyuki7.0]: https://img.shields.io/badge/Liteyuki-7.0-blue?style=for-the-badge
[Nonebot2]: https://img.shields.io/badge/Nonebot-2-red?style=for-the-badge [Python3.12+]: https://img.shields.io/badge/Python-3.12+-blue?style=for-the-badge
[Liteyuki6.0]: https://img.shields.io/badge/Liteyuki-6.0-blue?style=for-the-badge [Usage]: https://img.shields.io/badge/主页-文档-blue?style=for-the-badge
[Python3.10+]: https://img.shields.io/badge/Python-3.10+-blue?style=for-the-badge [Repo]: https://img.shields.io/badge/官方托管-仓库-blue?style=for-the-badge
[Usage]: https://img.shields.io/badge/文档-页面-blue?style=for-the-badge [Github]: https://img.shields.io/badge/Github-仓库-blue?style=for-the-badge
[onebot-link]:https://onebot.dev/ [LiteyukiLab]: https://img.shields.io/badge/轻雪社区-官方-blue?style=for-the-badge
[nonebot-link]:https://nonebot.dev/
[lightyuki-link]:/
[python-link]:https://www.python.org/ [python-link]:https://www.python.org/
[usage-link]:https://bot.liteyuki.icu/ [usage-link]:https://bot.liteyuki.org/
[banner]: https://socialify.git.ci/snowykami/LiteyukiBot/image?description=1&forks=1&issues=1&Plus&pulls=1&stargazers=1&theme=Auto&logo=https%3A%2F%2Fcdn.liteyuki.icu%2Fstatic%2Fimg%2Flogo.png [liteyuki-link]:https://bot.liteyuki.org/
[repo-link]:https://git.liteyuki.org/bot/app
[github-link]:https://github.com/LiteyukiStudio/LiteyukiBot
[liteyukilab-link]:https://lab.liteyuki.org/@LiteyukiBot
[banner]: https://socialify.git.ci/LiteyukiStudio/LiteyukiBot/image?description=1&forks=1&issues=1&Plus&pulls=1&stargazers=1&theme=Auto&logo=https%3a%2f%2fcdn.liteyuki.org%2flogos%2fbot.svg
## 开发环境配置
1. 项目使用uv进行包管理你也可以使用uv进行环境管理[安装uv](https://docs.astral.sh/uv/#installation)
2. 进入项目目录使用uv同步环境和依赖
```bash
uv sync --all # 安装包括dev和prod的所有依赖
```
3. VSCode扩展
- Python
- Mypy
- Ruff
4. 环境变量指定ENVIRONMENT=dev或prod或其他然后加载.env.{}文件,环境变量

12
docker-compose-dev.yaml Normal file
View File

@@ -0,0 +1,12 @@
services:
server:
container_name: liteyukibot
image: liteyukibot:v7
restart: always
volumes:
- ./plugins:/liteyukibot/plugins
- ./data:/liteyukibot/data
- ./configs:/liteyukibot/configs
- ./config.yaml:/liteyukibot/config.yaml
ports:
- "8090:8080"

View File

@@ -0,0 +1,16 @@
services:
liteyukibot:
container_name: bot
# Liteyuki latest: reg.liteyuki.org/bot/app:latest
# Liteyuki nightly: reg.liteyuki.org/bot/app:nightly
# GHCR latest: ghcr.io/liteyukistudio/bot-app:latest 暂未发布
# Docker Hub latest: docker.io/liteyukistudio/bot-app:latest 暂未发布
image: reg.liteyuki.org/bot/app:latest
restart: always
volumes:
- ./configs:/liteyukibot/configs # 配置目录,包含配置文件
- ./data:/liteyukibot/data # 数据目录,包含下载器安装的插件目录
- ./plugins:/liteyukibot/plugins # 外部插件目录,插件开发者可用
- ./config.yaml:/liteyukibot/config.yaml # 配置文件,包含所有配置项
ports:
- "8090:8080"

View File

@@ -1,10 +0,0 @@
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free
# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free
# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free
# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free
# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free

5
docs/.gitignore vendored
View File

@@ -1,5 +0,0 @@
node_modules/
./.vuepress/.cache/
./.vuepress/.temp/
./.vuepress/dist/

View File

@@ -1,14 +0,0 @@
import {defineClientConfig} from "vuepress/client";
import resourceStoreComp from "./components/res_store.vue";
import pluginStoreComp from "./components/plugin_store.vue";
//导入element-plus
import ElementPlus from 'element-plus';
export default defineClientConfig({
enhance: ({app, router, siteData}) => {
app.component("resourceStoreComp", resourceStoreComp);
app.component("pluginStoreComp", pluginStoreComp);
app.use(ElementPlus);
},
});

View File

@@ -1,126 +0,0 @@
<template>
<div class="item-card">
<div class="item-name">{{ props.item.name }}</div>
<div class="item-description">{{ props.item.desc }}</div>
<div class="item-bar">
<!-- 三个可点击svg一个github一个下载一个可点击"https://github.com/{{ username }}.png?size=80"个人头像配上id-->
<a :href=props.item.homepage class="btn">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 16 16">
<path fill="currentColor"
d="m7.775 3.275l1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0a.751.751 0 0 1 .018-1.042a.751.751 0 0 1 1.042-.018a1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018a.751.751 0 0 1-.018-1.042m-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018a.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0a.751.751 0 0 1-.018 1.042a.751.751 0 0 1-1.042.018a1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83"/>
</svg>
</a>
<!-- <button class="copy-btn btn"><div @click="copyToClipboard">安装</div></button> 点击后把安装命令写入剪贴板-->
<button class="btn copy-btn" @click="copyToClipboard">复制安装命令</button>
<div class="btn">
<a class="author-info" :href="`https://github.com/${props.item.author }`">
<img class="icon avatar" :src="`https://github.com/${ props.item.author }.png?size=80`" alt="">
<div class="author-name">{{ props.item.author }}</div>
</a>
</div>
<!-- 复制键复制安装命令npm install props.item.module_name-->
</div>
</div>
</template>
<script setup lang="ts">
import {defineProps, onMounted} from 'vue'
import Clipboard from 'clipboard'
// 复制安装命令按钮
// 构建复制成功和失败的提示
const props = defineProps({
item: Object
})
const copyToClipboard = () => {
const clipboard = new Clipboard('.copy-btn', {
text: () => `npm install ${props.item.module_name}`
})
clipboard.on('success', () => {
})
clipboard.on('error', () => {
})
}
// 复制到剪贴板的函数
</script>
<style scoped>
.item-card {
position: relative;
border-radius: 15px;
background-color: #00000011;
height: 160px;
padding: 16px;
margin: 10px;
box-sizing: border-box;
transition: background 0.3s ease;
}
.btn {
margin-right: 15px;
}
button {
background-color: #00000000;
border: none;
}
.copy-btn {
cursor: pointer;
color: #666;
}
.copy-btn:hover {
color: #111;
}
.item-name {
color: #111;
font-size: 20px;
margin-bottom: 10px;
}
.item-description {
color: #333;
font-size: 12px;
white-space: pre-wrap;
}
.icon {
width: 20px;
height: 20px;
color: $themeColor;
}
.author-info {
display: flex;
justify-content: left;
align-items: center;
}
.author-name {
font-size: 15px;
font-weight: normal;
}
.avatar {
border-radius: 50%;
margin: 0 10px;
}
.item-bar {
position: absolute;
bottom: 0;
height: 50px;
display: flex;
align-items: center;
box-sizing: border-box;
justify-content: space-between;
color: #00000055;
}
</style>

View File

@@ -1,39 +0,0 @@
<script setup lang="ts">
import {ref} from 'vue'
import ItemCard from './plugin_item_card.vue'
// 插件商店Nonebot
let items = ref([])
fetch('https://registry.nonebot.dev/plugins.json')
.then(response => response.json())
.then(data => {
items.value = data
})
.catch(error => console.error(error))
</script>
<template>
<div>
<h1>插件商店</h1>
<p>所有内容来自<a href="https://nonebot.dev/store/plugins">NoneBot插件商店</a>在此仅作引用具体请访问NoneBot插件商店</p>
<div class="market">
<!-- 布局商品-->
<ItemCard v-for="item in items" :key="item.id" :item="item"/>
</div>
</div>
</template>
<style scoped>
h1 {
color: #00a6ff;
text-align: center;
font-weight: bold;
}
.market {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 10px;
}
</style>

View File

@@ -1,90 +0,0 @@
<template>
<div class="item-card">
<div class="item-name">{{ props.item.name }}</div>
<div class="item-description">{{ props.item.description }}</div>
<div class="item-bar">
<!-- 三个可点击svg一个github一个下载一个可点击"https://github.com/{{ username }}.png?size=80"个人头像配上id-->
<a :href=props.item.link class="">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 16 16">
<path fill="currentColor"
d="m7.775 3.275l1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0a.751.751 0 0 1 .018-1.042a.751.751 0 0 1 1.042-.018a1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018a.751.751 0 0 1-.018-1.042m-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018a.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0a.751.751 0 0 1-.018 1.042a.751.751 0 0 1-1.042.018a1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83"/>
</svg>
</a>
<div><a class="author-info" :href="`https://github.com/${props.item.author }`">
<img class="icon avatar" :src="`https://github.com/${ props.item.author }.png?size=80`" alt="">
<div class="author-name">{{ props.item.author }}</div>
</a>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {defineProps} from 'vue'
const props = defineProps({
item: Object
})
</script>
<style scoped>
.item-card {
position: relative;
border-radius: 15px;
background-color: #00000011;
height: 160px;
padding: 16px;
margin: 10px;
box-sizing: border-box;
transition: background 0.3s ease;
}
.item-card:hover {
border: 2px solid $themeColor;
}
.item-name {
color: $themeColor;
font-size: 20px;
margin-bottom: 10px;
}
.item-description {
color: #333;
font-size: 15px;
white-space: pre-wrap;
}
.icon {
width: 20px;
height: 20px;
color: $themeColor;
}
.author-info {
display: flex;
justify-content: left;
align-items: center;
}
.author-name {
font-size: 15px;
font-weight: normal;
}
.avatar {
border-radius: 50%;
margin: 0 10px;
}
.item-bar {
position: absolute;
bottom: 0;
height: 50px;
display: flex;
align-items: center;
box-sizing: border-box;
justify-content: space-between;
color: #00000055;
}
</style>

View File

@@ -1,38 +0,0 @@
<script setup lang="ts">
import {ref} from 'vue'
import ItemCard from './res_item_card.vue'
// 从public/assets/resources.json加载插件
let items = ref([])
fetch('https://bot.liteyuki.icu/assets/resources.json')
.then(response => response.json())
.then(data => {
items.value = data
})
.catch(error => console.error(error))
</script>
<template>
<div>
<h1>主题/资源商店</h1>
<div class="market">
<!-- 布局商品-->
<ItemCard v-for="item in items" :key="item.id" :item="item" />
</div>
</div>
</template>
<style scoped>
h1 {
color: #00a6ff;
text-align: center;
font-weight: bold;
}
.market {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 10px;
}
</style>

View File

@@ -1,30 +0,0 @@
import {defineUserConfig} from "vuepress";
import theme from "./theme.js";
import viteBundler from "@vuepress/bundler-vite";
export default defineUserConfig({
base: "/",
lang: "zh-CN",
title: "LiteyukiBot 轻雪机器人",
description: "LiteyukiBot | 轻雪机器人 | An OneBot Standard ChatBot | 一个OneBot标准的聊天机器人",
head: [
// 设置 favor.ico.vuepress/public 下
['link', {rel: 'icon', href: 'https://cdn.liteyuki.icu/favicon.ico'},],
['link', {rel: 'stylesheet', href: 'https://cdn.bootcdn.net/ajax/libs/firacode/6.2.0/fira_code.min.css'}],
[
"meta",
{
name: "viewport",
content: "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no",
},
],
],
theme,
// 和 PWA 一起启用
// shouldPrefetch: false,
});

View File

@@ -1,20 +0,0 @@
import {navbar} from "vuepress-theme-hope";
export default navbar([
"/",
{
text: "项目部署",
link: "/deployment/",
prefix: "deployment/",
},
{
text: "使用手册",
link: "/usage/",
prefix: "usage/",
},
{
text: "商店",
link: "/store/resource",
prefix: "store/",
}
]);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path fill="#FDD7AD" d="M512 0 335.448 88.272l-70.616 35.312-70.624 35.312-176.552 88.28v529.648L512 1024l494.344-247.176V247.176z"/><path fill="#CBB292" d="m759.176 370.76-70.624 35.304-494.344-247.168 70.624-35.312zM512 494.344V1024L17.656 776.824V247.176z"/><path fill="#7F6E5D" d="M1006.344 247.168v529.656L512 1024V494.344l176.552-88.28v70.624l141.24-70.624v-70.616z"/><path fill="#7F5B53" d="M829.792 335.448v70.624L688.56 476.68v-70.624z"/><path fill="#CBB292" d="m829.792 335.448-70.624 35.312-494.344-247.176 70.624-35.312z"/><path fill="#2C3E50" d="m682.52 550.32 157.032-78.512a17.656 17.656 0 0 1 25.552 15.792v9.32a52.96 52.96 0 0 1-29.28 47.376L678.8 622.8a17.656 17.656 0 0 1-25.552-15.792v-9.312a52.96 52.96 0 0 1 29.28-47.376z"/></svg>

Before

Width:  |  Height:  |  Size: 854 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024"><defs><linearGradient id="a" x1="522.593" x2="522.593" y1="-70.302" y2="-335.937" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#fe5d5a" stop-opacity=".1"/><stop offset=".908" stop-color="#ef1220" stop-opacity=".5"/></linearGradient><linearGradient id="b" x1="107.12" x2="935.038" y1="-373.67" y2="-373.67" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#ff5e59"/><stop offset="1" stop-color="#f01422"/></linearGradient><linearGradient id="c" x1="519.405" x2="519.405" y1="-195.547" y2="-726.816" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#ffe2e2"/><stop offset=".888" stop-color="#ff8e8e"/></linearGradient><linearGradient id="d" x1="191.5" x2="483.9" y1="-564.9" y2="-564.9" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#e92700" stop-opacity=".3"/><stop offset=".013" stop-color="#ef1220" stop-opacity=".2"/></linearGradient><linearGradient id="e" x1="403.502" x2="253.121" y1="-847.32" y2="-586.853" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#ff5e59"/><stop offset=".201" stop-color="#f01422"/></linearGradient><linearGradient id="f" x1="330.485" x2="330.485" y1="-801.787" y2="-625.789" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#ff5e59"/><stop offset=".201" stop-color="#f01422"/></linearGradient><linearGradient id="g" x1="397.351" x2="256.845" y1="-647.231" y2="-890.596" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#ffa6a6"/><stop offset=".908" stop-color="#ff6b5d"/></linearGradient></defs><path fill="url(#a)" d="M501.2 662.3 327.6 763.8c-13.9 8.1-14.2 28.1-.5 36.7l179.1 97.7c10.9 5.9 24.1 5.9 34.9-.1l177-97.9c13.6-8.5 13.4-28.3-.3-36.5l-168.4-101c-14.8-9-33.3-9.1-48.2-.4Z"/><path fill="#f63037" d="m110.2 525.7-3.1 77.6 57.5 18.5L184 519.4Z"/><path fill="url(#b)" d="m476.6 363.5-328 154.6c-21 42.7-55.4 65.4-35.5 103.5 4.2 8 9.4 14.4 15.4 18.1l358.2 195.5c21.8 11.9 48.1 11.8 69.8-.2l354-195.8c27.2-16.9 34.8-90.3 7.3-106.8L573 364.1c-29.7-17.8-66.6-18-96.4-.6Z"/><path fill="url(#c)" d="M476.6 298.7 129.4 501.6c-27.8 16.3-28.4 56.3-1 73.3l358.2 195.5c21.8 11.9 48.1 11.8 69.8-.2l354-195.8c27.2-16.9 26.9-56.6-.6-73.1L573 299.3c-29.7-17.8-66.6-18-96.4-.6Z"/><path fill="#ff8989" fill-opacity=".31" d="m481.2 387.8 39.4 123.4c1.1 3.4 4 6 7.6 6.6l173.4 30.4-33-118.3c-.9-3.3-3.6-5.8-7-6.5l-180.4-35.6ZM327 499.2l40.4 101.1L496.7 525c2.5-1.5 3.7-4.5 2.7-7.3l-36-106.8-127.6 65c-8.6 4.3-12.4 14.4-8.8 23.3ZM523.8 540.5l-140.3 77.2L567.2 659c3.2.7 6.6.1 9.3-1.6l134.6-85-174.7-33.8c-4.3-1-8.7-.3-12.6 1.9Z"/><path fill="url(#d)" d="M483.9 406.1c0 35.46-65.46 64.2-146.2 64.2s-146.2-28.74-146.2-64.2c0-35.46 65.46-64.2 146.2-64.2s146.2 28.74 146.2 64.2Z"/><path fill="url(#e)" d="m254.2 188.4-123 83.1c-1.8 1.3-2.6 3.6-1.8 5.7l39.1 110.6c.6 1.7 2 2.9 3.8 3.2l221.8 40.5c1.3.3 2.7-.1 3.7-.8l131.7-93.6c1.9-1.4 2.6-3.9 1.7-6.1l-49.4-107c-.6-1.5-2.1-2.6-3.7-2.8l-220.3-33.5c-1.3-.2-2.6.1-3.6.7Z"/><path fill="url(#f)" d="m528.6 274.5 3 59.1-205 65.6-177.2-72.7-20-49.2 1.9-54.1Z"/><path fill="url(#g)" d="m250.6 138-112.3 76c-6 4.1-8.5 11.7-6.1 18.5l34.2 96.6c1.9 5.4 6.6 9.3 12.1 10.4l211 38.5c4.3.7 8.6-.2 12.1-2.7l120.5-85.5c6.3-4.4 8.4-12.7 5.3-19.7l-43.1-93.5c-2.2-4.9-6.8-8.3-12.1-9.1L262 135.6c-4-.7-8 .2-11.4 2.4Z"/><path fill="#fff" d="m419.8 252.8-79-11-29-57.7c-3.8-7.6-13.2-10.7-20.8-6.9-7.6 3.8-10.7 13.2-6.9 20.8l26.6 52.9-61.8 42.2c-7.1 4.8-8.9 14.5-4.1 21.5 3 4.4 7.9 6.8 12.8 6.8 3 0 6-.9 8.7-2.7l68-46.4 81.1 11.2c.7.1 1.4.1 2.1.1 7.6 0 14.3-5.6 15.3-13.4 1.4-8.4-4.5-16.2-13-17.4Z"/></svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1 +0,0 @@
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg>

Before

Width:  |  Height:  |  Size: 963 B

View File

@@ -1 +0,0 @@
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 960 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1536 1024"><path fill="#1296db" d="M1425.067.256H110.933A110.933 110.933 0 0 0 0 110.848v723.627a110.933 110.933 0 0 0 110.933 110.933h1314.39c61.269 0 110.933-49.75 110.677-110.677V110.848A110.933 110.933 0 0 0 1425.067.256z" class="selected" data-spm-anchor-id="a313x.7781069.0.i4"/><path fill="#FFF" d="M664.747 723.797V435.883L517.12 620.373l-147.456-184.49v288l-148.053-67.158V221.781h147.626l147.627 184.576 147.541-184.576h147.627v565.76z"/><path d="M1024 0h426.667A85.333 85.333 0 0 1 1536 85.333v768a85.333 85.333 0 0 1-85.333 85.334H1024V0z" opacity=".1"/><path fill="#FFF" d="m1256.96 731.307-170.667-216.491h113.75V304.64h113.749v210.176h113.835z" opacity=".5"/></svg>

Before

Width:  |  Height:  |  Size: 771 B

View File

@@ -1,39 +0,0 @@
[
{
"name": "KawaiiStatus",
"author": "SnowyKami",
"description": "可爱的状态卡片仿照koishi的制作",
"link": "https://cdn.liteyuki.icu/static/lrp/KawaiiStatus.zip",
"icon": "https://cdn.jsdelivr.net/gh/SnowyKami/CDN/img/KawaiiStatus.png"
},
{
"name": "MiSans字体包",
"author": "SnowyKami",
"description": "小米官方字体MiSans",
"link": "https://cdn.liteyuki.icu/static/lrp/MiSansFonts.zip"
},
{
"name": "MapleMono字体包",
"author": "SnowyKami",
"description": "适用于字母的字体包",
"link": "https://cdn.liteyuki.icu/static/lrp/MapleMonoFonts.zip"
},
{
"name": "野兽先辈主题HomoTheme",
"author": "SnowyKami",
"description": "野兽先辈主题包114514",
"link": "https://cdn.liteyuki.icu/static/lrp/HomoTheme.zip"
},
{
"name": "示例包2",
"author": "SnowyKami",
"description": "A simple bot that shows the status of the bot and the server.",
"link": ""
},
{
"name": "示例包3",
"author": "SnowyKami",
"description": "A simple bot that shows the status of the bot and the server.",
"link": ""
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" class="icon" viewBox="0 0 3280.944 2800"><path fill="#41b883" d="M1645.332 601.004h375.675L1081.82 2238.478 142.636 601.004h718.477l220.708 379.704 216.013-379.704z"/><path fill="#41b883" d="M142.636 601.004l939.185 1637.474 939.186-1637.474h-375.675l-563.51 982.484-568.208-982.484z"/><path fill="#35495e" d="M513.188 601.004l568.207 987.23 563.511-987.23h-347.498l-216.013 379.704-220.708-379.704zM1607.792 1311.83l594.678 2.293 187.353-316.325-598.662 2.292zM2198.506 1909.57C2867.436 732.7 2939.502 605.426 2937.874 603.78c-.715-.723 45.303-1.314 102.262-1.314s103.562.428 103.562.951c0 .523-208.57 367.978-463.491 816.567L2216.715 2235.6l-102.1.596-102.102.596z"/><path fill="#41b883" d="M1680.563 2233.328c0-1.34 168.208-298.145 440.375-777.048a4135645.775 4135645.775 0 00337.619-594.19l146.13-257.25 170.746-.04 170.747-.04-5.536 9.741c-3.044 5.358-43.727 77.302-90.407 159.875-85.356 150.992-337.562 595.163-656.602 1156.373l-172 302.559-170.536.588c-93.795.322-170.536.069-170.536-.567z"/><path fill="#35495e" d="M1429.783 1625.351l594.679 2.292 187.353-316.324-598.662 2.292z"/><path fill="#41b883" d="M1524.207 1464.903l608.285 6.877 173.746-320.909h-619.072z"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,25 +0,0 @@
import {sidebar} from "vuepress-theme-hope";
export default sidebar({
"/": [
"",
{
text: "项目部署",
icon: "laptop-code",
prefix: "deployment/",
children: "structure",
},
{
text: "使用手册",
icon: "book",
prefix: "usage/",
children: "structure",
},
{
text: "商店",
icon: "store",
prefix: "store/",
children: "structure",
}
],
});

View File

@@ -1,7 +0,0 @@
// you can change config here
$colors: #c0392b, #d35400, #f39c12, #27ae60, #16a085, #2980b9, #8e44ad, #2c3e50,
#7f8c8d !default;
body {
overflow-x: hidden;
}

View File

@@ -1,16 +0,0 @@
// place your custom styles here
#main-title {
font-family: ColorTube, "Fira Code", serif;
color: #ff0000 !important; /* 你想要的颜色 */
line-height: 2;
}
@font-face {
font-family: ColorTube;
src: url("/assets/fonts/ColorTube.woff") format("woff")
}
code {
font-family: "Fira Code", monospace !important;
}

View File

@@ -1,2 +0,0 @@
// you can change colors here
$theme-color: #00a6ff;

View File

@@ -1,192 +0,0 @@
import {hopeTheme} from "vuepress-theme-hope";
import navbar from "./navbar.js";
import sidebar from "./sidebar.js";
export default hopeTheme({
hostname: "https://vuepress-theme-hope-docs-demo.netlify.app",
author: {
name: "远野千束",
url: "https://snowykami.me",
},
iconAssets: "fontawesome-with-brands",
logo: "https://cdn.liteyuki.icu/static/img/liteyuki_icon_640.png",
repo: "https://github.com/snowykami/LiteyukiBot",
docsDir: "docs",
// 导航栏
navbar,
// 侧边栏
sidebar,
// 页脚
footer: "LiteyukiBot",
displayFooter: true,
// 加密配置
encrypt: {
config: {
"/demo/encrypt.html": ["1234"],
},
},
// 多语言配置
metaLocales: {
editLink: "在 GitHub 上编辑此页",
},
// 如果想要实时查看任何改变,启用它。注: 这对更新性能有很大负面影响
// hotReload: true,
// 在这里配置主题提供的插件
plugins: {
search: true,
// search: true,
comment: {
provider: "Giscus",
repo: "snowykami/LiteyukiBot",
repoId: "R_kgDOHVNKpQ",
category: "Announcements",
categoryId: "DIC_kwDOHVNKpc4CeWxj",
},
components: {
components: ["Badge", "VPCard"],
},
// 此处开启了很多功能用于演示,你应仅保留用到的功能。
mdEnhance: {
alert: true,
align: true,
attrs: true,
codetabs: true,
footnote: true,
component: true,
demo: true,
figure: true,
imgLazyload: true,
imgSize: true,
include: true,
mark: true,
stylize: [
{
matcher: "Recommended",
replacer: ({tag}) => {
if (tag === "em")
return {
tag: "Badge",
attrs: {type: "tip"},
content: "Recommended",
};
},
},
],
sub: true,
sup: true,
tabs: true,
vPre: true,
// 在启用之前安装 chart.js
// chart: true,
// insert component easily
// 在启用之前安装 echarts
// echarts: true,
// 在启用之前安装 flowchart.ts
// flowchart: true,
// gfm requires mathjax-full to provide tex support
// gfm: true,
// 在启用之前安装 katex
// katex: true,
// 在启用之前安装 mathjax-full
// mathjax: true,
// 在启用之前安装 mermaid
// mermaid: true,
// playground: {
// presets: ["ts", "vue"],
// },
// 在启用之前安装 reveal.js
// revealJs: {
// plugins: ["highlight", "math", "search", "notes", "zoom"],
// },
// 在启用之前安装 @vue/repl
// vuePlayground: true,
// install sandpack-vue3 before enabling it
// sandpack: true,
},
// 如果你需要 PWA。安装 @vuepress/plugin-pwa 并取消下方注释
// pwa: {
// favicon: "/favicon.ico",
// cacheHTML: true,
// cachePic: true,
// appendBase: true,
// apple: {
// icon: "/assets/icon/apple-icon-152.png",
// statusBarColor: "black",
// },
// msTile: {
// image: "/assets/icon/ms-icon-144.png",
// color: "#ffffff",
// },
// manifest: {
// icons: [
// {
// src: "/assets/icon/chrome-mask-512.png",
// sizes: "512x512",
// purpose: "maskable",
// type: "image/png",
// },
// {
// src: "/assets/icon/chrome-mask-192.png",
// sizes: "192x192",
// purpose: "maskable",
// type: "image/png",
// },
// {
// src: "/assets/icon/chrome-512.png",
// sizes: "512x512",
// type: "image/png",
// },
// {
// src: "/assets/icon/chrome-192.png",
// sizes: "192x192",
// type: "image/png",
// },
// ],
// shortcuts: [
// {
// name: "Demo",
// short_name: "Demo",
// url: "/demo/",
// icons: [
// {
// src: "/assets/icon/guide-maskable.png",
// sizes: "192x192",
// purpose: "maskable",
// type: "image/png",
// },
// ],
// },
// ],
// },
// },
},
});

View File

@@ -1,100 +0,0 @@
---
home: true
icon: home
title: 首页
heroImage: https://cdn.liteyuki.icu/static/img/logo.png
bgImage:
bgImageDark:
bgImageStyle:
background-attachment: fixed
heroText: LiteyukiBot
tagline: 轻雪机器人一个以轻量和简洁为设计理念基于Nonebot2的OneBot标准聊天机器人
actions:
- text: 快速部署
icon: lightbulb
link: ./deployment/install.html
type: primary
- text: 使用手册
icon: book
link: ./usage/basic_command.html
highlights:
- header: 简洁至上
image: /assets/image/layout.svg
bgImage: https://theme-hope-assets.vuejs.press/bg/2-light.svg
bgImageDark: https://theme-hope-assets.vuejs.press/bg/2-dark.svg
bgImageStyle:
background-repeat: repeat
background-size: initial
features:
- title: 基于Nonebot2
icon: robot
details: 拥有良好的生态支持
link: https://nonebot.dev/
- title: 可视化插件管理
icon: plug
details: 使用<code>npm</code>,无需命令行操作即可安装/卸载插件
- title: 点击交互
icon: mouse-pointer
details: 新的点击交互模式,拒绝手打指令
- title: 主题支持
icon: paint-brush
details: 支持多种主题,可自定义资源包,满足你的审美需求
link: https://bot.liteyuki.icu/usage/resource_pack.html
- title: 国际化
icon: globe
details: 支持多种语言包括i18n部分语言和自行扩展的语言代码
link: https://baike.baidu.com/item/i18n/6771940
- title: 简易配置
icon: cog
details: 无需过多配置,开箱即用
link: https://bot.liteyuki.icu/deployment/config.html
- title: 低占用
icon: memory
details: 使用更少的依赖和资源
- title: OneBot标准
icon: link
details: 支持OneBotv11/12标准的四种通信协议
link: https://onebot.dev/
- title: Alconna命令解析
icon: link
details: 使用Alconna实现高效命令解析
link: https://github.com/nonebot/plugin-alconna
- title: 便捷更新
icon: cloud-download
details: 聊天窗口命令更新,无需手动下载
- title: 服务支持
icon: server
details: 内置轻雪API可自动收集错误提供图床服务
- title: 开源
icon: code
details: 项目遵循MIT协议开源欢迎各位的贡献
- header: 快速部署
image: /assets/image/box.svg
bgImage: https://theme-hope-assets.vuejs.press/bg/3-light.svg
bgImageDark: https://theme-hope-assets.vuejs.press/bg/3-dark.svg
highlights:
- title: 安装 Git 和 Python3.10+
- title: 使用 <code>git clone https://github.com/snowykami/LiteyukiBot</code> 以克隆项目至本地。
details: 如果无法连接到GitHub可以使用 <code>git clone https://gitee.com/snowykami/LiteyukiBot</code>
- title: 使用 <code>cd LiteyukiBot</code> 切换到项目目录。
- title: 使用 <code>pip install -r requirements.txt</code> 安装项目依赖。
details: 如果你有多个 Python 环境,请使用 <code>pythonx -m pip install -r requirements.txt</code>
- title: 使用 <code>python main.py</code> 启动项目。
copyright: © 2021-2024 SnowyKami All Rights Reserved
---

View File

@@ -1,8 +0,0 @@
---
title: 项目部署
index: false
icon: laptop-code
category: 部署
---
<Catalog />

View File

@@ -1,61 +0,0 @@
---
title: 配置
icon: cog
order: 2
category: 使用指南
tag:
- 配置
- 部署
---
首次运行后生成`config.yml`,你可以修改配置项后重启轻雪,绝大多数情况下,你只需要修改`superusers``nickname`字段即可
## **基础配置项**
```yaml
command_start: [ "/", "" ] # 指令前缀,若没有""空命令头请开启alconna_use_command_start保证alconna解析正常
host: 127.0.0.1 # 监听地址默认为本机若要接收外部请求请填写0.0.0.0
port: 20216 # 绑定端口
nickname: [ "liteyuki" ] # 机器人昵称列表
superusers: [ "1919810" ] # 超级用户列表
```
## **其他配置**
以下为默认值,如需自定义请手动添加
```yaml
onebot_access_token: "" # 访问令牌,对公开放时建议设置
default_language: "zh-CN" # 默认语言
alconna_auto_completion: false # alconna是否自动补全指令默认false建议开启
# 开发者选项
log_level: "INFO" # 日志等级
log_icon: true # 是否显示日志等级图标(某些控制台字体不可用)
auto_report: true # 是否自动上报问题给轻雪服务器
auto_update: true # 是否自动更新轻雪每天4点检查更新
debug: false # 轻雪调试,开启后在调试时修改代码或资源会自动重载相应内容
safe_mode: false # 安全模式,开启后将不会加载任何第三方插件
# 其他Nonebot插件的配置项
custom_config_1: "custom_value1"
custom_config_2: "custom_value2"
...
```
> [!tip]
> 如果要使用dotenv配置文件请自行创建`.env.{ENVIRONMENT}`,并在`config.yml`中添加`environment:{ENVIRONMENT}`字段
## **OneBot实现端配置**
生产环境中推荐反向WebSocket
不同的实现端给出的字段可能不同,但是基本上都是一样的,这里给出一个参考值
| 字段 | 参考值 | 说明 |
|-------------|------------------------------------|----------------------------------|
| 协议 | 反向WebSocket | 推荐使用反向ws协议进行通信即轻雪作为服务端 |
| 地址 | ws://127.0.0.1:20216/onebot/v11/ws | 地址取决于配置文件,本机默认为`127.0.0.1:20216` |
| AccessToken | `""` | 如果你给轻雪配置了`AccessToken`,请在此填写相同的值 |
## **其他**
- 要使用其他通信方式请访问[OneBot Adapter](https://onebot.adapters.nonebot.dev/)获取详细信息
- 轻雪不局限于OneBot适配器你可以使用NoneBot2支持的任何适配器

View File

@@ -1,58 +0,0 @@
---
title: 答疑
icon: question
order: 3
category: 使用指南
tag:
- 配置
- 部署
---
## **常见问题**
- 设备上Python环境太乱了pip和python不对应怎么办
- 请使用`/path/to/python -m pip install -r requirements.txt`来安装依赖,
然后用`/path/to/python main.py`来启动Bot
其中`/path/to/python`是你要用来运行Bot的可执行文件
- 为什么我启动后机器人没有反应?
- 请检查配置文件的`command_start``superusers`,确认你有权限使用命令并按照正确的命令发送
- 确认命令头没有和`nickname{}`冲突,例如一个命令是`help`,但是`Bot`昵称有一个`help`那么将会被解析为nickname而不是命令
- 更新轻雪失败,报错`InvalidGitRepositoryError`
- 请正确安装`Git`,并使用克隆而非直接下载的方式部署轻雪
- 怎么登录聊天平台例如QQ
- 你有这个问题说明你不是很了解这个项目,本项目不负责实现登录功能,只负责处理和回应消息,登录功能由实现端(协议端)提供,
实现端本身不负责处理响应逻辑将消息按照OneBot标准处理好上报给轻雪
你需要使用Onebot标准的实现端来连接到轻雪并将消息上报给轻雪下面已经列出一些推荐的实现端
- `Playwright`安装失败
- 输入`playwright install`安装浏览器
- 有的插件安装后报错无法启动
- 请先查阅插件文档,确认插件必要配置项完好后,仍然出现问题,请联系插件作者或在安全模式`safe_mode: true`下启动轻雪,在安全模式下你可以使用`npm uninstall`卸载问题插件
- 其他问题
-
加入QQ群[775840726](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=SzmDYbfR6jY94o9KFNon7AwelRyI6M_u&authKey=ygeBdEmdFNyCWuNR4w0M1M8%2B5oDg7k%2FDfN0tzBkYcnbB%2FGHNnlVEnCIGbdftsnn7&noverify=0&group_code=775840726)
## **推荐方案(QQ)**
1. [Lagrange.OneBot](https://github.com/KonataDev/Lagrange.Core)基于NTQQ的OneBot实现目前Markdown消息支持Lagrange
2. [LLOneBot](https://github.com/LLOneBot/LLOneBot)NTQQ的OneBot插件需要安装NTQQ
3. [OpenShamrock](https://github.com/whitechi73/OpenShamrock)基于Lsposed的OneBot11实现
4. [TRSS-Yunzai](https://github.com/TimeRainStarSky/Yunzai),基于`node.js`,可使用`ws-plugin`进行通信
5. [go-cqhttp](https://github.com/Mrs4s/go-cqhttp)`go`语言实现的OneBot11实现端目前可用性较低
6. [Gensokyo](https://github.com/Hoshinonyaruko/Gensokyo),基于 OneBot QQ官方机器人Api Golang 原生实现,需要官方机器人权限
7. 人工实现的`Onebot`协议自己整一个WebSocket客户端看着QQ的消息然后给轻雪传输数据
## **推荐方案(Minecraft)**
1. [MinecraftOneBot](https://github.com/snowykami/MinecraftOnebot)我们专门为Minecraft开发的服务器Bot支持OneBotV11标准
使用其他项目连接请先自行查阅文档若有困难请联系对应开发者而不是Liteyuki的开发者
## **鸣谢**
- [Nonebot2](https://nonebot.dev)提供的框架支持
- [nonebot-plugin-htmlrender](https://github.com/kexue-z/nonebot-plugin-htmlrender/tree/master)提供的渲染功能
- [nonebot-plugin-alconna](https://github.com/ArcletProject/nonebot-plugin-alconna)提供的命令解析功能
- [MiSans](https://hyperos.mi.com/font/zh/)[MapleMono](https://gitee.com/mirrors/Maple-Mono)提供的字体,且遵守了相关字体开源协议

View File

@@ -1,50 +0,0 @@
---
title: 安装
icon: download
order: 1
category: 使用指南
tag:
- 安装
---
## **开始安装**
### **常规方法**
1. 安装 [`Git`](https://git-scm.com/download/) 和 [`Python3.10+`](https://www.python.org/downloads/release/python-31010/) 环境
2. 克隆项目 `git clone https://github.com/snowykami/LiteyukiBot`
3. 进入轻雪目录 `cd LiteyukiBot`
4. 安装依赖 `pip install -r requirements.txt`
5. 启动 `python main.py`
### **使用Docker(测试中)**
1. 安装 [`Docker`](https://docs.docker.com/get-docker/)
2. 克隆项目 `git clone https://github.com/snowykami/LiteyukiBot`
3. 进入轻雪目录 `cd LiteyukiBot`
4. 构建镜像 `docker build -t liteyukibot .`
5. 启动容器 `docker run -p 20216:20216 -v $(pwd):/liteyukibot -v $(pwd)/.cache:/root/.cache liteyukibot`
> [!tip]
> Windows请使用项目绝对目录`/path/to/LiteyukiBot`代替`$(pwd)` <br>
> 若你修改了端口号请将`20216:20216`中的`20216`替换为你的端口号
## **设备要求**
- Windows系统版本最低`Windows10+`/`Windows Server 2019+`
- Linux系统要支持Python3.10+,推荐`Ubuntu 20.04+`(~~别用你那b CentOS~~)
- CPU: 至少`1vCPU`
- 内存: Bot无其他插件会占用`200~300MB`,其他插件占用视具体插件而定,建议`1GB`以上
- 硬盘: 至少`1GB`空间
> [!warning]
> 如果设备上有多个环境,请使用`path/to/python -m pip install -r requirements.txt`来安装依赖,`path/to/python`为你的Python可执行文件路径
> [!tip]
> 推荐使用虚拟环境来运行轻雪,以避免依赖冲突,你可以使用`python -m venv venv`来创建虚拟环境,然后使用`venv\Scripts\activate`来激活虚拟环境
> [!warning]
> 轻雪的更新功能依赖Git如果你没有安装Git你将无法使用更新功能
#### 其他问题请移步至[答疑](/deployment/fandq)
[//]: # (#### 想在Linux命令行中拥有更好的体验试试[TRSS_Liteyuki轻雪机器人管理脚本]&#40;https://timerainstarsky.github.io/TRSS_Liteyuki/&#41;该功能仅供参考不是LiteyukiBot官方提供的功能)

View File

@@ -1,106 +0,0 @@
---
home: true
icon: home
title: 首页
heroImage: https://cdn.liteyuki.icu/static/img/logo.png
bgImage:
bgImageDark:
bgImageStyle:
background-attachment: fixed
heroText: HeavyyukiBot666 # LiteyukiBot 6
tagline: 重雪机器人一个以笨重和复杂为设计理念基于Koishi114514的OneBotv1919810标准聊天机器人可用于雪地清扫使用Typethon编写
#tagline: 轻雪机器人一个以轻量和简洁为设计理念基于Nonebot2的OneBot标准聊天机器人
actions:
- text: 快速结束 # 快速开始
icon: lightbulb
link: ./deployment/install.html
type: primary
- text: 奇怪的册子 # 使用手册
icon: book
link: ./usage/basic_command.html
#1. 安装 `Git` 和 `Python3.10+` 环境
#2. 克隆项目 `git clone https://github.com/snowykami/LiteyukiBot` (无法连接可以用`https://gitee.com/snowykami/LiteyukiBot`)
#3. 切换目录`cd LiteyukiBot`
#4. 安装依赖`pip install -r requirements.txt`(如果多个Python环境请指定后安装`pythonx -m pip install -r requirements.txt`)
#5. 启动`python main.py`
highlights:
- header: 简洁至下 # 简洁至上
image: /assets/image/layout.svg
bgImage: https://theme-hope-assets.vuejs.press/bg/2-light.svg
bgImageDark: https://theme-hope-assets.vuejs.press/bg/2-dark.svg
bgImageStyle:
background-repeat: repeat
background-size: initial
features:
- title: 基于Koishi.js233
icon: robot
details: 拥有良好的生态支持
link: https://nonebot.dev/
- title: 盲目插件管理
icon: plug
details: 基于nbshi使用<code>npmx和pip</code>,让你无法安装/卸载插件
- title: 点击无法交互
icon: mouse-pointer
details: 老的的点击交互模式,必须手打指令
- title: 猪蹄支持
icon: paint-brush
details: 支持多种主题,可自定义资源包,满足你的审美需求
- title: 非国际化
icon: globe
details: 支持多种语言包括i18n部分语言和自行扩展的语言代码
link: https://baike.baidu.com/item/i18n/6771940
- title: 超难配置
icon: cog
details: 无需过多配置,开箱即用
link: https://bot.liteyuki.icu/deployment/config.html
- title: 高占用
icon: memory
details: 使用更多的意义不明的依赖和资源
- title: 一个Bot标准
icon: link
details: 支持OneBotv11/12标准的四种通信协议
link: https://onebot.dev/
- title: Alconna
icon: link
details: 使用Alconna实现低效命令解析
link: https://github.com/nonebot/plugin-alconna
- title: 不准更新
icon: cloud-download
details: 要更新自己下新版本去
- title: 服务支持
icon: server
details: 内置轻雪API但随时可能跑路
- title: 闭源
icon: code
details: 要源代码自己逆向去
- header: 快速部署
image: /assets/image/box.svg
bgImage: https://theme-hope-assets.vuejs.press/bg/3-light.svg
bgImageDark: https://theme-hope-assets.vuejs.press/bg/3-dark.svg
highlights:
- title: 安装 Git 和 node.js+
- title: 使用 <code>git clone https://github.com/snowykami/LiteyukiBot</code> 以克隆项目至本地。
details: 如果无法连接到GitHub可以使用 <code>git clone https://gitee.com/snowykami/LiteyukiBot</code>
- title: 使用 <code>cd LiteyukiBot</code> 切换到项目目录。
- title: 使用 <code>npm install -r requirements.txt</code> 安装项目依赖。
details: 如果你有多个 node.js 环境,请使用 <code>pythonx -m npm install -r requirements.txt</code>
- title: 使用 <code>node main.py</code> 启动项目。
copyright: © 2021-2024 SnowyKami All Rights Reserved
---

View File

@@ -1,26 +0,0 @@
{
"name": "liteyuki",
"version": "2.0.0",
"description": "A project of vuepress-theme-hope",
"license": "MIT",
"type": "module",
"scripts": {
"docs:build": "vuepress-vite build .",
"docs:clean-dev": "vuepress-vite dev . --clean-cache",
"docs:dev": "vuepress-vite dev .",
"docs:update-package": "pnpm dlx vp-update"
},
"devDependencies": {
"@vuepress/bundler-vite": "2.0.0-rc.9",
"@vuepress/plugin-search": "2.0.0-rc.24",
"vue": "^3.4.21",
"vuepress": "2.0.0-rc.9",
"vuepress-plugin-search-pro": "2.0.0-rc.34",
"vuepress-theme-hope": "2.0.0-rc.32"
},
"dependencies": {
"clipboard": "^2.0.11",
"element-plus": "^2.7.0",
"element-ui": "^2.15.14"
}
}

3180
docs/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
---
title: 资源商店
icon: store
index: false
---

View File

@@ -1,8 +0,0 @@
---
title: 插件商店
icon: plug
order: 2
category: 使用手册
---
<pluginStoreComp />

View File

@@ -1,7 +0,0 @@
---
title: 资源商店
icon: box
order: 1
category: 使用手册
---
<resourceStoreComp />

View File

@@ -1,15 +0,0 @@
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2022",
"allowSyntheticDefaultImports": true
},
"include": [
"./.vuepress/**/*.ts",
"./.vuepress/**/*.vue"
],
"exclude": [
"node_modules"
]
}

View File

@@ -1,8 +0,0 @@
---
title: 使用手册
index: false
icon: laptop-code
category: 使用手册
---
<Catalog />

View File

@@ -1,16 +0,0 @@
---
title: 用户协议
icon: user-secret
order: 3
category: 使用手册
---
1. 本项目遵循`MIT`协议,你可以自由使用,修改,分发,但是请保留原作者信息
2. 你可以选择开启`auto_report`(默认开启),轻雪会收集以下内容
- 运行环境的设备信息CPU内存系统信息Python信息
- 插件信息(不含插件数据)
- 部分异常信息,
- 会话负载信息(不含隐私部分)
以上内容仅用于项目的优化,不包含任何隐私信息,且通过安全的方式传输到轻雪的服务器,若你不希望提供这些信息,可以在配置文件中把`auto_report`设定为`false`
3. 本项目不会收集用户的任何隐私信息,但请注意甄别第三方插件的安全性
4. 使用此项目代表你已经同意以上协议

View File

@@ -1,102 +0,0 @@
---
title: 基础命令
icon: comment
order: 1
category: 使用手册
---
## 基础插件
### **轻雪 `liteyuki`**
```shell
# 仅超级用户
reload-liteyuki # 重载轻雪
update-liteyuki # 更新轻雪
liteecho # 查看当前bot
status # 查看统计信息和状态
config set <key> value # 添加配置项,若存在则会覆盖,输入值会被执行以转换为正确的类型,"10"和10是不一样的
config get [key] # 查询配置项不带key返回配置项列表推荐私聊使用
switch-image-mode # 在普通图片和Markdown大图之间切换该功能需要commit:505468b及以后的Lagrange.OneBot
/api api_name [args] # 调用机器人API例如/api get_group_member_list group_id=1234567空格用%20
# 仅超级用户,群聊仅群主、管理员、超级用户可用
group enable/disable [group_id] # 在群聊启用/停用机器人group_id仅超级用户可用
# 所有人可用
liteyuki-docs # 查看轻雪文档
```
命令别名
```shell
status 状态,
reload-liteyuki 重启轻雪,
update-liteyuki 更新轻雪,
reload-resources 重载资源,
config 配置 | set 设置 | get 查询,
switch-image-mode 切换图片模式,
liteyuki-docs 轻雪文档
group 群聊 | enable 启用 | disable 停用
```
### **插件/包管理器 `liteyuki_pacman`**
- 插件管理
```shell
# 仅超级用户
npm update # 更新插件商店索引
npm install <plugin_name> # 安装插件
npm uninstall <plugin_name> # 卸载插件
npm search <keywords...> # 通过关键词搜索插件
npm enable-global/disable-global <plugin_name> # 全局启用/停用插件
# 群聊仅群主、管理员、超级用户可用,私聊所有人可用
npm enable/disable <plugin_name> # 当前会话启用/停用插件
npm list [page] [num] # 列出所有插件 page为页数num为每页显示数量
# 所有人
help <plugin_name> # 查看插件帮助
```
- 资源包管理
```shell
# 仅超级用户
rpm list [page] [num] # 列出所有资源包 page为页数num为每页显示数量
rpm load <pack_name> # 加载资源包
rpm unload <pack_name> # 卸载资源包
rpm change <pack_name> # 修改优先级
rpm reload # 重载所有资源包
```
命令别名
```shell
npm 插件管理 | update 更新 | install 安装 | uninstall 卸载 | search 搜索
enable 启用 | disable 停用 | enable-global 全局启用 | disable-global 全局停用
rpm 资源包 | load 加载 | unload 卸载 | change 更改 | reload 重载 | list 列表
help 帮助
```
> [!warning]
> 受限于NoneBot2钩子函数的依赖注入参数插件停用只能阻断传入响应对于主动推送的插件不生效请阅读插件主页的说明。
### **用户管理`liteyuki_user`**
```shell
# 所有人可用
profile # 查看用户信息菜单
profile set <key> [value] # 设置用户信息或打开属性设置菜单
profile get <key> # 获取用户信息
```
命令别名
```shell
profile 个人信息 | set 设置 | get 查询
```
> [!tip]
> **参数**`<param>`为必填参数,`[option]`为可选参数。
>
> **命令别名**:配置了命令别名的命令可以使用别名代替原命令,例如`npm install ~`可以使用`插件 安装 ~`代替。

View File

@@ -1,29 +0,0 @@
---
title: 功能命令
icon: comment
order: 2
category: 使用手册
---
## 功能插件命令
### **轻雪天气`liteyuki_weather`**
配置项
```yaml
weather_key: "" # 和风天气的天气key会自动判断key版本
```
命令
```shell
weather <keywords...> # 查询目标地实时天气,例如:"天气 北京 海淀", "weather Tokyo Shinjuku"
bind-city <keywords...> # 绑定查询城市,个人全局生效
```
命令别名
```shell
weather 天气, bind-city 绑定城市
```

View File

@@ -1,45 +0,0 @@
---
title: 轻雪API
icon: code
order: 4
category: 使用指南
tag:
- 配置
- 部署
---
## **轻雪API**
轻雪API是轻雪运行中部分服务的支持`go`语言编写,例如错误反馈,图床链接等,目前服务由轻雪服务器提供,用户无需额外部署
接口
- `url` `https://api.liteyuki.icu`
- `POST` `/register` 注册一个Bot
- 参数
- `name` `string` Bot名称
- `version` `string` Bot版本
- `version_id` `int` Bot版本ID
- `python` `string` Python版本
- `os` `string` 操作系统
- 返回
- `code` `int` 状态码
- `liteyuki_id` `string` 轻雪ID
- `POST` `/bug_report` 上报错误
- 参数
- `liteyuki_id` `string` 轻雪ID
- `content` `string` 错误信息
- `device_info` `string` 设备信息
- 返回
- `code` `int` 状态码
- `report_id` `string` 错误ID
- `POST` `/upload_image` 图床上传
- 参数
- `image` `file` 图片文件
- `liteyuki_id` `string` 轻雪ID,用于鉴权
- 返回
- `code` `int` 状态码
- `url` `string` 图床链接

View File

@@ -1,39 +0,0 @@
---
title: 资源包
icon: paint-brush
order: 3
category: 使用手册
---
## 简介
资源包,亦可根据用途称为主题包、字体包、语言包等,它允许你一定程度上自定义轻雪的外观,并且不用修改源代码
- [资源/主题商店](/store/)提供了一些资源包供你选择,你也可以自己制作资源包
- 资源包的制作很简单,如果你接触过`Minecraft`的资源包,那么你能够很快就上手,仅需按照原有路径进行文件替换即可,讲起打包成一个新的资源包。
- 部分内容制作需要一点点前端基础,例如`html``css`
- 轻雪原版资源包请查看`LiteyukiBot/liteyuki/resources`,可以在此基础上进行修改
- 欢迎各位投稿资源包到轻雪资源商店
## 加载资源包
- 资源包通常是以`.zip`格式压缩的,只需要将其解压到根目录`resources`目录下即可,注意不要嵌套文件夹,正常的路径应该是这样的
```shell
main.py
resources
└─resource_pack_1
├─metadata.yml
├─templates
└───...
└─resource_pack_2
├─metadata.yml
└─...
```
- 你自己制作的资源包也应该遵循这个规则,并且应该在`metadata.yml`中填写一些信息
- 若没有`metadata.yml`文件,则该文件夹不会被识别为资源包
```yaml
name: "资源包名称"
version: "1.0.0"
description: "资源包描述"
# 你可以自定义一些信息,但请保证以上三个字段
...
```
- 资源包加载遵循一个优先级即后加载的资源包会覆盖前面的资源包例如你在A包中定义了一个`index.html`文件B包也定义了一个`index.html`文件那么加载B包后A包中的`index.html`文件会被覆盖
- 对于不同资源包的不同文件是可以相对引用的例如你在A中定义了`templates/index.html`在B中定义了`templates/style.css`可以在A的`index.html`中用`./style.css`相对路径引用B中的css

View File

@@ -1,35 +0,0 @@
from nonebot.plugin import PluginMetadata
from .core import *
from .loader import *
from .dev_tools import *
__author__ = "snowykami"
__plugin_meta__ = PluginMetadata(
name="轻雪核心插件",
description="轻雪主程序插件,包含了许多初始化的功能",
usage="",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki" : True,
"toggleable": False,
}
)
from ..utils.base.language import Language, get_default_lang_code
print("\033[34m" + r"""
__ ______ ________ ________ __ __ __ __ __ __ ______
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ |
$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/
$$ | $$ | $$ | $$ |__ $$ \/$$/ $$ | $$ |$$ |/$$/ $$ |
$$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ |
$$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \ $$ |
$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
""" + "\033[0m")
sys_lang = Language(get_default_lang_code())
nonebot.logger.info(sys_lang.get("main.current_language", LANG=sys_lang.get("language.name")))
# nonebot.logger.info(sys_lang.get("main.enable_webdash", URL=f"http://127.0.0.1:{config.get('port', 20216)}"))

View File

@@ -1,41 +0,0 @@
import nonebot
from git import Repo
remote_urls = [
"https://github.com/snowykami/LiteyukiBot.git",
"https://gitee.com/snowykami/LiteyukiBot.git"
]
def detect_update() -> bool:
# 对每个远程仓库进行检查只要有一个仓库有更新就返回True
for remote_url in remote_urls:
repo = Repo(".")
repo.remotes.origin.set_url(remote_url)
repo.remotes.origin.fetch()
if repo.head.commit != repo.commit('origin/main'):
return True
def update_liteyuki() -> tuple[bool, str]:
"""更新轻雪
:return: 是否更新成功,更新变动"""
new_commit_detected = detect_update()
if new_commit_detected:
repo = Repo(".")
logs = ""
# 对每个远程仓库进行更新
for remote_url in remote_urls:
try:
logs += f"\nremote: {remote_url}"
repo.remotes.origin.set_url(remote_url)
repo.remotes.origin.pull()
diffs = repo.head.commit.diff("origin/main")
for diff in diffs.iter_change_type('M'):
logs += f"\n{diff.a_path}"
return True, logs
except:
continue
else:
return False, "Nothing Changed"

View File

@@ -1,324 +0,0 @@
import base64
import time
from typing import Any
import nonebot
import pip
from nonebot import Bot, get_driver, require
from nonebot.adapters.onebot.v11 import Message, escape, unescape
from nonebot.exception import MockApiException
from nonebot.internal.matcher import Matcher
from nonebot.permission import SUPERUSER
from liteyuki.utils.base.config import get_config, load_from_yaml
from liteyuki.utils.base.data_manager import StoredConfig, TempConfig, common_db
from liteyuki.utils.base.language import get_user_lang
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
from liteyuki.utils.base.reloader import Reloader
from .api import update_liteyuki
require("nonebot_plugin_alconna")
require("nonebot_plugin_apscheduler")
from nonebot_plugin_alconna import UniMessage, on_alconna, Alconna, Args, Subcommand, Arparma, MultiVar
from nonebot_plugin_apscheduler import scheduler
driver = get_driver()
markdown_image = common_db.first(StoredConfig(), default=StoredConfig()).config.get("markdown_image", False)
@on_alconna(
command=Alconna(
"liteecho",
Args["text", str, ""],
),
permission=SUPERUSER
).handle()
async def _(bot: T_Bot, matcher: Matcher, result: Arparma):
if result.main_args.get("text"):
await matcher.finish(Message(unescape(result.main_args.get("text"))))
else:
await matcher.finish(f"Hello, Liteyuki!\nBot {bot.self_id}")
@on_alconna(
aliases={"更新轻雪"},
command=Alconna(
"update-liteyuki"
),
permission=SUPERUSER
).handle()
async def _(bot: T_Bot, event: T_MessageEvent):
# 使用git pull更新
ulang = get_user_lang(str(event.user_id))
success, logs = update_liteyuki()
reply = "Liteyuki updated!\n"
reply += f"```\n{logs}\n```\n"
btn_restart = md.btn_cmd(ulang.get("liteyuki.restart_now"), "reload-liteyuki")
pip.main(["install", "-r", "requirements.txt"])
reply += f"{ulang.get('liteyuki.update_restart', RESTART=btn_restart)}"
await md.send_md(reply, bot, event=event, at_sender=False)
@on_alconna(
aliases={"重启轻雪"},
command=Alconna(
"reload-liteyuki"
),
permission=SUPERUSER
).handle()
async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
await matcher.send("Liteyuki reloading")
temp_data = common_db.first(TempConfig(), default=TempConfig())
temp_data.data.update(
{
"reload" : True,
"reload_time" : time.time(),
"reload_bot_id" : bot.self_id,
"reload_session_type": event.message_type,
"reload_session_id" : event.group_id if event.message_type == "group" else event.user_id,
"delta_time" : 0
}
)
common_db.save(temp_data)
Reloader.reload(0)
@on_alconna(
aliases={"配置"},
command=Alconna(
"config",
Subcommand(
"set",
Args["key", str]["value", Any],
alias=["设置"],
),
Subcommand(
"get",
Args["key", str, None],
alias=["查询", "获取"]
),
Subcommand(
"remove",
Args["key", str],
alias=["删除"]
)
),
permission=SUPERUSER
).handle()
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, matcher: Matcher):
ulang = get_user_lang(str(event.user_id))
stored_config: StoredConfig = common_db.first(StoredConfig(), default=StoredConfig())
if result.subcommands.get("set"):
key, value = result.subcommands.get("set").args.get("key"), result.subcommands.get("set").args.get("value")
try:
value = eval(value)
except:
pass
stored_config.config[key] = value
common_db.save(stored_config)
await matcher.finish(f"{ulang.get('liteyuki.config_set_success', KEY=key, VAL=value)}")
elif result.subcommands.get("get"):
key = result.subcommands.get("get").args.get("key")
file_config = load_from_yaml("config.yml")
reply = f"{ulang.get('liteyuki.current_config')}"
if key:
reply += f"```dotenv\n{key}={file_config.get(key, stored_config.config.get(key))}\n```"
else:
reply = f"{ulang.get('liteyuki.current_config')}"
reply += f"\n{ulang.get('liteyuki.static_config')}\n```dotenv"
for k, v in file_config.items():
reply += f"\n{k}={v}"
reply += "\n```"
if len(stored_config.config) > 0:
reply += f"\n{ulang.get('liteyuki.stored_config')}\n```dotenv"
for k, v in stored_config.config.items():
reply += f"\n{k}={v} {type(v)}"
reply += "\n```"
await md.send_md(reply, bot, event=event)
elif result.subcommands.get("remove"):
key = result.subcommands.get("remove").args.get("key")
if key in stored_config.config:
stored_config.config.pop(key)
common_db.save(stored_config)
await matcher.finish(f"{ulang.get('liteyuki.config_remove_success', KEY=key)}")
else:
await matcher.finish(f"{ulang.get('liteyuki.invalid_command', TEXT=key)}")
@on_alconna(
aliases={"切换图片模式"},
command=Alconna(
"switch-image-mode"
),
permission=SUPERUSER
).handle()
async def _(event: T_MessageEvent, matcher: Matcher):
global markdown_image
# 切换图片模式False以图片形式发送True以markdown形式发送
ulang = get_user_lang(str(event.user_id))
stored_config: StoredConfig = common_db.first(StoredConfig(), default=StoredConfig())
stored_config.config["markdown_image"] = not stored_config.config.get("markdown_image", False)
markdown_image = stored_config.config["markdown_image"]
common_db.save(stored_config)
await matcher.finish(ulang.get("liteyuki.image_mode_on" if stored_config.config["markdown_image"] else "liteyuki.image_mode_off"))
@on_alconna(
command=Alconna(
"liteyuki-docs",
),
aliases={"轻雪文档"},
).handle()
async def _(matcher: Matcher):
await matcher.finish("https://bot.liteyuki.icu/usage")
@on_alconna(
command=Alconna(
"/api",
Args["api", str]["args", MultiVar(str), ()],
),
permission=SUPERUSER
).handle()
async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher):
"""
调用API
Args:
result:
bot:
event:
Returns:
"""
api_name = result.main_args.get("api")
args: tuple[str] = result.main_args.get("args", ()) # 类似于url参数但每个参数间用空格分隔空格是%20
args_dict = {}
for arg in args:
key, value = arg.split("=", 1)
args_dict[key] = unescape(value.replace("%20", " "))
if api_name in need_user_id and "user_id" not in args_dict:
args_dict["user_id"] = str(event.user_id)
if api_name in need_group_id and "group_id" not in args_dict and event.message_type == "group":
args_dict["group_id"] = str(event.group_id)
if "message" in args_dict:
args_dict["message"] = Message(args_dict["message"])
try:
result = await bot.call_api(api_name, **args_dict)
except Exception as e:
result = str(e)
args_show = "\n".join("- %s: %s" % (k, v) for k, v in args_dict.items())
print(f"API: {api_name}\n\nArgs: \n{args_show}\n\nResult: {result}")
await matcher.finish(f"API: {api_name}\n\nArgs: \n{args_show}\n\nResult: {result}")
# system hook
@Bot.on_calling_api # 图片模式检测
async def test_for_md_image(bot: T_Bot, api: str, data: dict):
# 截获大图发送转换为markdown发送
if api in ["send_msg", "send_private_msg", "send_group_msg"] and markdown_image and data.get("user_id") != bot.self_id:
if api == "send_msg" and data.get("message_type") == "private" or api == "send_private_msg":
session_type = "private"
session_id = data.get("user_id")
elif api == "send_msg" and data.get("message_type") == "group" or api == "send_group_msg":
session_type = "group"
session_id = data.get("group_id")
else:
return
if len(data.get("message", [])) == 1 and data["message"][0].get("type") == "image":
file: str = data["message"][0].data.get("file")
# file:// http:// base64://
if file.startswith("http"):
result = await md.send_md(await md.image_async(file), bot, message_type=session_type, session_id=session_id)
elif file.startswith("file"):
file = file.replace("file://", "")
result = await md.send_image(open(file, "rb").read(), bot, message_type=session_type, session_id=session_id)
elif file.startswith("base64"):
file_bytes = base64.b64decode(file.replace("base64://", ""))
result = await md.send_image(file_bytes, bot, message_type=session_type, session_id=session_id)
else:
return
raise MockApiException(result=result)
@driver.on_startup
async def on_startup():
temp_data = common_db.first(TempConfig(), default=TempConfig())
# 储存重启信息
if temp_data.data.get("reload", False):
delta_time = time.time() - temp_data.data.get("reload_time", 0)
temp_data.data["delta_time"] = delta_time
common_db.save(temp_data) # 更新数据
@driver.on_shutdown
async def on_shutdown():
pass
@driver.on_bot_connect
async def _(bot: T_Bot):
temp_data = common_db.first(TempConfig(), default=TempConfig())
# 用于重启计时
if temp_data.data.get("reload", False):
temp_data.data["reload"] = False
reload_bot_id = temp_data.data.get("reload_bot_id", 0)
if reload_bot_id != bot.self_id:
return
reload_session_type = temp_data.data.get("reload_session_type", "private")
reload_session_id = temp_data.data.get("reload_session_id", 0)
delta_time = temp_data.data.get("delta_time", 0)
common_db.save(temp_data) # 更新数据
await bot.call_api(
"send_msg",
message_type=reload_session_type,
user_id=reload_session_id,
group_id=reload_session_id,
message="Liteyuki reloaded in %.2f s" % delta_time
)
# 每天4点更新
@scheduler.scheduled_job("cron", hour=4)
async def every_day_update():
if get_config("auto_update", default=True):
result, logs = update_liteyuki()
pip.main(["install", "-r", "requirements.txt"])
if result:
await broadcast_to_superusers(f"Liteyuki updated: ```\n{logs}\n```")
nonebot.logger.info(f"Liteyuki updated: {logs}")
Reloader.reload(5)
else:
nonebot.logger.info(logs)
# 安全的需要用户id的api
need_user_id = (
"send_private_msg",
"send_msg",
"set_group_card",
"set_group_special_title",
"get_stranger_info",
"get_group_member_info"
)
need_group_id = (
"send_group_msg",
"send_msg",
"set_group_card",
"set_group_name",
"set_group_special_title",
"get_group_member_info",
"get_group_member_list",
"get_group_honor_info"
)

View File

@@ -1,43 +0,0 @@
import time
import nonebot
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from liteyuki.utils.base.config import get_config
from liteyuki.utils.base.reloader import Reloader
from liteyuki.utils.base.resource import load_resources
if get_config("debug", False):
nonebot.logger.info("Liteyuki Reload is enable, watching for file changes...")
class CodeModifiedHandler(FileSystemEventHandler):
def on_modified(self, event):
if "liteyuki/resources" not in event.src_path.replace("\\", "/"):
nonebot.logger.debug(f"{event.src_path} has been modified, reloading bot...")
Reloader.reload()
class ResourceModifiedHandler(FileSystemEventHandler):
def on_modified(self, event):
if not event.is_directory:
nonebot.logger.debug(f"{event.src_path} has been modified, reloading resource...")
load_resources()
code_modified_handler = CodeModifiedHandler()
resource_modified_handle = ResourceModifiedHandler()
observer = Observer()
observer.schedule(resource_modified_handle, path="liteyuki/resources", recursive=True)
observer.schedule(resource_modified_handle, path="resources", recursive=True)
observer.schedule(code_modified_handler, path="liteyuki", recursive=True)
observer.start()
# try:
# while True:
# time.sleep(1)
# except KeyboardInterrupt:
# observer.stop()
# observer.join()

View File

@@ -1,27 +0,0 @@
import nonebot.plugin
from liteyuki.utils import init_log
from liteyuki.utils.base.config import get_config
from liteyuki.utils.base.data_manager import InstalledPlugin, plugin_db
from liteyuki.utils.base.resource import load_resources
from liteyuki.utils.message.tools import check_for_package
load_resources()
init_log()
nonebot.plugin.load_plugins("liteyuki/plugins")
# 从数据库读取已安装的插件
if not get_config("safe_mode", False):
# 安全模式下,不加载插件
installed_plugins: list[InstalledPlugin] = plugin_db.all(InstalledPlugin())
if installed_plugins:
for installed_plugin in installed_plugins:
if not check_for_package(installed_plugin.module_name):
nonebot.logger.error(f"{installed_plugin.module_name} not installed, but in loading database. please run `npm fixup` in chat to reinstall it.")
else:
nonebot.load_plugin(installed_plugin.module_name)
nonebot.plugin.load_plugins("plugins")
else:
nonebot.logger.info("Safe mode is on, no plugin loaded.")

View File

@@ -1,15 +0,0 @@
from nonebot.plugin import PluginMetadata
from .rt_guide import *
__plugin_meta__ = PluginMetadata(
name="CRT生成工具",
description="一些CRT牌子生成器",
usage="我觉得你应该会用",
type="application",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki" : True,
"toggleable" : True,
"default_enable": True,
}
)

View File

@@ -1,575 +0,0 @@
import os
import uuid
from typing import Tuple, Union, List
import nonebot
from PIL import Image, ImageFont, ImageDraw
default_color = (255, 255, 255, 255)
default_font = "resources/fonts/MiSans-Semibold.ttf"
def render_canvas_from_json(file: str, background: Image) -> "Canvas":
pass
class BasePanel:
def __init__(self,
uv_size: Tuple[Union[int, float], Union[int, float]] = (1.0, 1.0),
box_size: Tuple[Union[int, float], Union[int, float]] = (1.0, 1.0),
parent_point: Tuple[float, float] = (0.5, 0.5),
point: Tuple[float, float] = (0.5, 0.5)):
"""
:param uv_size: 底面板大小
:param box_size: 子(自身)面板大小
:param parent_point: 底面板锚点
:param point: 子(自身)面板锚点
"""
self.canvas: Canvas | None = None
self.uv_size = uv_size
self.box_size = box_size
self.parent_point = parent_point
self.point = point
self.parent: BasePanel | None = None
self.canvas_box: Tuple[float, float, float, float] = (0.0, 0.0, 1.0, 1.0)
# 此节点在父节点上的盒子
self.box = (
self.parent_point[0] - self.point[0] * self.box_size[0] / self.uv_size[0],
self.parent_point[1] - self.point[1] * self.box_size[1] / self.uv_size[1],
self.parent_point[0] + (1 - self.point[0]) * self.box_size[0] / self.uv_size[0],
self.parent_point[1] + (1 - self.point[1]) * self.box_size[1] / self.uv_size[1]
)
def load(self, only_calculate=False):
"""
将对象写入画布
此处仅作声明
由各子类重写
:return:
"""
self.actual_pos = self.canvas_box
def save_as(self, canvas_box, only_calculate=False):
"""
此函数执行时间较长,建议异步运行
:param only_calculate:
:param canvas_box 此节点在画布上的盒子,并不是在父节点上的盒子
:return:
"""
for name, child in self.__dict__.items():
# 此节点在画布上的盒子
if isinstance(child, BasePanel) and name not in ["canvas", "parent"]:
child.parent = self
if isinstance(self, Canvas):
child.canvas = self
else:
child.canvas = self.canvas
dxc = canvas_box[2] - canvas_box[0]
dyc = canvas_box[3] - canvas_box[1]
child.canvas_box = (
canvas_box[0] + dxc * child.box[0],
canvas_box[1] + dyc * child.box[1],
canvas_box[0] + dxc * child.box[2],
canvas_box[1] + dyc * child.box[3]
)
child.load(only_calculate)
child.save_as(child.canvas_box, only_calculate)
class Canvas(BasePanel):
def __init__(self, base_img: Image.Image):
self.base_img = base_img
self.canvas = self
super(Canvas, self).__init__()
self.draw_line_list = []
def export(self, file, alpha=False):
self.base_img = self.base_img.convert("RGBA")
self.save_as((0, 0, 1, 1))
draw = ImageDraw.Draw(self.base_img)
for line in self.draw_line_list:
draw.line(*line)
if not alpha:
self.base_img = self.base_img.convert("RGB")
self.base_img.save(file)
def delete(self):
os.remove(self.file)
def get_actual_box(self, path: str) -> Union[None, Tuple[float, float, float, float]]:
"""
获取控件实际相对大小
函数执行时间较长
:param path: 控件路径
:return:
"""
sub_obj = self
self.save_as((0, 0, 1, 1), True)
control_path = ""
for i, seq in enumerate(path.split(".")):
if seq not in sub_obj.__dict__:
raise KeyError(f"{control_path}中找不到控件:{seq}")
control_path += f".{seq}"
sub_obj = sub_obj.__dict__[seq]
return sub_obj.actual_pos
def get_actual_pixel_size(self, path: str) -> Union[None, Tuple[int, int]]:
"""
获取控件实际像素长宽
函数执行时间较长
:param path: 控件路径
:return:
"""
sub_obj = self
self.save_as((0, 0, 1, 1), True)
control_path = ""
for i, seq in enumerate(path.split(".")):
if seq not in sub_obj.__dict__:
raise KeyError(f"{control_path}中找不到控件:{seq}")
control_path += f".{seq}"
sub_obj = sub_obj.__dict__[seq]
dx = int(sub_obj.canvas.base_img.size[0] * (sub_obj.actual_pos[2] - sub_obj.actual_pos[0]))
dy = int(sub_obj.canvas.base_img.size[1] * (sub_obj.actual_pos[3] - sub_obj.actual_pos[1]))
return dx, dy
def get_actual_pixel_box(self, path: str) -> Union[None, Tuple[int, int, int, int]]:
"""
获取控件实际像素大小盒子
函数执行时间较长
:param path: 控件路径
:return:
"""
sub_obj = self
self.save_as((0, 0, 1, 1), True)
control_path = ""
for i, seq in enumerate(path.split(".")):
if seq not in sub_obj.__dict__:
raise KeyError(f"{control_path}中找不到控件:{seq}")
control_path += f".{seq}"
sub_obj = sub_obj.__dict__[seq]
x1 = int(sub_obj.canvas.base_img.size[0] * sub_obj.actual_pos[0])
y1 = int(sub_obj.canvas.base_img.size[1] * sub_obj.actual_pos[1])
x2 = int(sub_obj.canvas.base_img.size[2] * sub_obj.actual_pos[2])
y2 = int(sub_obj.canvas.base_img.size[3] * sub_obj.actual_pos[3])
return x1, y1, x2, y2
def get_parent_box(self, path: str) -> Union[None, Tuple[float, float, float, float]]:
"""
获取控件在父节点的大小
函数执行时间较长
:param path: 控件路径
:return:
"""
sub_obj = self.get_control_by_path(path)
on_parent_pos = (
(sub_obj.actual_pos[0] - sub_obj.parent.actual_pos[0]) / (sub_obj.parent.actual_pos[2] - sub_obj.parent.actual_pos[0]),
(sub_obj.actual_pos[1] - sub_obj.parent.actual_pos[1]) / (sub_obj.parent.actual_pos[3] - sub_obj.parent.actual_pos[1]),
(sub_obj.actual_pos[2] - sub_obj.parent.actual_pos[0]) / (sub_obj.parent.actual_pos[2] - sub_obj.parent.actual_pos[0]),
(sub_obj.actual_pos[3] - sub_obj.parent.actual_pos[1]) / (sub_obj.parent.actual_pos[3] - sub_obj.parent.actual_pos[1])
)
return on_parent_pos
def get_control_by_path(self, path: str) -> Union[BasePanel, "Img", "Rectangle", "Text"]:
sub_obj = self
self.save_as((0, 0, 1, 1), True)
control_path = ""
for i, seq in enumerate(path.split(".")):
if seq not in sub_obj.__dict__:
raise KeyError(f"{control_path}中找不到控件:{seq}")
control_path += f".{seq}"
sub_obj = sub_obj.__dict__[seq]
return sub_obj
def draw_line(self, path: str, p1: Tuple[float, float], p2: Tuple[float, float], color, width):
"""
画线
:param color:
:param width:
:param path:
:param p1:
:param p2:
:return:
"""
ac_pos = self.get_actual_box(path)
control = self.get_control_by_path(path)
dx = ac_pos[2] - ac_pos[0]
dy = ac_pos[3] - ac_pos[1]
xy_box = int((ac_pos[0] + dx * p1[0]) * control.canvas.base_img.size[0]), int((ac_pos[1] + dy * p1[1]) * control.canvas.base_img.size[1]), int(
(ac_pos[0] + dx * p2[0]) * control.canvas.base_img.size[0]), int((ac_pos[1] + dy * p2[1]) * control.canvas.base_img.size[1])
self.draw_line_list.append((xy_box, color, width))
class Panel(BasePanel):
def __init__(self, uv_size, box_size, parent_point, point):
super(Panel, self).__init__(uv_size, box_size, parent_point, point)
class TextSegment:
def __init__(self, text, **kwargs):
if not isinstance(text, str):
raise TypeError("请输入字符串")
self.text = text
self.color = kwargs.get("color", None)
self.font = kwargs.get("font", None)
@staticmethod
def text2text_segment_list(text: str):
"""
暂时没写好
:param text: %FFFFFFFF%1123%FFFFFFFF%21323
:return:
"""
pass
class Text(BasePanel):
def __init__(self, uv_size, box_size, parent_point, point, text: Union[str, list], font=default_font, color=(255, 255, 255, 255), vertical=False,
line_feed=False, force_size=False, fill=(0, 0, 0, 0), fillet=0, outline=(0, 0, 0, 0), outline_width=0, rectangle_side=0, font_size=None, dp: int = 5,
anchor: str = "la"):
"""
:param uv_size:
:param box_size:
:param parent_point:
:param point:
:param text: list[TextSegment] | str
:param font:
:param color:
:param vertical: 是否竖直
:param line_feed: 是否换行
:param force_size: 强制大小
:param dp: 字体大小递减精度
:param anchor : https://www.zhihu.com/question/474216280
:param fill: 底部填充颜色
:param fillet: 填充圆角
:param rectangle_side: 边框宽度
:param outline: 填充矩形边框颜色
:param outline_width: 填充矩形边框宽度
"""
self.actual_pos = None
self.outline_width = outline_width
self.outline = outline
self.fill = fill
self.fillet = fillet
self.font = font
self.text = text
self.color = color
self.force_size = force_size
self.vertical = vertical
self.line_feed = line_feed
self.dp = dp
self.font_size = font_size
self.rectangle_side = rectangle_side
self.anchor = anchor
super(Text, self).__init__(uv_size, box_size, parent_point, point)
def load(self, only_calculate=False):
"""限制区域像素大小"""
if isinstance(self.text, str):
self.text = [
TextSegment(text=self.text, color=self.color, font=self.font)
]
all_text = str()
for text in self.text:
all_text += text.text
limited_size = int((self.canvas_box[2] - self.canvas_box[0]) * self.canvas.base_img.size[0]), int((self.canvas_box[3] - self.canvas_box[1]) * self.canvas.base_img.size[1])
font_size = limited_size[1] if self.font_size is None else self.font_size
image_font = ImageFont.truetype(self.font, font_size)
actual_size = image_font.getsize(all_text)
while (actual_size[0] > limited_size[0] or actual_size[1] > limited_size[1]) and not self.force_size:
font_size -= self.dp
image_font = ImageFont.truetype(self.font, font_size)
actual_size = image_font.getsize(all_text)
draw = ImageDraw.Draw(self.canvas.base_img)
if isinstance(self.parent, Img) or isinstance(self.parent, Text):
self.parent.canvas_box = self.parent.actual_pos
dx0 = self.parent.canvas_box[2] - self.parent.canvas_box[0]
dy0 = self.parent.canvas_box[3] - self.parent.canvas_box[1]
dx1 = actual_size[0] / self.canvas.base_img.size[0]
dy1 = actual_size[1] / self.canvas.base_img.size[1]
start_point = [
int((self.parent.canvas_box[0] + dx0 * self.parent_point[0] - dx1 * self.point[0]) * self.canvas.base_img.size[0]),
int((self.parent.canvas_box[1] + dy0 * self.parent_point[1] - dy1 * self.point[1]) * self.canvas.base_img.size[1])
]
self.actual_pos = (
start_point[0] / self.canvas.base_img.size[0],
start_point[1] / self.canvas.base_img.size[1],
(start_point[0] + actual_size[0]) / self.canvas.base_img.size[0],
(start_point[1] + actual_size[1]) / self.canvas.base_img.size[1],
)
self.font_size = font_size
if not only_calculate:
for text_segment in self.text:
if text_segment.color is None:
text_segment.color = self.color
if text_segment.font is None:
text_segment.font = self.font
image_font = ImageFont.truetype(font=text_segment.font, size=font_size)
if self.fill[-1] > 0:
rectangle = Shape.rectangle(size=(actual_size[0] + 2 * self.rectangle_side, actual_size[1] + 2 * self.rectangle_side), fillet=self.fillet, fill=self.fill,
width=self.outline_width, outline=self.outline)
self.canvas.base_img.paste(im=rectangle, box=(start_point[0] - self.rectangle_side,
start_point[1] - self.rectangle_side,
start_point[0] + actual_size[0] + self.rectangle_side,
start_point[1] + actual_size[1] + self.rectangle_side),
mask=rectangle.split()[-1])
draw.text((start_point[0] - self.rectangle_side, start_point[1] - self.rectangle_side),
text_segment.text, text_segment.color, font=image_font, anchor=self.anchor)
text_width = image_font.getsize(text_segment.text)
start_point[0] += text_width[0]
class Img(BasePanel):
def __init__(self, uv_size, box_size, parent_point, point, img: Image.Image, keep_ratio=True):
self.img_base_img = img
self.keep_ratio = keep_ratio
super(Img, self).__init__(uv_size, box_size, parent_point, point)
def load(self, only_calculate=False):
self.preprocess()
self.img_base_img = self.img_base_img.convert("RGBA")
limited_size = int((self.canvas_box[2] - self.canvas_box[0]) * self.canvas.base_img.size[0]), \
int((self.canvas_box[3] - self.canvas_box[1]) * self.canvas.base_img.size[1])
if self.keep_ratio:
"""保持比例"""
actual_ratio = self.img_base_img.size[0] / self.img_base_img.size[1]
limited_ratio = limited_size[0] / limited_size[1]
if actual_ratio >= limited_ratio:
# 图片过长
self.img_base_img = self.img_base_img.resize(
(int(self.img_base_img.size[0] * limited_size[0] / self.img_base_img.size[0]),
int(self.img_base_img.size[1] * limited_size[0] / self.img_base_img.size[0]))
)
else:
self.img_base_img = self.img_base_img.resize(
(int(self.img_base_img.size[0] * limited_size[1] / self.img_base_img.size[1]),
int(self.img_base_img.size[1] * limited_size[1] / self.img_base_img.size[1]))
)
else:
"""不保持比例"""
self.img_base_img = self.img_base_img.resize(limited_size)
# 占比长度
if isinstance(self.parent, Img) or isinstance(self.parent, Text):
self.parent.canvas_box = self.parent.actual_pos
dx0 = self.parent.canvas_box[2] - self.parent.canvas_box[0]
dy0 = self.parent.canvas_box[3] - self.parent.canvas_box[1]
dx1 = self.img_base_img.size[0] / self.canvas.base_img.size[0]
dy1 = self.img_base_img.size[1] / self.canvas.base_img.size[1]
start_point = (
int((self.parent.canvas_box[0] + dx0 * self.parent_point[0] - dx1 * self.point[0]) * self.canvas.base_img.size[0]),
int((self.parent.canvas_box[1] + dy0 * self.parent_point[1] - dy1 * self.point[1]) * self.canvas.base_img.size[1])
)
alpha = self.img_base_img.split()[3]
self.actual_pos = (
start_point[0] / self.canvas.base_img.size[0],
start_point[1] / self.canvas.base_img.size[1],
(start_point[0] + self.img_base_img.size[0]) / self.canvas.base_img.size[0],
(start_point[1] + self.img_base_img.size[1]) / self.canvas.base_img.size[1],
)
if not only_calculate:
self.canvas.base_img.paste(self.img_base_img, start_point, alpha)
def preprocess(self):
pass
class Rectangle(Img):
def __init__(self, uv_size, box_size, parent_point, point, fillet: Union[int, float] = 0, img: Union[Image.Image] = None, keep_ratio=True,
color=default_color, outline_width=0, outline_color=default_color):
"""
圆角图
:param uv_size:
:param box_size:
:param parent_point:
:param point:
:param fillet: 圆角半径浮点或整数
:param img:
:param keep_ratio:
"""
self.fillet = fillet
self.color = color
self.outline_width = outline_width
self.outline_color = outline_color
super(Rectangle, self).__init__(uv_size, box_size, parent_point, point, img, keep_ratio)
def preprocess(self):
limited_size = (int(self.canvas.base_img.size[0] * (self.canvas_box[2] - self.canvas_box[0])),
int(self.canvas.base_img.size[1] * (self.canvas_box[3] - self.canvas_box[1])))
if not self.keep_ratio and self.img_base_img is not None and self.img_base_img.size[0] / self.img_base_img.size[1] != limited_size[0] / limited_size[1]:
self.img_base_img = self.img_base_img.resize(limited_size)
self.img_base_img = Shape.rectangle(size=limited_size, fillet=self.fillet, fill=self.color, width=self.outline_width, outline=self.outline_color)
class Color:
GREY = (128, 128, 128, 255)
RED = (255, 0, 0, 255)
GREEN = (0, 255, 0, 255)
BLUE = (0, 0, 255, 255)
YELLOW = (255, 255, 0, 255)
PURPLE = (255, 0, 255, 255)
CYAN = (0, 255, 255, 255)
WHITE = (255, 255, 255, 255)
BLACK = (0, 0, 0, 255)
@staticmethod
def hex2dec(colorHex: str) -> Tuple[int, int, int, int]:
"""
:param colorHex: FFFFFFFF ARGB-> (R, G, B, A)
:return:
"""
return int(colorHex[2:4], 16), int(colorHex[4:6], 16), int(colorHex[6:8], 16), int(colorHex[0:2], 16)
class Shape:
@staticmethod
def circular(radius: int, fill: tuple, width: int = 0, outline: tuple = Color.BLACK) -> Image.Image:
"""
:param radius: 半径(像素)
:param fill: 填充颜色
:param width: 轮廓粗细(像素)
:param outline: 轮廓颜色
:return: 圆形Image对象
"""
img = Image.new("RGBA", (radius * 2, radius * 2), color=radius)
draw = ImageDraw.Draw(img)
draw.ellipse(xy=(0, 0, radius * 2, radius * 2), fill=fill, outline=outline, width=width)
return img
@staticmethod
def rectangle(size: Tuple[int, int], fill: tuple, width: int = 0, outline: tuple = Color.BLACK, fillet: int = 0) -> Image.Image:
"""
:param fillet: 圆角半径(像素)
:param size: 长宽(像素)
:param fill: 填充颜色
:param width: 轮廓粗细(像素)
:param outline: 轮廓颜色
:return: 矩形Image对象
"""
img = Image.new("RGBA", size, color=fill)
draw = ImageDraw.Draw(img)
draw.rounded_rectangle(xy=(0, 0, size[0], size[1]), fill=fill, outline=outline, width=width, radius=fillet)
return img
@staticmethod
def ellipse(size: Tuple[int, int], fill: tuple, outline: int = 0, outline_color: tuple = Color.BLACK) -> Image.Image:
"""
:param size: 长宽(像素)
:param fill: 填充颜色
:param outline: 轮廓粗细(像素)
:param outline_color: 轮廓颜色
:return: 椭圆Image对象
"""
img = Image.new("RGBA", size, color=fill)
draw = ImageDraw.Draw(img)
draw.ellipse(xy=(0, 0, size[0], size[1]), fill=fill, outline=outline_color, width=outline)
return img
@staticmethod
def polygon(points: List[Tuple[int, int]], fill: tuple, outline: int, outline_color: tuple) -> Image.Image:
"""
:param points: 多边形顶点列表
:param fill: 填充颜色
:param outline: 轮廓粗细(像素)
:param outline_color: 轮廓颜色
:return: 多边形Image对象
"""
img = Image.new("RGBA", (max(points)[0], max(points)[1]), color=fill)
draw = ImageDraw.Draw(img)
draw.polygon(xy=points, fill=fill, outline=outline_color, width=outline)
return img
@staticmethod
def line(points: List[Tuple[int, int]], fill: tuple, width: int) -> Image:
"""
:param points: 线段顶点列表
:param fill: 填充颜色
:param width: 线段粗细(像素)
:return: 线段Image对象
"""
img = Image.new("RGBA", (max(points)[0], max(points)[1]), color=fill)
draw = ImageDraw.Draw(img)
draw.line(xy=points, fill=fill, width=width)
return img
class Utils:
@staticmethod
def central_clip_by_ratio(img: Image.Image, size: Tuple, use_cache=True):
"""
:param use_cache: 是否使用缓存,剪切过一次后默认生成缓存
:param img:
:param size: 仅为比例,满填充裁剪
:return:
"""
cache_file_path = str()
if use_cache:
filename_without_end = ".".join(os.path.basename(img.fp.name).split(".")[0:-1]) + f"_{size[0]}x{size[1]}" + ".png"
cache_file_path = os.path.join(".cache", filename_without_end)
if os.path.exists(cache_file_path):
nonebot.logger.info("本次使用缓存加载图片,不裁剪")
return Image.open(os.path.join(".cache", filename_without_end))
img_ratio = img.size[0] / img.size[1]
limited_ratio = size[0] / size[1]
if limited_ratio > img_ratio:
actual_size = (
img.size[0],
img.size[0] / size[0] * size[1]
)
box = (
0, (img.size[1] - actual_size[1]) // 2,
img.size[0], img.size[1] - (img.size[1] - actual_size[1]) // 2
)
else:
actual_size = (
img.size[1] / size[1] * size[0],
img.size[1],
)
box = (
(img.size[0] - actual_size[0]) // 2, 0,
img.size[0] - (img.size[0] - actual_size[0]) // 2, img.size[1]
)
img = img.crop(box).resize(size)
if use_cache:
img.save(cache_file_path)
return img
@staticmethod
def circular_clip(img: Image.Image):
"""
裁剪为alpha圆形
:param img:
:return:
"""
length = min(img.size)
alpha_cover = Image.new("RGBA", (length, length), color=(0, 0, 0, 0))
if img.size[0] > img.size[1]:
box = (
(img.size[0] - img[1]) // 2, 0,
(img.size[0] - img[1]) // 2 + img.size[1], img.size[1]
)
else:
box = (
0, (img.size[1] - img.size[0]) // 2,
img.size[0], (img.size[1] - img.size[0]) // 2 + img.size[0]
)
img = img.crop(box).resize((length, length))
draw = ImageDraw.Draw(alpha_cover)
draw.ellipse(xy=(0, 0, length, length), fill=(255, 255, 255, 255))
alpha = alpha_cover.split()[-1]
img.putalpha(alpha)
return img
@staticmethod
def open_img(path) -> Image.Image:
return Image.open(path, "RGBA")

View File

@@ -1,419 +0,0 @@
import json
from typing import List, Any
from PIL import Image
from arclet.alconna import Alconna
from nb_cli import run_sync
from nonebot import on_command
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, Args, MultiVar, Arparma, UniMessage
from pydantic import BaseModel
from .canvas import *
from ...utils.base.resource import get_path
resolution = 256
class Entrance(BaseModel):
identifier: str
size: tuple[int, int]
dest: List[str]
class Station(BaseModel):
identifier: str
chineseName: str
englishName: str
position: tuple[int, int]
class Line(BaseModel):
identifier: str
chineseName: str
englishName: str
color: Any
stations: List["Station"]
font_light = get_path("templates/fonts/MiSans/MiSans-Light.woff2")
font_bold = get_path("templates/fonts/MiSans/MiSans-Bold.woff2")
@run_sync
def generate_entrance_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
reso: int = resolution):
"""
Generates an entrance sign for the ride.
"""
width, height = ratio[0] * reso, ratio[1] * reso
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.WHITE))
# 加黑色图框
baseCanvas.outline = Img(
uv_size=(1, 1),
box_size=(1, 1),
parent_point=(0, 0),
point=(0, 0),
img=Shape.rectangle(
size=(width, height),
fillet=0,
fill=(0, 0, 0, 0),
width=15,
outline=Color.BLACK
)
)
baseCanvas.contentPanel = Panel(
uv_size=(width, height),
box_size=(width - 28, height - 28),
parent_point=(0.5, 0.5),
point=(0.5, 0.5),
)
linePanelHeight = 0.7 * ratio[1]
linePanelWidth = linePanelHeight * 1.3
# 画线路面板部分
for i, line in enumerate(lineInfo):
linePanel = baseCanvas.contentPanel.__dict__[f"Line_{i}_Panel"] = Panel(
uv_size=ratio,
box_size=(linePanelWidth, linePanelHeight),
parent_point=(i * linePanelWidth / ratio[0], 1),
point=(0, 1),
)
linePanel.colorCube = Img(
uv_size=(1, 1),
box_size=(0.15, 1),
parent_point=(0.125, 1),
point=(0, 1),
img=Shape.rectangle(
size=(100, 100),
fillet=0,
fill=line.color,
),
keep_ratio=False
)
textPanel = linePanel.TextPanel = Panel(
uv_size=(1, 1),
box_size=(0.625, 1),
parent_point=(1, 1),
point=(1, 1)
)
# 中文线路名
textPanel.namePanel = Panel(
uv_size=(1, 1),
box_size=(1, 2 / 3),
parent_point=(0, 0),
point=(0, 0),
)
nameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.namePanel".format(i))
textPanel.namePanel.text = Text(
uv_size=(1, 1),
box_size=(1, 1),
parent_point=(0.5, 0.5),
point=(0.5, 0.5),
text=line.chineseName,
color=Color.BLACK,
font_size=int(nameSize[1] * 0.5),
force_size=True,
font=font_bold
)
# 英文线路名
textPanel.englishNamePanel = Panel(
uv_size=(1, 1),
box_size=(1, 1 / 3),
parent_point=(0, 1),
point=(0, 1),
)
englishNameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.englishNamePanel".format(i))
textPanel.englishNamePanel.text = Text(
uv_size=(1, 1),
box_size=(1, 1),
parent_point=(0.5, 0.5),
point=(0.5, 0.5),
text=line.englishName,
color=Color.BLACK,
font_size=int(englishNameSize[1] * 0.6),
force_size=True,
font=font_light
)
# 画名称部分
namePanel = baseCanvas.contentPanel.namePanel = Panel(
uv_size=(1, 1),
box_size=(1, 0.4),
parent_point=(0.5, 0),
point=(0.5, 0),
)
namePanel.text = Text(
uv_size=(1, 1),
box_size=(1, 1),
parent_point=(0.5, 0.5),
point=(0.5, 0.5),
text=name,
color=Color.BLACK,
font_size=int(height * 0.3),
force_size=True,
font=font_bold
)
aliasesPanel = baseCanvas.contentPanel.aliasesPanel = Panel(
uv_size=(1, 1),
box_size=(1, 0.5),
parent_point=(0.5, 1),
point=(0.5, 1),
)
for j, alias in enumerate(aliases):
aliasesPanel.__dict__[alias] = Text(
uv_size=(1, 1),
box_size=(0.35, 0.5),
parent_point=(0.5, 0.5 * j),
point=(0.5, 0),
text=alias,
color=Color.BLACK,
font_size=int(height * 0.15),
font=font_light
)
# 画入口标识
entrancePanel = baseCanvas.contentPanel.entrancePanel = Panel(
uv_size=(1, 1),
box_size=(0.2, 1),
parent_point=(1, 0.5),
point=(1, 0.5),
)
# 中文文本
entrancePanel.namePanel = Panel(
uv_size=(1, 1),
box_size=(1, 0.5),
parent_point=(1, 0),
point=(1, 0),
)
entrancePanel.namePanel.text = Text(
uv_size=(1, 1),
box_size=(1, 1),
parent_point=(0, 0.5),
point=(0, 0.5),
text=f"{entranceIdentifier}出入口",
color=Color.BLACK,
font_size=int(height * 0.2),
force_size=True,
font=font_bold
)
# 英文文本
entrancePanel.englishNamePanel = Panel(
uv_size=(1, 1),
box_size=(1, 0.5),
parent_point=(1, 1),
point=(1, 1),
)
entrancePanel.englishNamePanel.text = Text(
uv_size=(1, 1),
box_size=(1, 1),
parent_point=(0, 0.5),
point=(0, 0.5),
text=f"Entrance {entranceIdentifier}",
color=Color.BLACK,
font_size=int(height * 0.15),
force_size=True,
font=font_light
)
return baseCanvas.base_img.tobytes()
crt_alc = on_alconna(
Alconna(
"crt",
Subcommand(
"entrance",
Args["name", str]["lines", str, ""]["entrance", int, 1], # /crt entrance 璧山&Bishan 1号线&Line1&#ff0000,27号线&Line1&#ff0000 1A
)
)
)
@crt_alc.assign("entrance")
async def _(result: Arparma):
args = result.subcommands.get("entrance").args
name = args["name"]
lines = args["lines"]
entrance = args["entrance"]
line_info = []
for line in lines.split(","):
line_args = line.split("&")
line_info.append(Line(
identifier=1,
chineseName=line_args[0],
englishName=line_args[1],
color=line_args[2],
stations=[]
))
img_bytes = await generate_entrance_sign(
name=name,
aliases=name.split("&"),
lineInfo=line_info,
entranceIdentifier=entrance,
ratio=(8, 1),
reso=256,
)
await crt_alc.finish(
UniMessage.image(raw=img_bytes)
)
def generate_platform_line_pic(line: Line, station: Station, ratio=None, reso: int = resolution):
"""
生成站台线路图
:param line: 线路对象
:param station: 本站点对象
:param ratio: 比例
:param reso: 分辨率1reso
:return: 两个方向的站牌
"""
if ratio is None:
ratio = [4, 1]
width, height = ratio[0] * reso, ratio[1] * reso
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.YELLOW))
# 加黑色图框
baseCanvas.linePanel = Panel(
uv_size=(1, 1),
box_size=(0.8, 0.15),
parent_point=(0.5, 0.5),
point=(0.5, 0.5),
)
# 直线块
baseCanvas.linePanel.recLine = Img(
uv_size=(1, 1),
box_size=(1, 1),
parent_point=(0.5, 0.5),
point=(0.5, 0.5),
img=Shape.rectangle(
size=(10, 10),
fill=line.color,
),
keep_ratio=False
)
# 灰色直线块
baseCanvas.linePanel.recLineGrey = Img(
uv_size=(1, 1),
box_size=(1, 1),
parent_point=(0.5, 0.5),
point=(0.5, 0.5),
img=Shape.rectangle(
size=(10, 10),
fill=Color.GREY,
),
keep_ratio=False
)
# 生成各站圆点
outline_width = 40
circleForward = Shape.circular(
radius=200,
fill=Color.WHITE,
width=outline_width,
outline=line.color,
)
circleThisPanel = Canvas(Image.new("RGBA", (200, 200), (0, 0, 0, 0)))
circleThisPanel.circleOuter = Img(
uv_size=(1, 1),
box_size=(1, 1),
parent_point=(0.5, 0.5),
point=(0.5, 0.5),
img=Shape.circular(
radius=200,
fill=Color.WHITE,
width=outline_width,
outline=line.color,
),
)
circleThisPanel.circleOuter.circleInner = Img(
uv_size=(1, 1),
box_size=(0.7, 0.7),
parent_point=(0.5, 0.5),
point=(0.5, 0.5),
img=Shape.circular(
radius=200,
fill=line.color,
width=0,
outline=line.color,
),
)
circleThisPanel.export("a.png", alpha=True)
circleThis = circleThisPanel.base_img
circlePassed = Shape.circular(
radius=200,
fill=Color.WHITE,
width=outline_width,
outline=Color.GREY,
)
arrival = False
distance = 1 / (len(line.stations) - 1)
for i, sta in enumerate(line.stations):
box_size = (1.618, 1.618)
if sta.identifier == station.identifier:
arrival = True
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
uv_size=(1, 1),
box_size=(1.8, 1.8),
parent_point=(distance * i, 0.5),
point=(0.5, 0.5),
img=circleThis,
keep_ratio=True
)
continue
if arrival:
# 后方站绘制
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
uv_size=(1, 1),
box_size=box_size,
parent_point=(distance * i, 0.5),
point=(0.5, 0.5),
img=circleForward,
keep_ratio=True
)
else:
# 前方站绘制
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
uv_size=(1, 1),
box_size=box_size,
parent_point=(distance * i, 0.5),
point=(0.5, 0.5),
img=circlePassed,
keep_ratio=True
)
return baseCanvas
def generate_platform_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
reso: int = resolution
):
pass
# def main():
# generate_entrance_sign(
# "璧山",
# aliases=["Bishan"],
# lineInfo=[
#
# Line(identifier="2", chineseName="1号线", englishName="Line 1", color=Color.RED, stations=[]),
# Line(identifier="3", chineseName="27号线", englishName="Line 27", color="#685bc7", stations=[]),
# Line(identifier="1", chineseName="璧铜线", englishName="BT Line", color="#685BC7", stations=[]),
# ],
# entranceIdentifier="1",
# ratio=(8, 1)
# )
#
#
# main()

View File

@@ -1,125 +0,0 @@
import nonebot
from nonebot import on_message, require
from nonebot.plugin import PluginMetadata
from liteyuki.utils.base.data import Database, LiteModel
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.message.message import MarkdownMessage as md
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna
from arclet.alconna import Arparma, Alconna, Args, Option, Subcommand
class Node(LiteModel):
TABLE_NAME: str = "node"
bot_id: str = ""
session_type: str = ""
session_id: str = ""
def __str__(self):
return f"{self.bot_id}.{self.session_type}.{self.session_id}"
class Push(LiteModel):
TABLE_NAME: str = "push"
source: Node = Node()
target: Node = Node()
inde: int = 0
pushes_db = Database("data/pushes.ldb")
pushes_db.auto_migrate(Push(), Node())
alc = Alconna(
"lep",
Subcommand(
"add",
Args["source", str],
Args["target", str],
Option("bidirectional", Args["bidirectional", bool])
),
Subcommand(
"rm",
Args["index", int],
),
Subcommand(
"list",
)
)
add_push = on_alconna(alc)
@add_push.handle()
async def _(result: Arparma):
"""bot_id.session_type.session_id"""
if result.subcommands.get("add"):
source = result.subcommands["add"].args.get("source")
target = result.subcommands["add"].args.get("target")
if source and target:
source = source.split(".")
target = target.split(".")
push1 = Push(
source=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
target=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
inde=len(pushes_db.all(Push(), default=[]))
)
pushes_db.save(push1)
if result.subcommands["add"].args.get("bidirectional"):
push2 = Push(
source=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
target=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
inde=len(pushes_db.all(Push(), default=[]))
)
pushes_db.save(push2)
await add_push.finish("添加成功")
else:
await add_push.finish("参数缺失")
elif result.subcommands.get("rm"):
index = result.subcommands["rm"].args.get("index")
if index is not None:
try:
pushes_db.delete(Push(), "inde = ?", index)
await add_push.finish("删除成功")
except IndexError:
await add_push.finish("索引错误")
else:
await add_push.finish("参数缺失")
elif result.subcommands.get("list"):
await add_push.finish(
"\n".join([f"{push.inde} {push.source.bot_id}.{push.source.session_type}.{push.source.session_id} -> "
f"{push.target.bot_id}.{push.target.session_type}.{push.target.session_id}" for i, push in
enumerate(pushes_db.all(Push(), default=[]))]))
else:
await add_push.finish("参数错误")
@on_message(block=False).handle()
async def _(event: T_MessageEvent, bot: T_Bot):
for push in pushes_db.all(Push(), default=[]):
if str(push.source) == f"{bot.self_id}.{event.message_type}.{event.user_id if event.message_type == 'private' else event.group_id}":
bot2 = nonebot.get_bot(push.target.bot_id)
msg_formatted = ""
for line in str(event.message).split("\n"):
msg_formatted += f"**{line.strip()}**\n"
push_message = (
f"> From {event.sender.nickname}@{push.source.session_type}.{push.source.session_id}\n> Bot {bot.self_id}\n\n"
f"{msg_formatted}")
await md.send_md(push_message, bot2, message_type=push.target.session_type,
session_id=push.target.session_id)
return
__author__ = "snowykami"
__plugin_meta__ = PluginMetadata(
name="轻雪事件推送",
description="事件推送插件支持单向和双向推送支持跨Bot推送",
usage="",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki": True,
}
)

View File

@@ -1,61 +0,0 @@
from nonebot import on_command, require
from nonebot.adapters.onebot.v11 import MessageSegment
from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent, v11
from liteyuki.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
from liteyuki.utils.message.html_tool import *
md_test = on_command("mdts", permission=SUPERUSER)
btn_test = on_command("btnts", permission=SUPERUSER)
latex_test = on_command("latex", permission=SUPERUSER)
placeholder = {
"&#91;": "[",
"&#93;": "]",
"&amp;": "&",
"&#44;": ",",
"\n" : r"\n",
"\"" : r'\\\"'
}
@md_test.handle()
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
await md.send_md(
str(arg),
bot,
message_type=event.message_type,
session_id=event.user_id if event.message_type == "private" else event.group_id
)
@btn_test.handle()
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
await md.send_btn(
str(arg),
bot,
message_type=event.message_type,
session_id=event.user_id if event.message_type == "private" else event.group_id
)
@latex_test.handle()
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
latex_text = f"$${str(arg)}$$"
img = await md_to_pic(latex_text)
await bot.send(event=event, message=MessageSegment.image(img))
__author__ = "snowykami"
__plugin_meta__ = PluginMetadata(
name="轻雪Markdown测试",
description="用于测试Markdown的插件",
usage="",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki": True,
}
)

View File

@@ -1,14 +0,0 @@
from nonebot.plugin import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="Minecraft工具箱",
description="一些Minecraft相关工具箱",
usage="我觉得你应该会用",
type="application",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki" : True,
"toggleable" : True,
"default_enable": True,
}
)

View File

@@ -1,15 +0,0 @@
from nonebot.plugin import PluginMetadata
from .minesweeper import *
__plugin_meta__ = PluginMetadata(
name="轻雪小游戏",
description="内置了一些小游戏",
usage="",
type="application",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki": True,
"toggleable" : True,
"default_enable" : True,
}
)

View File

@@ -1,169 +0,0 @@
import random
from pydantic import BaseModel
from liteyuki.utils.message.message import MarkdownMessage as md
class Dot(BaseModel):
row: int
col: int
mask: bool = True
value: int = 0
flagged: bool = False
class Minesweeper:
# 0-8: number of mines around, 9: mine, -1: undefined
NUMS = "⓪①②③④⑤⑥⑦⑧🅑⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
MASK = "🅜"
FLAG = "🅕"
MINE = "🅑"
def __init__(self, rows, cols, num_mines, session_type, session_id):
assert rows > 0 and cols > 0 and 0 < num_mines < rows * cols
self.session_type = session_type
self.session_id = session_id
self.rows = rows
self.cols = cols
self.num_mines = num_mines
self.board: list[list[Dot]] = [[Dot(row=i, col=j) for j in range(cols)] for i in range(rows)]
self.is_first = True
def reveal(self, row, col) -> bool:
"""
展开
Args:
row:
col:
Returns:
游戏是否继续
"""
if self.is_first:
# 第一次展开,生成地雷
self.generate_board(self.board[row][col])
self.is_first = False
if self.board[row][col].value == 9:
self.board[row][col].mask = False
return False
if not self.board[row][col].mask:
return True
self.board[row][col].mask = False
if self.board[row][col].value == 0:
self.reveal_neighbors(row, col)
return True
def is_win(self) -> bool:
"""
是否胜利
Returns:
"""
for row in range(self.rows):
for col in range(self.cols):
if self.board[row][col].mask and self.board[row][col].value != 9:
return False
return True
def generate_board(self, first_dot: Dot):
"""
避开第一个点,生成地雷
Args:
first_dot: 第一个点
Returns:
"""
generate_count = 0
while generate_count < self.num_mines:
row = random.randint(0, self.rows - 1)
col = random.randint(0, self.cols - 1)
if self.board[row][col].value == 9 or (row, col) == (first_dot.row, first_dot.col):
continue
self.board[row][col] = Dot(row=row, col=col, mask=True, value=9)
generate_count += 1
for row in range(self.rows):
for col in range(self.cols):
if self.board[row][col].value != 9:
self.board[row][col].value = self.count_adjacent_mines(row, col)
def count_adjacent_mines(self, row, col):
"""
计算周围地雷数量
Args:
row:
col:
Returns:
"""
count = 0
for r in range(max(0, row - 1), min(self.rows, row + 2)):
for c in range(max(0, col - 1), min(self.cols, col + 2)):
if self.board[r][c].value == 9:
count += 1
return count
def reveal_neighbors(self, row, col):
"""
递归展开,使用深度优先搜索
Args:
row:
col:
Returns:
"""
for r in range(max(0, row - 1), min(self.rows, row + 2)):
for c in range(max(0, col - 1), min(self.cols, col + 2)):
if self.board[r][c].mask:
self.board[r][c].mask = False
if self.board[r][c].value == 0:
self.reveal_neighbors(r, c)
def mark(self, row, col) -> bool:
"""
标记
Args:
row:
col:
Returns:
是否标记成功,如果已经展开则无法标记
"""
if self.board[row][col].mask:
self.board[row][col].flagged = not self.board[row][col].flagged
return self.board[row][col].flagged
def board_markdown(self) -> str:
"""
打印地雷板
Returns:
"""
dis = " "
start = "> " if self.cols >= 10 else ""
text = start + self.NUMS[0] + dis*2
# 横向两个雷之间的间隔字符
# 生成横向索引
for i in range(self.cols):
text += f"{self.NUMS[i]}" + dis
text += "\n\n"
for i, row in enumerate(self.board):
text += start + f"{self.NUMS[i]}" + dis*2
print([d.value for d in row])
for dot in row:
if dot.mask and not dot.flagged:
text += md.btn_cmd(self.MASK, f"minesweeper reveal {dot.row} {dot.col}")
elif dot.flagged:
text += md.btn_cmd(self.FLAG, f"minesweeper mark {dot.row} {dot.col}")
else:
text += self.NUMS[dot.value]
text += dis
text += "\n"
btn_mark = md.btn_cmd("标记", f"minesweeper mark ", enter=False)
btn_end = md.btn_cmd("结束", "minesweeper end", enter=True)
text += f" {btn_mark} {btn_end}"
return text

View File

@@ -1,103 +0,0 @@
from nonebot import require
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.message.message import MarkdownMessage as md
require("nonebot_plugin_alconna")
from .game import Minesweeper
from nonebot_plugin_alconna import Alconna, on_alconna, Subcommand, Args, Arparma
minesweeper = on_alconna(
aliases={"扫雷"},
command=Alconna(
"minesweeper",
Subcommand(
"start",
Args["row", int, 8]["col", int, 8]["mines", int, 10],
alias=["开始"],
),
Subcommand(
"end",
alias=["结束"]
),
Subcommand(
"reveal",
Args["row", int]["col", int],
alias=["展开"]
),
Subcommand(
"mark",
Args["row", int]["col", int],
alias=["标记"]
),
),
)
minesweeper_cache: list[Minesweeper] = []
def get_minesweeper_cache(event: T_MessageEvent) -> Minesweeper | None:
for i in minesweeper_cache:
if i.session_type == event.message_type:
if i.session_id == event.user_id or i.session_id == event.group_id:
return i
return None
@minesweeper.handle()
async def _(event: T_MessageEvent, result: Arparma, bot: T_Bot):
game = get_minesweeper_cache(event)
if result.subcommands.get("start"):
if game:
await minesweeper.finish("当前会话不能同时进行多个扫雷游戏")
else:
try:
new_game = Minesweeper(
rows=result.subcommands["start"].args["row"],
cols=result.subcommands["start"].args["col"],
num_mines=result.subcommands["start"].args["mines"],
session_type=event.message_type,
session_id=event.user_id if event.message_type == "private" else event.group_id,
)
minesweeper_cache.append(new_game)
await minesweeper.send("游戏开始")
await md.send_md(new_game.board_markdown(), bot, event=event)
except AssertionError:
await minesweeper.finish("参数错误")
elif result.subcommands.get("end"):
if game:
minesweeper_cache.remove(game)
await minesweeper.finish("游戏结束")
else:
await minesweeper.finish("当前没有扫雷游戏")
elif result.subcommands.get("reveal"):
if not game:
await minesweeper.finish("当前没有扫雷游戏")
else:
row = result.subcommands["reveal"].args["row"]
col = result.subcommands["reveal"].args["col"]
if not (0 <= row < game.rows and 0 <= col < game.cols):
await minesweeper.finish("参数错误")
if not game.reveal(row, col):
minesweeper_cache.remove(game)
await md.send_md(game.board_markdown(), bot, event=event)
await minesweeper.finish("游戏结束")
await md.send_md(game.board_markdown(), bot, event=event)
if game.is_win():
minesweeper_cache.remove(game)
await minesweeper.finish("游戏胜利")
elif result.subcommands.get("mark"):
if not game:
await minesweeper.finish("当前没有扫雷游戏")
else:
row = result.subcommands["mark"].args["row"]
col = result.subcommands["mark"].args["col"]
if not (0 <= row < game.rows and 0 <= col < game.cols):
await minesweeper.finish("参数错误")
game.board[row][col].flagged = not game.board[row][col].flagged
await md.send_md(game.board_markdown(), bot, event=event)
else:
await minesweeper.finish("参数错误")

View File

@@ -1,22 +0,0 @@
from nonebot.plugin import PluginMetadata
from .npm import *
from .rpm import *
__author__ = "snowykami"
__plugin_meta__ = PluginMetadata(
name="轻雪包管理器",
description="本地插件管理和插件商店支持,资源包管理,支持启用/停用,安装/卸载插件",
usage=(
"npm list\n"
"npm enable/disable <plugin_name>\n"
"npm search <keywords...>\n"
"npm install/uninstall <plugin_name>\n"
),
type="application",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki": True,
"toggleable" : False,
"default_enable" : True,
}
)

View File

@@ -1,245 +0,0 @@
import json
from typing import Optional
import aiofiles
import nonebot.plugin
from liteyuki.utils.base.data import LiteModel
from liteyuki.utils.base.data_manager import GlobalPlugin, Group, User, group_db, plugin_db, user_db
from liteyuki.utils.base.ly_typing import T_MessageEvent
__group_data = {} # 群数据缓存, {group_id: Group}
__user_data = {} # 用户数据缓存, {user_id: User}
__default_enable = {} # 插件默认启用状态缓存, {plugin_name: bool} static
__global_enable = {} # 插件全局启用状态缓存, {plugin_name: bool} dynamic
class PluginTag(LiteModel):
label: str
color: str = '#000000'
class StorePlugin(LiteModel):
name: str
desc: str
module_name: str # 插件商店中的模块名不等于本地的模块名,前者是文件夹名,后者是点分割模块名
project_link: str = ""
homepage: str = ""
author: str = ""
type: str | None = None
version: str | None = ""
time: str = ""
tags: list[PluginTag] = []
is_official: bool = False
def get_plugin_exist(plugin_name: str) -> bool:
"""
获取插件是否存在于加载列表
Args:
plugin_name:
Returns:
"""
for plugin in nonebot.plugin.get_loaded_plugins():
if plugin.name == plugin_name:
return True
return False
async def get_store_plugin(plugin_name: str) -> Optional[StorePlugin]:
"""
获取插件信息
Args:
plugin_name (str): 插件模块名
Returns:
Optional[StorePlugin]: 插件信息
"""
async with aiofiles.open("data/liteyuki/plugins.json", "r", encoding="utf-8") as f:
plugins: list[StorePlugin] = [StorePlugin(**pobj) for pobj in json.loads(await f.read())]
for plugin in plugins:
if plugin.module_name == plugin_name:
return plugin
return None
def get_plugin_default_enable(plugin_name: str) -> bool:
"""
获取插件默认启用状态,由插件定义,不存在则默认为启用,优先从缓存中获取
Args:
plugin_name (str): 插件模块名
Returns:
bool: 插件默认状态
"""
if plugin_name not in __default_enable:
plug = nonebot.plugin.get_plugin(plugin_name)
default_enable = (plug.metadata.extra.get("default_enable", True) if plug.metadata else True) if plug else True
__default_enable[plugin_name] = default_enable
return __default_enable[plugin_name]
def get_plugin_session_enable(event: T_MessageEvent, plugin_name: str) -> bool:
"""
获取插件当前会话启用状态
Args:
event: 会话事件
plugin_name (str): 插件模块名
Returns:
bool: 插件当前状态
"""
if event.message_type == "group":
group_id = str(event.group_id)
if group_id not in __group_data:
group: Group = group_db.first(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
__group_data[str(event.group_id)] = group
session = __group_data[group_id]
else:
# session: User = user_db.first(User(), "user_id = ?", event.user_id, default=User(user_id=str(event.user_id)))
user_id = str(event.user_id)
if user_id not in __user_data:
user: User = user_db.first(User(), "user_id = ?", user_id, default=User(user_id=user_id))
__user_data[user_id] = user
session = __user_data[user_id]
# 默认停用插件在启用列表内表示启用
# 默认停用插件不在启用列表内表示停用
# 默认启用插件在停用列表内表示停用
# 默认启用插件不在停用列表内表示启用
default_enable = get_plugin_default_enable(plugin_name)
if default_enable:
return plugin_name not in session.disabled_plugins
else:
return plugin_name in session.enabled_plugins
def set_plugin_session_enable(event: T_MessageEvent, plugin_name: str, enable: bool):
"""
设置插件会话启用状态,同时更新数据库和缓存
Args:
event:
plugin_name:
enable:
Returns:
"""
if event.message_type == "group":
session = group_db.first(Group(), "group_id = ?", str(event.group_id), default=Group(group_id=str(event.group_id)))
else:
session = user_db.first(User(), "user_id = ?", str(event.user_id), default=User(user_id=str(event.user_id)))
default_enable = get_plugin_default_enable(plugin_name)
if default_enable:
if enable:
session.disabled_plugins.remove(plugin_name)
else:
session.disabled_plugins.append(plugin_name)
else:
if enable:
session.enabled_plugins.append(plugin_name)
else:
session.enabled_plugins.remove(plugin_name)
if event.message_type == "group":
__group_data[str(event.group_id)] = session
print(session)
group_db.save(session)
else:
__user_data[str(event.user_id)] = session
user_db.save(session)
def get_plugin_global_enable(plugin_name: str) -> bool:
"""
获取插件全局启用状态, 优先从缓存中获取
Args:
plugin_name:
Returns:
"""
if plugin_name not in __global_enable:
plugin = plugin_db.first(
GlobalPlugin(),
"module_name = ?",
plugin_name,
default=GlobalPlugin(module_name=plugin_name, enabled=True))
__global_enable[plugin_name] = plugin.enabled
return __global_enable[plugin_name]
def set_plugin_global_enable(plugin_name: str, enable: bool):
"""
设置插件全局启用状态,同时更新数据库和缓存
Args:
plugin_name:
enable:
Returns:
"""
plugin = plugin_db.first(
GlobalPlugin(),
"module_name = ?",
plugin_name,
default=GlobalPlugin(module_name=plugin_name, enabled=True))
plugin.enabled = enable
plugin_db.save(plugin)
__global_enable[plugin_name] = enable
def get_plugin_can_be_toggle(plugin_name: str) -> bool:
"""
获取插件是否可以被启用/停用
Args:
plugin_name (str): 插件模块名
Returns:
bool: 插件是否可以被启用/停用
"""
plug = nonebot.plugin.get_plugin(plugin_name)
return plug.metadata.extra.get("toggleable", True) if plug and plug.metadata else True
def get_group_enable(group_id: str) -> bool:
"""
获取群组是否启用插机器人
Args:
group_id (str): 群组ID
Returns:
bool: 群组是否启用插件
"""
group_id = str(group_id)
if group_id not in __group_data:
group: Group = group_db.first(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
__group_data[group_id] = group
return __group_data[group_id].enable
def set_group_enable(group_id: str, enable: bool):
"""
设置群组是否启用插机器人
Args:
group_id (str): 群组ID
enable (bool): 是否启用
"""
group_id = str(group_id)
group: Group = group_db.first(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
group.enable = enable
__group_data[group_id] = group
group_db.save(group)

View File

@@ -1,641 +0,0 @@
import os
import sys
import aiohttp
import nonebot.plugin
import pip
from io import StringIO
from arclet.alconna import MultiVar
from nonebot import Bot, require
from nonebot.exception import FinishedException, IgnoredException, MockApiException
from nonebot.internal.adapter import Event
from nonebot.internal.matcher import Matcher
from nonebot.message import run_preprocessor
from nonebot.permission import SUPERUSER
from nonebot.plugin import Plugin, PluginMetadata
from nonebot.utils import run_sync
from liteyuki.utils.base.data_manager import InstalledPlugin
from liteyuki.utils.base.language import get_user_lang
from liteyuki.utils.base.ly_typing import T_Bot
from liteyuki.utils.message.message import MarkdownMessage as md
from liteyuki.utils.message.markdown import MarkdownComponent as mdc, compile_md, escape_md
from liteyuki.utils.base.permission import GROUP_ADMIN, GROUP_OWNER
from liteyuki.utils.message.tools import clamp
from .common import *
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma, Subcommand
# const
enable_global = "enable-global"
disable_global = "disable-global"
enable = "enable"
disable = "disable"
@on_alconna(
aliases={"插件"},
command=Alconna(
"npm",
Subcommand(
"enable",
Args["plugin_name", str],
alias=["e", "启用"],
),
Subcommand(
"disable",
Args["plugin_name", str],
alias=["d", "停用"],
),
Subcommand(
enable_global,
Args["plugin_name", str],
alias=["eg", "全局启用"],
),
Subcommand(
disable_global,
Args["plugin_name", str],
alias=["dg", "全局停用"],
),
# 安装部分
Subcommand(
"update",
alias=["u", "更新"],
),
Subcommand(
"search",
Args["keywords", MultiVar(str)],
alias=["s", "搜索"],
),
Subcommand(
"install",
Args["plugin_name", str],
alias=["i", "安装"],
),
Subcommand(
"uninstall",
Args["plugin_name", str],
alias=["r", "rm", "卸载"],
),
Subcommand(
"list",
Args["page", int, 1]["num", int, 10],
alias=["ls", "列表"],
)
)
).handle()
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, npm: Matcher):
if not os.path.exists("data/liteyuki/plugins.json"):
await npm_update()
# 判断会话类型
ulang = get_user_lang(str(event.user_id))
plugin_name = result.args.get("plugin_name")
sc = result.subcommands # 获取子命令
perm_s = await SUPERUSER(bot, event) # 判断是否为超级用户
# 支持对自定义command_start的判断
if sc.get("enable") or result.subcommands.get("disable"):
toggle = result.subcommands.get("enable") is not None
plugin_exist = get_plugin_exist(plugin_name)
session_enable = get_plugin_session_enable(event, plugin_name) # 获取插件当前状态
can_be_toggled = get_plugin_can_be_toggle(plugin_name) # 获取插件是否可以被启用/停用
if not plugin_exist:
await npm.finish(ulang.get("npm.plugin_not_found", NAME=plugin_name))
if not can_be_toggled:
await npm.finish(ulang.get("npm.plugin_cannot_be_toggled", NAME=plugin_name))
if session_enable == toggle:
await npm.finish(
ulang.get("npm.plugin_already", NAME=plugin_name, STATUS=ulang.get("npm.enable") if toggle else ulang.get("npm.disable")))
if event.message_type == "private":
session = user_db.first(User(), "user_id = ?", event.user_id, default=User(user_id=event.user_id))
else:
if await GROUP_ADMIN(bot, event) or await GROUP_OWNER(bot, event) or await SUPERUSER(bot, event):
session = group_db.first(Group(), "group_id = ?", event.group_id, default=Group(group_id=str(event.group_id)))
else:
raise FinishedException(ulang.get("Permission Denied"))
try:
set_plugin_session_enable(event, plugin_name, toggle)
except Exception as e:
nonebot.logger.error(e)
await npm.finish(
ulang.get(
"npm.toggle_failed",
NAME=plugin_name,
STATUS=ulang.get("npm.enable") if toggle else ulang.get("npm.disable"),
ERROR=str(e))
)
await npm.finish(
ulang.get(
"npm.toggle_success",
NAME=plugin_name,
STATUS=ulang.get("npm.enable") if toggle else ulang.get("npm.disable"))
)
elif sc.get(enable_global) or result.subcommands.get(disable_global) and await SUPERUSER(bot, event):
plugin_exist = get_plugin_exist(plugin_name)
toggle = result.subcommands.get(enable_global) is not None
can_be_toggled = get_plugin_can_be_toggle(plugin_name)
if not plugin_exist:
await npm.finish(ulang.get("npm.plugin_not_found", NAME=plugin_name))
if not can_be_toggled:
await npm.finish(ulang.get("npm.plugin_cannot_be_toggled", NAME=plugin_name))
global_enable = get_plugin_global_enable(plugin_name)
if global_enable == toggle:
await npm.finish(
ulang.get("npm.plugin_already", NAME=plugin_name, STATUS=ulang.get("npm.enable") if toggle else ulang.get("npm.disable")))
try:
set_plugin_global_enable(plugin_name, toggle)
except Exception as e:
await npm.finish(
ulang.get(
"npm.toggle_failed",
NAME=plugin_name,
STATUS=ulang.get("npm.enable") if toggle else ulang.get("npm.disable"),
ERROR=str(e))
)
await npm.finish(
ulang.get(
"npm.toggle_success",
NAME=plugin_name,
STATUS=ulang.get("npm.enable") if toggle else ulang.get("npm.disable"))
)
elif sc.get("update") and perm_s:
r = await npm_update()
if r:
await npm.finish(ulang.get("npm.store_update_success"))
else:
await npm.finish(ulang.get("npm.store_update_failed"))
elif sc.get("search"):
keywords: list[str] = result.subcommands["search"].args.get("keywords")
rs = await npm_search(keywords)
max_show = 10
if len(rs):
reply = f"{ulang.get('npm.search_result')} | {ulang.get('npm.total', TOTAL=len(rs))}\n***"
for storePlugin in rs[:min(max_show, len(rs))]:
btn_install_or_update = md.btn_cmd(
ulang.get("npm.update") if get_plugin_exist(storePlugin.module_name) else ulang.get("npm.install"),
"npm install %s" % storePlugin.module_name
)
link_page = md.btn_link(ulang.get("npm.homepage"), storePlugin.homepage)
link_pypi = md.btn_link(ulang.get("npm.pypi"), storePlugin.homepage)
reply += (f"\n# **{storePlugin.name}**\n"
f"\n> **{storePlugin.desc}**\n"
f"\n> {ulang.get('npm.author')}: {storePlugin.author}"
f"\n> *{md.escape(storePlugin.module_name)}*"
f"\n> {btn_install_or_update} {link_page} {link_pypi}\n\n***\n")
if len(rs) > max_show:
reply += f"\n{ulang.get('npm.too_many_results', HIDE_NUM=len(rs) - max_show)}"
else:
reply = ulang.get("npm.search_no_result")
await md.send_md(reply, bot, event=event)
elif sc.get("install") and perm_s:
plugin_name: str = result.subcommands["install"].args.get("plugin_name")
store_plugin = await get_store_plugin(plugin_name)
await npm.send(ulang.get("npm.installing", NAME=plugin_name))
r, log = await npm_install(plugin_name)
log = log.replace("\\", "/")
if not store_plugin:
await npm.finish(ulang.get("npm.plugin_not_found", NAME=plugin_name))
homepage_btn = md.btn_cmd(ulang.get("npm.homepage"), store_plugin.homepage)
if r:
r_load = nonebot.load_plugin(plugin_name) # 加载插件
installed_plugin = InstalledPlugin(module_name=plugin_name) # 构造插件信息模型
found_in_db_plugin = plugin_db.first(InstalledPlugin(), "module_name = ?", plugin_name) # 查询数据库中是否已经安装
if r_load:
if found_in_db_plugin is None:
plugin_db.save(installed_plugin)
info = md.escape(ulang.get("npm.install_success", NAME=store_plugin.name)) # markdown转义
await md.send_md(
f"{info}\n\n"
f"```\n{log}\n```",
bot,
event=event
)
else:
await npm.finish(ulang.get("npm.plugin_already_installed", NAME=store_plugin.name))
else:
info = ulang.get("npm.load_failed", NAME=plugin_name, HOMEPAGE=homepage_btn).replace("_", r"\\_")
await md.send_md(
f"{info}\n\n"
f"```\n{log}\n```\n",
bot,
event=event
)
else:
info = ulang.get("npm.install_failed", NAME=plugin_name, HOMEPAGE=homepage_btn).replace("_", r"\\_")
await md.send_md(
f"{info}\n\n"
f"```\n{log}\n```",
bot,
event=event
)
elif sc.get("uninstall") and perm_s:
plugin_name: str = result.subcommands["uninstall"].args.get("plugin_name") # type: ignore
found_installed_plugin: InstalledPlugin = plugin_db.first(InstalledPlugin(), "module_name = ?", plugin_name)
if found_installed_plugin:
plugin_db.delete(InstalledPlugin(), "module_name = ?", plugin_name)
reply = f"{ulang.get('npm.uninstall_success', NAME=found_installed_plugin.module_name)}"
await npm.finish(reply)
else:
await npm.finish(ulang.get("npm.plugin_not_installed", NAME=plugin_name))
elif sc.get("list"):
loaded_plugin_list = sorted(nonebot.get_loaded_plugins(), key=lambda x: x.name)
num_per_page = result.subcommands.get("list").args.get("num")
total = len(loaded_plugin_list) // num_per_page + (1 if len(loaded_plugin_list) % num_per_page else 0)
page = clamp(result.subcommands.get("list").args.get("page"), 1, total)
# 已加载插件 | 总计10 | 第1/3页
reply = (f"# {ulang.get('npm.loaded_plugins')} | "
f"{ulang.get('npm.total', TOTAL=len(nonebot.get_loaded_plugins()))} | "
f"{ulang.get('npm.page', PAGE=page, TOTAL=total)} \n***\n")
permission_oas = await GROUP_ADMIN(bot, event) or await GROUP_OWNER(bot, event) or await SUPERUSER(bot, event)
permission_s = await SUPERUSER(bot, event)
for storePlugin in loaded_plugin_list[(page - 1) * num_per_page: min(page * num_per_page, len(loaded_plugin_list))]:
# 检查是否有 metadata 属性
# 添加帮助按钮
btn_usage = md.btn_cmd(ulang.get("npm.usage"), f"help {storePlugin.name}", False)
store_plugin = await get_store_plugin(storePlugin.name)
session_enable = get_plugin_session_enable(event, storePlugin.name)
if store_plugin:
# btn_homepage = md.btn_link(ulang.get("npm.homepage"), store_plugin.homepage)
show_name = store_plugin.name
elif storePlugin.metadata:
# if storePlugin.metadata.extra.get("liteyuki"):
# btn_homepage = md.btn_link(ulang.get("npm.homepage"), "https://github.com/snowykami/LiteyukiBot")
# else:
# btn_homepage = ulang.get("npm.homepage")
show_name = storePlugin.metadata.name
else:
# btn_homepage = ulang.get("npm.homepage")
show_name = storePlugin.name
ulang.get("npm.no_description")
if storePlugin.metadata:
reply += f"\n**{md.escape(show_name)}**\n"
else:
reply += f"**{md.escape(show_name)}**\n"
reply += f"\n > {btn_usage}"
if permission_oas:
# 添加启用/停用插件按钮
cmd_toggle = f"npm {'disable' if session_enable else 'enable'} {storePlugin.name}"
text_toggle = ulang.get("npm.disable" if session_enable else "npm.enable")
can_be_toggle = get_plugin_can_be_toggle(storePlugin.name)
btn_toggle = text_toggle if not can_be_toggle else md.btn_cmd(text_toggle, cmd_toggle)
reply += f" {btn_toggle}"
if permission_s:
plugin_in_database = plugin_db.first(InstalledPlugin(), "module_name = ?", storePlugin.name)
# 添加移除插件和全局切换按钮
global_enable = get_plugin_global_enable(storePlugin.name)
btn_uninstall = (
md.btn_cmd(ulang.get("npm.uninstall"), f'npm uninstall {storePlugin.name}')) if plugin_in_database else ulang.get(
'npm.uninstall')
btn_toggle_global_text = ulang.get("npm.disable_global" if global_enable else "npm.enable_global")
cmd_toggle_global = f"npm {'disable' if global_enable else 'enable'}-global {storePlugin.name}"
btn_toggle_global = btn_toggle_global_text if not can_be_toggle else md.btn_cmd(btn_toggle_global_text, cmd_toggle_global)
reply += f" {btn_uninstall} {btn_toggle_global}"
reply += "\n\n***\n"
# 根据页数添加翻页按钮。第一页显示上一页文本而不是按钮,最后一页显示下一页文本而不是按钮
btn_prev = md.btn_cmd(ulang.get("npm.prev_page"), f"npm list {page - 1} {num_per_page}") if page > 1 else ulang.get("npm.prev_page")
btn_next = md.btn_cmd(ulang.get("npm.next_page"), f"npm list {page + 1} {num_per_page}") if page < total else ulang.get("npm.next_page")
reply += f"\n{btn_prev} {page}/{total} {btn_next}"
await md.send_md(reply, bot, event=event)
else:
if await SUPERUSER(bot, event):
btn_enable_global = md.btn_cmd(ulang.get("npm.enable_global"), "npm enable-global", False, False)
btn_disable_global = md.btn_cmd(ulang.get("npm.disable_global"), "npm disable-global", False, False)
btn_search = md.btn_cmd(ulang.get("npm.search"), "npm search ", False, False)
btn_uninstall_ = md.btn_cmd(ulang.get("npm.uninstall"), "npm uninstall ", False, False)
btn_install_ = md.btn_cmd(ulang.get("npm.install"), "npm install ", False, False)
btn_update = md.btn_cmd(ulang.get("npm.update_index"), "npm update", False, True)
btn_list = md.btn_cmd(ulang.get("npm.list_plugins"), "npm list ", False, False)
btn_disable = md.btn_cmd(ulang.get("npm.disable_session"), "npm disable ", False, False)
btn_enable = md.btn_cmd(ulang.get("npm.enable_session"), "npm enable ", False, False)
reply = (
f"\n# **{ulang.get('npm.help')}**"
f"\n{btn_update}"
f"\n\n>*{md.escape('npm update')}*\n"
f"\n{btn_install_}"
f"\n\n>*{md.escape('npm install <plugin_name')}*>\n"
f"\n{btn_uninstall_}"
f"\n\n>*{md.escape('npm uninstall <plugin_name')}*>\n"
f"\n{btn_search}"
f"\n\n>*{md.escape('npm search <keywords...')}*>\n"
f"\n{btn_disable_global}"
f"\n\n>*{md.escape('npm disable-global <plugin_name')}*>\n"
f"\n{btn_enable_global}"
f"\n\n>*{md.escape('npm enable-global <plugin_name')}*>\n"
f"\n{btn_disable}"
f"\n\n>*{md.escape('npm disable <plugin_name')}*>\n"
f"\n{btn_enable}"
f"\n\n>*{md.escape('npm enable <plugin_name')}*>\n"
f"\n{btn_list}"
f"\n\n>page为页数num为每页显示数量"
f"\n\n>*{md.escape('npm list [page] [num]')}*"
)
await md.send_md(reply, bot, event=event)
else:
btn_list = md.btn_cmd(ulang.get("npm.list_plugins"), "npm list ", False, False)
btn_disable = md.btn_cmd(ulang.get("npm.disable_session"), "npm disable ", False, False)
btn_enable = md.btn_cmd(ulang.get("npm.enable_session"), "npm enable ", False, False)
reply = (
f"\n# **{ulang.get('npm.help')}**"
f"\n{btn_disable}"
f"\n\n>*{md.escape('npm disable <plugin_name')}*>\n"
f"\n{btn_enable}"
f"\n\n>*{md.escape('npm enable <plugin_name')}*>\n"
f"\n{btn_list}"
f"\n\n>page为页数num为每页显示数量"
f"\n\n>*{md.escape('npm list [page] [num]')}*"
)
await md.send_md(reply, bot, event=event)
@on_alconna(
aliases={"群聊"},
command=Alconna(
"gm",
Subcommand(
enable,
Args["group_id", str, None],
alias=["e", "启用"],
),
Subcommand(
disable,
Args["group_id", str, None],
alias=["d", "停用"],
),
),
permission=SUPERUSER | GROUP_OWNER | GROUP_ADMIN
).handle()
async def _(bot: T_Bot, event: T_MessageEvent, gm: Matcher, result: Arparma):
ulang = get_user_lang(str(event.user_id))
to_enable = result.subcommands.get(enable) is not None
group_id = None
if await SUPERUSER(bot, event):
# 仅超级用户可以自定义群号
group_id = result.subcommands.get(enable, result.subcommands.get(disable)).args.get("group_id")
if group_id is None and event.message_type == "group":
group_id = str(event.group_id)
if group_id is None:
await gm.finish(ulang.get("liteyuki.invalid_command"), liteyuki_pass=True)
enabled = get_group_enable(group_id)
if enabled == to_enable:
await gm.finish(ulang.get("liteyuki.group_already", STATUS=ulang.get("npm.enable") if to_enable else ulang.get("npm.disable"), GROUP=group_id),
liteyuki_pass=True)
else:
set_group_enable(group_id, to_enable)
await gm.finish(
ulang.get("liteyuki.group_success", STATUS=ulang.get("npm.enable") if to_enable else ulang.get("npm.disable"), GROUP=group_id),
liteyuki_pass=True
)
@on_alconna(
aliases={"帮助"},
command=Alconna(
"help",
Args["plugin_name", str, None],
)
).handle()
async def _(result: Arparma, matcher: Matcher, event: T_MessageEvent, bot: T_Bot):
ulang = get_user_lang(str(event.user_id))
plugin_name = result.main_args.get("plugin_name")
if plugin_name:
searched_plugins = search_loaded_plugin(plugin_name)
if searched_plugins:
loaded_plugin = searched_plugins[0]
else:
await matcher.finish(ulang.get("npm.plugin_not_found", NAME=plugin_name))
if loaded_plugin:
if loaded_plugin.metadata is None:
loaded_plugin.metadata = PluginMetadata(name=plugin_name, description="", usage="")
# 从商店获取详细信息
store_plugin = await get_store_plugin(plugin_name)
if loaded_plugin.metadata.extra.get("liteyuki"):
store_plugin = StorePlugin(
name=loaded_plugin.metadata.name,
desc=loaded_plugin.metadata.description,
author="SnowyKami",
module_name=plugin_name,
homepage="https://github.com/snowykami/LiteyukiBot"
)
elif store_plugin is None:
store_plugin = StorePlugin(
name=loaded_plugin.metadata.name,
desc=loaded_plugin.metadata.description,
author="",
module_name=plugin_name,
homepage=""
)
if store_plugin:
link = store_plugin.homepage
elif loaded_plugin.metadata.extra.get("liteyuki"):
link = "https://github.com/snowykami/LiteyukiBot"
else:
link = None
reply = [
mdc.heading(escape_md(store_plugin.name)),
mdc.quote(store_plugin.module_name),
mdc.quote(mdc.bold(ulang.get("npm.author")) + " " +
(mdc.link(store_plugin.author, f"https://github.com/{store_plugin.author}") if store_plugin.author else "Unknown")),
mdc.quote(mdc.bold(ulang.get("npm.description")) + " " + mdc.paragraph(max(loaded_plugin.metadata.description, store_plugin.desc))),
mdc.heading(ulang.get("npm.usage"), 2),
mdc.quote(escape_md(loaded_plugin.metadata.usage)),
mdc.link(ulang.get("npm.homepage"), link) if link else mdc.paragraph(ulang.get("npm.no_homepage"))
]
await md.send_md(compile_md(reply), bot, event=event)
else:
await matcher.finish(ulang.get("npm.plugin_not_found", NAME=plugin_name))
else:
pass
# 传入事件阻断hook
@run_preprocessor
async def pre_handle(event: Event, matcher: Matcher):
plugin: Plugin = matcher.plugin
plugin_global_enable = get_plugin_global_enable(plugin.name)
if not plugin_global_enable:
raise IgnoredException("Plugin disabled globally")
if event.get_type() == "message":
plugin_session_enable = get_plugin_session_enable(event, plugin.name)
if not plugin_session_enable:
raise IgnoredException("Plugin disabled in session")
# 群聊开关阻断hook
@Bot.on_calling_api
async def block_disable_session(bot: Bot, api: str, args: dict):
if "group_id" in args and not args.get("liteyuki_pass", False):
group_id = args["group_id"]
if not get_group_enable(group_id):
nonebot.logger.debug(f"Group {group_id} disabled")
raise MockApiException(f"Group {group_id} disabled")
async def npm_update() -> bool:
"""
更新本地插件json缓存
Returns:
bool: 是否成功更新
"""
url_list = [
"https://registry.nonebot.dev/plugins.json",
]
for url in url_list:
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
if resp.status == 200:
async with aiofiles.open("data/liteyuki/plugins.json", "wb") as f:
data = await resp.read()
await f.write(data)
return True
return False
async def npm_search(keywords: list[str]) -> list[StorePlugin]:
"""
在本地缓存商店数据中搜索插件
Args:
keywords (list[str]): 关键词列表
Returns:
list[StorePlugin]: 插件列表
"""
plugin_blacklist = [
"nonebot_plugin_xiuxian_2",
"nonebot_plugin_htmlrender",
"nonebot_plugin_alconna",
]
results = []
async with aiofiles.open("data/liteyuki/plugins.json", "r", encoding="utf-8") as f:
plugins: list[StorePlugin] = [StorePlugin(**pobj) for pobj in json.loads(await f.read())]
for plugin in plugins:
if plugin.module_name in plugin_blacklist:
continue
plugin_text = ' '.join(
[
plugin.name,
plugin.desc,
plugin.author,
plugin.module_name,
' '.join([tag.label for tag in plugin.tags])
]
)
if all([keyword in plugin_text for keyword in keywords]):
results.append(plugin)
return results
@run_sync
def npm_install(plugin_package_name) -> tuple[bool, str]:
"""
异步安装插件使用pip安装
Args:
plugin_package_name:
Returns:
tuple[bool, str]: 是否成功,输出信息
"""
# 重定向标准输出
buffer = StringIO()
sys.stdout = buffer
sys.stderr = buffer
update = False
if get_plugin_exist(plugin_package_name):
update = True
mirrors = [
"https://pypi.tuna.tsinghua.edu.cn/simple", # 清华大学
"https://pypi.org/simple", # 官方源
]
# 使用pip安装包对每个镜像尝试一次成功后返回值
success = False
for mirror in mirrors:
try:
nonebot.logger.info(f"pip install try mirror: {mirror}")
if update:
result = pip.main(["install", "--upgrade", plugin_package_name, "-i", mirror])
else:
result = pip.main(["install", plugin_package_name, "-i", mirror])
success = result == 0
if success:
break
else:
nonebot.logger.warning(f"pip install failed, try next mirror.")
except Exception as e:
success = False
continue
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
return success, buffer.getvalue()
def search_loaded_plugin(keyword: str) -> list[Plugin]:
"""
搜索已加载插件
Args:
keyword (str): 关键词
Returns:
list[Plugin]: 插件列表
"""
if nonebot.get_plugin(keyword) is not None:
return [nonebot.get_plugin(keyword)]
else:
results = []
for plugin in nonebot.get_loaded_plugins():
if plugin.metadata is None:
plugin.metadata = PluginMetadata(name=plugin.name, description="", usage="")
if keyword in plugin.name + plugin.metadata.name + plugin.metadata.description:
results.append(plugin)
return results

View File

@@ -1,171 +0,0 @@
# 轻雪资源包管理器
import os
import yaml
from nonebot import require
from nonebot.permission import SUPERUSER
from liteyuki.utils.base.language import get_user_lang
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.message.message import MarkdownMessage as md
from liteyuki.utils.base.resource import (ResourceMetadata, add_resource_pack, change_priority, check_exist, check_status, get_loaded_resource_packs, get_resource_metadata, load_resources, remove_resource_pack)
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import Alconna, Args, on_alconna, Arparma, Subcommand
@on_alconna(
aliases={"资源包"},
command=Alconna(
"rpm",
Subcommand(
"list",
Args["page", int, 1]["num", int, 10],
alias=["ls", "列表", "列出"],
),
Subcommand(
"load",
Args["name", str],
alias=["安装"],
),
Subcommand(
"unload",
Args["name", str],
alias=["卸载"],
),
Subcommand(
"up",
Args["name", str],
alias=["上移"],
),
Subcommand(
"down",
Args["name", str],
alias=["下移"],
),
Subcommand(
"top",
Args["name", str],
alias=["置顶"],
),
Subcommand(
"reload",
alias=["重载"],
),
),
permission=SUPERUSER
).handle()
async def _(bot: T_Bot, event: T_MessageEvent, result: Arparma):
ulang = get_user_lang(str(event.user_id))
reply = ""
if result.subcommands.get("list"):
loaded_rps = get_loaded_resource_packs()
reply += f"{ulang.get('liteyuki.loaded_resources', NUM=len(loaded_rps))}\n"
for rp in loaded_rps:
btn_unload = md.btn_cmd(
ulang.get("npm.uninstall"),
f"rpm unload {rp.folder}"
)
btn_move_up = md.btn_cmd(
ulang.get("rpm.move_up"),
f"rpm up {rp.folder}"
)
btn_move_down = md.btn_cmd(
ulang.get("rpm.move_down"),
f"rpm down {rp.folder}"
)
btn_move_top = md.btn_cmd(
ulang.get("rpm.move_top"),
f"rpm top {rp.folder}"
)
# 添加新行
reply += (f"\n**{md.escape(rp.name)}**({md.escape(rp.folder)})\n\n"
f"> {btn_move_up} {btn_move_down} {btn_move_top} {btn_unload}\n\n***")
reply += f"\n\n{ulang.get('liteyuki.unloaded_resources')}\n"
loaded_folders = [rp.folder for rp in get_loaded_resource_packs()]
for folder in os.listdir("resources"):
if folder not in loaded_folders and os.path.exists(os.path.join("resources", folder, "metadata.yml")):
metadata = ResourceMetadata(
**yaml.load(
open(
os.path.join("resources", folder, "metadata.yml"),
encoding="utf-8"
),
Loader=yaml.FullLoader
)
)
metadata.folder = folder
metadata.path = os.path.join("resources", folder)
btn_load = md.btn_cmd(
ulang.get("npm.install"),
f"rpm load {metadata.folder}"
)
# 添加新行
reply += (f"\n**{md.escape(metadata.name)}**({md.escape(metadata.folder)})\n\n"
f"> {btn_load}\n\n***")
elif result.subcommands.get("load") or result.subcommands.get("unload"):
load = result.subcommands.get("load") is not None
rp_name = result.args.get("name")
r = False # 操作结果
if check_exist(rp_name):
if load != check_status(rp_name):
# 状态不同
if load:
r = add_resource_pack(rp_name)
else:
r = remove_resource_pack(rp_name)
rp_meta = get_resource_metadata(rp_name)
reply += ulang.get(
f"liteyuki.{'load' if load else 'unload'}_resource_{'success' if r else 'failed'}",
NAME=rp_meta.name
)
else:
# 重复操作
reply += ulang.get(f"liteyuki.resource_already_{'load' if load else 'unload'}ed", NAME=rp_name)
else:
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
if r:
btn_reload = md.btn_cmd(
ulang.get("liteyuki.reload_resources"),
f"rpm reload"
)
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
elif result.subcommands.get("up") or result.subcommands.get("down") or result.subcommands.get("top"):
rp_name = result.args.get("name")
if result.subcommands.get("up"):
delta = -1
elif result.subcommands.get("down"):
delta = 1
else:
delta = 0
if check_exist(rp_name):
if check_status(rp_name):
r = change_priority(rp_name, delta)
reply += ulang.get(f"liteyuki.change_priority_{'success' if r else 'failed'}", NAME=rp_name)
if r:
btn_reload = md.btn_cmd(
ulang.get("liteyuki.reload_resources"),
f"rpm reload"
)
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
else:
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
else:
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
elif result.subcommands.get("reload"):
load_resources()
reply = ulang.get(
"liteyuki.reload_resources_success",
NUM=len(get_loaded_resource_packs())
)
else:
btn_reload = md.btn_cmd(
ulang.get("liteyuki.reload_resources"),
f"rpm reload"
)
btn_list = md.btn_cmd(
ulang.get("liteyuki.list_resources"),
f"rpm list"
)
reply += f"{btn_list} \n {btn_reload}"
await md.send_md(reply, bot, event=event)

View File

@@ -1,24 +0,0 @@
from nonebot.plugin import PluginMetadata
from .status import *
__author__ = "snowykami"
__plugin_meta__ = PluginMetadata(
name="状态查看器",
description="",
usage=(
"MARKDOWN### 状态查看器\n"
"查看机器人的状态\n"
"### 用法\n"
"- `/status` 查看基本情况\n"
"- `/status memory` 查看内存使用情况\n"
"- `/status process` 查看进程情况\n"
),
type="application",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki": True,
"toggleable" : False,
"default_enable" : True,
}
)

View File

@@ -1,257 +0,0 @@
import platform
import time
import nonebot
import psutil
from cpuinfo import cpuinfo
from nonebot import require
from liteyuki.utils import __NAME__, __VERSION__
from liteyuki.utils.base.config import get_config
from liteyuki.utils.base.data_manager import TempConfig, common_db
from liteyuki.utils.base.language import Language
from liteyuki.utils.base.resource import get_loaded_resource_packs, get_path
from liteyuki.utils.message.html_tool import template2image
require("nonebot_plugin_apscheduler")
from nonebot_plugin_apscheduler import scheduler
protocol_names = {
0: "iPad",
1: "Android Phone",
2: "Android Watch",
3: "Mac",
5: "iPad",
6: "Android Pad",
}
"""
Universal Interface
data
- bot
- name: str
icon: str
id: int
protocol_name: str
groups: int
friends: int
message_sent: int
message_received: int
app_name: str
- hardware
- cpu
- percent: float
- name: str
- mem
- percent: float
- total: int
- used: int
- free: int
- swap
- percent: float
- total: int
- used: int
- free: int
- disk: list
- name: str
- percent: float
- total: int
"""
status_card_cache = {} # lang -> bytes
# 60s刷新一次
@scheduler.scheduled_job("cron", second="*/40")
async def refresh_status_card():
nonebot.logger.debug("Refreshing status card cache...")
global status_card_cache
bot_data = await get_bots_data()
hardware_data = await get_hardware_data()
liteyuki_data = await get_liteyuki_data()
for lang in status_card_cache.keys():
status_card_cache[lang] = await generate_status_card(
bot_data,
hardware_data,
liteyuki_data,
lang=lang,
use_cache=False
)
async def generate_status_card(bot: dict, hardware: dict, liteyuki: dict, lang="zh-CN", bot_id="0", use_cache=False) -> bytes:
if not use_cache:
return await template2image(
get_path("templates/status.html", abs_path=True),
{
"data": {
"bot" : bot,
"hardware" : hardware,
"liteyuki" : liteyuki,
"localization": get_local_data(lang)
}
},
debug=True
)
else:
if lang not in status_card_cache:
status_card_cache[lang] = await generate_status_card(bot, hardware, liteyuki, lang=lang, bot_id=bot_id)
return status_card_cache[lang]
def get_local_data(lang_code) -> dict:
lang = Language(lang_code)
return {
"friends" : lang.get("status.friends"),
"groups" : lang.get("status.groups"),
"plugins" : lang.get("status.plugins"),
"bots" : lang.get("status.bots"),
"message_sent" : lang.get("status.message_sent"),
"message_received": lang.get("status.message_received"),
"cpu" : lang.get("status.cpu"),
"memory" : lang.get("status.memory"),
"swap" : lang.get("status.swap"),
"disk" : lang.get("status.disk"),
"usage" : lang.get("status.usage"),
"total" : lang.get("status.total"),
"used" : lang.get("status.used"),
"free" : lang.get("status.free"),
"days" : lang.get("status.days"),
"hours" : lang.get("status.hours"),
"minutes" : lang.get("status.minutes"),
"seconds" : lang.get("status.seconds"),
"runtime" : lang.get("status.runtime"),
"threads" : lang.get("status.threads"),
"cores" : lang.get("status.cores"),
"process" : lang.get("status.process"),
"resources" : lang.get("status.resources"),
"description" : lang.get("status.description"),
}
async def get_bots_data(self_id: str = "0") -> dict:
"""获取当前所有机器人数据
Returns:
"""
result = {
"self_id": self_id,
"bots" : [],
}
for bot_id, bot in nonebot.get_bots().items():
groups = 0
friends = 0
status = {}
bot_name = bot_id
version_info = {}
try:
# API fetch
bot_name = (await bot.get_login_info())["nickname"]
groups = len(await bot.get_group_list())
friends = len(await bot.get_friend_list())
status = await bot.get_status()
version_info = await bot.get_version_info()
except Exception:
pass
statistics = status.get("stat", {})
app_name = version_info.get("app_name", "UnknownImplementation")
if app_name in ["Lagrange.OneBot", "LLOneBot", "Shamrock"]:
icon = f"https://q.qlogo.cn/g?b=qq&nk={bot_id}&s=640"
else:
icon = None
bot_data = {
"name" : bot_name,
"icon" : icon,
"id" : bot_id,
"protocol_name" : protocol_names.get(version_info.get("protocol_name"), "Online"),
"groups" : groups,
"friends" : friends,
"message_sent" : statistics.get("message_sent", 0),
"message_received": statistics.get("message_received", 0),
"app_name" : app_name
}
result["bots"].append(bot_data)
return result
async def get_hardware_data() -> dict:
mem = psutil.virtual_memory()
all_processes = psutil.Process().children(recursive=True)
all_processes.append(psutil.Process())
mem_used_process = 0
process_mem = {}
for process in all_processes:
try:
ps_name = process.name().replace(".exe", "")
if ps_name not in process_mem:
process_mem[ps_name] = 0
process_mem[ps_name] += process.memory_info().rss
mem_used_process += process.memory_info().rss
except Exception:
pass
swap = psutil.swap_memory()
cpu_brand_raw = cpuinfo.get_cpu_info().get("brand_raw", "Unknown")
if "AMD" in cpu_brand_raw:
brand = "AMD"
elif "Intel" in cpu_brand_raw:
brand = "Intel"
else:
brand = "Unknown"
result = {
"cpu" : {
"percent": psutil.cpu_percent(),
"name" : f"{brand} {cpuinfo.get_cpu_info().get('arch', 'Unknown')}",
"cores" : psutil.cpu_count(logical=False),
"threads": psutil.cpu_count(logical=True),
"freq" : psutil.cpu_freq().current # MHz
},
"memory": {
"percent" : mem.percent,
"total" : mem.total,
"used" : mem.used,
"free" : mem.free,
"usedProcess": mem_used_process,
},
"swap" : {
"percent": swap.percent,
"total" : swap.total,
"used" : swap.used,
"free" : swap.free
},
"disk" : [],
}
for disk in psutil.disk_partitions(all=True):
try:
disk_usage = psutil.disk_usage(disk.mountpoint)
if disk_usage.total == 0:
continue # 虚拟磁盘
result["disk"].append({
"name" : disk.mountpoint,
"percent": disk_usage.percent,
"total" : disk_usage.total,
"used" : disk_usage.used,
"free" : disk_usage.free
})
except:
pass
return result
async def get_liteyuki_data() -> dict:
temp_data: TempConfig = common_db.first(TempConfig(), default=TempConfig())
result = {
"name" : list(get_config("nickname", [__NAME__]))[0],
"version" : __VERSION__,
"plugins" : len(nonebot.get_loaded_plugins()),
"resources": len(get_loaded_resource_packs()),
"nonebot" : f"{nonebot.__version__}",
"python" : f"{platform.python_implementation()} {platform.python_version()}",
"system" : f"{platform.system()} {platform.release()}",
"runtime" : time.time() - temp_data.data.get("start_time", time.time()), # 运行时间秒数
"bots" : len(nonebot.get_bots())
}
return result

View File

@@ -1,52 +0,0 @@
from nonebot import require
from liteyuki.utils.base.resource import get_path
from liteyuki.utils.message.html_tool import template2image
from liteyuki.utils.base.language import get_user_lang
from .api import *
from ...utils.base.ly_typing import T_Bot, T_MessageEvent
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Subcommand, Arparma, UniMessage
status_alc = on_alconna(
aliases={"状态"},
command=Alconna(
"status",
Subcommand(
"memory",
alias={"mem", "m", "内存"},
),
Subcommand(
"process",
alias={"proc", "p", "进程"},
)
),
)
@status_alc.handle()
async def _(event: T_MessageEvent, bot: T_Bot):
ulang = get_user_lang(event.user_id)
if ulang.lang_code in status_card_cache:
image = status_card_cache[ulang.lang_code]
else:
image = await generate_status_card(
bot=await get_bots_data(),
hardware=await get_hardware_data(),
liteyuki=await get_liteyuki_data(),
lang=ulang.lang_code,
bot_id=bot.self_id,
use_cache=True
)
await status_alc.finish(UniMessage.image(raw=image))
@status_alc.assign("memory")
async def _():
print("memory")
@status_alc.assign("process")
async def _():
print("process")

View File

@@ -1,17 +0,0 @@
from nonebot.plugin import PluginMetadata
from .api import *
__author__ = "snowykami"
__plugin_meta__ = PluginMetadata(
name="联合黑名单(测试中...)",
description="",
usage="",
type="application",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki": True,
"toggleable" : True,
"default_enable" : True,
}
)

View File

@@ -1,60 +0,0 @@
import datetime
import aiohttp
import httpx
import nonebot
from nonebot import require
from nonebot.exception import IgnoredException
from nonebot.message import event_preprocessor
from nonebot_plugin_alconna.typings import Event
require("nonebot_plugin_apscheduler")
from nonebot_plugin_apscheduler import scheduler
blacklist_data: dict[str, set[str]] = {}
blacklist: set[str] = set()
@scheduler.scheduled_job("interval", minutes=10, next_run_time=datetime.datetime.now())
async def update_blacklist():
await request_for_blacklist()
async def request_for_blacklist():
global blacklist
urls = [
"https://cdn.liteyuki.icu/static/ubl/"
]
platforms = [
"qq"
]
for plat in platforms:
for url in urls:
url += f"{plat}.txt"
async with aiohttp.ClientSession() as client:
resp = await client.get(url)
blacklist_data[plat] = set((await resp.text()).splitlines())
blacklist = get_uni_set()
nonebot.logger.info("blacklists updated")
def get_uni_set() -> set:
s = set()
for new_set in blacklist_data.values():
s.update(new_set)
return s
@event_preprocessor
async def pre_handle(event: Event):
try:
user_id = str(event.get_user_id())
except:
return
if user_id in get_uni_set():
raise IgnoredException("UserId in blacklist")

View File

@@ -1,16 +0,0 @@
from nonebot.plugin import PluginMetadata
from .profile_manager import *
__author__ = "snowykami"
__plugin_meta__ = PluginMetadata(
name="轻雪用户管理",
description="用户管理插件",
usage="",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki" : True,
"toggleable" : False,
"default_enable": True,
}
)

View File

@@ -1,23 +0,0 @@
representative_timezones_list = [
"Etc/GMT+12", # 国际日期变更线西
"Pacific/Honolulu", # 夏威夷标准时间
"America/Anchorage", # 阿拉斯加标准时间
"America/Los_Angeles", # 美国太平洋标准时间
"America/Denver", # 美国山地标准时间
"America/Chicago", # 美国中部标准时间
"America/New_York", # 美国东部标准时间
"Europe/London", # 英国标准时间
"Europe/Paris", # 中欧标准时间
"Europe/Moscow", # 莫斯科标准时间
"Asia/Dubai", # 阿联酋标准时间
"Asia/Kolkata", # 印度标准时间
"Asia/Shanghai", # 中国标准时间
"Asia/Hong_Kong", # 中国香港标准时间
"Asia/Chongqing", # 中国重庆标准时间
"Asia/Macau", # 中国澳门标准时间
"Asia/Taipei", # 中国台湾标准时间
"Asia/Tokyo", # 日本标准时间
"Australia/Sydney", # 澳大利亚东部标准时间
"Pacific/Auckland" # 新西兰标准时间
]
representative_timezones_list.sort()

View File

@@ -1,148 +0,0 @@
from typing import Optional
import pytz
from nonebot import require
from liteyuki.utils.base.data import LiteModel, Database
from liteyuki.utils.base.data_manager import User, user_db, group_db
from liteyuki.utils.base.language import Language, change_user_lang, get_all_lang, get_user_lang
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
from liteyuki.utils.message.message import MarkdownMessage as md
from .const import representative_timezones_list
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
profile_alc = on_alconna(
Alconna(
"profile",
Subcommand(
"set",
Args["key", str]["value", str, None],
alias=["s", "设置"],
),
Subcommand(
"get",
Args["key", str],
alias=["g", "查询"],
),
),
aliases={"用户信息"}
)
# json储存
class Profile(LiteModel):
lang: str = "zh-CN"
nickname: str = ""
timezone: str = "Asia/Shanghai"
location: str = ""
@profile_alc.handle()
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
user: User = user_db.first(User(), "user_id = ?", event.user_id, default=User(user_id=str(event.user_id)))
ulang = get_user_lang(str(event.user_id))
if result.subcommands.get("set"):
if result.subcommands["set"].args.get("value"):
# 对合法性进行校验后设置
r = set_profile(result.args["key"], result.args["value"], str(event.user_id))
if r:
user.profile[result.args["key"]] = result.args["value"]
user_db.save(user) # 数据库保存
await profile_alc.finish(
ulang.get(
"user.profile.set_success",
ATTR=ulang.get(f"user.profile.{result.args['key']}"),
VALUE=result.args["value"]
)
)
else:
await profile_alc.finish(ulang.get("user.profile.set_failed", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
else:
# 未输入值,尝试呼出菜单
menu = get_profile_menu(result.args["key"], ulang)
if menu:
await md.send_md(menu, bot, event=event)
else:
await profile_alc.finish(ulang.get("user.profile.input_value", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
user.profile[result.args["key"]] = result.args["value"]
elif result.subcommands.get("get"):
if result.args["key"] in user.profile:
await profile_alc.finish(user.profile[result.args["key"]])
else:
await profile_alc.finish("无此键值")
else:
profile = Profile(**user.profile)
for k, v in user.profile.items():
profile.__setattr__(k, v)
reply = f"# {ulang.get('user.profile.info')}\n***\n"
hidden_attr = ["id", "TABLE_NAME"]
enter_attr = ["lang", "timezone"]
for key in sorted(profile.dict().keys()):
if key in hidden_attr:
continue
val = profile.dict()[key]
key_text = ulang.get(f"user.profile.{key}")
btn_set = md.btn_cmd(ulang.get("user.profile.edit"), f"profile set {key}",
enter=True if key in enter_attr else False)
reply += (f"\n**{key_text}** **{val}**\n"
f"\n> {ulang.get(f'user.profile.{key}.desc')}"
f"\n> {btn_set} \n\n***\n")
await md.send_md(reply, bot, event=event)
def get_profile_menu(key: str, ulang: Language) -> Optional[str]:
"""获取属性的markdown菜单
Args:
ulang: 用户语言
key: 属性键
Returns:
"""
setting_name = ulang.get(f"user.profile.{key}")
no_menu = ["id", "nickname", "location"]
if key in no_menu:
return None
reply = f"**{setting_name} {ulang.get('user.profile.settings')}**\n***\n"
if key == "lang":
for lang_code, lang_name in get_all_lang().items():
btn_set_lang = md.btn_cmd(f"{lang_name}({lang_code})", f"profile set {key} {lang_code}")
reply += f"\n{btn_set_lang}\n***\n"
elif key == "timezone":
for tz in representative_timezones_list:
btn_set_tz = md.btn_cmd(tz, f"profile set {key} {tz}")
reply += f"{btn_set_tz}\n***\n"
return reply
def set_profile(key: str, value: str, user_id: str) -> bool:
"""设置属性使用if分支对每一个合法性进行检查
Args:
user_id:
key:
value:
Returns:
是否成功设置输入合法性不通过返回False
"""
if key == "lang":
if value in get_all_lang():
change_user_lang(user_id, value)
return True
elif key == "timezone":
if value in pytz.all_timezones:
return True
elif key == "nickname":
return True

View File

@@ -1,27 +0,0 @@
from nonebot.plugin import PluginMetadata
from nonebot import get_driver
from .qweather import *
__plugin_meta__ = PluginMetadata(
name="轻雪天气",
description="基于和风天气api的天气插件",
usage="",
type="application",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki" : True,
"toggleable" : True,
"default_enable": True,
}
)
from ...utils.base.data_manager import set_memory_data
driver = get_driver()
@driver.on_startup
async def _():
# 检查是否为开发者模式
is_dev = await check_key_dev(get_config("weather_key", ""))
set_memory_data("weather.is_dev", is_dev)

View File

@@ -1,171 +0,0 @@
import aiohttp
from .qw_models import *
import httpx
from ...utils.base.data_manager import get_memory_data
from ...utils.base.language import Language
dev_url = "https://devapi.qweather.com/" # 开发HBa
com_url = "https://api.qweather.com/" # 正式环境
def get_qw_lang(lang: str) -> str:
if lang in ["zh-HK", "zh-TW"]:
return "zh-hant"
elif lang.startswith("zh"):
return "zh"
elif lang.startswith("en"):
return "en"
else:
return lang
async def check_key_dev(key: str) -> bool:
url = "https://api.qweather.com/v7/weather/now?"
params = {
"location": "101010100",
"key" : key,
}
async with httpx.AsyncClient() as client:
resp = await client.get(url, params=params)
return (resp.json()).get("code") != "200" # 查询不到付费数据为开发版
def get_local_data(ulang_code: str) -> dict:
"""
获取本地化数据
Args:
ulang_code:
Returns:
"""
ulang = Language(ulang_code)
return {
"monday" : ulang.get("weather.monday"),
"tuesday" : ulang.get("weather.tuesday"),
"wednesday": ulang.get("weather.wednesday"),
"thursday" : ulang.get("weather.thursday"),
"friday" : ulang.get("weather.friday"),
"saturday" : ulang.get("weather.saturday"),
"sunday" : ulang.get("weather.sunday"),
"today" : ulang.get("weather.today"),
"tomorrow" : ulang.get("weather.tomorrow"),
"day" : ulang.get("weather.day"),
"night" : ulang.get("weather.night"),
"no_aqi" : ulang.get("weather.no_aqi"),
}
async def city_lookup(
location: str,
key: str,
adm: str = "",
number: int = 20,
lang: str = "zh",
) -> CityLookup:
"""
通过关键字搜索城市信息
Args:
location:
key:
adm:
number:
lang: 可传入标准i18n语言代码如zh-CN、en-US等
Returns:
"""
url = "https://geoapi.qweather.com/v2/city/lookup?"
params = {
"location": location,
"adm" : adm,
"number" : number,
"key" : key,
"lang" : lang,
}
async with httpx.AsyncClient() as client:
resp = await client.get(url, params=params)
return CityLookup.parse_obj(resp.json())
async def get_weather_now(
key: str,
location: str,
lang: str = "zh",
unit: str = "m",
dev: bool = get_memory_data("is_dev", True),
) -> dict:
url_path = "v7/weather/now?"
url = dev_url + url_path if dev else com_url + url_path
params = {
"location": location,
"key" : key,
"lang" : lang,
"unit" : unit,
}
async with httpx.AsyncClient() as client:
resp = await client.get(url, params=params)
return resp.json()
async def get_weather_daily(
key: str,
location: str,
lang: str = "zh",
unit: str = "m",
dev: bool = get_memory_data("is_dev", True),
) -> dict:
url_path = "v7/weather/%dd?" % (7 if dev else 30)
url = dev_url + url_path if dev else com_url + url_path
params = {
"location": location,
"key" : key,
"lang" : lang,
"unit" : unit,
}
async with httpx.AsyncClient() as client:
resp = await client.get(url, params=params)
return resp.json()
async def get_weather_hourly(
key: str,
location: str,
lang: str = "zh",
unit: str = "m",
dev: bool = get_memory_data("is_dev", True),
) -> dict:
url_path = "v7/weather/%dh?" % (24 if dev else 168)
url = dev_url + url_path if dev else com_url + url_path
params = {
"location": location,
"key" : key,
"lang" : lang,
"unit" : unit,
}
async with httpx.AsyncClient() as client:
resp = await client.get(url, params=params)
return resp.json()
async def get_airquality(
key: str,
location: str,
lang: str,
pollutant: bool = False,
station: bool = False,
dev: bool = get_memory_data("is_dev", True),
) -> dict:
url_path = f"airquality/v1/now/{location}?"
url = dev_url + url_path if dev else com_url + url_path
params = {
"key" : key,
"lang" : lang,
"pollutant": pollutant,
"station" : station,
}
async with httpx.AsyncClient() as client:
resp = await client.get(url, params=params)
return resp.json()

View File

@@ -1,62 +0,0 @@
from liteyuki.utils.base.data import LiteModel
class Location(LiteModel):
name: str = ""
id: str = ""
lat: str = ""
lon: str = ""
adm2: str = ""
adm1: str = ""
country: str = ""
tz: str = ""
utcOffset: str = ""
isDst: str = ""
type: str = ""
rank: str = ""
fxLink: str = ""
sources: str = ""
license: str = ""
class CityLookup(LiteModel):
code: str = ""
location: list[Location] = [Location()]
class Now(LiteModel):
obsTime: str = ""
temp: str = ""
feelsLike: str = ""
icon: str = ""
text: str = ""
wind360: str = ""
windDir: str = ""
windScale: str = ""
windSpeed: str = ""
humidity: str = ""
precip: str = ""
pressure: str = ""
vis: str = ""
cloud: str = ""
dew: str = ""
sources: str = ""
license: str = ""
class WeatherNow(LiteModel):
code: str = ""
updateTime: str = ""
fxLink: str = ""
now: Now = Now()
class Daily(LiteModel):
pass
class WeatherDaily(LiteModel):
code: str = ""
updateTime: str = ""
fxLink: str = ""
daily: list[str] = []

View File

@@ -1,95 +0,0 @@
from nonebot import require, on_endswith
from nonebot.adapters.onebot.v11 import MessageSegment
from nonebot.internal.matcher import Matcher
from liteyuki.utils.base.config import get_config
from liteyuki.utils.base.ly_typing import T_MessageEvent
from .qw_api import *
from liteyuki.utils.base.data_manager import User, user_db
from liteyuki.utils.base.language import Language, get_user_lang
from liteyuki.utils.base.resource import get_path
from liteyuki.utils.message.html_tool import template2image
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma
@on_alconna(
aliases={"天气"},
command=Alconna(
"weather",
Args["keywords", MultiVar(str), []],
),
).handle()
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
"""await alconna.send("weather", city)"""
kws = result.main_args.get("keywords")
image = await get_weather_now_card(matcher, event, kws)
await matcher.finish(MessageSegment.image(image))
@on_endswith(("天气", "weather")).handle()
async def _(event: T_MessageEvent, matcher: Matcher):
"""await alconna.send("weather", city)"""
kws = event.message.extract_plain_text()
image = await get_weather_now_card(matcher, event, [kws.replace("天气", "").replace("weather", "")], False)
await matcher.finish(MessageSegment.image(image))
async def get_weather_now_card(matcher: Matcher, event: T_MessageEvent, keyword: list[str], tip: bool = True):
ulang = get_user_lang(event.user_id)
qw_lang = get_qw_lang(ulang.lang_code)
key = get_config("weather_key")
is_dev = get_memory_data("weather.is_dev", True)
user: User = user_db.first(User(), "user_id = ?", event.user_id, default=User())
# params
unit = user.profile.get("unit", "m")
stored_location = user.profile.get("location", None)
if not key:
await matcher.finish(ulang.get("weather.no_key") if tip else None)
if keyword:
if len(keyword) >= 2:
adm = keyword[0]
city = keyword[-1]
else:
adm = ""
city = keyword[0]
city_info = await city_lookup(city, key, adm=adm, lang=qw_lang)
city_name = " ".join(keyword)
else:
if not stored_location:
await matcher.finish(ulang.get("liteyuki.invalid_command", TEXT="location") if tip else None)
city_info = await city_lookup(stored_location, key, lang=qw_lang)
city_name = stored_location
if city_info.code == "200":
location_data = city_info.location[0]
else:
await matcher.finish(ulang.get("weather.city_not_found", CITY=city_name) if tip else None)
weather_now = await get_weather_now(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
weather_daily = await get_weather_daily(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
weather_hourly = await get_weather_hourly(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
aqi = await get_airquality(key, location_data.id, lang=qw_lang, dev=is_dev)
image = await template2image(
template=get_path("templates/weather_now.html", abs_path=True),
templates={
"data": {
"params" : {
"unit": unit,
"lang": ulang.lang_code,
},
"weatherNow" : weather_now,
"weatherDaily" : weather_daily,
"weatherHourly": weather_hourly,
"aqi" : aqi,
"location" : location_data.dump(),
"localization" : get_local_data(ulang.lang_code)
}
},
debug=True,
wait=1
)
return image

View File

@@ -1,161 +0,0 @@
import datetime
import time
import aiohttp
from nonebot import require
from nonebot.plugin import PluginMetadata
from liteyuki.utils.base.config import get_config
from liteyuki.utils.base.data import Database, LiteModel
from liteyuki.utils.base.resource import get_path
from liteyuki.utils.message.html_tool import template2image
require("nonebot_plugin_alconna")
require("nonebot_plugin_apscheduler")
from nonebot_plugin_apscheduler import scheduler
from nonebot_plugin_alconna import Alconna, AlconnaResult, CommandResult, Subcommand, UniMessage, on_alconna, Args
__author__ = "snowykami"
__plugin_meta__ = PluginMetadata(
name="签名服务器状态",
description="适用于ntqq的签名状态查看",
usage="",
type="application",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki" : True,
"toggleable" : True,
"default_enable": True,
}
)
SIGN_COUNT_URLS: dict[str, str] = get_config("sign_count_urls", None)
SIGN_COUNT_DURATION = get_config("sign_count_duration", 10)
class SignCount(LiteModel):
TABLE_NAME: str = "sign_count"
time: float = 0.0
count: int = 0
sid: str = ""
sign_db = Database("data/liteyuki/ntqq_sign.ldb")
sign_db.auto_migrate(SignCount())
sign_status = on_alconna(Alconna(
"sign",
Subcommand(
"chart",
Args["limit", int, 10000]
),
Subcommand(
"count"
),
Subcommand(
"data"
)
))
cache_img: bytes = None
@sign_status.assign("count")
async def _():
reply = "Current sign count:"
for name, count in (await get_now_sign()).items():
reply += f"\n{name}: {count[1]}"
await sign_status.send(reply)
@sign_status.assign("data")
async def _():
query_stamp = [1, 5, 10, 15]
reply = "Count from last " + ", ".join([str(i) for i in query_stamp]) + "mins"
for name, url in SIGN_COUNT_URLS.items():
count_data = []
for stamp in query_stamp:
count_rows = sign_db.all(SignCount(), "sid = ? and time > ?", url, time.time() - 60 * stamp)
if len(count_rows) < 2:
count_data.append(-1)
else:
count_data.append(count_rows[-1].count - count_rows[0].count)
reply += f"\n{name}: " + ", ".join([str(i) for i in count_data])
await sign_status.send(reply)
@sign_status.assign("chart")
async def _(arp: CommandResult = AlconnaResult()):
limit = arp.result.subcommands.get("chart").args.get("limit")
if limit == 10000:
if cache_img:
await sign_status.send(UniMessage.image(raw=cache_img))
return
img = await generate_chart(limit)
await sign_status.send(UniMessage.image(raw=img))
@scheduler.scheduled_job("interval", seconds=SIGN_COUNT_DURATION, next_run_time=datetime.datetime.now())
async def update_sign_count():
global cache_img
if not SIGN_COUNT_URLS:
return
data = await get_now_sign()
for name, count in data.items():
await save_sign_count(count[0], count[1], SIGN_COUNT_URLS[name])
cache_img = await generate_chart(10000)
async def get_now_sign() -> dict[str, tuple[float, int]]:
"""
Get the sign count and the time of the latest sign
Returns:
tuple[float, int] | None: (time, count)
"""
data = {}
now = time.time()
async with aiohttp.ClientSession() as client:
for name, url in SIGN_COUNT_URLS.items():
async with client.get(url) as resp:
count = (await resp.json())["count"]
data[name] = (now, count)
return data
async def save_sign_count(timestamp: float, count: int, sid: str):
"""
Save the sign count to the database
Args:
sid: the sign id use url as the id
count:
timestamp (float): the time of the sign count (int): the count of the sign
"""
sign_db.save(SignCount(time=timestamp, count=count, sid=sid))
async def generate_chart(limit):
data = []
for name, url in SIGN_COUNT_URLS.items():
count_rows = sign_db.all(SignCount(), "sid = ? ORDER BY id DESC LIMIT ?", url, limit)
count_rows.reverse()
data.append(
{
"name" : name,
# "data": [[row.time, row.count] for row in count_rows]
"times" : [row.time for row in count_rows],
"counts": [row.count for row in count_rows]
}
)
print(len(count_rows))
img = await template2image(
template=get_path("templates/sign_status.html", debug=True),
templates={
"data": data
},
debug=True
)
return img

View File

@@ -1,3 +0,0 @@
name: Sign Status
description: for Lagrange
version: 2024.4.26

View File

@@ -1,4 +0,0 @@
.sign-chart {
height: 400px;
background-color: rgba(255, 255, 255, 0.7);
}

View File

@@ -1,75 +0,0 @@
// 数据类型声明
// import * as echarts from 'echarts';
let data = JSON.parse(document.getElementById("data").innerText) // object
const signChartDivTemplate = document.importNode(document.getElementById("sign-chart-template").content, true)
data.forEach((item) => {
let signChartDiv = signChartDivTemplate.cloneNode(true)
let chartID = item["name"]
// 初始化ECharts实例
// 设置id
signChartDiv.querySelector(".sign-chart").id = chartID
document.body.appendChild(signChartDiv)
let signChart = echarts.init(document.getElementById(chartID))
let timeCount = []
item["counts"].forEach((count, index) => {
// 计算平均值index - 1的count + index的count + index + 1的count /3
if (index > 0) {
timeCount.push((item["counts"][index] - item["counts"][index - 1]))
}
})
console.log(timeCount)
signChart.setOption(
{
animation: false,
title: {
text: item["name"],
textStyle: {
color: '#000000' // 设置标题文本颜色为红色
}
},
xAxis: {
type: 'category',
data: item["times"].map(timestampToTime),
},
yAxis: [
{
type: 'value',
min: Math.min(...item["counts"]),
},
{
type: 'value',
min: Math.min(...timeCount),
}
],
series: [
{
data: item["counts"],
type: 'line',
yAxisIndex: 0
},
{
data: timeCount,
type: 'line',
yAxisIndex: 1
}
]
}
)
})
function timestampToTime(timestamp) {
let date = new Date(timestamp * 1000)
let Y = date.getFullYear() + '-'
let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
let D = date.getDate() + ' '
let h = date.getHours() + ':'
let m = date.getMinutes() + ':'
let s = date.getSeconds()
return M + D + h + m + s
}

Some files were not shown because too many files have changed in this diff Show More