Init
This commit is contained in:
513
.cursor/rules/core.mdc
Normal file
513
.cursor/rules/core.mdc
Normal 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
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
188
.gitignore
vendored
Normal file
188
.gitignore
vendored
Normal 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
3
.gitmodules
vendored
Normal 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
0
Application/__init__.py
Normal file
62
Application/app.py
Normal file
62
Application/app.py
Normal 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
80
Application/web.py
Normal 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
6
Assets/config.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"properties": {},
|
||||||
|
"find": {
|
||||||
|
"main_webhook_url": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
1
Convention
Submodule
1
Convention
Submodule
Submodule Convention added at 3fa432a2bb
2
CoreModules/__init__.py
Normal file
2
CoreModules/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"""核心模块"""
|
||||||
|
|
||||||
152
CoreModules/database.py
Normal file
152
CoreModules/database.py
Normal 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
23
CoreModules/flags.py
Normal 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
34
CoreModules/middleware.py
Normal 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
95
CoreModules/models.py
Normal 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的映射")
|
||||||
|
|
||||||
87
CoreModules/plugin_interface.py
Normal file
87
CoreModules/plugin_interface.py
Normal 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
2
CoreRouters/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"""路由模块"""
|
||||||
|
|
||||||
428
CoreRouters/callback.py
Normal file
428
CoreRouters/callback.py
Normal 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
57
CoreRouters/health.py
Normal 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
111
CoreRouters/private.py
Normal 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
21
LICENSE
Normal 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
0
Plugins/__init__.py
Normal file
58
README.md
Normal file
58
README.md
Normal 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
0
__init__.py
Normal file
5
__main__.py
Normal file
5
__main__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from Application.app import main
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user