获取授权码

This commit is contained in:
wanjia 2025-04-17 16:14:00 +08:00
parent 90656ab36a
commit d3ca3ef85f
19 changed files with 2693 additions and 239 deletions

335
feishu/API_README.md Normal file
View 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();
```

View 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

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
本期节目内容简介
在参加各类比赛时,肯定会遇到竞争对手。英语单词 rival、opponent、competitor 和 contestant 的含义相似,都可以用来指“与他人之间存在竞争关系的人或团队”。在本集《你问我答》节目中,我们将通过和体育比赛有关的实例来为大家阐释这四个近义词之间的区别和用法。
欢迎你加入并和我们一起讨论英语学习的方方面面。请通过微博“BBC英语教学”或邮件与我们取得联系。我们的邮箱地址是 questions.chinaelt@bbc.co.uk。
文字稿
(关于台词的备注: 请注意这不是广播节目的逐字稿件。本文稿可能没有体现录制、编辑过程中对节目做出的改变。)
Feifei
大家好,欢迎收听 BBC 英语教学的《你问我答》节目,我是冯菲菲。每集节目中,我们会回答大家在英语学习时遇到的一个问题。本集的问题来自 Adela。我们来听一下她的问题。

View File

@ -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"]}}

View File

@ -34,7 +34,7 @@
"filename": "test2.txt",
"mimeType": "text/plain",
"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",
"body": "你好\r\n",
"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": []
}
]

View File

@ -1,5 +1,5 @@
==================================================
记录时间: 2025-04-10 16:52:49
记录时间: 2025-04-17 15:43:23
==================================================
时间: 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
--------------------------------------------------

View File

@ -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)

View File

@ -1,3 +0,0 @@
google-api-python-client==1.7.8
google-auth-httplib2==0.0.3
google-auth-oauthlib==0.4.0

View File

@ -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"}

View File

@ -1 +0,0 @@
你好

View File

@ -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"}

View File

@ -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"}

View File

@ -18,7 +18,8 @@ from django.contrib import admin
from django.urls import path, include
from django.conf import settings
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 = [
# 管理后台
@ -36,6 +37,12 @@ urlpatterns = [
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'),
# 飞书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),

View File

@ -4,6 +4,7 @@ from channels.db import database_sync_to_async
from channels.exceptions import StopConsumer
import logging
from rest_framework.authtoken.models import Token
from urllib.parse import parse_qs
logger = logging.getLogger(__name__)
@ -11,19 +12,20 @@ class NotificationConsumer(AsyncWebsocketConsumer):
async def connect(self):
"""建立WebSocket连接"""
try:
# 获取token
headers = dict(self.scope['headers'])
auth_header = headers.get(b'authorization', b'').decode()
# 从URL参数中获取token
query_string = self.scope.get('query_string', 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()
return
token_key = auth_header.split(' ')[1]
# 验证token
self.user = await self.get_user_from_token(token_key)
if not self.user:
logger.warning(f"WebSocket连接尝试但token无效: {token_key}")
await self.close()
return
@ -34,8 +36,10 @@ class NotificationConsumer(AsyncWebsocketConsumer):
self.channel_name
)
await self.accept()
logger.info(f"用户 {self.user.username} WebSocket连接成功")
except Exception as e:
logger.error(f"WebSocket连接错误: {str(e)}")
await self.close()
@database_sync_to_async

View 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
)

View File

@ -18,6 +18,7 @@ from email import encoders
import warnings
import mimetypes
import requests
import django.db.utils
# 忽略oauth2client相关的所有警告
warnings.filterwarnings('ignore', message='file_cache is unavailable when using oauth2client >= 4.0.0 or google-auth')
@ -175,12 +176,23 @@ class GmailIntegration:
try:
# 确保是有效的JSON
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)
except json.JSONDecodeError as e:
logger.error(f"client_secret不是有效的JSON: {str(e)}")
return False
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}")
@ -202,33 +214,11 @@ class GmailIntegration:
logger.error("没有提供client_secret_json且找不到有效凭证")
return False
# 提取重定向URI
redirect_uri = None
if isinstance(self.client_secret, dict):
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 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
# 强制使用非浏览器认证模式避免localhost连接问题
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
# 获取授权URL并抛出异常
auth_url = flow.step1_get_authorize_url()
@ -375,15 +365,7 @@ class GmailIntegration:
return self._create_knowledge_base_basic(talent_email)
def _create_knowledge_base_basic(self, talent_email):
"""
使用基本方法创建知识库当KnowledgeBaseViewSet创建失败时的后备方案
Args:
talent_email (str): 达人Gmail邮箱地址
Returns:
tuple: (knowledge_base, created) - 知识库对象和是否新创建的标志
"""
"""创建基础知识库,不处理映射关系"""
try:
# 根据达人邮箱生成一个唯一的标识名称
kb_name = f"Gmail-{talent_email.split('@')[0]}"
@ -410,13 +392,51 @@ class GmailIntegration:
return existing_kb, False
# 创建新知识库
knowledge_base = KnowledgeBase.objects.create(
name=kb_name,
desc=f"{talent_email}的Gmail邮件交流记录",
type="private",
user_id=self.user.id,
documents=[]
)
try:
knowledge_base = KnowledgeBase.objects.create(
name=kb_name,
desc=f"{talent_email}的Gmail邮件交流记录",
type="private",
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:
@ -439,13 +459,37 @@ class GmailIntegration:
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
except Exception as e:
logger.error(f"创建或获取Gmail-达人知识库基本方法失败: {str(e)}")
import traceback
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
def get_conversations(self, talent_gmail):
@ -1991,13 +2035,24 @@ class GmailIntegration:
try:
# 确保是有效的JSON
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)
except json.JSONDecodeError as e:
logger.error(f"client_secret不是有效的JSON: {str(e)}")
return False
else:
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}")
@ -2012,28 +2067,9 @@ class GmailIntegration:
logger.info(f"设置token存储: {self.token_storage_path}")
store = file.Storage(self.token_storage_path)
# 提取重定向URI
redirect_uri = None
if isinstance(self.client_secret, dict):
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}")
# 强制使用非浏览器认证模式
redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
logger.info(f"使用非浏览器认证模式: {redirect_uri}")
# 从client_secret创建flow
logger.info("从client_secret创建授权流程")

View File

@ -29,6 +29,12 @@ from .views import (
generate_conversation_summary,
get_recommended_reply
)
from .feishu_chat_views import (
process_feishu_table,
run_auto_chat,
feishu_user_goal,
check_goal_status
)
# 创建路由器
router = DefaultRouter()
@ -74,4 +80,10 @@ urlpatterns = [
path('user-goal/', manage_user_goal, name='manage_user_goal'),
path('conversation-summary/', generate_conversation_summary, name='generate_conversation_summary'),
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'),
]

View File

@ -6916,3 +6916,301 @@ def check_creator_kb_api(request):
'data': None
}, 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
)