Compare commits
3 Commits
b64af631e3
...
7ffb0de74c
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7ffb0de74c | ||
![]() |
45d49d4b4a | ||
![]() |
083971a94a |
10
gmail/attachments/test2.txt
Normal file
10
gmail/attachments/test2.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
本期节目内容简介
|
||||||
|
在参加各类比赛时,肯定会遇到竞争对手。英语单词 rival、opponent、competitor 和 contestant 的含义相似,都可以用来指“与他人之间存在竞争关系的人或团队”。在本集《你问我答》节目中,我们将通过和体育比赛有关的实例来为大家阐释这四个近义词之间的区别和用法。
|
||||||
|
|
||||||
|
欢迎你加入并和我们一起讨论英语学习的方方面面。请通过微博“BBC英语教学”或邮件与我们取得联系。我们的邮箱地址是 questions.chinaelt@bbc.co.uk。
|
||||||
|
|
||||||
|
文字稿
|
||||||
|
(关于台词的备注: 请注意这不是广播节目的逐字稿件。本文稿可能没有体现录制、编辑过程中对节目做出的改变。)
|
||||||
|
|
||||||
|
Feifei
|
||||||
|
大家好,欢迎收听 BBC 英语教学的《你问我答》节目,我是冯菲菲。每集节目中,我们会回答大家在英语学习时遇到的一个问题。本集的问题来自 Adela。我们来听一下她的问题。
|
@ -1 +1 @@
|
|||||||
{"installed":{"client_id":"240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com","project_id":"first-renderer-454910-c1","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"]}}
|
{"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"]}}
|
@ -1,20 +1,121 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"id": "195d5c6094b7e85f",
|
||||||
"subject": "这是主题",
|
"subject": "这是主题",
|
||||||
"from": "crush wds <ardonisierni@gmail.com>",
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
"date": "2025-03-27 12:04:29",
|
"date": "2025-03-27 12:04:29",
|
||||||
"body": "你好呀\r\n"
|
"body": "你好呀\r\n",
|
||||||
|
"attachments": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "195d5c72dbafcc13",
|
||||||
"subject": "",
|
"subject": "",
|
||||||
"from": "wds crush <crushwds@gmail.com>",
|
"from": "wds crush <crushwds@gmail.com>",
|
||||||
"date": "2025-03-27 12:05:54",
|
"date": "2025-03-27 12:05:54",
|
||||||
"body": "你那里天气怎么样\r\n"
|
"body": "你那里天气怎么样\r\n",
|
||||||
|
"attachments": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "195d604a5ad5fabf",
|
||||||
"subject": "",
|
"subject": "",
|
||||||
"from": "wds crush <crushwds@gmail.com>",
|
"from": "wds crush <crushwds@gmail.com>",
|
||||||
"date": "2025-03-27 13:13:03",
|
"date": "2025-03-27 13:13:03",
|
||||||
"body": "吃饭了吗\r\n"
|
"body": "吃饭了吗\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1960eec066745682",
|
||||||
|
"subject": "Re: 这是主题",
|
||||||
|
"from": "wds crush <crushwds@gmail.com>",
|
||||||
|
"date": "2025-04-07 14:24:28",
|
||||||
|
"body": "测试附件内容\r\n\r\n\r\ncrush wds <ardonisierni@gmail.com> 于2025年3月27日周四 12:04写道:\r\n\r\n> 你好呀\r\n>\r\n",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"filename": "test2.txt",
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"size": 996,
|
||||||
|
"attachmentId": "ANGjdJ8O1uGve4uPqFSC2C4sMeC5jXJ3DGilhB1By705ZLGbOF30m6uITRtJHWsnB7yREKVslhYRdu4GKKvrrkWw-63ogqaZPGgi0WuSoB0OxIWXwbQSjaayUOPvc3P8y1g9A2mMAm5k0DkjH_LP0QMmulhXMg8ZgUcm4CZR7-HTBYztZSfWOoUeYkNugdzV5_2ax3GT34P5uaGHh3i4Ge6y-XDN-TDq3i1w9u_eTjZowoyVzjTj48uaQmzmFU36TQxg8ncovHk0sNZEygzCE1gbHbS1uM9N1tUHOOa6FWEpajHgz2aQwLh65SsMRqKe-LognFES-J_082IHddWs"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961a1981e915eb3",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-09 18:29:51",
|
||||||
|
"body": "测试角色1\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961a19cc997f933",
|
||||||
|
"subject": "",
|
||||||
|
"from": "wds crush <crushwds@gmail.com>",
|
||||||
|
"date": "2025-04-09 18:30:21",
|
||||||
|
"body": "测试角色2\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961de790e0b39e6",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crushwds@gmail.com",
|
||||||
|
"date": "2025-04-10 00:13:57",
|
||||||
|
"body": "您好,关于我们之前讨论的产品,我想确认一些细节...",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961dee46652dc86",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crushwds@gmail.com",
|
||||||
|
"date": "2025-04-10 00:21:17",
|
||||||
|
"body": "您好,关于我们之前讨论的产品,我想确认一些细节...",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961dfd7d36bf92c",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crushwds@gmail.com",
|
||||||
|
"date": "2025-04-10 00:37:54",
|
||||||
|
"body": "您好,关于我们之前讨论的产品,我想确认一些细节...",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961e39ad02da5f0",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crushwds@gmail.com",
|
||||||
|
"date": "2025-04-10 01:43:38",
|
||||||
|
"body": "您好,关于我们之前讨论的产品,我想确认一些细节...",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961df3bd554a4af",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-10 12:27:03",
|
||||||
|
"body": "yes\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961e3944c8aab04",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-10 13:42:58",
|
||||||
|
"body": "1\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961e39660feebd9",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-10 13:43:07",
|
||||||
|
"body": "2\r\n",
|
||||||
|
"attachments": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1961ec2028c16037",
|
||||||
|
"subject": "",
|
||||||
|
"from": "crush wds <ardonisierni@gmail.com>",
|
||||||
|
"date": "2025-04-10 16:12:20",
|
||||||
|
"body": "你好\r\n",
|
||||||
|
"attachments": []
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -1,5 +1,5 @@
|
|||||||
==================================================
|
==================================================
|
||||||
记录时间: 2025-03-27 13:15:18
|
记录时间: 2025-04-10 16:52:49
|
||||||
==================================================
|
==================================================
|
||||||
|
|
||||||
时间: 2025-03-27 12:04:29
|
时间: 2025-03-27 12:04:29
|
||||||
@ -23,3 +23,85 @@
|
|||||||
吃饭了吗
|
吃饭了吗
|
||||||
|
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-07 14:24:28
|
||||||
|
发件人: wds crush <crushwds@gmail.com>
|
||||||
|
主题: Re: 这是主题
|
||||||
|
内容:
|
||||||
|
测试附件内容
|
||||||
|
|
||||||
|
|
||||||
|
crush wds <ardonisierni@gmail.com> 于2025年3月27日周四 12:04写道:
|
||||||
|
|
||||||
|
> 你好呀
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
附件:
|
||||||
|
- test2.txt (text/plain, 996 字节)
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-09 18:29:51
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
测试角色1
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-09 18:30:21
|
||||||
|
发件人: wds crush <crushwds@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
测试角色2
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 00:13:57
|
||||||
|
发件人: crushwds@gmail.com
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
您好,关于我们之前讨论的产品,我想确认一些细节...
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 00:21:17
|
||||||
|
发件人: crushwds@gmail.com
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
您好,关于我们之前讨论的产品,我想确认一些细节...
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 00:37:54
|
||||||
|
发件人: crushwds@gmail.com
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
您好,关于我们之前讨论的产品,我想确认一些细节...
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 01:43:38
|
||||||
|
发件人: crushwds@gmail.com
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
您好,关于我们之前讨论的产品,我想确认一些细节...
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 12:27:03
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
yes
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 13:42:58
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
1
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 13:43:07
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
2
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
时间: 2025-04-10 16:12:20
|
||||||
|
发件人: crush wds <ardonisierni@gmail.com>
|
||||||
|
主题:
|
||||||
|
内容:
|
||||||
|
你好
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
@ -13,7 +13,7 @@ os.environ['HTTP_PROXY'] = 'http://127.0.0.1:7890'
|
|||||||
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
|
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
|
||||||
|
|
||||||
# Gmail API 认证
|
# Gmail API 认证
|
||||||
SCOPES = 'https://www.googleapis.com/auth/gmail.readonly'
|
SCOPES = ['https://mail.google.com/']
|
||||||
store = file.Storage('storage.json')
|
store = file.Storage('storage.json')
|
||||||
creds = store.get()
|
creds = store.get()
|
||||||
if not creds or creds.invalid:
|
if not creds or creds.invalid:
|
||||||
@ -21,18 +21,47 @@ if not creds or creds.invalid:
|
|||||||
creds = tools.run_flow(flow, store)
|
creds = tools.run_flow(flow, store)
|
||||||
GMAIL = discovery.build('gmail', 'v1', http=creds.authorize(Http()))
|
GMAIL = discovery.build('gmail', 'v1', http=creds.authorize(Http()))
|
||||||
|
|
||||||
|
def download_attachment(message_id, attachment_id, filename):
|
||||||
|
"""下载邮件附件"""
|
||||||
|
try:
|
||||||
|
attachment = GMAIL.users().messages().attachments().get(
|
||||||
|
userId='me',
|
||||||
|
messageId=message_id,
|
||||||
|
id=attachment_id
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
data = attachment['data']
|
||||||
|
file_data = base64.urlsafe_b64decode(data)
|
||||||
|
|
||||||
|
# 创建附件目录
|
||||||
|
if not os.path.exists('attachments'):
|
||||||
|
os.makedirs('attachments')
|
||||||
|
|
||||||
|
# 保存附件
|
||||||
|
filepath = os.path.join('attachments', filename)
|
||||||
|
with open(filepath, 'wb') as f:
|
||||||
|
f.write(file_data)
|
||||||
|
|
||||||
|
return filepath
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error downloading attachment: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
def get_email_content(message):
|
def get_email_content(message):
|
||||||
"""提取邮件内容"""
|
"""提取邮件内容"""
|
||||||
try:
|
try:
|
||||||
|
message_id = message['id'] # 获取邮件ID
|
||||||
payload = message['payload']
|
payload = message['payload']
|
||||||
headers = payload['headers']
|
headers = payload['headers']
|
||||||
|
|
||||||
# 获取邮件基本信息
|
# 获取邮件基本信息
|
||||||
email_data = {
|
email_data = {
|
||||||
|
'id': message_id, # 保存邮件ID
|
||||||
'subject': '',
|
'subject': '',
|
||||||
'from': '',
|
'from': '',
|
||||||
'date': '',
|
'date': '',
|
||||||
'body': ''
|
'body': '',
|
||||||
|
'attachments': [] # 新增附件列表
|
||||||
}
|
}
|
||||||
|
|
||||||
# 提取头部信息
|
# 提取头部信息
|
||||||
@ -45,16 +74,37 @@ def get_email_content(message):
|
|||||||
date = parser.parse(header['value'])
|
date = parser.parse(header['value'])
|
||||||
email_data['date'] = date.strftime('%Y-%m-%d %H:%M:%S')
|
email_data['date'] = date.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
# 提取邮件正文
|
# 定义一个递归函数来处理所有部分和附件
|
||||||
if 'parts' in payload:
|
def process_parts(parts):
|
||||||
parts = payload['parts']
|
|
||||||
for part in parts:
|
for part in parts:
|
||||||
if part['mimeType'] == 'text/plain':
|
# 检查是否是附件
|
||||||
|
if 'filename' in part and part['filename']:
|
||||||
|
attachment = {
|
||||||
|
'filename': part['filename'],
|
||||||
|
'mimeType': part['mimeType'],
|
||||||
|
'size': part['body'].get('size', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
# 如果有附件内容数据,可以获取附件ID
|
||||||
|
if 'attachmentId' in part['body']:
|
||||||
|
attachment['attachmentId'] = part['body']['attachmentId']
|
||||||
|
|
||||||
|
email_data['attachments'].append(attachment)
|
||||||
|
|
||||||
|
# 处理文本内容
|
||||||
|
if part['mimeType'] == 'text/plain' and not email_data['body']:
|
||||||
data = part['body'].get('data', '')
|
data = part['body'].get('data', '')
|
||||||
if data:
|
if data:
|
||||||
text = base64.urlsafe_b64decode(data).decode('utf-8')
|
text = base64.urlsafe_b64decode(data).decode('utf-8')
|
||||||
email_data['body'] = text
|
email_data['body'] = text
|
||||||
break
|
|
||||||
|
# 递归处理多部分内容
|
||||||
|
if 'parts' in part:
|
||||||
|
process_parts(part['parts'])
|
||||||
|
|
||||||
|
# 处理邮件正文和附件
|
||||||
|
if 'parts' in payload:
|
||||||
|
process_parts(payload['parts'])
|
||||||
elif 'body' in payload:
|
elif 'body' in payload:
|
||||||
data = payload['body'].get('data', '')
|
data = payload['body'].get('data', '')
|
||||||
if data:
|
if data:
|
||||||
@ -123,6 +173,13 @@ def save_conversations(conversations, output_file):
|
|||||||
f.write(f"主题: {msg['subject']}\n")
|
f.write(f"主题: {msg['subject']}\n")
|
||||||
f.write("内容:\n")
|
f.write("内容:\n")
|
||||||
f.write(f"{msg['body']}\n")
|
f.write(f"{msg['body']}\n")
|
||||||
|
|
||||||
|
# 添加附件信息
|
||||||
|
if msg['attachments']:
|
||||||
|
f.write("\n附件:\n")
|
||||||
|
for att in msg['attachments']:
|
||||||
|
f.write(f" - {att['filename']} ({att['mimeType']}, {att['size']} 字节)\n")
|
||||||
|
|
||||||
f.write("-" * 50 + "\n")
|
f.write("-" * 50 + "\n")
|
||||||
|
|
||||||
print(f"对话记录已保存到: {output_file}")
|
print(f"对话记录已保存到: {output_file}")
|
||||||
@ -152,18 +209,39 @@ def main():
|
|||||||
if conversations:
|
if conversations:
|
||||||
print(f"找到 {len(conversations)} 条对话记录")
|
print(f"找到 {len(conversations)} 条对话记录")
|
||||||
|
|
||||||
# 保存对话记录(追加模式)
|
# 统计附件
|
||||||
|
total_attachments = 0
|
||||||
|
for msg in conversations:
|
||||||
|
total_attachments += len(msg['attachments'])
|
||||||
|
|
||||||
|
# 保存对话记录
|
||||||
save_conversations(conversations, output_file)
|
save_conversations(conversations, output_file)
|
||||||
|
|
||||||
# 打印对话统计
|
# 打印对话统计
|
||||||
print("\n对话统计:")
|
print("\n对话统计:")
|
||||||
print(f"总消息数: {len(conversations)}")
|
print(f"总消息数: {len(conversations)}")
|
||||||
|
print(f"总附件数: {total_attachments}")
|
||||||
senders = {}
|
senders = {}
|
||||||
for msg in conversations:
|
for msg in conversations:
|
||||||
sender = msg['from']
|
sender = msg['from']
|
||||||
senders[sender] = senders.get(sender, 0) + 1
|
senders[sender] = senders.get(sender, 0) + 1
|
||||||
for sender, count in senders.items():
|
for sender, count in senders.items():
|
||||||
print(f"{sender}: {count} 条消息")
|
print(f"{sender}: {count} 条消息")
|
||||||
|
|
||||||
|
# 提示用户是否下载附件
|
||||||
|
if total_attachments > 0:
|
||||||
|
download_choice = input(f"\n发现 {total_attachments} 个附件,是否下载? (y/n): ")
|
||||||
|
if download_choice.lower() == 'y':
|
||||||
|
print("\n开始下载附件...")
|
||||||
|
downloaded = 0
|
||||||
|
for msg in conversations:
|
||||||
|
for att in msg['attachments']:
|
||||||
|
if 'attachmentId' in att:
|
||||||
|
filepath = download_attachment(msg['id'], att['attachmentId'], att['filename'])
|
||||||
|
if filepath:
|
||||||
|
downloaded += 1
|
||||||
|
print(f"已下载: {att['filename']} -> {filepath}")
|
||||||
|
print(f"\n完成! 成功下载了 {downloaded}/{total_attachments} 个附件到 'attachments' 目录")
|
||||||
else:
|
else:
|
||||||
print("未找到对话记录")
|
print("未找到对话记录")
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
{"access_token": "ya29.a0AeXRPp7jfizXj6NfUavzABcDOEH-PD7TckkeCwQWFksLZwkFXqAaVe1gCeZZIWtP4hMD3F2-K7-hdSQAzCiyP7Z_72YPr_r3UDfflr6XOV7czJ937QknW_236kLJK1DDggpB4BFCB1cXtSJa6V4pbyez4lkka021mZWegOdGaCgYKATkSARISFQHGX2Mi7Ua3f90G-JLtvzu2YJgvoA0175", "client_id": "240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com", "client_secret": "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", "refresh_token": "1//0eUNnePCc4hEKCgYIARAAGA4SNwF-L9IrIRfxg1JwnMu0WHZAu-uCWT7cx1PncId3bPZr4x53Mo57xGXr2WjiF4szuSQQkeL4hNU", "token_expiry": "2025-03-27T06:13:12Z", "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.a0AeXRPp7jfizXj6NfUavzABcDOEH-PD7TckkeCwQWFksLZwkFXqAaVe1gCeZZIWtP4hMD3F2-K7-hdSQAzCiyP7Z_72YPr_r3UDfflr6XOV7czJ937QknW_236kLJK1DDggpB4BFCB1cXtSJa6V4pbyez4lkka021mZWegOdGaCgYKATkSARISFQHGX2Mi7Ua3f90G-JLtvzu2YJgvoA0175", "expires_in": 3599, "scope": "https://www.googleapis.com/auth/gmail.modify", "token_type": "Bearer", "refresh_token_expires_in": 599654}, "scopes": ["https://www.googleapis.com/auth/gmail.modify"], "token_info_uri": "https://oauth2.googleapis.com/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"}
|
{"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"}
|
@ -1 +0,0 @@
|
|||||||
{"token": "ya29.a0AeXRPp5MvarP9U4JZ3ZlV_xpIeHNL7IK1HEQLX6qhY-thS5IOFsdHrcNwbdNqkYrClFm4yGiMCXzWO-IilVbdSrDfEDw-Qfs7V9oTsZS2RYjbgcSR9rLUtZlL8ObnaljQie5VVzQZysHPZcMSdWiqabcQq2UsU5TNuOWo4J7_waCgYKAQESARISFQHGX2MiAR1O_HNYd0F6z0Rj-vSybA0177", "refresh_token": "1//0e1Usc30XxlXRCgYIARAAGA4SNwF-L9IrOOjEN7EkOSatDMHzTy0KaNdp_jf1XtLn7a5pFSljo3IKnrDdvVnxTO_FX5Asj_UDiAw", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com", "client_secret": "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", "scopes": ["https://www.googleapis.com/auth/gmail.readonly"], "universe_domain": "googleapis.com", "account": "", "expiry": "2025-03-27T04:39:31.274041Z"}
|
|
10
gmail_attachments/1960eec066745682_test2.txt
Normal file
10
gmail_attachments/1960eec066745682_test2.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
本期节目内容简介
|
||||||
|
在参加各类比赛时,肯定会遇到竞争对手。英语单词 rival、opponent、competitor 和 contestant 的含义相似,都可以用来指“与他人之间存在竞争关系的人或团队”。在本集《你问我答》节目中,我们将通过和体育比赛有关的实例来为大家阐释这四个近义词之间的区别和用法。
|
||||||
|
|
||||||
|
欢迎你加入并和我们一起讨论英语学习的方方面面。请通过微博“BBC英语教学”或邮件与我们取得联系。我们的邮箱地址是 questions.chinaelt@bbc.co.uk。
|
||||||
|
|
||||||
|
文字稿
|
||||||
|
(关于台词的备注: 请注意这不是广播节目的逐字稿件。本文稿可能没有体现录制、编辑过程中对节目做出的改变。)
|
||||||
|
|
||||||
|
Feifei
|
||||||
|
大家好,欢迎收听 BBC 英语教学的《你问我答》节目,我是冯菲菲。每集节目中,我们会回答大家在英语学习时遇到的一个问题。本集的问题来自 Adela。我们来听一下她的问题。
|
@ -0,0 +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"}
|
@ -142,7 +142,7 @@ TIME_ZONE = 'Asia/Shanghai'
|
|||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
USE_TZ = False
|
USE_TZ = True # 将此项设置为True以启用时区支持
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -294,3 +294,30 @@ REST_FRAMEWORK = {
|
|||||||
'rest_framework.parsers.MultiPartParser'
|
'rest_framework.parsers.MultiPartParser'
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Gmail API配置
|
||||||
|
GOOGLE_CLOUD_PROJECT = 'knowledge-454905' # 更新为当前使用的项目ID
|
||||||
|
GMAIL_API_SCOPES = ['https://mail.google.com/']
|
||||||
|
GMAIL_TOPIC_NAME = 'gmail-watch-topic'
|
||||||
|
|
||||||
|
# Gmail webhook地址 (开发环境使用本机内网穿透地址)
|
||||||
|
GMAIL_WEBHOOK_URL = 'https://a7a4-116-227-35-74.ngrok-free.app/api/user/gmail/webhook/'
|
||||||
|
|
||||||
|
# 如果在生产环境,使用以下固定地址
|
||||||
|
# GMAIL_WEBHOOK_URL = 'https://你的域名/api/user/gmail/webhook/'
|
||||||
|
|
||||||
|
# 媒体文件目录
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
|
# 时区设置
|
||||||
|
TIME_ZONE = 'Asia/Shanghai'
|
||||||
|
USE_I18N = True
|
||||||
|
USE_L10N = True
|
||||||
|
USE_TZ = True # 将此项设置为True以启用时区支持
|
||||||
|
|
||||||
|
# DeepSeek API配置
|
||||||
|
DEEPSEEK_API_KEY = "sk-xqbujijjqqmlmlvkhvxeogqjtzslnhdtqxqgiyuhwpoqcjvf"
|
||||||
|
# 注意:这里需要更新为有效的DeepSeek API密钥
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ 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 # 直接导入视图函数
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# 管理后台
|
# 管理后台
|
||||||
@ -26,6 +27,10 @@ urlpatterns = [
|
|||||||
# API路由
|
# API路由
|
||||||
path('api/', include('user_management.urls')),
|
path('api/', include('user_management.urls')),
|
||||||
|
|
||||||
|
# 专用Gmail Webhook路由 - 直接匹配根路径
|
||||||
|
path('api/user/gmail/webhook/', gmail_webhook, name='root_gmail_webhook'), # 修改为正确路径
|
||||||
|
path('gmail/webhook/', gmail_webhook, name='alt_gmail_webhook'), # 添加备用路径
|
||||||
|
|
||||||
# 媒体文件服务
|
# 媒体文件服务
|
||||||
*static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),
|
*static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),
|
||||||
|
|
||||||
|
1
temp_client_secret.json
Normal file
1
temp_client_secret.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"installed": {"client_id": "240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com", "project_id": "first-renderer-454910-c1", "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"]}}
|
2354
user_management/gmail_integration.py
Normal file
2354
user_management/gmail_integration.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
|||||||
|
# 管理命令包
|
@ -0,0 +1 @@
|
|||||||
|
# Gmail管理命令
|
@ -0,0 +1,80 @@
|
|||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from user_management.models import GmailCredential, User
|
||||||
|
from user_management.gmail_integration import GmailIntegration
|
||||||
|
import logging
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '更新Gmail凭证中的邮箱信息'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('--email', type=str, help='指定用户邮箱')
|
||||||
|
parser.add_argument('--gmail', type=str, help='指定要设置的Gmail邮箱')
|
||||||
|
parser.add_argument('--all', action='store_true', help='更新所有凭证')
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
email = options.get('email')
|
||||||
|
gmail = options.get('gmail')
|
||||||
|
update_all = options.get('all')
|
||||||
|
|
||||||
|
if update_all:
|
||||||
|
# 更新所有凭证
|
||||||
|
credentials = GmailCredential.objects.filter(is_active=True)
|
||||||
|
self.stdout.write(f"找到 {credentials.count()} 个活跃的Gmail凭证")
|
||||||
|
|
||||||
|
for credential in credentials:
|
||||||
|
self._update_credential(credential, gmail)
|
||||||
|
elif email:
|
||||||
|
# 更新指定用户的凭证
|
||||||
|
try:
|
||||||
|
user = User.objects.get(email=email)
|
||||||
|
credentials = GmailCredential.objects.filter(user=user, is_active=True)
|
||||||
|
|
||||||
|
if not credentials.exists():
|
||||||
|
raise CommandError(f"未找到用户 {email} 的Gmail凭证")
|
||||||
|
|
||||||
|
for credential in credentials:
|
||||||
|
self._update_credential(credential, gmail)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
raise CommandError(f"未找到用户 {email}")
|
||||||
|
else:
|
||||||
|
self.stdout.write("请提供--email参数或--all参数")
|
||||||
|
|
||||||
|
def _update_credential(self, credential, gmail=None):
|
||||||
|
"""更新单个凭证"""
|
||||||
|
user = credential.user
|
||||||
|
self.stdout.write(f"正在更新用户 {user.email} 的Gmail凭证...")
|
||||||
|
|
||||||
|
if gmail:
|
||||||
|
# 如果指定了Gmail邮箱,直接使用
|
||||||
|
credential.gmail_email = gmail
|
||||||
|
credential.save()
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"已手动设置Gmail邮箱为: {gmail}"))
|
||||||
|
return
|
||||||
|
|
||||||
|
# 尝试使用API获取Gmail邮箱
|
||||||
|
try:
|
||||||
|
# 从凭证数据中恢复服务
|
||||||
|
creds = pickle.loads(credential.credentials)
|
||||||
|
|
||||||
|
if creds and not creds.invalid:
|
||||||
|
# 创建Gmail集成实例
|
||||||
|
integration = GmailIntegration(user=user)
|
||||||
|
integration.credentials = creds
|
||||||
|
|
||||||
|
# 尝试调用API获取用户资料
|
||||||
|
profile = integration.gmail_service.users().getProfile(userId='me').execute()
|
||||||
|
gmail_email = profile.get('emailAddress')
|
||||||
|
|
||||||
|
if gmail_email:
|
||||||
|
credential.gmail_email = gmail_email
|
||||||
|
credential.save()
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"已更新Gmail邮箱为: {gmail_email}"))
|
||||||
|
else:
|
||||||
|
self.stdout.write(self.style.WARNING("无法从API获取Gmail邮箱"))
|
||||||
|
else:
|
||||||
|
self.stdout.write(self.style.ERROR("凭证无效,请重新授权"))
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(self.style.ERROR(f"更新失败: {str(e)}"))
|
@ -0,0 +1,71 @@
|
|||||||
|
# Generated by Django 5.1.5 on 2025-04-09 15:16
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('user_management', '0004_knowledgebasedocument'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GmailAttachment',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('gmail_message_id', models.CharField(max_length=100, verbose_name='Gmail消息ID')),
|
||||||
|
('filename', models.CharField(max_length=255, verbose_name='文件名')),
|
||||||
|
('filepath', models.CharField(max_length=500, verbose_name='文件路径')),
|
||||||
|
('mimetype', models.CharField(max_length=100, verbose_name='MIME类型')),
|
||||||
|
('filesize', models.IntegerField(default=0, verbose_name='文件大小')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('chat_message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gmail_attachments', to='user_management.chathistory')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Gmail附件',
|
||||||
|
'verbose_name_plural': 'Gmail附件',
|
||||||
|
'db_table': 'gmail_attachments',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GmailCredential',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('token_path', models.CharField(max_length=255, verbose_name='Token存储路径')),
|
||||||
|
('last_history_id', models.CharField(blank=True, max_length=100, null=True, verbose_name='上次同步历史ID')),
|
||||||
|
('watch_expiration', models.DateTimeField(blank=True, null=True, verbose_name='监听过期时间')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='是否激活')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gmail_credentials', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Gmail认证凭据',
|
||||||
|
'verbose_name_plural': 'Gmail认证凭据',
|
||||||
|
'db_table': 'gmail_credentials',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GmailTalentMapping',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('talent_email', models.EmailField(max_length=254, verbose_name='达人邮箱')),
|
||||||
|
('conversation_id', models.CharField(max_length=100, verbose_name='对话ID')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='是否激活')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gmail_mappings', to='user_management.knowledgebase')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gmail_talent_mappings', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Gmail达人映射',
|
||||||
|
'verbose_name_plural': 'Gmail达人映射',
|
||||||
|
'db_table': 'gmail_talent_mappings',
|
||||||
|
'unique_together': {('user', 'talent_email')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.5 on 2025-04-09 16:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('user_management', '0005_gmailattachment_gmailcredential_gmailtalentmapping'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='gmailcredential',
|
||||||
|
name='credentials',
|
||||||
|
field=models.BinaryField(blank=True, null=True, verbose_name='序列化的凭证数据'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,57 @@
|
|||||||
|
# Generated by Django 5.1.5 on 2025-04-10 09:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('user_management', '0006_gmailcredential_credentials'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='gmailcredential',
|
||||||
|
options={},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gmailcredential',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gmailcredential',
|
||||||
|
name='credentials',
|
||||||
|
field=models.BinaryField(default=b''),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gmailcredential',
|
||||||
|
name='is_active',
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gmailcredential',
|
||||||
|
name='last_history_id',
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gmailcredential',
|
||||||
|
name='token_path',
|
||||||
|
field=models.CharField(max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gmailcredential',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gmailcredential',
|
||||||
|
name='watch_expiration',
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterModelTable(
|
||||||
|
name='gmailcredential',
|
||||||
|
table='gmail_credential',
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.5 on 2025-04-11 04:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('user_management', '0007_alter_gmailcredential_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='auto_recommend_reply',
|
||||||
|
field=models.BooleanField(default=False, help_text='是否启用自动推荐回复功能'),
|
||||||
|
),
|
||||||
|
]
|
@ -396,12 +396,13 @@ class UserProfile(models.Model):
|
|||||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
|
||||||
department = models.CharField(max_length=100, blank=True, help_text="部门")
|
department = models.CharField(max_length=100, blank=True, help_text="部门")
|
||||||
group = models.CharField(max_length=100, blank=True, help_text="小组")
|
group = models.CharField(max_length=100, blank=True, help_text="小组")
|
||||||
|
auto_recommend_reply = models.BooleanField(default=False, help_text="是否启用自动推荐回复功能")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'user_profiles'
|
db_table = 'user_profiles'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.user.username}'s profile"
|
return f"{self.user.username}的个人资料"
|
||||||
|
|
||||||
class KnowledgeBasePermission(models.Model):
|
class KnowledgeBasePermission(models.Model):
|
||||||
"""知识库权限模型 - 实现知识库和用户的多对多关系"""
|
"""知识库权限模型 - 实现知识库和用户的多对多关系"""
|
||||||
@ -707,3 +708,61 @@ class KnowledgeBaseDocument(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.knowledge_base.name} - {self.document_name}"
|
return f"{self.knowledge_base.name} - {self.document_name}"
|
||||||
|
|
||||||
|
class GmailCredential(models.Model):
|
||||||
|
"""Gmail认证信息"""
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gmail_credentials')
|
||||||
|
gmail_email = models.EmailField(max_length=255, null=True, blank=True, help_text="实际授权的Gmail账号,可能与user.email不同")
|
||||||
|
credentials = models.BinaryField() # 序列化的凭证对象
|
||||||
|
token_path = models.CharField(max_length=255) # token存储路径
|
||||||
|
last_history_id = models.CharField(max_length=255, null=True, blank=True) # 最后处理的historyId
|
||||||
|
watch_expiration = models.DateTimeField(null=True, blank=True) # 监听过期时间
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'gmail_credential'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.username}的Gmail认证"
|
||||||
|
|
||||||
|
class GmailTalentMapping(models.Model):
|
||||||
|
"""Gmail达人映射关系模型"""
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gmail_talent_mappings')
|
||||||
|
talent_email = models.EmailField(verbose_name='达人邮箱')
|
||||||
|
knowledge_base = models.ForeignKey(KnowledgeBase, on_delete=models.CASCADE, related_name='gmail_mappings')
|
||||||
|
conversation_id = models.CharField(max_length=100, verbose_name='对话ID')
|
||||||
|
is_active = models.BooleanField(default=True, verbose_name='是否激活')
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'gmail_talent_mappings'
|
||||||
|
unique_together = ['user', 'talent_email']
|
||||||
|
verbose_name = 'Gmail达人映射'
|
||||||
|
verbose_name_plural = 'Gmail达人映射'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.username} - {self.talent_email}"
|
||||||
|
|
||||||
|
class GmailAttachment(models.Model):
|
||||||
|
"""Gmail附件模型"""
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
chat_message = models.ForeignKey(ChatHistory, on_delete=models.CASCADE, related_name='gmail_attachments')
|
||||||
|
gmail_message_id = models.CharField(max_length=100, verbose_name='Gmail消息ID')
|
||||||
|
filename = models.CharField(max_length=255, verbose_name='文件名')
|
||||||
|
filepath = models.CharField(max_length=500, verbose_name='文件路径')
|
||||||
|
mimetype = models.CharField(max_length=100, verbose_name='MIME类型')
|
||||||
|
filesize = models.IntegerField(default=0, verbose_name='文件大小')
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'gmail_attachments'
|
||||||
|
verbose_name = 'Gmail附件'
|
||||||
|
verbose_name_plural = 'Gmail附件'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.filename} ({self.gmail_message_id})"
|
||||||
|
@ -13,7 +13,19 @@ from .views import (
|
|||||||
RegisterView,
|
RegisterView,
|
||||||
LoginView,
|
LoginView,
|
||||||
LogoutView,
|
LogoutView,
|
||||||
ChatHistoryViewSet
|
ChatHistoryViewSet,
|
||||||
|
user_profile,
|
||||||
|
user_register,
|
||||||
|
setup_gmail_integration,
|
||||||
|
send_gmail_message,
|
||||||
|
gmail_webhook,
|
||||||
|
get_gmail_attachments,
|
||||||
|
download_gmail_attachment,
|
||||||
|
get_gmail_talents,
|
||||||
|
refresh_gmail_watch,
|
||||||
|
check_gmail_auth,
|
||||||
|
import_gmail_from_sender,
|
||||||
|
sync_talent_emails
|
||||||
)
|
)
|
||||||
|
|
||||||
# 创建路由器
|
# 创建路由器
|
||||||
@ -42,4 +54,16 @@ urlpatterns = [
|
|||||||
path('users/<str:pk>/', user_detail, name='user-detail'),
|
path('users/<str:pk>/', user_detail, name='user-detail'),
|
||||||
path('users/<str:pk>/update/', user_update, name='user-update'),
|
path('users/<str:pk>/update/', user_update, name='user-update'),
|
||||||
path('users/<str:pk>/delete/', user_delete, name='user-delete'),
|
path('users/<str:pk>/delete/', user_delete, name='user-delete'),
|
||||||
|
|
||||||
|
# Gmail集成API
|
||||||
|
path('gmail/setup/', setup_gmail_integration, name='setup_gmail_integration'),
|
||||||
|
path('gmail/send/', send_gmail_message, name='send_gmail_message'),
|
||||||
|
path('gmail/webhook/', gmail_webhook, name='gmail_webhook'),
|
||||||
|
path('gmail/attachments/', get_gmail_attachments, name='get_gmail_attachments'),
|
||||||
|
path('gmail/download/', download_gmail_attachment, name='download_gmail_attachment'),
|
||||||
|
path('gmail/talents/', get_gmail_talents, name='get_gmail_talents'),
|
||||||
|
path('gmail/refresh-watch/', refresh_gmail_watch, name='refresh_gmail_watch'),
|
||||||
|
path('gmail/check-auth/', check_gmail_auth, name='check_gmail_auth'),
|
||||||
|
path('gmail/import-from-sender/', import_gmail_from_sender, name='import_gmail_from_sender'),
|
||||||
|
path('gmail/sync-talent/', sync_talent_emails, name='sync_talent_emails'),
|
||||||
]
|
]
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user