获取授权码
This commit is contained in:
parent
90656ab36a
commit
d3ca3ef85f
335
feishu/API_README.md
Normal file
335
feishu/API_README.md
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
# 飞书多维表格自动AI对话 API接口文档
|
||||||
|
|
||||||
|
本文档描述了飞书多维表格自动AI对话功能的REST API接口。
|
||||||
|
|
||||||
|
## 基本信息
|
||||||
|
|
||||||
|
- 基础URL: `/api/feishu/`
|
||||||
|
- 认证方式: JWT Token (需要在请求头中添加 `Authorization: Bearer <token>`)
|
||||||
|
- 请求方式: POST/GET
|
||||||
|
- 返回格式: JSON
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 请确保使用正确的URL路径:完整路径为 `/api/feishu/process-table/` 而非 `/api/v1/feishu/process-table/`
|
||||||
|
2. 飞书授权令牌有效期较短,为了避免授权问题,请在请求中提供正确的 `app_id` 和 `app_secret` 参数:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"table_id": "your_table_id",
|
||||||
|
"view_id": "your_view_id",
|
||||||
|
"app_id": "cli_a5c97daacb9e500d",
|
||||||
|
"app_secret": "fdVeOCLXmuIHZVmSV0VbJh9wd0Kq1o5y"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. 查看运行日志中的错误信息,根据错误代码进行故障排除
|
||||||
|
|
||||||
|
## 接口列表
|
||||||
|
|
||||||
|
### 1. 处理飞书表格数据
|
||||||
|
|
||||||
|
从飞书多维表格读取数据,处理重复邮箱,可选择性地进行自动对话。
|
||||||
|
|
||||||
|
- **URL**: `/api/feishu/process-table/`
|
||||||
|
- **方法**: `POST`
|
||||||
|
- **权限**: 仅限组长角色
|
||||||
|
- **请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必需 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| table_id | string | 是 | 表格ID |
|
||||||
|
| view_id | string | 是 | 视图ID |
|
||||||
|
| app_token | string | 否 | 飞书应用TOKEN |
|
||||||
|
| access_token | string | 否 | 用户访问令牌 |
|
||||||
|
| goal_template | string | 否 | 目标内容模板 |
|
||||||
|
| auto_chat | boolean | 否 | 是否自动执行AI对话 |
|
||||||
|
| turns | integer | 否 | 自动对话轮次 |
|
||||||
|
|
||||||
|
- **请求示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"table_id": "tbl3oikG3F8YYtVA",
|
||||||
|
"view_id": "vewSOIsmxc",
|
||||||
|
"app_id": "cli_a5c97daacb9e500d",
|
||||||
|
"app_secret": "fdVeOCLXmuIHZVmSV0VbJh9wd0Kq1o5y",
|
||||||
|
"goal_template": "与达人{handle}(邮箱:{email})建立联系,最终目标是达成合作。",
|
||||||
|
"auto_chat": true,
|
||||||
|
"turns": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **成功响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"records_count": 100,
|
||||||
|
"duplicate_emails_count": 5,
|
||||||
|
"processing_results": {
|
||||||
|
"total": 5,
|
||||||
|
"success": 4,
|
||||||
|
"failure": 1,
|
||||||
|
"details": [
|
||||||
|
{
|
||||||
|
"email": "example1@gmail.com",
|
||||||
|
"handle": "example1",
|
||||||
|
"status": "success"
|
||||||
|
},
|
||||||
|
// ...其他处理结果
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"chat_results": [
|
||||||
|
{
|
||||||
|
"email": "example1@gmail.com",
|
||||||
|
"result": {
|
||||||
|
"status": "success",
|
||||||
|
"turns_completed": 5,
|
||||||
|
"goal_achieved": true,
|
||||||
|
"summary_status": "success",
|
||||||
|
"conversation_id": "feishu_ai_12345"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ...其他自动对话结果
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 执行自动对话
|
||||||
|
|
||||||
|
为指定邮箱的达人执行自动对话,支持真实邮件发送和接收。
|
||||||
|
|
||||||
|
- **URL**: `/api/feishu/auto-chat/`
|
||||||
|
- **方法**: `POST`
|
||||||
|
- **权限**: 仅限组长角色
|
||||||
|
- **请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必需 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| email | string | 是 | 达人邮箱 |
|
||||||
|
| force_send | boolean | 否 | 是否强制发送新邮件(即使没有新回复) |
|
||||||
|
| subject | string | 否 | 邮件主题(仅当force_send=true时使用) |
|
||||||
|
| content | string | 否 | 邮件内容(仅当force_send=true时使用) |
|
||||||
|
|
||||||
|
- **请求示例 (自动回复)**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "example@gmail.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **请求示例 (强制发送)**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "example@gmail.com",
|
||||||
|
"force_send": true,
|
||||||
|
"subject": "关于合作细节的讨论",
|
||||||
|
"content": "您好,\n\n针对您提出的问题,我们可以提供以下方案...\n\n期待您的回复。\n\n祝好,\n运营团队"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **成功响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"email_sent": true,
|
||||||
|
"turns_completed": 1,
|
||||||
|
"goal_achieved": false,
|
||||||
|
"summary_status": "not_needed",
|
||||||
|
"conversation_id": "feishu_ai_12345"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **待回复响应** (没有检测到新的达人回复):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"message": "没有新的达人回复,不需要回复",
|
||||||
|
"turns_completed": 0,
|
||||||
|
"goal_achieved": false,
|
||||||
|
"email_sent": false,
|
||||||
|
"conversation_id": "feishu_ai_12345"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 设置用户总目标
|
||||||
|
|
||||||
|
设置针对特定达人的对话总目标。
|
||||||
|
|
||||||
|
- **URL**: `/api/feishu/user-goal/`
|
||||||
|
- **方法**: `POST`
|
||||||
|
- **权限**: 仅限组长角色
|
||||||
|
- **请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必需 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| email | string | 是 | 达人邮箱 |
|
||||||
|
| goal | string | 是 | 目标内容 |
|
||||||
|
|
||||||
|
- **请求示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "example@gmail.com",
|
||||||
|
"goal": "与达人建立联系并了解其账号情况,评估合作潜力,处理合作需求,最终目标是达成合作并签约。"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **成功响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"action": "create",
|
||||||
|
"goal": {
|
||||||
|
"id": "12345",
|
||||||
|
"content": "与达人建立联系并了解其账号情况,评估合作潜力,处理合作需求,最终目标是达成合作并签约。",
|
||||||
|
"created_at": "2024-01-01 12:00:00",
|
||||||
|
"updated_at": "2024-01-01 12:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 获取用户总目标
|
||||||
|
|
||||||
|
获取当前用户的总目标设置。
|
||||||
|
|
||||||
|
- **URL**: `/api/feishu/user-goal/`
|
||||||
|
- **方法**: `GET`
|
||||||
|
- **权限**: 仅限组长角色
|
||||||
|
- **请求参数**: 无
|
||||||
|
|
||||||
|
- **成功响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"action": "retrieve",
|
||||||
|
"goal": {
|
||||||
|
"id": "12345",
|
||||||
|
"content": "与达人建立联系并了解其账号情况,评估合作潜力,处理合作需求,最终目标是达成合作并签约。",
|
||||||
|
"created_at": "2024-01-01 12:00:00",
|
||||||
|
"updated_at": "2024-01-01 12:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 检查目标完成状态
|
||||||
|
|
||||||
|
检查与特定达人的对话是否已达成目标。
|
||||||
|
|
||||||
|
- **URL**: `/api/feishu/check-goal/`
|
||||||
|
- **方法**: `GET`
|
||||||
|
- **权限**: 仅限组长角色
|
||||||
|
- **请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必需 | 描述 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| email | string | 是 | 达人邮箱 |
|
||||||
|
|
||||||
|
- **请求示例**:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/feishu/check-goal/?email=example@gmail.com
|
||||||
|
```
|
||||||
|
|
||||||
|
- **成功响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"email": "example@gmail.com",
|
||||||
|
"goal_achieved": true,
|
||||||
|
"last_message_time": "2024-01-01 12:00:00",
|
||||||
|
"last_message": "好的,我们已经确认了合作细节。期待与您的成功合作![目标已达成]",
|
||||||
|
"summary": "本次对话主要讨论了合作条款和细节。达成了产品发布时间、价格区间、佣金比例等共识。双方已同意合作,下一步将签署正式合同。"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 错误响应
|
||||||
|
|
||||||
|
所有接口在出错时会返回统一格式的错误信息:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "错误描述信息"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
常见HTTP状态码:
|
||||||
|
|
||||||
|
- 400: 请求参数错误
|
||||||
|
- 403: 权限不足
|
||||||
|
- 404: 资源不存在
|
||||||
|
- 500: 服务器内部错误
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 使用curl命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 获取授权令牌
|
||||||
|
TOKEN=$(curl -s -X POST http://yourserver.com/api/auth/login/ -d '{"username":"admin","password":"yourpassword"}' -H "Content-Type: application/json" | jq -r '.token')
|
||||||
|
|
||||||
|
# 处理飞书表格数据
|
||||||
|
curl -X POST http://yourserver.com/api/feishu/process-table/ \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"table_id":"tbl3oikG3F8YYtVA",
|
||||||
|
"view_id":"vewSOIsmxc",
|
||||||
|
"app_id":"cli_a5c97daacb9e500d",
|
||||||
|
"app_secret":"fdVeOCLXmuIHZVmSV0VbJh9wd0Kq1o5y",
|
||||||
|
"auto_chat":true
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 检查目标完成状态
|
||||||
|
curl -X GET "http://yourserver.com/api/feishu/check-goal/?email=example@gmail.com" \
|
||||||
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用JavaScript
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 获取授权令牌
|
||||||
|
async function getToken() {
|
||||||
|
const response = await fetch('http://yourserver.com/api/auth/login/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: 'admin',
|
||||||
|
password: 'yourpassword'
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
return data.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理飞书表格数据
|
||||||
|
async function processFeishuTable() {
|
||||||
|
const token = await getToken();
|
||||||
|
const response = await fetch('http://yourserver.com/api/feishu/process-table/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
table_id: 'tbl3oikG3F8YYtVA',
|
||||||
|
view_id: 'vewSOIsmxc',
|
||||||
|
app_id: 'cli_a5c97daacb9e500d',
|
||||||
|
app_secret: 'fdVeOCLXmuIHZVmSV0VbJh9wd0Kq1o5y',
|
||||||
|
auto_chat: true
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
processFeishuTable();
|
||||||
|
```
|
104
feishu/README_feishu_ai_chat.md
Normal file
104
feishu/README_feishu_ai_chat.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# 飞书多维表格自动AI对话工具
|
||||||
|
|
||||||
|
这是一个基于飞书多维表格数据的自动化AI对话工具,可以通过读取飞书表格数据,自动与达人进行实际的电子邮件对话。
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
1. **读取飞书多维表格数据**:从指定的飞书表格获取达人信息
|
||||||
|
2. **检测重复邮箱**:自动识别表格中的重复邮箱记录
|
||||||
|
3. **创建知识库**:为每位达人自动创建知识库,存储历史对话内容
|
||||||
|
4. **设置用户总目标**:针对每位达人设置对话目标
|
||||||
|
5. **自动化AI对话**:
|
||||||
|
- 发送初始邮件给达人
|
||||||
|
- 监听达人回复
|
||||||
|
- 生成智能回复并通过Gmail发送
|
||||||
|
- 检测目标达成状态
|
||||||
|
6. **发送提醒**:当对话目标达成时,自动通知团队领导
|
||||||
|
|
||||||
|
## 核心优势
|
||||||
|
|
||||||
|
- **真实邮件互动**:通过Gmail实际发送和接收邮件,与达人进行真实互动
|
||||||
|
- **智能回复生成**:使用DeepSeek AI生成专业、有针对性的回复
|
||||||
|
- **目标导向**:所有对话都围绕预设目标进行,提高对话效率
|
||||||
|
- **自动化处理**:减少人工干预,自动化处理大量达人沟通
|
||||||
|
- **灵活控制**:支持强制发送模式,可随时人工介入
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 处理飞书表格
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 从飞书多维表格读取数据并处理重复邮箱
|
||||||
|
python feishu_ai_chat.py process_table --table_id tbl3oikG3F8YYtVA --view_id vewSOIsmxc --app_id cli_a5c97daacb9e500d --app_secret fdVeOCLXmuIHZVmSV0VbJh9wd0Kq1o5y
|
||||||
|
|
||||||
|
# 自动处理并启动对话
|
||||||
|
python feishu_ai_chat.py process_table --table_id tbl3oikG3F8YYtVA --view_id vewSOIsmxc --auto_chat
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 执行自动对话
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查新回复并回应
|
||||||
|
python feishu_ai_chat.py auto_chat --email example@gmail.com
|
||||||
|
|
||||||
|
# 强制发送新邮件
|
||||||
|
python feishu_ai_chat.py auto_chat --email example@gmail.com --force_send --content "自定义邮件内容"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 设置用户总目标
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python feishu_ai_chat.py set_goal --email example@gmail.com --goal "与达人建立联系并了解其账号情况,评估合作潜力,处理合作需求,最终目标是达成合作并签约。"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 检查目标完成状态
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python feishu_ai_chat.py check_goal --email example@gmail.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 使用方法
|
||||||
|
|
||||||
|
系统提供RESTful API接口,详见 [API_README.md](API_README.md)。
|
||||||
|
|
||||||
|
## 实际对话流程
|
||||||
|
|
||||||
|
1. **初始化阶段**:
|
||||||
|
- 系统读取飞书表格数据
|
||||||
|
- 识别达人邮箱并创建知识库
|
||||||
|
- 设置对话总目标
|
||||||
|
|
||||||
|
2. **首次对话**:
|
||||||
|
- 系统发送第一封邮件给达人
|
||||||
|
- 邮件内容包含简短介绍和合作意向
|
||||||
|
|
||||||
|
3. **等待回复**:
|
||||||
|
- 系统监听达人回复
|
||||||
|
- 当收到回复后,自动同步到知识库
|
||||||
|
|
||||||
|
4. **自动回复**:
|
||||||
|
- 分析达人回复内容
|
||||||
|
- 生成智能回复
|
||||||
|
- 通过Gmail发送回复邮件
|
||||||
|
|
||||||
|
5. **目标检测**:
|
||||||
|
- 持续检测对话是否达成预设目标
|
||||||
|
- 当达成目标时发送通知
|
||||||
|
|
||||||
|
6. **对话总结**:
|
||||||
|
- 生成对话总结,包括关键点和后续行动项
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 使用前请确保已配置Gmail集成和正确的API密钥
|
||||||
|
2. 达人的邮件回复可能需要一定时间,建议设置定时任务检查新回复
|
||||||
|
3. 在自动对话过程中,可以随时通过强制发送模式介入对话
|
||||||
|
4. 对话总目标应该明确具体,这将影响AI生成回复的质量
|
||||||
|
5. 执行自动对话前,请确保飞书表格中的达人邮箱准确
|
||||||
|
|
||||||
|
## 技术说明
|
||||||
|
|
||||||
|
- 使用飞书开放平台API读取多维表格数据
|
||||||
|
- 通过Gmail API实现邮件发送和接收
|
||||||
|
- 利用DeepSeek API生成智能回复和对话总结
|
||||||
|
- Django框架提供Web API接口
|
1189
feishu/feishu_ai_chat.py
Normal file
1189
feishu/feishu_ai_chat.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,10 +0,0 @@
|
|||||||
本期节目内容简介
|
|
||||||
在参加各类比赛时,肯定会遇到竞争对手。英语单词 rival、opponent、competitor 和 contestant 的含义相似,都可以用来指“与他人之间存在竞争关系的人或团队”。在本集《你问我答》节目中,我们将通过和体育比赛有关的实例来为大家阐释这四个近义词之间的区别和用法。
|
|
||||||
|
|
||||||
欢迎你加入并和我们一起讨论英语学习的方方面面。请通过微博“BBC英语教学”或邮件与我们取得联系。我们的邮箱地址是 questions.chinaelt@bbc.co.uk。
|
|
||||||
|
|
||||||
文字稿
|
|
||||||
(关于台词的备注: 请注意这不是广播节目的逐字稿件。本文稿可能没有体现录制、编辑过程中对节目做出的改变。)
|
|
||||||
|
|
||||||
Feifei
|
|
||||||
大家好,欢迎收听 BBC 英语教学的《你问我答》节目,我是冯菲菲。每集节目中,我们会回答大家在英语学习时遇到的一个问题。本集的问题来自 Adela。我们来听一下她的问题。
|
|
@ -1 +0,0 @@
|
|||||||
{"installed":{"client_id":"266164728215-v84lngbp3vgr4ulql01sqkg5vaigf4a5.apps.googleusercontent.com","project_id":"knowledge-454905","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}}
|
|
@ -34,7 +34,7 @@
|
|||||||
"filename": "test2.txt",
|
"filename": "test2.txt",
|
||||||
"mimeType": "text/plain",
|
"mimeType": "text/plain",
|
||||||
"size": 996,
|
"size": 996,
|
||||||
"attachmentId": "ANGjdJ8O1uGve4uPqFSC2C4sMeC5jXJ3DGilhB1By705ZLGbOF30m6uITRtJHWsnB7yREKVslhYRdu4GKKvrrkWw-63ogqaZPGgi0WuSoB0OxIWXwbQSjaayUOPvc3P8y1g9A2mMAm5k0DkjH_LP0QMmulhXMg8ZgUcm4CZR7-HTBYztZSfWOoUeYkNugdzV5_2ax3GT34P5uaGHh3i4Ge6y-XDN-TDq3i1w9u_eTjZowoyVzjTj48uaQmzmFU36TQxg8ncovHk0sNZEygzCE1gbHbS1uM9N1tUHOOa6FWEpajHgz2aQwLh65SsMRqKe-LognFES-J_082IHddWs"
|
"attachmentId": "ANGjdJ-lpRprHQbSJhwdSR5-iiBiHvZfV_H59db3znGETs9vTjBU-egQng8f0Js7wX79HGTt7lKDhhlq95xbmfOUf81mCR9vLMu9QhNS1P4ph8oVc5aDSHUBQk6rj8Xqj8EmD-gL--bB2CjXNLuNZ-khuKcyABUUH7FKorP4UFebWKh9Phfy1Js_QhNeSy8wfcQuCiOQ78pYimuwA-dtbuDusRUmAX2VyM6SyJD1eR1hzeoUQoICZ7AJoxErf6whPdLN8qv1O_ZSeXjhbSkdeSwlScuSgXClHWUw8a8lDx_gWka7WfF2OtYdB4wItaYHFQcaRlK4qqES2IM-azOV"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -117,5 +117,109 @@
|
|||||||
"date": "2025-04-10 16:12:20",
|
"date": "2025-04-10 16:12:20",
|
||||||
"body": "你好\r\n",
|
"body": "你好\r\n",
|
||||||
"attachments": []
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961f18398a4567b",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-10 17:46:31",
|
||||||
|
"body": "实时\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961f19379ea6165",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-10 17:47:35",
|
||||||
|
"body": "111111\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961f1cf6e12b80c",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-10 17:51:41",
|
||||||
|
"body": "再来一次\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961f2673f0621b8",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-10 18:02:01",
|
||||||
|
"body": "坎坎坷坷\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961f2cd0d18aff6",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-10 18:08:31",
|
||||||
|
"body": "再来一次\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961f2e8aaac674e",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-10 18:10:54",
|
||||||
|
"body": "3234\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961f36448599caf",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-10 18:19:19",
|
||||||
|
"body": "4234\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "19622d0344d49679",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crushwds@gmail.com",
|
||||||
|
"date": "2025-04-10 23:06:32",
|
||||||
|
"body": "0101 我是02",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "19622bfa19835a0a",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-11 10:48:14",
|
||||||
|
"body": "4.11\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "19622c0b5d9ea09f",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-11 10:49:24",
|
||||||
|
"body": "4.11 2\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "19622c17299a5432",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-11 10:50:13",
|
||||||
|
"body": "休息休息\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "19622c6f41eb9e5f",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-11 10:56:14",
|
||||||
|
"body": "反反复复\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "196236321d6dc082",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-11 13:46:49",
|
||||||
|
"body": "0202我是01\r\n",
|
||||||
|
"attachments": []
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -1,5 +1,5 @@
|
|||||||
==================================================
|
==================================================
|
||||||
记录时间: 2025-04-10 16:52:49
|
记录时间: 2025-04-17 15:43:23
|
||||||
==================================================
|
==================================================
|
||||||
|
|
||||||
时间: 2025-03-27 12:04:29
|
时间: 2025-03-27 12:04:29
|
||||||
@ -105,3 +105,93 @@ yes
|
|||||||
你好
|
你好
|
||||||
|
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 17:46:31
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
实时
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 17:47:35
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
111111
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 17:51:41
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
再来一次
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 18:02:01
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
坎坎坷坷
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 18:08:31
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
再来一次
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 18:10:54
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
3234
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 18:19:19
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
4234
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 23:06:32
|
||||||
|
发件人: crushwds@gmail.com
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
0101 我是02
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-11 10:48:14
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
4.11
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-11 10:49:24
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
4.11 2
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-11 10:50:13
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
休息休息
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-11 10:56:14
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
反反复复
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-11 13:46:49
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
0202我是01
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
@ -1,146 +0,0 @@
|
|||||||
'''
|
|
||||||
Reading GMAIL using Python
|
|
||||||
- Abhishek Chhibber
|
|
||||||
'''
|
|
||||||
|
|
||||||
'''
|
|
||||||
This script does the following:
|
|
||||||
- Go to Gmal inbox
|
|
||||||
- Find and read all the unread messages
|
|
||||||
- Extract details (Date, Sender, Subject, Snippet, Body) and export them to a .csv file / DB
|
|
||||||
- Mark the messages as Read - so that they are not read again
|
|
||||||
'''
|
|
||||||
|
|
||||||
'''
|
|
||||||
Before running this script, the user should get the authentication by following
|
|
||||||
the link: https://developers.google.com/gmail/api/quickstart/python
|
|
||||||
Also, credentials.json should be saved in the same directory as this file
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Importing required libraries
|
|
||||||
from apiclient import discovery
|
|
||||||
from apiclient import errors
|
|
||||||
from httplib2 import Http
|
|
||||||
from oauth2client import file, client, tools
|
|
||||||
import base64
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import dateutil.parser as parser
|
|
||||||
from datetime import datetime
|
|
||||||
import datetime
|
|
||||||
import csv
|
|
||||||
|
|
||||||
|
|
||||||
# Creating a storage.JSON file with authentication details
|
|
||||||
SCOPES = 'https://www.googleapis.com/auth/gmail.modify' # we are using modify and not readonly, as we will be marking the messages Read
|
|
||||||
store = file.Storage('storage.json')
|
|
||||||
creds = store.get()
|
|
||||||
if not creds or creds.invalid:
|
|
||||||
flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
|
|
||||||
creds = tools.run_flow(flow, store)
|
|
||||||
GMAIL = discovery.build('gmail', 'v1', http=creds.authorize(Http()))
|
|
||||||
|
|
||||||
user_id = 'me'
|
|
||||||
label_id_one = 'INBOX'
|
|
||||||
label_id_two = 'UNREAD'
|
|
||||||
|
|
||||||
# Getting all the unread messages from Inbox
|
|
||||||
# labelIds can be changed accordingly
|
|
||||||
unread_msgs = GMAIL.users().messages().list(userId='me',labelIds=[label_id_one, label_id_two]).execute()
|
|
||||||
|
|
||||||
# We get a dictonary. Now reading values for the key 'messages'
|
|
||||||
mssg_list = unread_msgs['messages']
|
|
||||||
|
|
||||||
print ("Total unread messages in inbox: ", str(len(mssg_list)))
|
|
||||||
|
|
||||||
final_list = [ ]
|
|
||||||
|
|
||||||
|
|
||||||
for mssg in mssg_list:
|
|
||||||
temp_dict = { }
|
|
||||||
m_id = mssg['id'] # get id of individual message
|
|
||||||
message = GMAIL.users().messages().get(userId=user_id, id=m_id).execute() # fetch the message using API
|
|
||||||
payld = message['payload'] # get payload of the message
|
|
||||||
headr = payld['headers'] # get header of the payload
|
|
||||||
|
|
||||||
|
|
||||||
for one in headr: # getting the Subject
|
|
||||||
if one['name'] == 'Subject':
|
|
||||||
msg_subject = one['value']
|
|
||||||
temp_dict['Subject'] = msg_subject
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
for two in headr: # getting the date
|
|
||||||
if two['name'] == 'Date':
|
|
||||||
msg_date = two['value']
|
|
||||||
date_parse = (parser.parse(msg_date))
|
|
||||||
m_date = (date_parse.date())
|
|
||||||
temp_dict['Date'] = str(m_date)
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for three in headr: # getting the Sender
|
|
||||||
if three['name'] == 'From':
|
|
||||||
msg_from = three['value']
|
|
||||||
temp_dict['Sender'] = msg_from
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
temp_dict['Snippet'] = message['snippet'] # fetching message snippet
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
# Fetching message body
|
|
||||||
mssg_parts = payld['parts'] # fetching the message parts
|
|
||||||
part_one = mssg_parts[0] # fetching first element of the part
|
|
||||||
part_body = part_one['body'] # fetching body of the message
|
|
||||||
part_data = part_body['data'] # fetching data from the body
|
|
||||||
clean_one = part_data.replace("-","+") # decoding from Base64 to UTF-8
|
|
||||||
clean_one = clean_one.replace("_","/") # decoding from Base64 to UTF-8
|
|
||||||
clean_two = base64.b64decode (bytes(clean_one, 'UTF-8')) # decoding from Base64 to UTF-8
|
|
||||||
soup = BeautifulSoup(clean_two , "lxml" )
|
|
||||||
mssg_body = soup.body()
|
|
||||||
# mssg_body is a readible form of message body
|
|
||||||
# depending on the end user's requirements, it can be further cleaned
|
|
||||||
# using regex, beautiful soup, or any other method
|
|
||||||
temp_dict['Message_body'] = mssg_body
|
|
||||||
|
|
||||||
except :
|
|
||||||
pass
|
|
||||||
|
|
||||||
print (temp_dict)
|
|
||||||
final_list.append(temp_dict) # This will create a dictonary item in the final list
|
|
||||||
|
|
||||||
# This will mark the messagea as read
|
|
||||||
GMAIL.users().messages().modify(userId=user_id, id=m_id,body={ 'removeLabelIds': ['UNREAD']}).execute()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
print ("Total messaged retrived: ", str(len(final_list)))
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
The final_list will have dictionary in the following format:
|
|
||||||
|
|
||||||
{ 'Sender': '"email.com" <name@email.com>',
|
|
||||||
'Subject': 'Lorem ipsum dolor sit ametLorem ipsum dolor sit amet',
|
|
||||||
'Date': 'yyyy-mm-dd',
|
|
||||||
'Snippet': 'Lorem ipsum dolor sit amet'
|
|
||||||
'Message_body': 'Lorem ipsum dolor sit amet'}
|
|
||||||
|
|
||||||
|
|
||||||
The dictionary can be exported as a .csv or into a databse
|
|
||||||
'''
|
|
||||||
|
|
||||||
#exporting the values as .csv
|
|
||||||
with open('CSV_NAME.csv', 'w', encoding='utf-8', newline = '') as csvfile:
|
|
||||||
fieldnames = ['Sender','Subject','Date','Snippet','Message_body']
|
|
||||||
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter = ',')
|
|
||||||
writer.writeheader()
|
|
||||||
for val in final_list:
|
|
||||||
writer.writerow(val)
|
|
@ -1,3 +0,0 @@
|
|||||||
google-api-python-client==1.7.8
|
|
||||||
google-auth-httplib2==0.0.3
|
|
||||||
google-auth-oauthlib==0.4.0
|
|
@ -1 +1 @@
|
|||||||
{"access_token": "ya29.a0AZYkNZga-tjDnp1lsXRohu1Tji-eVV88RaLnPjxr3HpYuBDW_6boys1aqnRnete1pT-E7ygZ5drpb0Hhbt9o15ryqbfeaKqS4HTDG_iIVvFn3npNNLSqIdvsf98burhBOnR-Nf6ty7xCsPLyFaO15bG2LybRgGL1mubVNMXSaCgYKAdQSARISFQHGX2MicVi2eoShd196_WeptFDUZg0175", "client_id": "266164728215-v84lngbp3vgr4ulql01sqkg5vaigf4a5.apps.googleusercontent.com", "client_secret": "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", "refresh_token": "1//0eAXpVapw8WjjCgYIARAAGA4SNwF-L9Irm0iHkQzqzM7Hn39nctE-DOWKTsm89Ge3nG0bfdfqloRvLMiN4YWHEKcDpLdPIuZel0Q", "token_expiry": "2025-04-10T09:51:34Z", "token_uri": "https://oauth2.googleapis.com/token", "user_agent": null, "revoke_uri": "https://oauth2.googleapis.com/revoke", "id_token": null, "id_token_jwt": null, "token_response": {"access_token": "ya29.a0AZYkNZga-tjDnp1lsXRohu1Tji-eVV88RaLnPjxr3HpYuBDW_6boys1aqnRnete1pT-E7ygZ5drpb0Hhbt9o15ryqbfeaKqS4HTDG_iIVvFn3npNNLSqIdvsf98burhBOnR-Nf6ty7xCsPLyFaO15bG2LybRgGL1mubVNMXSaCgYKAdQSARISFQHGX2MicVi2eoShd196_WeptFDUZg0175", "expires_in": 3599, "refresh_token": "1//0eAXpVapw8WjjCgYIARAAGA4SNwF-L9Irm0iHkQzqzM7Hn39nctE-DOWKTsm89Ge3nG0bfdfqloRvLMiN4YWHEKcDpLdPIuZel0Q", "scope": "https://mail.google.com/", "token_type": "Bearer", "refresh_token_expires_in": 604799}, "scopes": ["https://mail.google.com/"], "token_info_uri": "https://oauth2.googleapis.com/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"}
|
{"access_token": "ya29.a0AZYkNZj4ckWMF2gR1vQBVIrPGpcwFCOP_Ent8_epgJhS1d13ta5pdFpnMmrE3CnWmGiPL2cF83f7e4TKR7yVmW_sPaWmVGEnBVz_fIS2EbZrq27m9b6hBEVCNTHbTA_wMueySVfSxB-dewQdPIxMIG-cySbjmcOMM7xHlj-HaCgYKAd8SARYSFQHGX2MirZiNWqjAqxHPPwmxuaGUSA0175", "client_id": "266164728215-v84lngbp3vgr4ulql01sqkg5vaigf4a5.apps.googleusercontent.com", "client_secret": "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", "refresh_token": "1//0eoW48WnH7cnKCgYIARAAGA4SNwF-L9Irq1-3Mr1DScClOG7KrCl5rBxxgTk3pnUATsv1nQo9x7wZczlih6oFkmaJRkCBwKQdAhM", "token_expiry": "2025-04-17T08:42:47Z", "token_uri": "https://oauth2.googleapis.com/token", "user_agent": null, "revoke_uri": "https://oauth2.googleapis.com/revoke", "id_token": null, "id_token_jwt": null, "token_response": {"access_token": "ya29.a0AZYkNZj4ckWMF2gR1vQBVIrPGpcwFCOP_Ent8_epgJhS1d13ta5pdFpnMmrE3CnWmGiPL2cF83f7e4TKR7yVmW_sPaWmVGEnBVz_fIS2EbZrq27m9b6hBEVCNTHbTA_wMueySVfSxB-dewQdPIxMIG-cySbjmcOMM7xHlj-HaCgYKAd8SARYSFQHGX2MirZiNWqjAqxHPPwmxuaGUSA0175", "expires_in": 3599, "refresh_token": "1//0eoW48WnH7cnKCgYIARAAGA4SNwF-L9Irq1-3Mr1DScClOG7KrCl5rBxxgTk3pnUATsv1nQo9x7wZczlih6oFkmaJRkCBwKQdAhM", "scope": "https://mail.google.com/", "token_type": "Bearer"}, "scopes": ["https://mail.google.com/"], "token_info_uri": "https://oauth2.googleapis.com/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"}
|
@ -1 +0,0 @@
|
|||||||
你好
|
|
@ -0,0 +1 @@
|
|||||||
|
{"access_token": "ya29.a0AZYkNZiIIeDZSsXkxaKmK66_GECsMkKxlXHMs5JSGuNUg0HM_-WEy0lM9iW4s_0m9-KOpvRpPfOytHgUjVmu-iS_CeAb7YjIkInauRkm1kWoi3YO-MpyE4k2E-VYN_EToxPQ7kOFgzmuzmgD7YzzrhkQUbYfwRR7avB23QIYaCgYKAX0SARYSFQHGX2Mi4OfTzGLIiHgiikzkvqlHQw0175", "client_id": "266164728215-v84lngbp3vgr4ulql01sqkg5vaigf4a5.apps.googleusercontent.com", "client_secret": "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", "refresh_token": "1//0eNMbEHj3Tg2eCgYIARAAGA4SNwF-L9IrPoT7E3X2KyYx_ImbGl_en_kWItMvImasMtmWaX5QY1FFwWjv9qlP0aUOCmDatlQIn9M", "token_expiry": "2025-04-17T09:12:42Z", "token_uri": "https://oauth2.googleapis.com/token", "user_agent": null, "revoke_uri": "https://oauth2.googleapis.com/revoke", "id_token": null, "id_token_jwt": null, "token_response": {"access_token": "ya29.a0AZYkNZiIIeDZSsXkxaKmK66_GECsMkKxlXHMs5JSGuNUg0HM_-WEy0lM9iW4s_0m9-KOpvRpPfOytHgUjVmu-iS_CeAb7YjIkInauRkm1kWoi3YO-MpyE4k2E-VYN_EToxPQ7kOFgzmuzmgD7YzzrhkQUbYfwRR7avB23QIYaCgYKAX0SARYSFQHGX2Mi4OfTzGLIiHgiikzkvqlHQw0175", "expires_in": 3599, "refresh_token": "1//0eNMbEHj3Tg2eCgYIARAAGA4SNwF-L9IrPoT7E3X2KyYx_ImbGl_en_kWItMvImasMtmWaX5QY1FFwWjv9qlP0aUOCmDatlQIn9M", "scope": "https://mail.google.com/", "token_type": "Bearer"}, "scopes": ["https://mail.google.com/"], "token_info_uri": "https://oauth2.googleapis.com/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"}
|
@ -0,0 +1 @@
|
|||||||
|
{"access_token": "ya29.a0AZYkNZjT8BbLKF3B3a5jGq2Qb6uYUBT3honlyAqKAT9PO4QBtvffLXR6Dy7Uhfn0ECZAyo-APdhM29oEYH6mGyIilb8zwRhkagyz7g0SfaJ9TBR6OewrkAhLqxKomAFG3Zy1TuGKAJ2Ql_NpdYTnMMvBXlvSbJxxUqCvc6aPaCgYKAXsSARYSFQHGX2MiFkN-OJM7jUydE6eNoUjSaA0175", "client_id": "266164728215-v84lngbp3vgr4ulql01sqkg5vaigf4a5.apps.googleusercontent.com", "client_secret": "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", "refresh_token": "1//0eEj3chLf7z1fCgYIARAAGA4SNwF-L9IrdLb5aMU_EPeZV23OAzUmbg73hwpBGGzr0sT6QCtnLJPsBuInx40cZX9C-WZeBbUaa8s", "token_expiry": "2025-04-17T09:00:48Z", "token_uri": "https://oauth2.googleapis.com/token", "user_agent": null, "revoke_uri": "https://oauth2.googleapis.com/revoke", "id_token": null, "id_token_jwt": null, "token_response": {"access_token": "ya29.a0AZYkNZjT8BbLKF3B3a5jGq2Qb6uYUBT3honlyAqKAT9PO4QBtvffLXR6Dy7Uhfn0ECZAyo-APdhM29oEYH6mGyIilb8zwRhkagyz7g0SfaJ9TBR6OewrkAhLqxKomAFG3Zy1TuGKAJ2Ql_NpdYTnMMvBXlvSbJxxUqCvc6aPaCgYKAXsSARYSFQHGX2MiFkN-OJM7jUydE6eNoUjSaA0175", "expires_in": 3599, "refresh_token": "1//0eEj3chLf7z1fCgYIARAAGA4SNwF-L9IrdLb5aMU_EPeZV23OAzUmbg73hwpBGGzr0sT6QCtnLJPsBuInx40cZX9C-WZeBbUaa8s", "scope": "https://mail.google.com/", "token_type": "Bearer"}, "scopes": ["https://mail.google.com/"], "token_info_uri": "https://oauth2.googleapis.com/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"}
|
@ -18,7 +18,8 @@ from django.contrib import admin
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from user_management.views import gmail_webhook, feishu_sync_api, feishu_to_kb_api, check_creator_kb_api # 直接导入视图函数
|
from user_management.views import gmail_webhook, feishu_sync_api, feishu_to_kb_api, check_creator_kb_api
|
||||||
|
from user_management.feishu_chat_views import process_feishu_table, run_auto_chat, feishu_user_goal, check_goal_status
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# 管理后台
|
# 管理后台
|
||||||
@ -36,6 +37,12 @@ urlpatterns = [
|
|||||||
path('api/feishu/to_kb', feishu_to_kb_api, name='feishu_to_kb_api'),
|
path('api/feishu/to_kb', feishu_to_kb_api, name='feishu_to_kb_api'),
|
||||||
path('api/feishu/check_kb', check_creator_kb_api, name='check_creator_kb_api'),
|
path('api/feishu/check_kb', check_creator_kb_api, name='check_creator_kb_api'),
|
||||||
|
|
||||||
|
# 飞书AI聊天相关API - 直接注册
|
||||||
|
path('api/feishu/process-table/', process_feishu_table, name='direct_process_feishu_table'),
|
||||||
|
path('api/feishu/auto-chat/', run_auto_chat, name='direct_run_auto_chat'),
|
||||||
|
path('api/feishu/user-goal/', feishu_user_goal, name='direct_feishu_user_goal'),
|
||||||
|
path('api/feishu/check-goal/', check_goal_status, name='direct_check_goal_status'),
|
||||||
|
|
||||||
# 媒体文件服务
|
# 媒体文件服务
|
||||||
*static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),
|
*static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ from channels.db import database_sync_to_async
|
|||||||
from channels.exceptions import StopConsumer
|
from channels.exceptions import StopConsumer
|
||||||
import logging
|
import logging
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -11,19 +12,20 @@ class NotificationConsumer(AsyncWebsocketConsumer):
|
|||||||
async def connect(self):
|
async def connect(self):
|
||||||
"""建立WebSocket连接"""
|
"""建立WebSocket连接"""
|
||||||
try:
|
try:
|
||||||
# 获取token
|
# 从URL参数中获取token
|
||||||
headers = dict(self.scope['headers'])
|
query_string = self.scope.get('query_string', b'').decode()
|
||||||
auth_header = headers.get(b'authorization', b'').decode()
|
query_params = parse_qs(query_string)
|
||||||
|
token_key = query_params.get('token', [''])[0]
|
||||||
|
|
||||||
if not auth_header.startswith('Token '):
|
if not token_key:
|
||||||
|
logger.warning("WebSocket连接尝试,但没有提供token")
|
||||||
await self.close()
|
await self.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
token_key = auth_header.split(' ')[1]
|
|
||||||
|
|
||||||
# 验证token
|
# 验证token
|
||||||
self.user = await self.get_user_from_token(token_key)
|
self.user = await self.get_user_from_token(token_key)
|
||||||
if not self.user:
|
if not self.user:
|
||||||
|
logger.warning(f"WebSocket连接尝试,但token无效: {token_key}")
|
||||||
await self.close()
|
await self.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -34,8 +36,10 @@ class NotificationConsumer(AsyncWebsocketConsumer):
|
|||||||
self.channel_name
|
self.channel_name
|
||||||
)
|
)
|
||||||
await self.accept()
|
await self.accept()
|
||||||
|
logger.info(f"用户 {self.user.username} WebSocket连接成功")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"WebSocket连接错误: {str(e)}")
|
||||||
await self.close()
|
await self.close()
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
|
434
user_management/feishu_chat_views.py
Normal file
434
user_management/feishu_chat_views.py
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
import logging
|
||||||
|
import traceback
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from user_management.models import (
|
||||||
|
GmailTalentMapping, ChatHistory, ConversationSummary
|
||||||
|
)
|
||||||
|
from user_management.gmail_integration import GmailIntegration
|
||||||
|
|
||||||
|
from feishu.feishu_ai_chat import (
|
||||||
|
fetch_table_records, find_duplicate_email_creators,
|
||||||
|
process_duplicate_emails, auto_chat_session,
|
||||||
|
check_goal_achieved
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
def process_feishu_table(request):
|
||||||
|
"""
|
||||||
|
从飞书多维表格读取数据,处理重复邮箱
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
table_id: 表格ID
|
||||||
|
view_id: 视图ID
|
||||||
|
app_token: 飞书应用TOKEN (可选)
|
||||||
|
access_token: 用户访问令牌 (可选)
|
||||||
|
app_id: 应用ID (可选)
|
||||||
|
app_secret: 应用密钥 (可选)
|
||||||
|
goal_template: 目标内容模板 (可选)
|
||||||
|
auto_chat: 是否自动执行AI对话 (可选)
|
||||||
|
turns: 自动对话轮次 (可选)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 检查用户权限 - 只允许组长使用
|
||||||
|
if request.user.role != 'leader':
|
||||||
|
return Response(
|
||||||
|
{"error": "只有组长角色的用户可以使用此功能"},
|
||||||
|
status=status.HTTP_403_FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取参数
|
||||||
|
table_id = request.data.get("table_id", "tbl3oikG3F8YYtVA") # 默认表格ID
|
||||||
|
view_id = request.data.get("view_id", "vewSOIsmxc") # 默认视图ID
|
||||||
|
app_token = request.data.get("app_token", "XYE6bMQUOaZ5y5svj4vcWohGnmg")
|
||||||
|
access_token = request.data.get("access_token", "u-fK0HvbXVte.G2xzYs5oxV6k1nHu1glvFgG00l0Ma24VD")
|
||||||
|
app_id = request.data.get("app_id", "cli_a5c97daacb9e500d")
|
||||||
|
app_secret = request.data.get("app_secret", "fdVeOCLXmuIHZVmSV0VbJh9wd0Kq1o5y")
|
||||||
|
goal_template = request.data.get(
|
||||||
|
"goal_template",
|
||||||
|
"与达人{handle}(邮箱:{email})建立联系并了解其账号情况,评估合作潜力,处理合作需求,最终目标是达成合作并签约。"
|
||||||
|
)
|
||||||
|
auto_chat = request.data.get("auto_chat", False)
|
||||||
|
turns = request.data.get("turns", 5)
|
||||||
|
|
||||||
|
logger.info(f"处理飞书表格数据: table_id={table_id}, view_id={view_id}, app_id={app_id}")
|
||||||
|
|
||||||
|
# 从飞书表格获取记录
|
||||||
|
records = fetch_table_records(
|
||||||
|
app_token,
|
||||||
|
table_id,
|
||||||
|
view_id,
|
||||||
|
access_token,
|
||||||
|
app_id,
|
||||||
|
app_secret
|
||||||
|
)
|
||||||
|
|
||||||
|
if not records:
|
||||||
|
logger.warning("未获取到任何记录,可能是表格ID或视图ID不正确,或无权限访问")
|
||||||
|
|
||||||
|
# 尝试使用SDK中的search方法直接获取
|
||||||
|
try:
|
||||||
|
import lark_oapi as lark
|
||||||
|
from lark_oapi.api.bitable.v1 import (
|
||||||
|
SearchAppTableRecordRequest,
|
||||||
|
SearchAppTableRecordRequestBody
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建client
|
||||||
|
client = lark.Client.builder() \
|
||||||
|
.enable_set_token(True) \
|
||||||
|
.log_level(lark.LogLevel.DEBUG) \
|
||||||
|
.build()
|
||||||
|
|
||||||
|
# 构造请求对象
|
||||||
|
request = SearchAppTableRecordRequest.builder() \
|
||||||
|
.app_token(app_token) \
|
||||||
|
.table_id(table_id) \
|
||||||
|
.page_size(20) \
|
||||||
|
.request_body(SearchAppTableRecordRequestBody.builder().build()) \
|
||||||
|
.build()
|
||||||
|
|
||||||
|
# 发起请求
|
||||||
|
option = lark.RequestOption.builder().user_access_token(access_token).build()
|
||||||
|
response = client.bitable.v1.app_table_record.search(request, option)
|
||||||
|
|
||||||
|
if not response.success():
|
||||||
|
logger.error(f"直接搜索请求失败: {response.code}, {response.msg}")
|
||||||
|
return Response(
|
||||||
|
{"message": f"未获取到任何记录,错误: {response.msg}"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取记录
|
||||||
|
records = response.data.items
|
||||||
|
if not records:
|
||||||
|
return Response(
|
||||||
|
{"message": "未获取到任何记录"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"尝试直接搜索时出错: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return Response(
|
||||||
|
{"message": f"未获取到任何记录,错误: {str(e)}"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
# 查找重复邮箱的创作者
|
||||||
|
duplicate_emails = find_duplicate_email_creators(records)
|
||||||
|
|
||||||
|
if not duplicate_emails:
|
||||||
|
return Response(
|
||||||
|
{"message": "未发现重复邮箱"},
|
||||||
|
status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
|
||||||
|
# 处理重复邮箱记录
|
||||||
|
results = process_duplicate_emails(duplicate_emails, goal_template)
|
||||||
|
|
||||||
|
# 如果需要自动对话
|
||||||
|
chat_results = []
|
||||||
|
if auto_chat and results['success'] > 0:
|
||||||
|
# 为每个成功创建的记录执行自动对话
|
||||||
|
for detail in results['details']:
|
||||||
|
if detail['status'] == 'success':
|
||||||
|
email = detail['email']
|
||||||
|
chat_result = auto_chat_session(request.user, email, max_turns=turns)
|
||||||
|
chat_results.append({
|
||||||
|
'email': email,
|
||||||
|
'result': chat_result
|
||||||
|
})
|
||||||
|
|
||||||
|
# 返回处理结果
|
||||||
|
return Response({
|
||||||
|
'status': 'success',
|
||||||
|
'records_count': len(records),
|
||||||
|
'duplicate_emails_count': len(duplicate_emails),
|
||||||
|
'processing_results': results,
|
||||||
|
'chat_results': chat_results
|
||||||
|
}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理飞书表格时出错: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return Response(
|
||||||
|
{"error": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
def run_auto_chat(request):
|
||||||
|
"""
|
||||||
|
为指定邮箱执行自动对话
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
email: 达人邮箱
|
||||||
|
force_send: 是否强制发送新邮件(即使没有新回复)(可选)
|
||||||
|
subject: 邮件主题(可选,仅当force_send=true时使用)
|
||||||
|
content: 邮件内容(可选,仅当force_send=true时使用)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 检查用户权限 - 只允许组长使用
|
||||||
|
if request.user.role != 'leader':
|
||||||
|
return Response(
|
||||||
|
{"error": "只有组长角色的用户可以使用此功能"},
|
||||||
|
status=status.HTTP_403_FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取参数
|
||||||
|
email = request.data.get("email")
|
||||||
|
force_send = request.data.get("force_send", False)
|
||||||
|
subject = request.data.get("subject")
|
||||||
|
content = request.data.get("content")
|
||||||
|
|
||||||
|
# 验证必要参数
|
||||||
|
if not email:
|
||||||
|
return Response(
|
||||||
|
{"error": "缺少参数email"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# 如果强制发送且没有提供内容
|
||||||
|
if force_send and not content:
|
||||||
|
return Response(
|
||||||
|
{"error": "当force_send=true时,必须提供content参数"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# 首先尝试同步最新邮件
|
||||||
|
try:
|
||||||
|
# 创建Gmail集成实例
|
||||||
|
gmail_integration = GmailIntegration(request.user)
|
||||||
|
|
||||||
|
# 同步最新邮件
|
||||||
|
logger.info(f"正在同步与 {email} 的最新邮件...")
|
||||||
|
sync_result = gmail_integration.sync_talent_emails(email)
|
||||||
|
|
||||||
|
if sync_result.get('status') == 'success':
|
||||||
|
logger.info(f"成功同步邮件: {sync_result.get('message', 'No message')}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"同步邮件警告: {sync_result.get('message', 'Unknown warning')}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"同步邮件出错: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
# 仅记录错误,不中断流程
|
||||||
|
|
||||||
|
# 如果是强制发送模式
|
||||||
|
if force_send:
|
||||||
|
try:
|
||||||
|
# 获取知识库映射
|
||||||
|
mapping = GmailTalentMapping.objects.filter(
|
||||||
|
user=request.user,
|
||||||
|
talent_email=email,
|
||||||
|
is_active=True
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not mapping:
|
||||||
|
return Response(
|
||||||
|
{"error": f"找不到与邮箱 {email} 的映射关系"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
# 直接发送邮件
|
||||||
|
mail_subject = subject if subject else "关于合作的洽谈"
|
||||||
|
mail_result = gmail_integration.send_email(
|
||||||
|
to_email=email,
|
||||||
|
subject=mail_subject,
|
||||||
|
body=content,
|
||||||
|
conversation_id=mapping.conversation_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if mail_result['status'] != 'success':
|
||||||
|
return Response(
|
||||||
|
{"error": f"邮件发送失败: {mail_result.get('message', 'Unknown error')}"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
# 保存发送的内容到对话历史
|
||||||
|
ChatHistory.objects.create(
|
||||||
|
user=request.user,
|
||||||
|
knowledge_base=mapping.knowledge_base,
|
||||||
|
conversation_id=mapping.conversation_id,
|
||||||
|
role='assistant',
|
||||||
|
content=content
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
'status': 'success',
|
||||||
|
'message': f"已强制发送邮件到 {email}",
|
||||||
|
'email_sent': True,
|
||||||
|
'conversation_id': mapping.conversation_id
|
||||||
|
}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"强制发送邮件时出错: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return Response(
|
||||||
|
{"error": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
# 执行自动对话
|
||||||
|
result = auto_chat_session(request.user, email)
|
||||||
|
|
||||||
|
# 返回结果
|
||||||
|
return Response(result, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"执行自动对话时出错: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return Response(
|
||||||
|
{"error": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_view(['GET', 'POST'])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
def feishu_user_goal(request):
|
||||||
|
"""
|
||||||
|
设置或获取用户总目标
|
||||||
|
|
||||||
|
GET 请求:
|
||||||
|
获取当前用户总目标
|
||||||
|
|
||||||
|
POST 请求参数:
|
||||||
|
email: 达人邮箱
|
||||||
|
goal: 目标内容
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 检查用户权限 - 只允许组长使用
|
||||||
|
if request.user.role != 'leader':
|
||||||
|
return Response(
|
||||||
|
{"error": "只有组长角色的用户可以使用此功能"},
|
||||||
|
status=status.HTTP_403_FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
# 创建Gmail集成实例
|
||||||
|
gmail_integration = GmailIntegration(request.user)
|
||||||
|
# 获取总目标
|
||||||
|
result = gmail_integration.manage_user_goal()
|
||||||
|
return Response(result, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
# 获取参数
|
||||||
|
email = request.data.get("email")
|
||||||
|
goal = request.data.get("goal")
|
||||||
|
|
||||||
|
# 验证必要参数
|
||||||
|
if not email:
|
||||||
|
return Response(
|
||||||
|
{"error": "缺少参数email"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
if not goal:
|
||||||
|
return Response(
|
||||||
|
{"error": "缺少参数goal"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# 设置用户总目标
|
||||||
|
gmail_integration = GmailIntegration(request.user)
|
||||||
|
result = gmail_integration.manage_user_goal(goal)
|
||||||
|
|
||||||
|
return Response(result, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"管理用户总目标时出错: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return Response(
|
||||||
|
{"error": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
def check_goal_status(request):
|
||||||
|
"""
|
||||||
|
检查目标完成状态
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
email: 达人邮箱
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 检查用户权限 - 只允许组长使用
|
||||||
|
if request.user.role != 'leader':
|
||||||
|
return Response(
|
||||||
|
{"error": "只有组长角色的用户可以使用此功能"},
|
||||||
|
status=status.HTTP_403_FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取参数
|
||||||
|
email = request.query_params.get("email")
|
||||||
|
|
||||||
|
# 验证必要参数
|
||||||
|
if not email:
|
||||||
|
return Response(
|
||||||
|
{"error": "缺少参数email"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# 查找Gmail映射关系
|
||||||
|
mapping = GmailTalentMapping.objects.filter(
|
||||||
|
user=request.user,
|
||||||
|
talent_email=email,
|
||||||
|
is_active=True
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not mapping:
|
||||||
|
return Response(
|
||||||
|
{"error": f"找不到与邮箱 {email} 的映射关系"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取对话历史中最后的AI回复
|
||||||
|
last_ai_message = ChatHistory.objects.filter(
|
||||||
|
user=request.user,
|
||||||
|
knowledge_base=mapping.knowledge_base,
|
||||||
|
conversation_id=mapping.conversation_id,
|
||||||
|
role='assistant',
|
||||||
|
is_deleted=False
|
||||||
|
).order_by('-created_at').first()
|
||||||
|
|
||||||
|
if not last_ai_message:
|
||||||
|
return Response(
|
||||||
|
{"error": f"找不到与邮箱 {email} 的对话历史"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查目标是否已达成
|
||||||
|
goal_achieved = check_goal_achieved(last_ai_message.content)
|
||||||
|
|
||||||
|
# 获取对话总结
|
||||||
|
summary = ConversationSummary.objects.filter(
|
||||||
|
user=request.user,
|
||||||
|
talent_email=email,
|
||||||
|
is_active=True
|
||||||
|
).order_by('-updated_at').first()
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'status': 'success',
|
||||||
|
'email': email,
|
||||||
|
'goal_achieved': goal_achieved,
|
||||||
|
'last_message_time': last_ai_message.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'last_message': last_ai_message.content,
|
||||||
|
'summary': summary.summary if summary else None
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(result, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"检查目标状态时出错: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return Response(
|
||||||
|
{"error": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
@ -18,6 +18,7 @@ from email import encoders
|
|||||||
import warnings
|
import warnings
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import requests
|
import requests
|
||||||
|
import django.db.utils
|
||||||
|
|
||||||
# 忽略oauth2client相关的所有警告
|
# 忽略oauth2client相关的所有警告
|
||||||
warnings.filterwarnings('ignore', message='file_cache is unavailable when using oauth2client >= 4.0.0 or google-auth')
|
warnings.filterwarnings('ignore', message='file_cache is unavailable when using oauth2client >= 4.0.0 or google-auth')
|
||||||
@ -175,12 +176,23 @@ class GmailIntegration:
|
|||||||
try:
|
try:
|
||||||
# 确保是有效的JSON
|
# 确保是有效的JSON
|
||||||
json_data = json.loads(self.client_secret)
|
json_data = json.loads(self.client_secret)
|
||||||
|
# 强制设置redirect_uris为非浏览器模式,避免localhost连接拒绝问题
|
||||||
|
for key in ['web', 'installed']:
|
||||||
|
if key in json_data and 'redirect_uris' in json_data[key]:
|
||||||
|
json_data[key]['redirect_uris'] = ['urn:ietf:wg:oauth:2.0:oob']
|
||||||
|
logger.info("已强制设置redirect_uri为非浏览器模式")
|
||||||
json.dump(json_data, f)
|
json.dump(json_data, f)
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
logger.error(f"client_secret不是有效的JSON: {str(e)}")
|
logger.error(f"client_secret不是有效的JSON: {str(e)}")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
json.dump(self.client_secret, f)
|
# 如果是字典,也进行相同处理
|
||||||
|
client_secret_dict = dict(self.client_secret)
|
||||||
|
for key in ['web', 'installed']:
|
||||||
|
if key in client_secret_dict and 'redirect_uris' in client_secret_dict[key]:
|
||||||
|
client_secret_dict[key]['redirect_uris'] = ['urn:ietf:wg:oauth:2.0:oob']
|
||||||
|
logger.info("已强制设置redirect_uri为非浏览器模式")
|
||||||
|
json.dump(client_secret_dict, f)
|
||||||
|
|
||||||
logger.info(f"已将client_secret写入临时文件: {client_secret_path}")
|
logger.info(f"已将client_secret写入临时文件: {client_secret_path}")
|
||||||
|
|
||||||
@ -202,33 +214,11 @@ class GmailIntegration:
|
|||||||
logger.error("没有提供client_secret_json且找不到有效凭证")
|
logger.error("没有提供client_secret_json且找不到有效凭证")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 提取重定向URI
|
# 强制使用非浏览器认证模式,避免localhost连接问题
|
||||||
redirect_uri = None
|
logger.info("使用非浏览器认证模式")
|
||||||
if isinstance(self.client_secret, dict):
|
redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
|
||||||
for key in ['web', 'installed']:
|
flow = client.flow_from_clientsecrets('client_secret.json', self.SCOPES)
|
||||||
if key in self.client_secret and 'redirect_uris' in self.client_secret[key]:
|
flow.redirect_uri = redirect_uri
|
||||||
redirect_uri = self.client_secret[key]['redirect_uris'][0]
|
|
||||||
break
|
|
||||||
elif isinstance(self.client_secret, str):
|
|
||||||
try:
|
|
||||||
json_data = json.loads(self.client_secret)
|
|
||||||
for key in ['web', 'installed']:
|
|
||||||
if key in json_data and 'redirect_uris' in json_data[key]:
|
|
||||||
redirect_uri = json_data[key]['redirect_uris'][0]
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 如果找不到重定向URI,使用默认值
|
|
||||||
if not redirect_uri or redirect_uri == 'urn:ietf:wg:oauth:2.0:oob':
|
|
||||||
logger.info("使用非浏览器认证模式")
|
|
||||||
redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
|
|
||||||
flow = client.flow_from_clientsecrets('client_secret.json', self.SCOPES)
|
|
||||||
flow.redirect_uri = redirect_uri
|
|
||||||
else:
|
|
||||||
logger.info(f"使用重定向URI: {redirect_uri}")
|
|
||||||
flow = client.flow_from_clientsecrets('client_secret.json', self.SCOPES)
|
|
||||||
flow.redirect_uri = redirect_uri
|
|
||||||
|
|
||||||
# 获取授权URL并抛出异常
|
# 获取授权URL并抛出异常
|
||||||
auth_url = flow.step1_get_authorize_url()
|
auth_url = flow.step1_get_authorize_url()
|
||||||
@ -375,15 +365,7 @@ class GmailIntegration:
|
|||||||
return self._create_knowledge_base_basic(talent_email)
|
return self._create_knowledge_base_basic(talent_email)
|
||||||
|
|
||||||
def _create_knowledge_base_basic(self, talent_email):
|
def _create_knowledge_base_basic(self, talent_email):
|
||||||
"""
|
"""创建基础知识库,不处理映射关系"""
|
||||||
使用基本方法创建知识库(当KnowledgeBaseViewSet创建失败时的后备方案)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
talent_email (str): 达人Gmail邮箱地址
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
tuple: (knowledge_base, created) - 知识库对象和是否新创建的标志
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# 根据达人邮箱生成一个唯一的标识名称
|
# 根据达人邮箱生成一个唯一的标识名称
|
||||||
kb_name = f"Gmail-{talent_email.split('@')[0]}"
|
kb_name = f"Gmail-{talent_email.split('@')[0]}"
|
||||||
@ -410,13 +392,51 @@ class GmailIntegration:
|
|||||||
return existing_kb, False
|
return existing_kb, False
|
||||||
|
|
||||||
# 创建新知识库
|
# 创建新知识库
|
||||||
knowledge_base = KnowledgeBase.objects.create(
|
try:
|
||||||
name=kb_name,
|
knowledge_base = KnowledgeBase.objects.create(
|
||||||
desc=f"与{talent_email}的Gmail邮件交流记录",
|
name=kb_name,
|
||||||
type="private",
|
desc=f"与{talent_email}的Gmail邮件交流记录",
|
||||||
user_id=self.user.id,
|
type="private",
|
||||||
documents=[]
|
user_id=self.user.id,
|
||||||
)
|
documents=[]
|
||||||
|
)
|
||||||
|
except django.db.utils.IntegrityError as e:
|
||||||
|
# 处理名称重复的情况
|
||||||
|
logger.warning(f"知识库名称'{kb_name}'已存在,尝试获取或创建带随机后缀的名称")
|
||||||
|
|
||||||
|
# 先尝试查找已存在的知识库(不限制用户)
|
||||||
|
existing_kb = KnowledgeBase.objects.filter(name=kb_name).first()
|
||||||
|
|
||||||
|
if existing_kb and str(existing_kb.user_id) == str(self.user.id):
|
||||||
|
# 如果存在且属于当前用户,直接使用
|
||||||
|
logger.info(f"找到属于当前用户的知识库: {kb_name}")
|
||||||
|
|
||||||
|
# 创建映射关系
|
||||||
|
GmailTalentMapping.objects.update_or_create(
|
||||||
|
user=self.user,
|
||||||
|
talent_email=talent_email,
|
||||||
|
defaults={
|
||||||
|
'knowledge_base': existing_kb,
|
||||||
|
'is_active': True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return existing_kb, False
|
||||||
|
else:
|
||||||
|
# 如果不存在或不属于当前用户,创建带随机后缀的新知识库
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=5))
|
||||||
|
new_kb_name = f"{kb_name}-{suffix}"
|
||||||
|
|
||||||
|
logger.info(f"创建带随机后缀的知识库: {new_kb_name}")
|
||||||
|
knowledge_base = KnowledgeBase.objects.create(
|
||||||
|
name=new_kb_name,
|
||||||
|
desc=f"与{talent_email}的Gmail邮件交流记录",
|
||||||
|
type="private",
|
||||||
|
user_id=self.user.id,
|
||||||
|
documents=[]
|
||||||
|
)
|
||||||
|
|
||||||
# 创建外部知识库
|
# 创建外部知识库
|
||||||
try:
|
try:
|
||||||
@ -439,13 +459,37 @@ class GmailIntegration:
|
|||||||
is_active=True
|
is_active=True
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"成功创建新知识库: {kb_name}, ID: {knowledge_base.id}")
|
logger.info(f"成功创建新知识库: {knowledge_base.name}, ID: {knowledge_base.id}")
|
||||||
return knowledge_base, True
|
return knowledge_base, True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"创建或获取Gmail-达人知识库基本方法失败: {str(e)}")
|
logger.error(f"创建或获取Gmail-达人知识库基本方法失败: {str(e)}")
|
||||||
import traceback
|
import traceback
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
# 尝试直接获取已存在的知识库作为最后手段
|
||||||
|
try:
|
||||||
|
kb_name = f"Gmail-{talent_email.split('@')[0]}"
|
||||||
|
existing_kb = KnowledgeBase.objects.filter(name__startswith=kb_name).first()
|
||||||
|
|
||||||
|
if existing_kb:
|
||||||
|
logger.info(f"在错误处理中找到可用的知识库: {existing_kb.name}")
|
||||||
|
|
||||||
|
# 创建映射关系
|
||||||
|
GmailTalentMapping.objects.update_or_create(
|
||||||
|
user=self.user,
|
||||||
|
talent_email=talent_email,
|
||||||
|
defaults={
|
||||||
|
'knowledge_base': existing_kb,
|
||||||
|
'is_active': True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return existing_kb, False
|
||||||
|
except Exception as inner_e:
|
||||||
|
logger.error(f"错误处理中尝试获取知识库失败: {str(inner_e)}")
|
||||||
|
|
||||||
|
# 如果所有尝试都失败,抛出异常
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def get_conversations(self, talent_gmail):
|
def get_conversations(self, talent_gmail):
|
||||||
@ -1991,13 +2035,24 @@ class GmailIntegration:
|
|||||||
try:
|
try:
|
||||||
# 确保是有效的JSON
|
# 确保是有效的JSON
|
||||||
json_data = json.loads(self.client_secret)
|
json_data = json.loads(self.client_secret)
|
||||||
|
# 强制设置redirect_uris为非浏览器模式,避免localhost连接拒绝问题
|
||||||
|
for key in ['web', 'installed']:
|
||||||
|
if key in json_data and 'redirect_uris' in json_data[key]:
|
||||||
|
json_data[key]['redirect_uris'] = ['urn:ietf:wg:oauth:2.0:oob']
|
||||||
|
logger.info("已强制设置redirect_uri为非浏览器模式")
|
||||||
json.dump(json_data, f)
|
json.dump(json_data, f)
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
logger.error(f"client_secret不是有效的JSON: {str(e)}")
|
logger.error(f"client_secret不是有效的JSON: {str(e)}")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
logger.info("client_secret是字典,直接写入文件")
|
logger.info("client_secret是字典,直接写入文件")
|
||||||
json.dump(self.client_secret, f)
|
# 如果是字典,也进行相同处理
|
||||||
|
client_secret_dict = dict(self.client_secret)
|
||||||
|
for key in ['web', 'installed']:
|
||||||
|
if key in client_secret_dict and 'redirect_uris' in client_secret_dict[key]:
|
||||||
|
client_secret_dict[key]['redirect_uris'] = ['urn:ietf:wg:oauth:2.0:oob']
|
||||||
|
logger.info("已强制设置redirect_uri为非浏览器模式")
|
||||||
|
json.dump(client_secret_dict, f)
|
||||||
|
|
||||||
logger.info(f"已将client_secret写入临时文件: {client_secret_path}")
|
logger.info(f"已将client_secret写入临时文件: {client_secret_path}")
|
||||||
|
|
||||||
@ -2012,28 +2067,9 @@ class GmailIntegration:
|
|||||||
logger.info(f"设置token存储: {self.token_storage_path}")
|
logger.info(f"设置token存储: {self.token_storage_path}")
|
||||||
store = file.Storage(self.token_storage_path)
|
store = file.Storage(self.token_storage_path)
|
||||||
|
|
||||||
# 提取重定向URI
|
# 强制使用非浏览器认证模式
|
||||||
redirect_uri = None
|
redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
|
||||||
if isinstance(self.client_secret, dict):
|
logger.info(f"使用非浏览器认证模式: {redirect_uri}")
|
||||||
for key in ['web', 'installed']:
|
|
||||||
if key in self.client_secret and 'redirect_uris' in self.client_secret[key]:
|
|
||||||
redirect_uri = self.client_secret[key]['redirect_uris'][0]
|
|
||||||
break
|
|
||||||
elif isinstance(self.client_secret, str):
|
|
||||||
try:
|
|
||||||
json_data = json.loads(self.client_secret)
|
|
||||||
for key in ['web', 'installed']:
|
|
||||||
if key in json_data and 'redirect_uris' in json_data[key]:
|
|
||||||
redirect_uri = json_data[key]['redirect_uris'][0]
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 如果找不到重定向URI,使用默认值
|
|
||||||
if not redirect_uri:
|
|
||||||
redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
|
|
||||||
|
|
||||||
logger.info(f"使用重定向URI: {redirect_uri}")
|
|
||||||
|
|
||||||
# 从client_secret创建flow
|
# 从client_secret创建flow
|
||||||
logger.info("从client_secret创建授权流程")
|
logger.info("从client_secret创建授权流程")
|
||||||
|
@ -29,6 +29,12 @@ from .views import (
|
|||||||
generate_conversation_summary,
|
generate_conversation_summary,
|
||||||
get_recommended_reply
|
get_recommended_reply
|
||||||
)
|
)
|
||||||
|
from .feishu_chat_views import (
|
||||||
|
process_feishu_table,
|
||||||
|
run_auto_chat,
|
||||||
|
feishu_user_goal,
|
||||||
|
check_goal_status
|
||||||
|
)
|
||||||
|
|
||||||
# 创建路由器
|
# 创建路由器
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
@ -74,4 +80,10 @@ urlpatterns = [
|
|||||||
path('user-goal/', manage_user_goal, name='manage_user_goal'),
|
path('user-goal/', manage_user_goal, name='manage_user_goal'),
|
||||||
path('conversation-summary/', generate_conversation_summary, name='generate_conversation_summary'),
|
path('conversation-summary/', generate_conversation_summary, name='generate_conversation_summary'),
|
||||||
path('recommended-reply/', get_recommended_reply, name='get_recommended_reply'),
|
path('recommended-reply/', get_recommended_reply, name='get_recommended_reply'),
|
||||||
|
|
||||||
|
# 飞书AI聊天相关API
|
||||||
|
path('feishu/process-table/', process_feishu_table, name='process_feishu_table'),
|
||||||
|
path('feishu/auto-chat/', run_auto_chat, name='run_auto_chat'),
|
||||||
|
path('feishu/user-goal/', feishu_user_goal, name='feishu_user_goal'),
|
||||||
|
path('feishu/check-goal/', check_goal_status, name='check_goal_status'),
|
||||||
]
|
]
|
||||||
|
@ -6916,3 +6916,301 @@ def check_creator_kb_api(request):
|
|||||||
'data': None
|
'data': None
|
||||||
}, status=500)
|
}, status=500)
|
||||||
|
|
||||||
|
from feishu.feishu_ai_chat import (
|
||||||
|
fetch_table_records, find_duplicate_email_creators,
|
||||||
|
process_duplicate_emails, auto_chat_session,
|
||||||
|
handle_set_goal, handle_check_goal
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加飞书多维表格AI对话接口
|
||||||
|
@api_view(['POST'])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
def process_feishu_table(request):
|
||||||
|
"""
|
||||||
|
从飞书多维表格读取数据,处理重复邮箱
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
table_id: 表格ID
|
||||||
|
view_id: 视图ID
|
||||||
|
app_token: 飞书应用TOKEN (可选)
|
||||||
|
access_token: 用户访问令牌 (可选)
|
||||||
|
goal_template: 目标内容模板 (可选)
|
||||||
|
auto_chat: 是否自动执行AI对话 (可选)
|
||||||
|
turns: 自动对话轮次 (可选)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 检查用户权限 - 只允许组长使用
|
||||||
|
if request.user.role != 'leader':
|
||||||
|
return Response(
|
||||||
|
{"error": "只有组长角色的用户可以使用此功能"},
|
||||||
|
status=status.HTTP_403_FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取参数
|
||||||
|
table_id = request.data.get("table_id")
|
||||||
|
view_id = request.data.get("view_id")
|
||||||
|
app_token = request.data.get("app_token", "XYE6bMQUOaZ5y5svj4vcWohGnmg")
|
||||||
|
access_token = request.data.get("access_token", "u-ecM5BmzKx4uHz3sG0FouQSk1l9kxgl_3Xa00l5Ma24Jy")
|
||||||
|
goal_template = request.data.get(
|
||||||
|
"goal_template",
|
||||||
|
"与达人{handle}(邮箱:{email})建立联系并了解其账号情况,评估合作潜力,处理合作需求,最终目标是达成合作并签约。"
|
||||||
|
)
|
||||||
|
auto_chat = request.data.get("auto_chat", False)
|
||||||
|
turns = request.data.get("turns", 5)
|
||||||
|
|
||||||
|
# 验证必要参数
|
||||||
|
if not table_id:
|
||||||
|
return Response(
|
||||||
|
{"error": "缺少参数table_id"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
if not view_id:
|
||||||
|
return Response(
|
||||||
|
{"error": "缺少参数view_id"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# 从飞书表格获取记录
|
||||||
|
records = fetch_table_records(
|
||||||
|
app_token,
|
||||||
|
table_id,
|
||||||
|
view_id,
|
||||||
|
access_token
|
||||||
|
)
|
||||||
|
|
||||||
|
if not records:
|
||||||
|
return Response(
|
||||||
|
{"message": "未获取到任何记录"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
# 查找重复邮箱的创作者
|
||||||
|
duplicate_emails = find_duplicate_email_creators(records)
|
||||||
|
|
||||||
|
if not duplicate_emails:
|
||||||
|
return Response(
|
||||||
|
{"message": "未发现重复邮箱"},
|
||||||
|
status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
|
||||||
|
# 处理重复邮箱记录
|
||||||
|
results = process_duplicate_emails(duplicate_emails, goal_template)
|
||||||
|
|
||||||
|
# 如果需要自动对话
|
||||||
|
chat_results = []
|
||||||
|
if auto_chat and results['success'] > 0:
|
||||||
|
# 为每个成功创建的记录执行自动对话
|
||||||
|
for detail in results['details']:
|
||||||
|
if detail['status'] == 'success':
|
||||||
|
email = detail['email']
|
||||||
|
chat_result = auto_chat_session(request.user, email, max_turns=turns)
|
||||||
|
chat_results.append({
|
||||||
|
'email': email,
|
||||||
|
'result': chat_result
|
||||||
|
})
|
||||||
|
|
||||||
|
# 返回处理结果
|
||||||
|
return Response({
|
||||||
|
'status': 'success',
|
||||||
|
'records_count': len(records),
|
||||||
|
'duplicate_emails_count': len(duplicate_emails),
|
||||||
|
'processing_results': results,
|
||||||
|
'chat_results': chat_results
|
||||||
|
}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理飞书表格时出错: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return Response(
|
||||||
|
{"error": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
def run_auto_chat(request):
|
||||||
|
"""
|
||||||
|
为指定邮箱执行自动对话
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
email: 达人邮箱
|
||||||
|
turns: 对话轮次 (可选)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 检查用户权限 - 只允许组长使用
|
||||||
|
if request.user.role != 'leader':
|
||||||
|
return Response(
|
||||||
|
{"error": "只有组长角色的用户可以使用此功能"},
|
||||||
|
status=status.HTTP_403_FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取参数
|
||||||
|
email = request.data.get("email")
|
||||||
|
turns = request.data.get("turns", 5)
|
||||||
|
|
||||||
|
# 验证必要参数
|
||||||
|
if not email:
|
||||||
|
return Response(
|
||||||
|
{"error": "缺少参数email"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# 执行自动对话
|
||||||
|
result = auto_chat_session(request.user, email, max_turns=turns)
|
||||||
|
|
||||||
|
# 返回结果
|
||||||
|
return Response(result, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"执行自动对话时出错: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return Response(
|
||||||
|
{"error": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_view(['GET', 'POST'])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
def feishu_user_goal(request):
|
||||||
|
"""
|
||||||
|
设置或获取用户总目标
|
||||||
|
|
||||||
|
GET 请求:
|
||||||
|
获取当前用户总目标
|
||||||
|
|
||||||
|
POST 请求参数:
|
||||||
|
email: 达人邮箱
|
||||||
|
goal: 目标内容
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 检查用户权限 - 只允许组长使用
|
||||||
|
if request.user.role != 'leader':
|
||||||
|
return Response(
|
||||||
|
{"error": "只有组长角色的用户可以使用此功能"},
|
||||||
|
status=status.HTTP_403_FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
# 创建Gmail集成实例
|
||||||
|
gmail_integration = GmailIntegration(request.user)
|
||||||
|
# 获取总目标
|
||||||
|
result = gmail_integration.manage_user_goal()
|
||||||
|
return Response(result, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
# 获取参数
|
||||||
|
email = request.data.get("email")
|
||||||
|
goal = request.data.get("goal")
|
||||||
|
|
||||||
|
# 验证必要参数
|
||||||
|
if not email:
|
||||||
|
return Response(
|
||||||
|
{"error": "缺少参数email"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
if not goal:
|
||||||
|
return Response(
|
||||||
|
{"error": "缺少参数goal"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# 设置用户总目标
|
||||||
|
gmail_integration = GmailIntegration(request.user)
|
||||||
|
result = gmail_integration.manage_user_goal(goal)
|
||||||
|
|
||||||
|
return Response(result, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"管理用户总目标时出错: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return Response(
|
||||||
|
{"error": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
def check_goal_status(request):
|
||||||
|
"""
|
||||||
|
检查目标完成状态
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
email: 达人邮箱
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 检查用户权限 - 只允许组长使用
|
||||||
|
if request.user.role != 'leader':
|
||||||
|
return Response(
|
||||||
|
{"error": "只有组长角色的用户可以使用此功能"},
|
||||||
|
status=status.HTTP_403_FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取参数
|
||||||
|
email = request.query_params.get("email")
|
||||||
|
|
||||||
|
# 验证必要参数
|
||||||
|
if not email:
|
||||||
|
return Response(
|
||||||
|
{"error": "缺少参数email"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# 查找Gmail映射关系
|
||||||
|
mapping = GmailTalentMapping.objects.filter(
|
||||||
|
user=request.user,
|
||||||
|
talent_email=email,
|
||||||
|
is_active=True
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not mapping:
|
||||||
|
return Response(
|
||||||
|
{"error": f"找不到与邮箱 {email} 的映射关系"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取对话历史中最后的AI回复
|
||||||
|
last_ai_message = ChatHistory.objects.filter(
|
||||||
|
user=request.user,
|
||||||
|
knowledge_base=mapping.knowledge_base,
|
||||||
|
conversation_id=mapping.conversation_id,
|
||||||
|
role='assistant',
|
||||||
|
is_deleted=False
|
||||||
|
).order_by('-created_at').first()
|
||||||
|
|
||||||
|
if not last_ai_message:
|
||||||
|
return Response(
|
||||||
|
{"error": f"找不到与邮箱 {email} 的对话历史"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
# 导入检查函数
|
||||||
|
from feishu.feishu_ai_chat import check_goal_achieved
|
||||||
|
|
||||||
|
# 检查目标是否已达成
|
||||||
|
goal_achieved = check_goal_achieved(last_ai_message.content)
|
||||||
|
|
||||||
|
# 获取对话总结
|
||||||
|
summary = ConversationSummary.objects.filter(
|
||||||
|
user=request.user,
|
||||||
|
talent_email=email,
|
||||||
|
is_active=True
|
||||||
|
).order_by('-updated_at').first()
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'status': 'success',
|
||||||
|
'email': email,
|
||||||
|
'goal_achieved': goal_achieved,
|
||||||
|
'last_message_time': last_ai_message.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'last_message': last_ai_message.content,
|
||||||
|
'summary': summary.summary if summary else None
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(result, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"检查目标状态时出错: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return Response(
|
||||||
|
{"error": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user