Sync from GitHub: 8be01a61a2247c26411c1cd49f4aaf0985690108
Browse files- app.py +46 -57
- test_auth.py +26 -30
app.py
CHANGED
|
@@ -11,13 +11,12 @@ from urllib.parse import urlparse, unquote
|
|
| 11 |
import google.auth.exceptions
|
| 12 |
|
| 13 |
# ---------------------------------------------------------
|
| 14 |
-
# 0. 配置日志
|
| 15 |
# ---------------------------------------------------------
|
| 16 |
-
#
|
| 17 |
-
http.client.HTTPConnection.debuglevel = 1
|
| 18 |
|
| 19 |
-
|
| 20 |
-
logging.basicConfig(level=logging.DEBUG)
|
| 21 |
logger = logging.getLogger(__name__)
|
| 22 |
|
| 23 |
# ---------------------------------------------------------
|
|
@@ -32,12 +31,7 @@ def get_drive_service():
|
|
| 32 |
refresh_token = os.environ.get("G_REFRESH_TOKEN")
|
| 33 |
|
| 34 |
if not all([client_id, client_secret, refresh_token]):
|
| 35 |
-
|
| 36 |
-
raise EnvironmentError("❌ 缺少必要的 OAuth 环境变量 (G_CLIENT_ID, G_CLIENT_SECRET, G_REFRESH_TOKEN)")
|
| 37 |
-
|
| 38 |
-
logger.info("🔑 正在构建凭据对象...")
|
| 39 |
-
logger.debug(f"Client ID: {client_id[:5]}...")
|
| 40 |
-
logger.debug(f"Refresh Token: {refresh_token[:5]}...")
|
| 41 |
|
| 42 |
creds = Credentials(
|
| 43 |
token=None,
|
|
@@ -69,11 +63,12 @@ class StreamingUploadFile(io.IOBase):
|
|
| 69 |
raise
|
| 70 |
|
| 71 |
def seek(self, offset, whence=io.SEEK_SET):
|
|
|
|
| 72 |
if whence == io.SEEK_SET and offset == self.position:
|
| 73 |
return self.position
|
| 74 |
if whence == io.SEEK_CUR and offset == 0:
|
| 75 |
return self.position
|
| 76 |
-
|
| 77 |
return self.position
|
| 78 |
|
| 79 |
def tell(self):
|
|
@@ -94,21 +89,13 @@ def process_upload(file_url, progress=gr.Progress()):
|
|
| 94 |
return "❌ 错误: 请输入有效的 URL"
|
| 95 |
|
| 96 |
try:
|
| 97 |
-
# ---
|
| 98 |
-
logger.info("🔍 开始上传前验证 Token...")
|
| 99 |
try:
|
| 100 |
service = get_drive_service()
|
| 101 |
-
# 尝试做一个轻量级请求来验证 Token
|
| 102 |
-
service.about().get(fields="user").execute()
|
| 103 |
-
logger.info("✅ Token 验证通过!")
|
| 104 |
-
except google.auth.exceptions.RefreshError as re:
|
| 105 |
-
logger.error(f"❌ Token 刷新失败 (无效或过期): {re}")
|
| 106 |
-
return f"❌ **鉴权失败**: Refresh Token 无效或已过期。\n详情: {re}\n请重新生成 Token。"
|
| 107 |
except Exception as e:
|
| 108 |
-
|
| 109 |
-
return f"❌ **鉴权错误**: 无法连接 Google Drive API。\n详情: {e}"
|
| 110 |
|
| 111 |
-
# ---
|
| 112 |
progress(0, desc="🚀 初始化连接...")
|
| 113 |
logger.info(f"📥 开始下载 URL: {file_url}")
|
| 114 |
|
|
@@ -117,21 +104,15 @@ def process_upload(file_url, progress=gr.Progress()):
|
|
| 117 |
|
| 118 |
filename = get_filename_from_response(response, file_url)
|
| 119 |
filesize = int(response.headers.get('Content-Length', 0))
|
| 120 |
-
|
| 121 |
msg_size = f"{filesize / 1024 / 1024:.2f} MB" if filesize > 0 else "未知大小"
|
| 122 |
-
|
| 123 |
-
|
| 124 |
|
| 125 |
-
# ---
|
| 126 |
folder_id = os.environ.get("GDRIVE_FOLDER_ID")
|
| 127 |
file_metadata = {'name': filename}
|
| 128 |
-
if folder_id:
|
| 129 |
-
|
| 130 |
-
if folder_id.strip():
|
| 131 |
-
file_metadata['parents'] = [folder_id]
|
| 132 |
-
logger.info(f"📂 目标文件夹 ID: {folder_id}")
|
| 133 |
-
else:
|
| 134 |
-
logger.warning("⚠️ GDRIVE_FOLDER_ID 为空,将上传到根目录")
|
| 135 |
|
| 136 |
stream_wrapper = StreamingUploadFile(response)
|
| 137 |
|
|
@@ -142,8 +123,7 @@ def process_upload(file_url, progress=gr.Progress()):
|
|
| 142 |
chunksize=10 * 1024 * 1024
|
| 143 |
)
|
| 144 |
|
| 145 |
-
progress(0.2, desc="☁️ 正在流式上传
|
| 146 |
-
logger.info("🚀 发起 create 请求...")
|
| 147 |
|
| 148 |
request = service.files().create(
|
| 149 |
body=file_metadata,
|
|
@@ -151,53 +131,62 @@ def process_upload(file_url, progress=gr.Progress()):
|
|
| 151 |
fields='id, webContentLink, webViewLink'
|
| 152 |
)
|
| 153 |
|
| 154 |
-
# ---
|
| 155 |
file = None
|
| 156 |
-
|
| 157 |
-
while
|
| 158 |
-
status,
|
| 159 |
if status:
|
| 160 |
progress_percent = int(status.progress() * 100)
|
| 161 |
-
#
|
| 162 |
-
logger.debug(f"⏳ 上传进度: {progress_percent}%")
|
| 163 |
|
| 164 |
-
file =
|
| 165 |
file_id = file.get('id')
|
| 166 |
logger.info(f"✅ 上传完成,File ID: {file_id}")
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
|
| 175 |
-
web_link = file.get('webContentLink', file.get('webViewLink'))
|
| 176 |
return f"""✅ **转存成功!**
|
| 177 |
|
| 178 |
**文件名**: {filename}
|
| 179 |
-
**
|
| 180 |
-
**
|
| 181 |
"""
|
| 182 |
|
| 183 |
except BrokenPipeError:
|
| 184 |
-
logger.error("❌ BrokenPipeError
|
| 185 |
-
return "❌ **上传中断**: 连接被 Google 拒绝。
|
| 186 |
except Exception as e:
|
| 187 |
-
logger.error(f"❌
|
| 188 |
return f"❌ **发生错误**: {str(e)}"
|
| 189 |
|
| 190 |
# ---------------------------------------------------------
|
| 191 |
# 3. 构建界面
|
| 192 |
# ---------------------------------------------------------
|
| 193 |
with gr.Blocks(title="URL to Drive Saver") as demo:
|
| 194 |
-
gr.Markdown("# 🚀 URL to Google Drive Saver
|
| 195 |
|
| 196 |
with gr.Row():
|
| 197 |
url_input = gr.Textbox(label="文件 URL", placeholder="https://example.com/video.mp4")
|
| 198 |
submit_btn = gr.Button("开始转存", variant="primary")
|
| 199 |
|
| 200 |
-
output_markdown = gr.Markdown(label="
|
| 201 |
|
| 202 |
submit_btn.click(
|
| 203 |
fn=process_upload,
|
|
|
|
| 11 |
import google.auth.exceptions
|
| 12 |
|
| 13 |
# ---------------------------------------------------------
|
| 14 |
+
# 0. 配置日志
|
| 15 |
# ---------------------------------------------------------
|
| 16 |
+
# 关闭过于详细的 HTTP 调试日志,以免刷屏
|
| 17 |
+
# http.client.HTTPConnection.debuglevel = 1
|
| 18 |
|
| 19 |
+
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 20 |
logger = logging.getLogger(__name__)
|
| 21 |
|
| 22 |
# ---------------------------------------------------------
|
|
|
|
| 31 |
refresh_token = os.environ.get("G_REFRESH_TOKEN")
|
| 32 |
|
| 33 |
if not all([client_id, client_secret, refresh_token]):
|
| 34 |
+
raise EnvironmentError("❌ 缺少必要的 OAuth 环境变量")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
creds = Credentials(
|
| 37 |
token=None,
|
|
|
|
| 63 |
raise
|
| 64 |
|
| 65 |
def seek(self, offset, whence=io.SEEK_SET):
|
| 66 |
+
# Google Drive Upload 可能会尝试 seek(0) 来获取大小或重试
|
| 67 |
if whence == io.SEEK_SET and offset == self.position:
|
| 68 |
return self.position
|
| 69 |
if whence == io.SEEK_CUR and offset == 0:
|
| 70 |
return self.position
|
| 71 |
+
# 忽略不支持的 seek 操作,通常不影响流式上传
|
| 72 |
return self.position
|
| 73 |
|
| 74 |
def tell(self):
|
|
|
|
| 89 |
return "❌ 错误: 请输入有效的 URL"
|
| 90 |
|
| 91 |
try:
|
| 92 |
+
# --- 1. 鉴权 ---
|
|
|
|
| 93 |
try:
|
| 94 |
service = get_drive_service()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
except Exception as e:
|
| 96 |
+
return f"❌ **鉴权错误**: {str(e)}"
|
|
|
|
| 97 |
|
| 98 |
+
# --- 2. 下载 ---
|
| 99 |
progress(0, desc="🚀 初始化连接...")
|
| 100 |
logger.info(f"📥 开始下载 URL: {file_url}")
|
| 101 |
|
|
|
|
| 104 |
|
| 105 |
filename = get_filename_from_response(response, file_url)
|
| 106 |
filesize = int(response.headers.get('Content-Length', 0))
|
|
|
|
| 107 |
msg_size = f"{filesize / 1024 / 1024:.2f} MB" if filesize > 0 else "未知大小"
|
| 108 |
+
|
| 109 |
+
progress(0.1, desc=f"📥 准备: {filename} ({msg_size})")
|
| 110 |
|
| 111 |
+
# --- 3. 上传配置 ---
|
| 112 |
folder_id = os.environ.get("GDRIVE_FOLDER_ID")
|
| 113 |
file_metadata = {'name': filename}
|
| 114 |
+
if folder_id and folder_id.strip():
|
| 115 |
+
file_metadata['parents'] = [folder_id]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
stream_wrapper = StreamingUploadFile(response)
|
| 118 |
|
|
|
|
| 123 |
chunksize=10 * 1024 * 1024
|
| 124 |
)
|
| 125 |
|
| 126 |
+
progress(0.2, desc="☁️ 正在流式上传...")
|
|
|
|
| 127 |
|
| 128 |
request = service.files().create(
|
| 129 |
body=file_metadata,
|
|
|
|
| 131 |
fields='id, webContentLink, webViewLink'
|
| 132 |
)
|
| 133 |
|
| 134 |
+
# --- 4. 执行上传 ---
|
| 135 |
file = None
|
| 136 |
+
response_upload = None
|
| 137 |
+
while response_upload is None:
|
| 138 |
+
status, response_upload = request.next_chunk()
|
| 139 |
if status:
|
| 140 |
progress_percent = int(status.progress() * 100)
|
| 141 |
+
# 可以在日志里看进度,不需要频繁打扰前端
|
| 142 |
+
# logger.debug(f"⏳ 上传进度: {progress_percent}%")
|
| 143 |
|
| 144 |
+
file = response_upload
|
| 145 |
file_id = file.get('id')
|
| 146 |
logger.info(f"✅ 上传完成,File ID: {file_id}")
|
| 147 |
|
| 148 |
+
# --- 5. 权限设置 (容错处理) ---
|
| 149 |
+
link_status = "🔒 私有文件 (仅自己可见)"
|
| 150 |
+
web_link = f"https://drive.google.com/file/d/{file_id}/view"
|
| 151 |
+
|
| 152 |
+
try:
|
| 153 |
+
progress(0.9, desc="🔓 尝试设置公开权限...")
|
| 154 |
+
service.permissions().create(
|
| 155 |
+
fileId=file_id,
|
| 156 |
+
body={'role': 'reader', 'type': 'anyone'}
|
| 157 |
+
).execute()
|
| 158 |
+
link_status = "🌍 公开链接"
|
| 159 |
+
# 获取直链
|
| 160 |
+
web_link = file.get('webContentLink', web_link)
|
| 161 |
+
except Exception as perm_err:
|
| 162 |
+
logger.warning(f"⚠️ 无法设置为公开权限 (可能是 Google 安全策略限制): {perm_err}")
|
| 163 |
+
link_status = "🔒 私有文件 (Google 拒绝了公开分享,请去网盘查看)"
|
| 164 |
|
|
|
|
| 165 |
return f"""✅ **转存成功!**
|
| 166 |
|
| 167 |
**文件名**: {filename}
|
| 168 |
+
**状态**: {link_status}
|
| 169 |
+
**文件链接**: [点击打开 Google Drive]({web_link})
|
| 170 |
"""
|
| 171 |
|
| 172 |
except BrokenPipeError:
|
| 173 |
+
logger.error("❌ BrokenPipeError")
|
| 174 |
+
return "❌ **上传中断**: 连接被 Google 拒绝。请检查网络或 Token。"
|
| 175 |
except Exception as e:
|
| 176 |
+
logger.error(f"❌ 错误: {str(e)}", exc_info=True)
|
| 177 |
return f"❌ **发生错误**: {str(e)}"
|
| 178 |
|
| 179 |
# ---------------------------------------------------------
|
| 180 |
# 3. 构建界面
|
| 181 |
# ---------------------------------------------------------
|
| 182 |
with gr.Blocks(title="URL to Drive Saver") as demo:
|
| 183 |
+
gr.Markdown("# 🚀 URL to Google Drive Saver")
|
| 184 |
|
| 185 |
with gr.Row():
|
| 186 |
url_input = gr.Textbox(label="文件 URL", placeholder="https://example.com/video.mp4")
|
| 187 |
submit_btn = gr.Button("开始转存", variant="primary")
|
| 188 |
|
| 189 |
+
output_markdown = gr.Markdown(label="结果")
|
| 190 |
|
| 191 |
submit_btn.click(
|
| 192 |
fn=process_upload,
|
test_auth.py
CHANGED
|
@@ -2,28 +2,26 @@ import os
|
|
| 2 |
import logging
|
| 3 |
from google.oauth2.credentials import Credentials
|
| 4 |
from googleapiclient.discovery import build
|
| 5 |
-
import
|
| 6 |
|
| 7 |
-
#
|
| 8 |
-
http.client.HTTPConnection.debuglevel = 1
|
| 9 |
logging.basicConfig(level=logging.DEBUG)
|
| 10 |
logger = logging.getLogger(__name__)
|
| 11 |
|
| 12 |
def test_auth_only():
|
| 13 |
-
print("
|
| 14 |
-
print("
|
| 15 |
print("="*50)
|
| 16 |
|
| 17 |
client_id = os.environ.get("G_CLIENT_ID")
|
| 18 |
client_secret = os.environ.get("G_CLIENT_SECRET")
|
| 19 |
refresh_token = os.environ.get("G_REFRESH_TOKEN")
|
| 20 |
|
| 21 |
-
print(f"Client ID: {client_id[:
|
| 22 |
-
print(f"
|
| 23 |
-
print(f"Refresh Token: {refresh_token[:10]}... (Len: {len(str(refresh_token))})")
|
| 24 |
|
| 25 |
if not all([client_id, client_secret, refresh_token]):
|
| 26 |
-
print("❌
|
| 27 |
return
|
| 28 |
|
| 29 |
try:
|
|
@@ -35,33 +33,31 @@ def test_auth_only():
|
|
| 35 |
client_secret=client_secret
|
| 36 |
)
|
| 37 |
|
| 38 |
-
|
| 39 |
-
print("\n🔄 正在尝试刷新 Access Token...")
|
| 40 |
-
from google.auth.transport.requests import Request
|
| 41 |
-
creds.refresh(Request())
|
| 42 |
-
print(f"✅ Token 刷新成功! 新 Access Token: {creds.token[:10]}...")
|
| 43 |
-
|
| 44 |
-
# 构建 Service 并调用简单的 API
|
| 45 |
-
print("\n📡 正在连接 Google Drive API...")
|
| 46 |
service = build("drive", "v3", credentials=creds)
|
| 47 |
|
| 48 |
-
print("
|
| 49 |
-
about = service.about().get(fields="user").execute()
|
| 50 |
-
|
|
|
|
|
|
|
| 51 |
|
| 52 |
print("\n" + "="*50)
|
| 53 |
-
print(
|
| 54 |
-
print(f"👋 用户名: {user_info.get('displayName')}")
|
| 55 |
-
print(f"📧 邮箱: {user_info.get('emailAddress')}")
|
| 56 |
print("="*50)
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
print("
|
| 60 |
-
print(f"
|
| 61 |
-
print(f"错误信息: {e}")
|
| 62 |
print("="*50)
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
if __name__ == "__main__":
|
| 67 |
test_auth_only()
|
|
|
|
| 2 |
import logging
|
| 3 |
from google.oauth2.credentials import Credentials
|
| 4 |
from googleapiclient.discovery import build
|
| 5 |
+
import google.auth.exceptions
|
| 6 |
|
| 7 |
+
# 配置日志
|
|
|
|
| 8 |
logging.basicConfig(level=logging.DEBUG)
|
| 9 |
logger = logging.getLogger(__name__)
|
| 10 |
|
| 11 |
def test_auth_only():
|
| 12 |
+
print("="*50)
|
| 13 |
+
print("🚀 Google Drive 鉴权独立测试")
|
| 14 |
print("="*50)
|
| 15 |
|
| 16 |
client_id = os.environ.get("G_CLIENT_ID")
|
| 17 |
client_secret = os.environ.get("G_CLIENT_SECRET")
|
| 18 |
refresh_token = os.environ.get("G_REFRESH_TOKEN")
|
| 19 |
|
| 20 |
+
print(f"Client ID (前5位): {client_id[:5] if client_id else 'MISSING'}")
|
| 21 |
+
print(f"Refresh Token (前5位): {refresh_token[:5] if refresh_token else 'MISSING'}")
|
|
|
|
| 22 |
|
| 23 |
if not all([client_id, client_secret, refresh_token]):
|
| 24 |
+
print("❌ 环境变量缺失,无法测试。")
|
| 25 |
return
|
| 26 |
|
| 27 |
try:
|
|
|
|
| 33 |
client_secret=client_secret
|
| 34 |
)
|
| 35 |
|
| 36 |
+
print("\n⏳ 正在构建 Drive 服务并刷新 Token...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
service = build("drive", "v3", credentials=creds)
|
| 38 |
|
| 39 |
+
print("🔍 正在请求用户信息 (about.get)...")
|
| 40 |
+
about = service.about().get(fields="user,storageQuota").execute()
|
| 41 |
+
|
| 42 |
+
user = about.get('user', {})
|
| 43 |
+
quota = about.get('storageQuota', {})
|
| 44 |
|
| 45 |
print("\n" + "="*50)
|
| 46 |
+
print("✅ 鉴权成功!连接正常!")
|
|
|
|
|
|
|
| 47 |
print("="*50)
|
| 48 |
+
print(f"👤 用户名: {user.get('displayName')}")
|
| 49 |
+
print(f"📧 邮箱: {user.get('emailAddress')}")
|
| 50 |
+
print(f"💾 已用空间: {int(quota.get('usage', 0)) / 1024 / 1024 / 1024:.2f} GB")
|
| 51 |
+
print(f"☁️ 总空间: {int(quota.get('limit', 0)) / 1024 / 1024 / 1024:.2f} GB")
|
|
|
|
| 52 |
print("="*50)
|
| 53 |
+
|
| 54 |
+
except google.auth.exceptions.RefreshError as e:
|
| 55 |
+
print("\n❌ 鉴权失败: Refresh Token 无效或过期")
|
| 56 |
+
print(f"详细错误: {e}")
|
| 57 |
+
print("👉 即使您觉得是新的,也请重新生成。Google 可能因为 IP 变动暂时封锁了旧 Token。")
|
| 58 |
+
except Exception as e:
|
| 59 |
+
print("\n❌ 发生未知错误")
|
| 60 |
+
print(f"详细错误: {e}")
|
| 61 |
|
| 62 |
if __name__ == "__main__":
|
| 63 |
test_auth_only()
|