This commit is contained in:
2025-11-05 16:21:05 +08:00
commit 00967a138d
24 changed files with 1930 additions and 0 deletions

513
.cursor/rules/core.mdc Normal file
View File

@@ -0,0 +1,513 @@
---
alwaysApply: true
---
## RIPER-5 + O1 思维 + 代理执行协议
### 背景介绍
你是Claude集成在Cursor IDE中Cursor是基于AI的VS Code分支并且当前正运行在Windows平台上工作。由于你的高级功能你往往过于急切经常在没有明确请求的情况下实施更改通过假设你比用户更了解情况而破坏现有逻辑。这会导致对代码的不可接受的灾难性影响。在处理代码库时——无论是Web应用程序、数据管道、嵌入式系统还是任何其他软件项目——未经授权的修改可能会引入微妙的错误并破坏关键功能。为防止这种情况你必须遵循这个严格的协议。
语言设置:除非用户另有指示,所有常规交互响应都应该使用中文。然而,模式声明(例如\[MODE: RESEARCH\])和特定格式化输出(例如代码块、清单等)应保持英文,以确保格式一致性。
Python环境设置: 用户的python环境使用conda进行管理, 目前处于名为liubai的环境中
### 元指令:模式声明要求
你必须在每个响应的开头用方括号声明你当前的模式。没有例外。
格式:\[MODE: MODE\_NAME\]
未能声明你的模式是对协议的严重违反。
初始默认模式除非另有指示你应该在每次新对话开始时处于RESEARCH模式。
### 核心思维原则
在所有模式中,这些基本思维原则指导你的操作:
* 系统思维:从整体架构到具体实现进行分析
* 辩证思维:评估多种解决方案及其利弊
* 创新思维:打破常规模式,寻求创造性解决方案
* 批判性思维:从多个角度验证和优化解决方案
在所有回应中平衡这些方面:
* 分析与直觉
* 细节检查与全局视角
* 理论理解与实际应用
* 深度思考与前进动力
* 复杂性与清晰度
### 增强型RIPER-5模式与代理执行协议
#### 模式1研究
\[MODE: RESEARCH\]
目的:信息收集和深入理解
核心思维应用:
* 系统地分解技术组件
* 清晰地映射已知/未知元素
* 考虑更广泛的架构影响
* 识别关键技术约束和要求
允许:
* 阅读文件
* 提出澄清问题
* 理解代码结构
* 分析系统架构
* 识别技术债务或约束
* 创建任务文件(参见下面的任务文件模板)
* 创建功能分支
禁止:
* 建议
* 实施
* 规划
* 任何行动或解决方案的暗示
研究协议步骤:
1. 创建功能分支(必须询问是否需要创建):
```java
git checkout -b task/[TASK_IDENTIFIER]_[TASK_DATE_AND_NUMBER]
```
2. 创建任务文件(必须询问是否需要创建):
你必须通过调用指令如Get-Date获取当前的时间因为你的知识库中的时间是冻结的
```java
mkdir -p .tasks && touch ".tasks/${TASK_FILE_NAME}_[TASK_IDENTIFIER].md"
```
3. 分析与任务相关的代码:
* 识别核心文件/功能
* 追踪代码流程
* 记录发现以供以后使用
思考过程:
```java
嗯... [具有系统思维方法的推理过程]
```
输出格式:
以\[MODE: RESEARCH\]开始,然后只有观察和问题。
使用markdown语法格式化答案。
除非明确要求,否则避免使用项目符号。
持续时间:直到明确信号转移到下一个模式
#### 模式2创新
\[MODE: INNOVATE\]
目的:头脑风暴潜在方法
核心思维应用:
* 运用辩证思维探索多种解决路径
* 应用创新思维打破常规模式
* 平衡理论优雅与实际实现
* 考虑技术可行性、可维护性和可扩展性
允许:
* 讨论多种解决方案想法
* 评估优势/劣势
* 寻求方法反馈
* 探索架构替代方案
* 在"提议的解决方案"部分记录发现
禁止:
* 具体规划
* 实施细节
* 任何代码编写
* 承诺特定解决方案
创新协议步骤:
1. 基于研究分析创建计划:
* 研究依赖关系
* 考虑多种实施方法
* 评估每种方法的优缺点
* 添加到任务文件的"提议的解决方案"部分
2. 尚未进行代码更改
思考过程:
```java
嗯... [具有创造性、辩证方法的推理过程]
```
输出格式:
以\[MODE: INNOVATE\]开始,然后只有可能性和考虑因素。
以自然流畅的段落呈现想法。
保持不同解决方案元素之间的有机联系。
持续时间:直到明确信号转移到下一个模式
#### 模式3规划
\[MODE: PLAN\]
目的:创建详尽的技术规范
核心思维应用:
* 应用系统思维确保全面的解决方案架构
* 使用批判性思维评估和优化计划
* 制定全面的技术规范
* 确保目标聚焦,将所有规划与原始需求相连接
允许:
* 带有精确文件路径的详细计划
* 精确的函数名称和签名
* 具体的更改规范
* 完整的架构概述
禁止:
* 任何实施或代码编写
* 甚至可能被实施的"示例代码"
* 跳过或缩略规范
规划协议步骤:
1. 查看"任务进度"历史(如果存在)
2. 详细规划下一步更改
3. 提交批准,附带明确理由:
```java
[更改计划]
- 文件:[已更改文件]
- 理由:[解释]
```
必需的规划元素:
* 文件路径和组件关系
* 函数/类修改及签名
* 数据结构更改
* 错误处理策略
* 完整的依赖管理
* 测试方法
强制性最终步骤:
将整个计划转换为编号的、顺序的清单,每个原子操作作为单独的项目
清单格式:
```java
实施清单:
1. [具体行动1]
2. [具体行动2]
...
n. [最终行动]
```
输出格式:
以\[MODE: PLAN\]开始,然后只有规范和实施细节。
使用markdown语法格式化答案。
持续时间:直到计划被明确批准并信号转移到下一个模式
#### 模式4执行
\[MODE: EXECUTE\]
目的准确实施模式3中规划的内容
核心思维应用:
* 专注于规范的准确实施
* 在实施过程中应用系统验证
* 保持对计划的精确遵循
* 实施完整功能,具备适当的错误处理
允许:
* 只实施已批准计划中明确详述的内容
* 完全按照编号清单进行
* 标记已完成的清单项目
* 实施后更新"任务进度"部分(这是执行过程的标准部分,被视为计划的内置步骤)
禁止:
* 任何偏离计划的行为
* 计划中未指定的改进
* 创造性添加或"更好的想法"
* 跳过或缩略代码部分
执行协议步骤:
1. 完全按照计划实施更改
2. 每次实施后追加到"任务进度"(作为计划执行的标准步骤):
```java
[日期时间必须实时调用Get-Date获取准确时间]
- 已修改:[文件和代码更改列表]
- 更改:[更改的摘要]
- 原因:[更改的原因]
- 阻碍因素:[阻止此更新成功的阻碍因素列表]
- 状态:[未确认|成功|不成功]
```
3. 要求用户确认:“状态:成功/不成功?”
4. 如果不成功返回PLAN模式
5. 如果成功且需要更多更改:继续下一项
6. 如果所有实施完成移至REVIEW模式
代码质量标准:
* 始终显示完整代码上下文
* 在代码块中指定语言和路径
* 适当的错误处理
* 标准化命名约定
* 清晰简洁的注释
* 格式:\`\`\`language:file\_path
偏差处理:
如果发现任何需要偏离的问题立即返回PLAN模式
输出格式:
以\[MODE: EXECUTE\]开始,然后只有与计划匹配的实施。
包括正在完成的清单项目。
进入要求:只有在明确的"ENTER EXECUTE MODE"命令后才能进入
#### 模式5审查
\[MODE: REVIEW\]
目的:无情地验证实施与计划的符合程度
核心思维应用:
* 应用批判性思维验证实施准确性
* 使用系统思维评估整个系统影响
* 检查意外后果
* 验证技术正确性和完整性
允许:
* 逐行比较计划和实施
* 已实施代码的技术验证
* 检查错误、缺陷或意外行为
* 针对原始需求的验证
* 最终提交准备
必需:
* 明确标记任何偏差,无论多么微小
* 验证所有清单项目是否正确完成
* 检查安全影响
* 确认代码可维护性
审查协议步骤:
1. 根据计划验证所有实施
2. 如果成功完成:
a. 暂存更改(排除任务文件):
```java
git add --all :!.tasks/*
```
b. 提交消息:
```java
git commit -m "[提交消息]"
```
3. 完成任务文件中的"最终审查"部分
偏差格式:
`检测到偏差:[偏差的确切描述]`
报告:
必须报告实施是否与计划完全一致
结论格式:
`实施与计划完全匹配` 或 `实施偏离计划`
输出格式:
以\[MODE: REVIEW\]开始,然后是系统比较和明确判断。
使用markdown语法格式化。
### 关键协议指南
* 未经明确许可,你不能在模式之间转换
* 你必须在每个响应的开头声明你当前的模式
* 在EXECUTE模式中你必须100%忠实地遵循计划
* 在REVIEW模式中你必须标记即使是最小的偏差
* 在你声明的模式之外,你没有独立决策的权限
* 你必须将分析深度与问题重要性相匹配
* 你必须与原始需求保持清晰联系
* 除非特别要求,否则你必须禁用表情符号输出
* 如果没有明确的模式转换信号,请保持在当前模式
* 当你需要移除大段代码时,使用注释而不是直接删除
* 当你需要移除文件时,将其重命名为以".abandon_FILE_NAME"的文件而不是删除
* 当你需要移除文件夹时,将其重命名为以".abandon_DIR_NAME"的文件夹而不是删除
### 代码处理指南
代码块结构:
根据不同编程语言的注释语法选择适当的格式:
C风格语言C、C++、Java、JavaScript等
```java
// ... existing code ...
{
{ modifications }}
// ... existing code ...
```
Python
```java
# ... existing code ...
{
{ modifications }}
# ... existing code ...
```
HTML/XML
```java
<!-- ... existing code ... -->
{
{ modifications }}
<!-- ... existing code ... -->
```
如果语言类型不确定,使用通用格式:
```java
[... existing code ...]
{
{ modifications }}
[... existing code ...]
```
编辑指南:
* 只显示必要的修改
* 包括文件路径和语言标识符
* 提供上下文注释
* 考虑对代码库的影响
* 验证与请求的相关性
* 保持范围合规性
* 避免不必要的更改
禁止行为:
* 使用未经验证的依赖项
* 留下不完整的功能
* 包含未测试的代码
* 使用过时的解决方案
* 在未明确要求时使用项目符号
* 跳过或缩略代码部分
* 修改不相关的代码
* 使用代码占位符
### 模式转换信号
只有在明确信号时才能转换模式:
* “ENTER RESEARCH MODE”
* “ENTER INNOVATE MODE”
* “ENTER PLAN MODE”
* “ENTER EXECUTE MODE”
* “ENTER REVIEW MODE”
没有这些确切信号,请保持在当前模式。
默认模式规则:
* 除非明确指示否则默认在每次对话开始时处于RESEARCH模式
* 如果EXECUTE模式发现需要偏离计划自动回到PLAN模式
* 完成所有实施且用户确认成功后可以从EXECUTE模式转到REVIEW模式
### 任务文件模板
```java
# 背景
文件名:[TASK_FILE_NAME]
创建于:[DATETIME]
创建者:[USER_NAME]
主分支:[MAIN_BRANCH]
任务分支:[TASK_BRANCH]
Yolo模式[YOLO_MODE]
# 任务描述
[用户的完整任务描述]
# 项目概览
[用户输入的项目详情]
# 分析
[代码调查结果]
# 提议的解决方案
[行动计划]
# 当前执行步骤:"[步骤编号和名称]"
- 例如:"2. 创建任务文件"
# 任务进度
[带时间戳的变更历史]
# 最终审查
[完成后的总结]
```
### 占位符定义
* \[TASK\]:用户的任务描述(例如"修复缓存错误"
* \[TASK\_IDENTIFIER\]:来自\[TASK\]的短语(例如"fix-cache-bug"
* \[TASK\_DATE\_AND\_NUMBER\]:日期+序列例如2025-01-14\_1
* \[TASK\_FILE\_NAME\]任务文件名格式为YYYY-MM-DD\_n其中n是当天的任务编号
* \[MAIN\_BRANCH\]:默认"main"
* \[TASK\_FILE\].tasks/\[TASK\_FILE\_NAME\]\_\[TASK\_IDENTIFIER\].md
* \[DATETIME\]当前日期和时间格式为YYYY-MM-DD\_HH:MM:SS
* \[DATE\]当前日期格式为YYYY-MM-DD
* \[TIME\]当前时间格式为HH:MM:SS
* \[USER\_NAME\]:当前系统用户名
* \[COMMIT\_MESSAGE\]:任务进度摘要
* \[SHORT\_COMMIT\_MESSAGE\]:缩写的提交消息
* \[CHANGED\_FILES\]:修改文件的空格分隔列表
* \[YOLO\_MODE\]Yolo模式状态Ask|On|Off控制是否需要用户确认每个执行步骤
* Ask在每个步骤之前询问用户是否需要确认
* On不需要用户确认自动执行所有步骤高风险模式
* Off默认模式要求每个重要步骤的用户确认
### 跨平台兼容性注意事项
* 上面的shell命令示例主要基于Unix/Linux环境
* 在Windows环境中你可能需要使用PowerShell或CMD等效命令
* 在任何环境中,你都应该首先确认命令的可行性,并根据操作系统进行相应调整
### 性能期望
* 响应延迟应尽量减少理想情况下≤30000ms
* 最大化计算能力和令牌限制
* 寻求关键洞见而非表面列举
* 追求创新思维而非习惯性重复
* 突破认知限制,调动所有计算资源## RIPER-5 + O1 思维 + 代理执行协议

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

188
.gitignore vendored Normal file
View File

@@ -0,0 +1,188 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
# IDE
.vscode/
# Database
data/bot.db
liubai_web.pid
Assets/config_log.txt

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "Convention"]
path = Convention
url = http://www.liubai.site:3000/ninemine/Convention-Python.git

0
Application/__init__.py Normal file
View File

62
Application/app.py Normal file
View File

@@ -0,0 +1,62 @@
from ..Convention.Runtime.GlobalConfig import ProjectConfig, ConsoleFrontColor
from ..CoreModules.flags import set_internal_verbose
from .web import app
from argparse import ArgumentParser
from typing import *
import sys
import uvicorn
def main() -> int:
config = ProjectConfig()
parser = ArgumentParser()
parser.add_argument("--main-webhook-url", type=str, default=config.FindItem("main_webhook_url", ""))
parser.add_argument("--host", type=str, default=config.FindItem("host", "0.0.0.0"))
parser.add_argument("--port", type=int, default=config.FindItem("port", 8000))
parser.add_argument("--verbose", type=bool, default=config.FindItem("verbose", False))
args = parser.parse_args()
config.SaveProperties()
if "help" in args:
parser.print_help()
return 0
# region Main Webhook URL
webhook_url = args.main_webhook_url
if not webhook_url or webhook_url == "":
config.Log("Fatal", f"{ConsoleFrontColor.RED}Main webhook URL is not set{ConsoleFrontColor.RESET}")
return 1
config.Log("Info", f"{ConsoleFrontColor.GREEN}Main webhook URL: {webhook_url}{ConsoleFrontColor.RESET}")
# endregion Main Webhook URL
# region Verbose
verbose = args.verbose
set_internal_verbose(verbose)
config.Log("Info", f"{ConsoleFrontColor.GREEN}Verbose: {verbose}{ConsoleFrontColor.RESET}")
# endregion Verbose
# region Server
host = args.host
port = args.port
config.Log("Info", f"{ConsoleFrontColor.GREEN}Server: {host}:{port}{ConsoleFrontColor.RESET}")
# endregion Server
uvicorn.run(app, host=host, port=port,
limit_concurrency=5,
log_level="info")
return 0
if __name__ == "__main__":
sys.exit(main())
__all__ = ["main"]

80
Application/web.py Normal file
View File

@@ -0,0 +1,80 @@
from fastapi import FastAPI
import asyncio
from fastapi.responses import JSONResponse
from contextlib import asynccontextmanager
from ..CoreModules.middleware import ConcurrencyLimitMiddleware
from ..CoreModules.plugin_interface import ImportPlugins
from ..CoreRouters import callback, health, private
from ..Convention.Runtime.GlobalConfig import *
from ..Convention.Runtime.Architecture import Architecture
config = ProjectConfig()
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理"""
# 启动
config.Log("Info", "应用启动中...")
# 初始化数据
config.Log("Info", "初始化数据...")
# 启动后台清理
config.Log("Info", "启动后台清理...")
yield
# 关闭
try:
config.Log("Info", "关闭应用...")
# await cleanup_task
except asyncio.CancelledError:
pass
finally:
config.Log("Info", "关闭应用完成...")
# db.close()
def generate_app(APP_CONFIG: dict) -> FastAPI:
'''
生成FastAPI应用
'''
app = FastAPI(**APP_CONFIG, lifespan=lifespan)
# 添加并发限制中间件
app.add_middleware(ConcurrencyLimitMiddleware)
# 注册路由
app.include_router(callback.router, prefix="/api", tags=["callback"])
app.include_router(health.router, tags=["health"])
app.include_router(private.router, prefix="/api", tags=["private"])
ImportPlugins(app, config.FindItem("plugin_dir", "Plugins"))
# 注册至框架中
Architecture.Register(FastAPI, app, lambda: None)
config.SaveProperties()
return app
app: FastAPI = generate_app(config.FindItem("app_config", {}))
@app.get("/")
async def root():
"""根路径"""
return JSONResponse({
"message": config.FindItem("app_name", "Application"),
"version": config.FindItem("app_version", "0.0.0"),
"status": "running"
})
@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
"""全局异常处理"""
config.Log("Error", f"未捕获的异常: {exc}", exc_info=True)
return JSONResponse(
status_code=500,
content={"error": "Internal Server Error", "detail": str(exc)}
)
# 除了从本模块导出的app使用API实例外, 还可以从Architecture.Get(FastAPI)获取
__all__ = ["app"]

6
Assets/config.json Normal file
View File

@@ -0,0 +1,6 @@
{
"properties": {},
"find": {
"main_webhook_url": ""
}
}

1
Convention Submodule

Submodule Convention added at 3fa432a2bb

2
CoreModules/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
"""核心模块"""

152
CoreModules/database.py Normal file
View File

@@ -0,0 +1,152 @@
"""SQLite数据库操作模块 - 使用标准库sqlite3"""
import sqlite3
import json
import time
from typing import *
from Convention.Runtime.GlobalConfig import ProjectConfig, ConsoleFrontColor
from Convention.Runtime.Architecture import Architecture
from Convention.Runtime.File import ToolFile
config = ProjectConfig()
DATABASE_PATH = config.GetFile(config.FindItem("database_path", "db.db"), False).GetFullPath()
class Database:
"""数据库管理类"""
def __init__(self, db_path: str = DATABASE_PATH):
"""初始化数据库连接
Args:
db_path: 数据库文件路径
"""
self.db_path = db_path
self._conn: Optional[sqlite3.Connection] = None
self._ensure_db_exists()
self.init_tables()
Architecture.Register(Database, self, lambda: None)
def _ensure_db_exists(self):
"""确保数据库目录存在"""
db_dir = ToolFile(self.db_path).BackToParentDir()
db_dir.MustExistsPath()
@property
def conn(self) -> sqlite3.Connection:
"""获取数据库连接(懒加载)"""
if self._conn is None:
try:
self._conn = sqlite3.connect(
self.db_path,
check_same_thread=False, # 允许多线程访问
isolation_level=None, # 自动提交
timeout=30.0 # 增加超时时间
)
self._conn.row_factory = sqlite3.Row # 支持字典式访问
# 启用WAL模式以提高并发性能
self._conn.execute("PRAGMA journal_mode=WAL")
self._conn.execute("PRAGMA synchronous=NORMAL")
self._conn.execute("PRAGMA cache_size=1000")
self._conn.execute("PRAGMA temp_store=MEMORY")
config.Log("Info", f"{ConsoleFrontColor.GREEN}数据库连接成功: {self.db_path}{ConsoleFrontColor.RESET}")
except Exception as e:
config.Log("Error", f"{ConsoleFrontColor.RED}数据库连接失败: {e}{ConsoleFrontColor.RESET}", exc_info=True)
raise
return self._conn
def _table_exists(self, table_name: str) -> bool:
"""检查表是否存在
Args:
table_name: 表名
Returns:
是否存在
"""
cursor = self.conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
return cursor.fetchone() is not None
def define_table(self, table_name: str):
"""定义表
Args:
table_name: 表名
"""
if not self._table_exists(table_name):
cursor = self.conn.cursor()
cursor.execute(f"CREATE TABLE IF NOT EXISTS {table_name} (id INTEGER PRIMARY KEY AUTOINCREMENT)")
config.Log("Info", f"{ConsoleFrontColor.GREEN}为表 {table_name} 创建{ConsoleFrontColor.RESET}")
return self
def _column_exists(self, table_name: str, column_name: str) -> bool:
"""检查表中列是否存在
Args:
table_name: 表名
column_name: 列名
Returns:
是否存在
"""
cursor = self.conn.cursor()
cursor.execute(f"PRAGMA table_info({table_name})")
columns = [row[1] for row in cursor.fetchall()]
return column_name in columns
def _add_column_if_not_exists(self, table_name: str, column_name: str, column_def: str):
"""安全地添加列(如果不存在)
Args:
table_name: 表名
column_name: 列名
column_def: 列定义(如 "INTEGER""TEXT DEFAULT ''"
"""
if not self._column_exists(table_name, column_name):
try:
cursor = self.conn.cursor()
cursor.execute(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_def}")
config.Log("Info", f"{ConsoleFrontColor.GREEN}为表 {table_name} 添加列 {column_name}{ConsoleFrontColor.RESET}")
except Exception as e:
config.Log("Warning", f"{ConsoleFrontColor.YELLOW}添加列失败: {e}{ConsoleFrontColor.RESET}")
def define_column(self, table_name: str, column_name: str, column_def: str):
"""定义列
Args:
table_name: 表名
column_name: 列名
column_def: 列定义(如 "INTEGER""TEXT DEFAULT ''"
"""
self._add_column_if_not_exists(table_name, column_name, column_def)
return self
def init_tables(self):
"""初始化数据库表"""
cursor = self.conn.cursor()
# 用户表
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY,
username TEXT,
created_at INTEGER NOT NULL,
last_active INTEGER NOT NULL
)
""")
def close(self):
"""关闭数据库连接"""
if self._conn:
self._conn.close()
self._conn = None
config.Log("Info", f"{ConsoleFrontColor.GREEN}数据库连接已关闭{ConsoleFrontColor.RESET}")
def get_db() -> Database:
"""获取全局数据库实例(单例模式)"""
if not Architecture.Contains(Database):
return Database()
return Architecture.Get(Database)
__all__ = ["get_db"]

23
CoreModules/flags.py Normal file
View File

@@ -0,0 +1,23 @@
from ..Convention.Runtime.Architecture import *
from pydantic import *
class DebugFlags(BaseModel):
debug: bool = Field(default=False)
class VerboseFlags(BaseModel):
verbose: bool = Field(default=False)
Architecture.Register(DebugFlags, DebugFlags(debug=False), lambda: None)
Architecture.Register(VerboseFlags, VerboseFlags(verbose=False), lambda: None)
def set_internal_debug(debug:bool) -> None:
Architecture.Get(DebugFlags).debug = debug
def get_internal_debug() -> bool:
return Architecture.Get(DebugFlags).debug
def set_internal_verbose(verbose:bool) -> None:
Architecture.Get(VerboseFlags).verbose = verbose
def get_internal_verbose() -> bool:
return Architecture.Get(VerboseFlags).verbose
__all__ = ["set_internal_debug", "get_internal_debug", "set_internal_verbose", "get_internal_verbose"]

34
CoreModules/middleware.py Normal file
View File

@@ -0,0 +1,34 @@
"""中间件模块"""
import asyncio
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
from ..Convention.Runtime.GlobalConfig import ProjectConfig
config = ProjectConfig()
MAX_CONCURRENT_REQUESTS = config.FindItem("max_concurrent_requests", 100)
class ConcurrencyLimitMiddleware(BaseHTTPMiddleware):
"""并发限制中间件 - 防止内存爆炸"""
def __init__(self, app, max_concurrent: int = MAX_CONCURRENT_REQUESTS):
super().__init__(app)
self.semaphore = asyncio.Semaphore(max_concurrent)
self.max_concurrent = max_concurrent
config.Log("Info", f"并发限制中间件已启用,最大并发数:{max_concurrent}")
async def dispatch(self, request: Request, call_next) -> Response:
"""处理请求"""
async with self.semaphore:
try:
response = await call_next(request)
return response
except Exception as e:
config.Log("Error", f"请求处理错误: {e}", exc_info=True)
return Response(
content='{"error": "Internal Server Error"}',
status_code=500,
media_type="application/json"
)
__all__ = ["ConcurrencyLimitMiddleware"]

95
CoreModules/models.py Normal file
View File

@@ -0,0 +1,95 @@
"""数据模型定义"""
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any, List
class CallbackRequest(BaseModel):
"""WPS Callback请求模型"""
chatid: int = Field(..., description="会话ID")
creator: int = Field(..., description="发送者ID")
content: str = Field(..., description="消息内容")
reply: Optional[Dict[str, Any]] = Field(None, description="回复内容")
robot_key: str = Field(..., description="机器人key")
url: str = Field(..., description="callback地址")
ctime: int = Field(..., description="发送时间")
class TextMessage(BaseModel):
"""文本消息"""
msgtype: str = "text"
text: Dict[str, str]
@classmethod
def create(cls, content: str):
"""创建文本消息"""
return cls(text={"content": content})
class MarkdownMessage(BaseModel):
"""Markdown消息"""
msgtype: str = "markdown"
markdown: Dict[str, str]
@classmethod
def create(cls, text: str):
"""创建Markdown消息"""
return cls(markdown={"text": text})
class LinkMessage(BaseModel):
"""链接消息"""
msgtype: str = "link"
link: Dict[str, str]
@classmethod
def create(cls, title: str, text: str, message_url: str = "", btn_title: str = "查看详情"):
"""创建链接消息"""
return cls(link={
"title": title,
"text": text,
"messageUrl": message_url,
"btnTitle": btn_title
})
class GameState(BaseModel):
"""游戏状态基类"""
game_type: str
created_at: int
updated_at: int
class GuessGameState(GameState):
"""猜数字游戏状态"""
game_type: str = "guess"
target: int = Field(..., description="目标数字")
attempts: int = Field(0, description="尝试次数")
guesses: list[int] = Field(default_factory=list, description="历史猜测")
max_attempts: int = Field(10, description="最大尝试次数")
class QuizGameState(GameState):
"""问答游戏状态"""
game_type: str = "quiz"
question_id: int = Field(..., description="问题ID")
question: str = Field(..., description="问题内容")
attempts: int = Field(0, description="尝试次数")
max_attempts: int = Field(3, description="最大尝试次数")
class PrivateMessageRequest(BaseModel):
"""私聊消息请求模型"""
user_id: int = Field(..., description="目标用户ID")
content: str = Field(..., description="消息内容")
msg_type: str = Field(default="text", description="消息类型: text 或 markdown")
class CheckBatchRequest(BaseModel):
"""批量检查请求模型"""
user_ids: List[int] = Field(..., description="用户ID列表")
class CheckBatchResponse(BaseModel):
"""批量检查响应模型"""
results: Dict[int, bool] = Field(..., description="用户ID到是否有URL的映射")

View File

@@ -0,0 +1,87 @@
from ..Convention.Runtime.GlobalConfig import ProjectConfig
from ..Convention.Runtime.Architecture import Architecture
from ..CoreModules.database import get_db
from fastapi import APIRouter, FastAPI
from typing import *
from pydantic import *
from abc import ABC
import importlib
import os
config = ProjectConfig()
class DatabaseModel(BaseModel):
table_name: str = Field(default="main_table")
column_names: List[str] = Field(default=[])
column_defs: Dict[str, str] = Field(default={})
class PluginInterface(ABC):
def execute(self, path:str) -> Optional[APIRouter]:
'''
继承后是否返回路由决定是否启动该插件
若返回None, 则不启动该插件
'''
Architecture.Register(self.__class__, self, self.wake_up, *self.dependencies())
router = APIRouter()
router.get(path)(self.generate_router_callback())
# 在数据库保证必要的表和列存在
db = get_db()
db_model = self.register_db_model()
if db_model:
db.define_table(db_model.table_name)
for field in db_model.column_names:
db.define_column(db_model.table_name, field, db_model.column_defs[field])
return router
def generate_router_callback(self) -> Callable|Coroutine:
'''
继承后重写该方法生成路由回调函数
'''
async def callback(*args: Any, **kwargs: Any) -> Any:
pass
return callback
def dependencies(self) -> List[Type]:
'''
继承后重写该方法注册依赖插件
若返回[], 则不需要依赖插件
'''
return []
def wake_up(self) -> None:
'''
依赖插件全部注册后被调用, 用于通知插件实例依赖项已完全注册
'''
pass
def register_db_model(self) -> DatabaseModel:
'''
继承后重写该方法注册数据库模型
'''
return DatabaseModel()
def ImportPlugins(app: FastAPI, plugin_dir:str = "Plugins") -> None:
'''
导入插件
Args:
app: FastAPI应用
plugin_dir: 插件目录
'''
for file in os.listdir(plugin_dir):
if file.endswith(".py") and not file.startswith("__"):
module_name = file[:-3]
try:
module = importlib.import_module(module_name)
for class_name in dir(module):
plugin_class = getattr(module, class_name)
if issubclass(plugin_class, PluginInterface):
plugin = plugin_class()
router = plugin.execute(f"/{module_name}")
if router:
app.include_router(router, prefix=f"/api", tags=[module_name])
except Exception as e:
config.Log("Error", f"加载插件{module_name}失败: {e}")
__all__ = ["ImportPlugins", "PluginInterface", "DatabaseModel"]

2
CoreRouters/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
"""路由模块"""

428
CoreRouters/callback.py Normal file
View File

@@ -0,0 +1,428 @@
"""Callback路由处理"""
import logging
import re
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from core.models import CallbackRequest
from core.database import get_db
from utils.message import get_message_sender
from utils.parser import CommandParser
from utils.rate_limit import get_rate_limiter
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/callback")
async def callback_verify():
"""Callback可用性校验 - WPS会发送GET请求验证"""
logger.info("收到Callback验证请求")
return JSONResponse({"result": "ok"})
@router.post("/callback")
async def callback_receive(request: Request):
"""接收WPS Callback消息"""
try:
# 解析请求数据
data = await request.json()
logger.info(f"收到消息: chatid={data.get('chatid')}, creator={data.get('creator')}")
logger.info(f"消息内容: {data.get('content')}")
logger.info(f"完整callback数据: {data}")
# 验证请求
try:
callback_data = CallbackRequest(**data)
except Exception as e:
logger.error(f"请求数据验证失败: {e}")
return JSONResponse({"result": "ok"}) # 仍返回ok以避免重试
# 解析指令
parse_result = CommandParser.parse(callback_data.content)
if not parse_result:
# 不是有效指令,忽略
logger.debug("非有效指令,忽略")
return JSONResponse({"result": "ok"})
game_type, command = parse_result
logger.info(f"识别指令: game_type={game_type}, command={command}")
# 检查是否包含 @s 参数(私聊标志)
use_private_url = False
# 使用正则表达式匹配独立的 @s 参数(前后有空格或字符串边界)
if re.search(r'\s+@s\s+|\s+@s$|^@s\s+|^@s$', command):
use_private_url = True
# 从命令中移除 @s 参数,保持其他参数不变
command = re.sub(r'\s+@s(\s+|$)|^@s\s+', ' ', command).strip()
logger.info(f"检测到 @s 参数将优先使用个人URL发送反馈清理后的命令: {command}")
# 检查限流
rate_limiter = get_rate_limiter()
if not rate_limiter.is_allowed():
remaining = rate_limiter.get_remaining()
reset_time = int(rate_limiter.get_reset_time())
sender = get_message_sender()
await sender.send_text(
f"⚠️ 消息发送过于频繁,请等待 {reset_time} 秒后再试\n"
f"剩余配额: {remaining}"
)
return JSONResponse({"result": "ok"})
# 更新用户信息
db = get_db()
db.get_or_create_user(callback_data.creator)
# 处理指令
response_text = await handle_command(
game_type=game_type,
command=command,
chat_id=callback_data.chatid,
user_id=callback_data.creator
)
# 发送回复
if response_text:
# 如果使用了 @s 参数优先发送到个人URL
if use_private_url:
db = get_db()
user_webhook_url = db.get_user_webhook_url(callback_data.creator)
if user_webhook_url:
# 有个人URL发送到个人URL
from utils.message import send_private_message
# 判断消息类型
if game_type == 'ai_chat':
msg_type = 'markdown'
elif response_text.startswith('#'):
msg_type = 'markdown'
else:
msg_type = 'text'
success = await send_private_message(
user_id=callback_data.creator,
content=response_text,
msg_type=msg_type
)
if not success:
# 如果私聊发送失败回退到主URL
logger.warning(f"个人URL发送失败回退到主URL: user_id={callback_data.creator}")
sender = get_message_sender()
if game_type == 'ai_chat':
try:
await sender.send_markdown(response_text)
except Exception as send_md_err:
logger.error(f"发送Markdown消息失败改用文本发送: {send_md_err}")
await sender.send_text(response_text)
else:
if response_text.startswith('#'):
await sender.send_markdown(response_text)
else:
await sender.send_text(response_text)
# 成功发送到个人URL不向主URL发送
else:
# 没有个人URL回退到主URL
logger.info(f"用户 {callback_data.creator} 没有注册个人URL使用主URL发送")
sender = get_message_sender()
if game_type == 'ai_chat':
try:
await sender.send_markdown(response_text)
except Exception as send_md_err:
logger.error(f"发送Markdown消息失败改用文本发送: {send_md_err}")
await sender.send_text(response_text)
else:
if response_text.startswith('#'):
await sender.send_markdown(response_text)
else:
await sender.send_text(response_text)
else:
# 没有 @s 参数正常发送到主URL
sender = get_message_sender()
# AI 对话:统一按 Markdown 发送(按任务决策)
if game_type == 'ai_chat':
try:
await sender.send_markdown(response_text)
except Exception as send_md_err:
logger.error(f"发送Markdown消息失败改用文本发送: {send_md_err}")
await sender.send_text(response_text)
else:
# 其他模块保持原有启发式:以 # 开头视为 Markdown否则文本
if response_text.startswith('#'):
await sender.send_markdown(response_text)
else:
await sender.send_text(response_text)
return JSONResponse({"result": "ok"})
except Exception as e:
logger.error(f"处理Callback异常: {e}", exc_info=True)
# 仍然返回ok避免WPS重试
return JSONResponse({"result": "ok"})
async def handle_command(game_type: str, command: str,
chat_id: int, user_id: int) -> str:
"""处理游戏指令
Args:
game_type: 游戏类型
command: 完整指令
chat_id: 会话ID
user_id: 用户ID
Returns:
回复文本
"""
try:
# 帮助指令
if game_type == 'help':
from games.base import get_help_message
return get_help_message()
# 统计指令
if game_type == 'stats':
from games.base import get_stats_message
return get_stats_message(user_id)
# 注册系统
if game_type == 'register':
return await handle_register_command(command, chat_id, user_id)
# 骰娘游戏
if game_type == 'dice':
from games.dice import DiceGame
game = DiceGame()
return await game.handle(command, chat_id, user_id)
# 石头剪刀布
if game_type == 'rps':
from games.rps import RPSGame
game = RPSGame()
return await game.handle(command, chat_id, user_id)
# 运势占卜
if game_type == 'fortune':
from games.fortune import FortuneGame
game = FortuneGame()
return await game.handle(command, chat_id, user_id)
# 猜数字
if game_type == 'guess':
from games.guess import GuessGame
game = GuessGame()
return await game.handle(command, chat_id, user_id)
# 问答游戏
if game_type == 'quiz':
from games.quiz import QuizGame
game = QuizGame()
return await game.handle(command, chat_id, user_id)
# 成语接龙
if game_type == 'idiom':
from games.idiom import IdiomGame
game = IdiomGame()
return await game.handle(command, chat_id, user_id)
# 五子棋
if game_type == 'gomoku':
from games.gomoku import GomokuGame
game = GomokuGame()
return await game.handle(command, chat_id, user_id)
# 积分系统
if game_type == 'points':
from games.points import PointsGame
game = PointsGame()
return await game.handle(command, chat_id, user_id)
# 炼金系统
if game_type == 'alchemy':
from games.alchemy import AlchemyGame
game = AlchemyGame()
return await game.handle(command, chat_id, user_id)
# 冒险系统
if game_type == 'adventure':
from games.adventure import AdventureGame
game = AdventureGame()
return await game.handle(command, chat_id, user_id)
# 积分赠送系统
if game_type == 'gift':
from games.gift import GiftGame
game = GiftGame()
return await game.handle(command, chat_id, user_id)
# 复述功能
if game_type == 'say':
# 提取参数并原样返回
_, args = CommandParser.extract_command_args(command)
args = args.strip()
if not args:
return "用法:.say 你想让我说的话\n别名:.说 / .复述"
return args
# 私聊功能
if game_type == 'talk':
return await handle_talk_command(command, chat_id, user_id)
# AI对话系统
if game_type == 'ai_chat':
from games.ai_chat import AIChatGame
game = AIChatGame()
return await game.handle(command, chat_id, user_id)
# 赌场系统
if game_type == 'casino':
from games.casino import CasinoGame
game = CasinoGame()
return await game.handle(command, chat_id, user_id)
# 狼人杀系统
if game_type == 'werewolf':
from games.werewolf import WerewolfGame
game = WerewolfGame()
return await game.handle(command, chat_id, user_id)
# 未知游戏类型
logger.warning(f"未知游戏类型: {game_type}")
return "❌ 未知的游戏类型"
except Exception as e:
logger.error(f"处理游戏指令异常: {e}", exc_info=True)
return f"❌ 处理指令时出错: {str(e)}"
async def handle_register_command(command: str, chat_id: int, user_id: int) -> str:
"""处理注册命令
Args:
command: 完整指令 ".register name"".register url <url>"
chat_id: 会话ID
user_id: 用户ID
Returns:
注册结果消息
"""
try:
# 提取参数
_, args = CommandParser.extract_command_args(command)
args = args.strip()
# 验证参数
if not args:
return "❌ 请提供要注册的内容!\n\n正确格式:\n`.register <名称>` - 注册用户名\n`.register url <URL>` - 注册webhook URL\n\n示例:\n`.register 张三`\n`.register url https://example.com/webhook?key=xxx`"
# 检查是否为url子命令
parts = args.split(maxsplit=1)
if parts and parts[0].lower() == 'url':
# 处理URL注册
if len(parts) < 2:
return "❌ 请提供webhook URL\n\n正确格式:`.register url <URL>`\n\n示例:\n`.register url https://example.com/webhook?key=xxx`"
webhook_url = parts[1].strip()
# URL验证
if not webhook_url.startswith(('http://', 'https://')):
return "❌ URL格式无效必须以 http:// 或 https:// 开头。"
# 设置URL
db = get_db()
success = db.set_user_webhook_url(user_id, webhook_url)
if success:
return f"✅ Webhook URL注册成功\n\n**您的个人URL**{webhook_url}\n\n私聊消息将发送到此URL。"
else:
return "❌ 注册失败!请稍后重试。"
else:
# 原有的名称注册逻辑
if len(args) > 20:
return "❌ 名称过长最多支持20个字符。"
# 更新用户名称
db = get_db()
success = db.update_user_name(user_id, args)
if success:
return f"✅ 注册成功!\n\n**您的名称**{args}\n\n之后您可以使用这个名称参与各种游戏和功能。"
else:
return "❌ 注册失败!请稍后重试。"
except Exception as e:
logger.error(f"处理注册指令错误: {e}", exc_info=True)
return f"❌ 处理指令出错: {str(e)}"
async def handle_talk_command(command: str, chat_id: int, user_id: int) -> str:
"""处理私聊命令
Args:
command: 完整指令 ".talk <username> <content>"
chat_id: 会话ID
user_id: 发送者用户ID
Returns:
处理结果消息
"""
try:
# 提取参数
_, args = CommandParser.extract_command_args(command)
args = args.strip()
# 验证参数
if not args:
return "❌ 请提供用户名和消息内容!\n\n正确格式:`.talk <用户名> <消息内容>`\n\n示例:\n`.talk 张三 你好,想和你聊聊`\n`.talk 李四 这是一条私聊消息`"
# 解析username和content第一个单词是username剩余部分是content
parts = args.split(maxsplit=1)
if len(parts) < 2:
return "❌ 请提供用户名和消息内容!\n\n正确格式:`.talk <用户名> <消息内容>`\n\n示例:\n`.talk 张三 你好,想和你聊聊`"
target_username = parts[0].strip()
content = parts[1].strip()
if not target_username:
return "❌ 用户名不能为空!\n\n正确格式:`.talk <用户名> <消息内容>`"
if not content:
return "❌ 消息内容不能为空!\n\n正确格式:`.talk <用户名> <消息内容>`"
# 通过用户名查找目标用户
db = get_db()
target_user = db.get_user_by_name(target_username)
if not target_user:
return f"❌ 找不到用户名为「{target_username}」的用户!\n\n提示:目标用户需要使用 `.register <名称>` 注册用户名。"
target_user_id = target_user['user_id']
# 检查目标用户是否有注册名称(应该有,因为是通过名称找到的)
if not target_user.get('username'):
return f"❌ 用户「{target_username}」尚未注册用户名!"
# 检查目标用户是否有个人webhook URL
if not db.has_webhook_url(target_user_id):
return f"❌ 用户「{target_username}」尚未注册个人webhook URL\n\n提示:目标用户需要使用 `.register url <URL>` 注册个人URL后才能接收私聊消息。"
# 发送私聊消息
from utils.message import send_private_message
success = await send_private_message(
user_id=target_user_id,
content=content,
msg_type='text'
)
if success:
# 私聊消息发送成功不向主URL发送提示消息
return ""
else:
# 发送失败时仍然需要提示用户
return f"❌ 发送私聊消息失败,请稍后重试。"
except Exception as e:
logger.error(f"处理私聊指令错误: {e}", exc_info=True)
return f"❌ 处理指令出错: {str(e)}"

57
CoreRouters/health.py Normal file
View File

@@ -0,0 +1,57 @@
"""健康检查路由"""
import psutil
import os
from fastapi import APIRouter
from fastapi.responses import JSONResponse
from CoreModules.database import get_db
from Convention.Runtime.GlobalConfig import ProjectConfig, ConsoleFrontColor
config = ProjectConfig()
router = APIRouter()
@router.get("/health")
async def health_check():
"""健康检查"""
return JSONResponse({
"status": "healthy",
"service": config.FindItem("app_name", "Application")
})
@router.get("/stats")
async def system_stats():
"""系统资源统计(开发用)"""
try:
process = psutil.Process(os.getpid())
memory_mb = process.memory_info().rss / 1024 / 1024
# 数据库统计
db = get_db()
cursor = db.conn.cursor()
cursor.execute("SELECT COUNT(*) FROM users")
user_count = cursor.fetchone()[0]
cursor.execute("SELECT COUNT(*) FROM game_states")
active_games = cursor.fetchone()[0]
return JSONResponse({
"system": {
"memory_mb": round(memory_mb, 2),
"threads": process.num_threads(),
"cpu_percent": process.cpu_percent()
},
"database": {
"users": user_count,
"active_games": active_games
}
})
except Exception as e:
config.Log("Error", f"{ConsoleFrontColor.RED}获取系统统计失败: {e}{ConsoleFrontColor.RESET}", exc_info=True)
return JSONResponse(
status_code=500,
content={"error": str(e)}
)

111
CoreRouters/private.py Normal file
View File

@@ -0,0 +1,111 @@
"""私聊相关API路由"""
import logging
from typing import List, Dict
from fastapi import APIRouter, HTTPException
from fastapi.responses import JSONResponse
from core.database import get_db
from core.models import PrivateMessageRequest, CheckBatchRequest, CheckBatchResponse
from utils.message import send_private_message
logger = logging.getLogger(__name__)
router = APIRouter()
@router.post("/private/send")
async def send_private(request: PrivateMessageRequest):
"""发送私聊消息
请求体:
{
"user_id": 123456,
"content": "消息内容",
"msg_type": "text" // 可选,默认为"text"
}
"""
try:
# 验证msg_type
if request.msg_type not in ['text', 'markdown']:
raise HTTPException(
status_code=400,
detail="msg_type必须是'text''markdown'"
)
# 调用send_private_message
success = await send_private_message(
user_id=request.user_id,
content=request.content,
msg_type=request.msg_type
)
if not success:
# 检查用户是否有个人URL
db = get_db()
has_url = db.has_webhook_url(request.user_id)
if not has_url:
raise HTTPException(
status_code=400,
detail=f"用户 {request.user_id} 没有注册个人webhook URL"
)
else:
raise HTTPException(
status_code=500,
detail="消息发送失败,请稍后重试"
)
return JSONResponse({
"success": True,
"message": "消息发送成功"
})
except HTTPException:
raise
except Exception as e:
logger.error(f"发送私聊消息API错误: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail=f"服务器内部错误: {str(e)}"
)
@router.get("/private/check/{user_id}")
async def check_user_webhook(user_id: int):
"""检查用户是否有个人webhook URL"""
try:
db = get_db()
has_webhook_url = db.has_webhook_url(user_id)
return JSONResponse({
"user_id": user_id,
"has_webhook_url": has_webhook_url
})
except Exception as e:
logger.error(f"检查用户webhook URL错误: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail=f"服务器内部错误: {str(e)}"
)
@router.post("/private/check-batch")
async def check_users_webhook_batch(request: CheckBatchRequest):
"""批量检查用户是否有个人webhook URL
请求体:
{
"user_ids": [123456, 789012, ...]
}
"""
try:
db = get_db()
results = db.check_users_webhook_urls(request.user_ids)
return CheckBatchResponse(results=results)
except Exception as e:
logger.error(f"批量检查用户webhook URL错误: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail=f"服务器内部错误: {str(e)}"
)

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 ninemine
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.

0
Plugins/__init__.py Normal file
View File

58
README.md Normal file
View File

@@ -0,0 +1,58 @@
# README
## Clone
use recursive
```bash
git clone --recursive <repository_url>
```
or
```bash
git clone <repository_url>
cd Convention
git submodule update --init --recursive
```
## First Start
use
```bash
python app.py
```
to start and generate **Assets** Folder(generate by [ProjectConfig](Convention/Runtime/GlobalConfig.py))
## Assets
Every default argument define in **Assets/config.json**,
properties in **find** is define the arguments value where you not setting
Sometimes some property not exists in config,
just because program not running to the place where argument been referenced
## Arguments
### Commandline and Config
- **--main-webhook-url** main target of the message will be send, **needed**
- **--host** default: 0.0.0.0
- **--port** default: 8000
- **--verbose** default: false
### Only Config
- **max_concurrent_requests** default: 100
- **database_path** file on [Assets](Assets), default: db.db
- **plugin_dir** where plugins load, default: Plugins
## Plugins
First import interface and define class
```python
from CoreModules.plugin_interface import PluginInterface, DatabaseModel
class MyPlugin()
```

0
__init__.py Normal file
View File

5
__main__.py Normal file
View File

@@ -0,0 +1,5 @@
from Application.app import main
import sys
if __name__ == "__main__":
sys.exit(main())