zxlwq commited on
Commit
8b025f7
·
verified ·
1 Parent(s): eb288b8

Upload 4 files

Browse files
Files changed (4) hide show
  1. .gitattributes +35 -35
  2. README.md +10 -10
  3. main.py +1062 -0
  4. requirements.txt +16 -0
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,10 +1,10 @@
1
- ---
2
- title: T
3
- emoji: 🏆
4
- colorFrom: purple
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ ---
2
+ title: T
3
+ emoji: 📊
4
+ colorFrom: gray
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
main.py ADDED
@@ -0,0 +1,1062 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import asyncio
3
+ import json
4
+ import logging
5
+ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
6
+ from telegram.ext import (
7
+ Application,
8
+ CommandHandler,
9
+ ConversationHandler,
10
+ MessageHandler,
11
+ CallbackQueryHandler,
12
+ CallbackContext,
13
+ ContextTypes,
14
+ filters,
15
+ )
16
+ import httpx
17
+
18
+ from pikpakapi import PikPakApi
19
+
20
+ from typing import Union, Any, Dict, List, Optional
21
+ from fastapi import (
22
+ FastAPI,
23
+ APIRouter,
24
+ Depends,
25
+ Request,
26
+ Query,
27
+ Body,
28
+ Path,
29
+ Response,
30
+ HTTPException,
31
+ status,
32
+ Request,
33
+ )
34
+ from fastapi.responses import StreamingResponse, HTMLResponse, JSONResponse
35
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
36
+ from fastapi.templating import Jinja2Templates
37
+ from fastapi.middleware.cors import CORSMiddleware
38
+ from pydantic import BaseModel, Extra
39
+
40
+
41
+ class PostRequest(BaseModel):
42
+ class Config:
43
+ extra = Extra.allow
44
+
45
+
46
+ class FileRequest(BaseModel):
47
+ size: int = 100
48
+ parent_id: str | None = ""
49
+ next_page_token: str | None = ""
50
+ additional_filters: Dict | None = {}
51
+
52
+ class Config:
53
+ extra = Extra.allow
54
+
55
+
56
+ class OfflineRequest(BaseModel):
57
+ file_url: str = ""
58
+ parent_id: str | None = ""
59
+ name: str | None = ""
60
+
61
+ class Config:
62
+ extra = Extra.allow
63
+
64
+
65
+ security = HTTPBearer()
66
+ # SECRET_TOKEN = "SECRET_TOKEN"
67
+ SECRET_TOKEN = os.getenv("SECRET_TOKEN")
68
+ if SECRET_TOKEN is None:
69
+ raise ValueError("请在环境变量中设置SECRET_TOKEN,确保安全!")
70
+
71
+ THUNDERX_USERNAME = os.getenv("THUNDERX_USERNAME")
72
+ if THUNDERX_USERNAME is None:
73
+ raise ValueError("请在环境变量中设置THUNDERX_USERNAME,用户名【邮箱】用来登陆!")
74
+
75
+
76
+ THUNDERX_PASSWORD = os.getenv("THUNDERX_PASSWORD")
77
+ if THUNDERX_PASSWORD is None:
78
+ raise ValueError("请在环境变量中设置THUNDERX_PASSWORD,密码用来登陆!")
79
+
80
+ PROXY_URL = os.getenv("PROXY_URL")
81
+ TG_BOT_TOKEN = os.getenv("TG_BOT_TOKEN")
82
+ TG_WEBHOOK_URL = os.getenv("TG_WEBHOOK_URL")
83
+
84
+
85
+ async def verify_token(
86
+ request: Request, credentials: HTTPAuthorizationCredentials = Depends(security)
87
+ ):
88
+ # excluded_paths = ["/"] # 需要排除的路径列表
89
+ # if request.url.path in excluded_paths:
90
+ # return # 直接跳过验证
91
+
92
+ # 验证Bearer格式
93
+ if credentials.scheme != "Bearer":
94
+ raise HTTPException(
95
+ status_code=status.HTTP_401_UNAUTHORIZED,
96
+ detail="Invalid authentication scheme",
97
+ )
98
+
99
+ # 验证令牌内容
100
+ if credentials.credentials != SECRET_TOKEN:
101
+ raise HTTPException(
102
+ status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token"
103
+ )
104
+
105
+
106
+ def format_bytes(size: int) -> str:
107
+ # 预设单位
108
+ units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
109
+
110
+ # 确保字节数是正数
111
+ if size < 0:
112
+ raise ValueError("字节大小不能为负数")
113
+
114
+ # 选择合适的单位
115
+ unit_index = 0
116
+ while size >= 1024 and unit_index < len(units) - 1:
117
+ size /= 1024.0
118
+ unit_index += 1
119
+
120
+ # 格式化输出,保留两位小数
121
+ return f"{size:.2f} {units[unit_index]}"
122
+
123
+
124
+ app = FastAPI()
125
+
126
+
127
+ app.add_middleware(
128
+ CORSMiddleware,
129
+ allow_origins=["*"],
130
+ allow_credentials=True,
131
+ allow_methods=["*"],
132
+ allow_headers=["*"],
133
+ )
134
+
135
+ api_router = APIRouter(dependencies=[Depends(verify_token)])
136
+ front_router = APIRouter()
137
+
138
+ templates = Jinja2Templates(
139
+ directory="templates", variable_start_string="{[", variable_end_string="]}"
140
+ )
141
+
142
+
143
+ async def log_token(THUNDERX_CLIENT, extra_data):
144
+ logging.info(f"Token: {THUNDERX_CLIENT.encoded_token}, Extra Data: {extra_data}")
145
+
146
+
147
+ THUNDERX_CLIENT = None
148
+ TG_BOT_APPLICATION = None
149
+ TG_BASE_URL = "https://tg.alist.dpdns.org/bot"
150
+
151
+
152
+ ###################TG机器人功能区###################
153
+ # ❗❗❗❗❗❗❗❗❗注意TG机器人callbackdata不能超过64位,否则会报无效按钮的错误
154
+ # 定义命令处理函数
155
+ async def start(update: Update, context):
156
+ commands = (
157
+ "🚀欢迎使用我的机器人!\n\n"
158
+ "📋可用命令:\n"
159
+ "•直接发送magent:开头的磁力将直接离线下载\n"
160
+ "•直接发送分享码:开头的分享ID将直接离线下载\n"
161
+ "•/tasks - 查看下载任务\n"
162
+ "•/files - 查看文件列表\n"
163
+ "•/shares - 查看分享列表\n"
164
+ "•/quota - 查看存储空间\n"
165
+ "•/emptytrash - 清空回收站\n"
166
+ "•/help - 获取帮助信息\n"
167
+ )
168
+ await update.message.reply_text(commands)
169
+
170
+
171
+ async def help(update: Update, context):
172
+ commands = (
173
+ "🚀欢迎使用我的机器人!\n\n"
174
+ "📋可用命令:\n"
175
+ "•直接发送magent:开头的磁力将直接离线下载\n"
176
+ "•直接发送分享码:开头的分享ID将直接离线下载\n"
177
+ "•/tasks - 查看下载任务\n"
178
+ "•/files - 查看文件列表\n"
179
+ "•/shares - 查看分享列表\n"
180
+ "•/quota - 查看存储空间\n"
181
+ "•/emptytrash - 清空回收站\n"
182
+ "•/help - 获取帮助信息\n"
183
+ )
184
+ await update.message.reply_text(commands)
185
+
186
+
187
+ async def quota(update: Update, context):
188
+ """
189
+ 返回信息
190
+ {
191
+ "kind": "drive#about",
192
+ "quota": {
193
+ "kind": "drive#quota",
194
+ "limit": "72057604737418240",
195
+ "usage": "18700975438",
196
+ "usage_in_trash": "0",
197
+ "play_times_limit": "2",
198
+ "play_times_usage": "0",
199
+ "is_unlimited": true
200
+ },
201
+ "expires_at": "2026-04-08T21:47:59.000+08:00",
202
+ "quotas": {}
203
+ }
204
+ """
205
+ quota_info = await THUNDERX_CLIENT.get_quota_info()
206
+ if quota_info["quota"]["usage"] is None:
207
+ await update.message.reply_text("❌未找到使用信息,请稍后再试!")
208
+ else:
209
+ await update.message.reply_text(
210
+ f"✅使用信息:\n{format_bytes(int(quota_info['quota']['usage']))}/{format_bytes(int(quota_info['quota']['limit']))}\n⏰到期时间:\n{quota_info['expires_at']}"
211
+ )
212
+
213
+
214
+ async def tg_emptytrash(update: Update, context):
215
+ """
216
+ 返回信息
217
+ """
218
+ result = await THUNDERX_CLIENT.emptytrash()
219
+ if result["task_id"] is None:
220
+ await update.message.reply_text("❌未成功创建任务,请稍后重试!!")
221
+ else:
222
+ await update.message.reply_text(f"✅操作成功")
223
+
224
+
225
+ # 消息处理
226
+ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
227
+ text = update.message.text
228
+ if text.lower().startswith("magnet:"):
229
+ result = await THUNDERX_CLIENT.offline_download(text, "", "")
230
+ if result["task"]["id"] is not None:
231
+ await update.message.reply_text(f"✅操作成功")
232
+ else:
233
+ await update.message.reply_text(f"❌未成功创建任务,请稍后重试!!")
234
+ elif text.lower().startswith("分享码:"):
235
+ share_id = text.split(":")[1]
236
+ result = await THUNDERX_CLIENT.restore(share_id, None, None)
237
+ if isinstance(result, str):
238
+ await update.message.reply_text(f"❌未成功创建任务:{result},请稍后重试!!")
239
+ else:
240
+ await update.message.reply_text(f"操作结果:{result['restore_status']}")
241
+
242
+ else:
243
+ await update.message.reply_text(f"收到不支持的消息:{text}")
244
+
245
+
246
+ # 消息处理
247
+ async def handle_copy_text(update: Update, context: CallbackContext):
248
+ query = update.callback_query
249
+ await query.answer()
250
+
251
+ # 获取操作类型和文件 ID
252
+ action, text = (query.data.split(":")[0], query.data.split(":", 1)[1])
253
+ await query.edit_message_text(f"{text}")
254
+
255
+
256
+ #################### 分享操作 #############################
257
+ async def tg_show_shares(update: Update, context: CallbackContext):
258
+ shares = await THUNDERX_CLIENT.get_share_list("")
259
+ keyboard = []
260
+
261
+ if shares["data"] is None:
262
+ await update.message.reply_text("❌未找到分享!!")
263
+ else:
264
+ # 为每个文件创建按钮和操作选项
265
+ for share in shares["data"]:
266
+ keyboard.append(
267
+ [
268
+ InlineKeyboardButton(
269
+ f"{share['title']}",
270
+ callback_data=f"copy_text:{share['share_id']}",
271
+ ),
272
+ InlineKeyboardButton(
273
+ f"{share['share_id']}",
274
+ callback_data=f"copy_text:分享码:{share['share_id']}",
275
+ ),
276
+ InlineKeyboardButton(
277
+ f"取消",
278
+ callback_data=f"del_s:{share['share_id']}",
279
+ ),
280
+ ]
281
+ )
282
+ reply_markup = InlineKeyboardMarkup(keyboard)
283
+ await update.message.reply_text(f"📋分享列表:", reply_markup=reply_markup)
284
+
285
+
286
+ # 处理任务操作的回调
287
+ async def handle_share_operation(update: Update, context: CallbackContext):
288
+ query = update.callback_query
289
+ await query.answer()
290
+
291
+ # 获取操作类型和文件 ID
292
+ action, share_id = (query.data.split(":")[0], query.data.split(":")[1])
293
+
294
+ # 需要确认的操作
295
+ if action in ["del_s"]:
296
+ # 生成确认消息
297
+ keyboard = [
298
+ [InlineKeyboardButton("确认", callback_data=f"yes_s_{action}:{share_id}")],
299
+ [InlineKeyboardButton("取消", callback_data=f"no_s_{action}:{share_id}")],
300
+ ]
301
+ reply_markup = InlineKeyboardMarkup(keyboard)
302
+ await query.edit_message_text(
303
+ f"你确定要{action}分享 {share_id} 吗?", reply_markup=reply_markup
304
+ )
305
+
306
+
307
+ async def handle_share_confirmation(update: Update, context: CallbackContext):
308
+ query = update.callback_query
309
+ await query.answer()
310
+
311
+ # 获取确认操作的类型和文件 ID
312
+ action, share_id = (query.data.split(":")[0], query.data.split(":")[1])
313
+
314
+ if action == "yes_s_del_s":
315
+ await THUNDERX_CLIENT.share_batch_delete([share_id])
316
+ await query.edit_message_text(f"✅分享 {share_id} 已取消。")
317
+
318
+
319
+ async def handle_share_cancel(update: Update, context: CallbackContext):
320
+ query = update.callback_query
321
+ await query.answer()
322
+ await query.edit_message_text(f"操作已取消")
323
+
324
+
325
+ #################### 文件操作 #############################
326
+
327
+
328
+ async def tg_show_files(update: Update, context: CallbackContext):
329
+ files = await THUNDERX_CLIENT.file_list(100, "", "", {})
330
+ keyboard = []
331
+
332
+ if files["files"] is None:
333
+ await update.message.reply_text("❌未找到文件!!")
334
+ else:
335
+ # 为每个文件创建按钮和操作选项
336
+ for file in files["files"]:
337
+ if file["kind"].lower() == "drive#folder":
338
+ keyboard.append(
339
+ [
340
+ InlineKeyboardButton(
341
+ f"查看📁: {file['name']}",
342
+ callback_data=f"ls_f:{file['id']}:{file['parent_id']}",
343
+ ),
344
+ InlineKeyboardButton(
345
+ f"删除",
346
+ callback_data=f"del_f:{file['id']}:{file['parent_id']}",
347
+ ),
348
+ InlineKeyboardButton(
349
+ f"分享",
350
+ callback_data=f"sh_f:{file['id']}:{file['parent_id']}",
351
+ ),
352
+ ]
353
+ )
354
+ else:
355
+ keyboard.append(
356
+ [
357
+ InlineKeyboardButton(
358
+ f"下载📄: {file['name']}",
359
+ callback_data=f"dw_f:{file['id']}:{file['parent_id']}",
360
+ ),
361
+ InlineKeyboardButton(
362
+ f"删除",
363
+ callback_data=f"del_f:{file['id']}:{file['parent_id']}",
364
+ ),
365
+ InlineKeyboardButton(
366
+ f"分享",
367
+ callback_data=f"sh_f:{file['id']}:{file['parent_id']}",
368
+ ),
369
+ ]
370
+ )
371
+
372
+ reply_markup = InlineKeyboardMarkup(keyboard)
373
+ await update.message.reply_text(f"📋文件列表:", reply_markup=reply_markup)
374
+
375
+
376
+ async def handle_file_confirmation(update: Update, context: CallbackContext):
377
+ query = update.callback_query
378
+ await query.answer()
379
+
380
+ # 获取确认操作的类型和文件 ID
381
+ action, file_id = (query.data.split(":")[0], query.data.split(":")[1])
382
+
383
+ if action == "yes_f_del_f":
384
+ await THUNDERX_CLIENT.delete_forever([file_id])
385
+ await query.edit_message_text(f"✅文件 {file_id} 已删除。")
386
+
387
+
388
+ async def handle_file_cancel(update: Update, context: CallbackContext):
389
+ query = update.callback_query
390
+ await query.answer()
391
+ # 获取取消操作的类型和文件 ID
392
+ # action, file_id, parent_id = (
393
+ # query.data.split(":")[0],
394
+ # query.data.split(":")[1],
395
+ # query.data.split(":")[2],
396
+ # )
397
+ # 返回文件夹列表
398
+ await query.edit_message_text(f"操作已取消")
399
+
400
+
401
+ # 处理任务操作的回调
402
+ async def handle_file_operation(update: Update, context: CallbackContext):
403
+ query = update.callback_query
404
+ await query.answer()
405
+
406
+ # 获取操作类型和文件 ID
407
+ action, file_id, parent_id = (
408
+ query.data.split(":")[0],
409
+ query.data.split(":")[1],
410
+ query.data.split(":")[2],
411
+ )
412
+
413
+ # 需要确认的操作
414
+ if action in ["del_f"]:
415
+ # 生成确认消息
416
+ keyboard = [
417
+ [InlineKeyboardButton("确认", callback_data=f"yes_f_{action}:{file_id}")],
418
+ [InlineKeyboardButton("取消", callback_data=f"no_f_{action}:{file_id}")],
419
+ ]
420
+ reply_markup = InlineKeyboardMarkup(keyboard)
421
+ await query.edit_message_text(
422
+ f"你确定要{action}文件 {file_id} 吗?", reply_markup=reply_markup
423
+ )
424
+ else:
425
+ # 不需要确认的操作,直接处理
426
+ await perform_file_action(update, context, action, file_id, parent_id)
427
+
428
+
429
+ async def perform_file_action(
430
+ update: Update, context: CallbackContext, action: str, file_id: str, parent_id: str
431
+ ):
432
+
433
+ if action == "ls_f":
434
+ files = await THUNDERX_CLIENT.file_list(100, file_id, "", {})
435
+ keyboard = []
436
+
437
+ if files["files"] is None:
438
+ await update.message.reply_text("❌未找到文件!!")
439
+ else:
440
+ keyboard.append(
441
+ [
442
+ InlineKeyboardButton(
443
+ f"↩️返回���级",
444
+ callback_data=f"ls_f:{parent_id}:{parent_id}",
445
+ ),
446
+ ]
447
+ )
448
+ # 为每个文件创建按钮和操作选项
449
+ for file in files["files"]:
450
+ if file["kind"].lower() == "drive#folder":
451
+ keyboard.append(
452
+ [
453
+ InlineKeyboardButton(
454
+ f"查看📁: {file['name']}",
455
+ callback_data=f"ls_f:{file['id']}:{file['parent_id']}",
456
+ ),
457
+ InlineKeyboardButton(
458
+ f"删除",
459
+ callback_data=f"del_f:{file['id']}:{file['parent_id']}",
460
+ ),
461
+ InlineKeyboardButton(
462
+ f"分享",
463
+ callback_data=f"sh_f:{file['id']}:{file['parent_id']}",
464
+ ),
465
+ ]
466
+ )
467
+ else:
468
+ keyboard.append(
469
+ [
470
+ InlineKeyboardButton(
471
+ f"下载📄: {file['name']}",
472
+ callback_data=f"dw_f:{file['id']}:{file['parent_id']}",
473
+ ),
474
+ InlineKeyboardButton(
475
+ f"删除",
476
+ callback_data=f"del_f:{file['id']}:{file['parent_id']}",
477
+ ),
478
+ InlineKeyboardButton(
479
+ f"分享",
480
+ callback_data=f"sh_f:{file['id']}:{file['parent_id']}",
481
+ ),
482
+ ]
483
+ )
484
+
485
+ reply_markup = InlineKeyboardMarkup(keyboard)
486
+ # await update.message.reply_text(f"📋文件列表:", reply_markup=reply_markup)
487
+ await update.callback_query.edit_message_text(
488
+ f"📋文件列表:", reply_markup=reply_markup
489
+ )
490
+ elif action == "dw_f":
491
+ result = await THUNDERX_CLIENT.get_download_url(file_id)
492
+ download_url = result["web_content_link"]
493
+ for media in result["medias"]:
494
+ if media["link"]["url"] is not None:
495
+ download_url = media["link"]["url"]
496
+ break
497
+ if download_url is not None:
498
+ await update.callback_query.edit_message_text(f"{download_url}")
499
+ else:
500
+ await update.callback_query.edit_message_text(f"❌未找到文件下载地址!!")
501
+ elif action == "sh_f":
502
+ result = await THUNDERX_CLIENT.file_batch_share([file_id], False, -1)
503
+ share_id = result["share_id"]
504
+ if share_id is not None:
505
+ await update.callback_query.edit_message_text(f"分享码:{share_id}")
506
+ else:
507
+ await update.callback_query.edit_message_text(f"❌分享失败!!")
508
+
509
+
510
+ #################### 离线任务处理 ##########################
511
+ # 确认操作的回调
512
+ async def handle_task_confirmation(update: Update, context: CallbackContext):
513
+ query = update.callback_query
514
+ await query.answer()
515
+
516
+ # 获取确认操作的类型和文件 ID
517
+ action, task_id = query.data.split(":")[0], query.data.split(":")[1]
518
+
519
+ if action == "confirm_task_delete_task":
520
+ await THUNDERX_CLIENT.delete_tasks([task_id])
521
+ await query.edit_message_text(f"✅任务 {task_id} 已删除。")
522
+
523
+
524
+ async def handle_task_cancel(update: Update, context: CallbackContext):
525
+ query = update.callback_query
526
+ await query.answer()
527
+ # 获取取消操作的类型和文件 ID
528
+ action, file_id = query.data.split(":")[0], query.data.split(":")[1]
529
+ # 返回文件夹列表
530
+ await query.edit_message_text(f"操作已取消")
531
+
532
+
533
+ async def tg_show_task(update: Update, context: CallbackContext):
534
+ """
535
+ {
536
+ "tasks": [
537
+ {
538
+ "kind": "drive#task",
539
+ "id": "VONrJ4Skj4Qs7ALhxXlFudfJAA",
540
+ "name": "Billy Elliot (2000) 1080p (Deep61)[TGx]",
541
+ "type": "offline",
542
+ "user_id": "2000403406",
543
+ "statuses": [],
544
+ "status_size": 2,
545
+ "params": {
546
+ "folder_type": "",
547
+ "predict_type": "1",
548
+ "url": "magnet:?xt=urn:btih:96451E6F1ADBC8827B43621B74EDB30DF45012D6"
549
+ },
550
+ "file_id": "VONrJ4dZ8zf9KVWQuVEKmW8sTT",
551
+ "file_name": "Billy Elliot (2000) 1080p (Deep61)[TGx]",
552
+ "file_size": "3748030421",
553
+ "message": "Task timeout",
554
+ "created_time": "2025-04-15T10:38:54.320+08:00",
555
+ "updated_time": "2025-04-17T10:39:12.189+08:00",
556
+ "third_task_id": "",
557
+ "phase": "PHASE_TYPE_ERROR",
558
+ "progress": 0,
559
+ "icon_link": "https://backstage-img.xunleix.com/65d616355857aef8af40b89f187a8cf2770cb0ce",
560
+ "callback": "",
561
+ "reference_resource": {
562
+ "@type": "type.googleapis.com/drive.ReferenceFile",
563
+ "kind": "drive#folder",
564
+ "id": "VONrJ4dZ8zf9KVWQuVEKmW8sTT",
565
+ "parent_id": "VONS0fwXf3FNvt-g_IlMVKPxAA",
566
+ "name": "Billy Elliot (2000) 1080p (Deep61)[TGx]",
567
+ "size": "3748030421",
568
+ "mime_type": "",
569
+ "icon_link": "https://backstage-img.xunleix.com/65d616355857aef8af40b89f187a8cf2770cb0ce",
570
+ "hash": "",
571
+ "phase": "PHASE_TYPE_ERROR",
572
+ "audit": null,
573
+ "thumbnail_link": "",
574
+ "params": {},
575
+ "space": "",
576
+ "medias": [],
577
+ "starred": false,
578
+ "tags": []
579
+ },
580
+ "space": ""
581
+ }
582
+ ],
583
+ "next_page_token": "",
584
+ "expires_in": 60,
585
+ "expires_in_ms": 60000
586
+ }
587
+ """
588
+ tasks = await THUNDERX_CLIENT.offline_list(
589
+ size=100,
590
+ next_page_token=None,
591
+ phase=None,
592
+ )
593
+ keyboard = []
594
+
595
+ if tasks["tasks"] is None:
596
+ await update.message.reply_text("❌未找到任务!!")
597
+ else:
598
+ # 为每个文件创建按钮和操作选项
599
+ for task in tasks["tasks"]:
600
+ # 为每个文件添加操作按钮:删除
601
+ keyboard.append(
602
+ [
603
+ InlineKeyboardButton(
604
+ f"取消任务: {task['name']}",
605
+ callback_data=f"delete_task:{task['id']}",
606
+ ),
607
+ ]
608
+ )
609
+
610
+ reply_markup = InlineKeyboardMarkup(keyboard)
611
+ await update.message.reply_text(f"📋任务列表:", reply_markup=reply_markup)
612
+
613
+
614
+ # 处理任务操作的回调
615
+ async def handle_tasks_operation(update: Update, context: CallbackContext):
616
+ query = update.callback_query
617
+ await query.answer()
618
+
619
+ # 获取操作类型和文件 ID
620
+ action, task_id = query.data.split(":")
621
+
622
+ # 需要确认的操作
623
+ if action in ["delete_task"]:
624
+ # 生成确认消息
625
+ keyboard = [
626
+ [
627
+ InlineKeyboardButton(
628
+ "确认", callback_data=f"confirm_task_{action}:{task_id}"
629
+ )
630
+ ],
631
+ [
632
+ InlineKeyboardButton(
633
+ "取消", callback_data=f"cancel_task_{action}:{task_id}"
634
+ )
635
+ ],
636
+ ]
637
+ reply_markup = InlineKeyboardMarkup(keyboard)
638
+ await query.edit_message_text(
639
+ f"你确定要{action}任务 {task_id} 吗?", reply_markup=reply_markup
640
+ )
641
+ else:
642
+ # 不需要确认的操作,直接处理
643
+ await perform_task_action(update, context, action, task_id)
644
+
645
+
646
+ async def perform_task_action(
647
+ update: Update, context: CallbackContext, action: str, file_id: str
648
+ ):
649
+ if action == "cancel_task":
650
+ await update.callback_query.edit_message_text(f"你选择了取消任务:{file_id}")
651
+
652
+
653
+ @app.on_event("startup")
654
+ async def init_client():
655
+ global THUNDERX_CLIENT
656
+ global TG_BOT_APPLICATION
657
+ if not os.path.exists("thunderx.txt"):
658
+ THUNDERX_CLIENT = PikPakApi(
659
+ username=THUNDERX_USERNAME,
660
+ password=THUNDERX_PASSWORD,
661
+ httpx_client_args=None,
662
+ token_refresh_callback=log_token,
663
+ token_refresh_callback_kwargs={"extra_data": "test"},
664
+ )
665
+ await THUNDERX_CLIENT.login()
666
+ await THUNDERX_CLIENT.refresh_access_token()
667
+ with open("thunderx.json", "w") as f:
668
+ f.write(json.dumps(THUNDERX_CLIENT.to_dict(), indent=4))
669
+ else:
670
+ with open("thunderx.txt", "r") as f:
671
+ data = json.load(f)
672
+ THUNDERX_CLIENT = PikPakApi.from_dict(data)
673
+ # await client.refresh_access_token()
674
+ print(json.dumps(THUNDERX_CLIENT.get_user_info(), indent=4))
675
+
676
+ print(
677
+ json.dumps(
678
+ await THUNDERX_CLIENT.events(),
679
+ indent=4,
680
+ )
681
+ )
682
+
683
+ if TG_BOT_TOKEN is None:
684
+ print("未设置TG_BOT_TOKEN无法实现TG机器人功能!")
685
+ else:
686
+ TG_BOT_APPLICATION = (
687
+ Application.builder().base_url(TG_BASE_URL).token(TG_BOT_TOKEN).build()
688
+ )
689
+ # await TG_BOT_APPLICATION.bot.delete_webhook()
690
+ await TG_BOT_APPLICATION.bot.set_webhook(
691
+ url=TG_WEBHOOK_URL, allowed_updates=Update.ALL_TYPES
692
+ )
693
+
694
+ await TG_BOT_APPLICATION.bot.set_my_commands(
695
+ commands=[
696
+ ("/start", "开始"),
697
+ ("/tasks", "查看下载任务"),
698
+ ("/files", "查看文件列表"),
699
+ ("/shares", "查看分享列表"),
700
+ ("/quota", "查看存储空间"),
701
+ ("/emptytrash", "清空回收���"),
702
+ ("/help", "获取帮助信息"),
703
+ ]
704
+ )
705
+ TG_BOT_APPLICATION.add_handler(
706
+ CallbackQueryHandler(handle_tasks_operation, pattern="^delete_task:")
707
+ )
708
+ # 处理取消任务操作
709
+ TG_BOT_APPLICATION.add_handler(
710
+ CallbackQueryHandler(handle_task_cancel, pattern="^cancel_task")
711
+ )
712
+ # 处理确认操作(确认删除、复制等)
713
+ TG_BOT_APPLICATION.add_handler(
714
+ CallbackQueryHandler(handle_task_confirmation, pattern="^confirm_task")
715
+ )
716
+
717
+ ########## 分享操作 ###############
718
+ TG_BOT_APPLICATION.add_handler(
719
+ CallbackQueryHandler(handle_share_operation, pattern="^del_s:")
720
+ )
721
+ # 处理取消任务操作
722
+ TG_BOT_APPLICATION.add_handler(
723
+ CallbackQueryHandler(handle_share_cancel, pattern="^no_s")
724
+ )
725
+ # 处理确认操作(确认删除、复制等)
726
+ TG_BOT_APPLICATION.add_handler(
727
+ CallbackQueryHandler(handle_share_confirmation, pattern="^yes_s")
728
+ )
729
+
730
+ ########## 文件操作 ###############
731
+
732
+ TG_BOT_APPLICATION.add_handler(
733
+ CallbackQueryHandler(
734
+ handle_file_operation, pattern="^(del_f|ls_f|dw_f|sh_f):"
735
+ )
736
+ )
737
+ # 处理取消任务操作
738
+ TG_BOT_APPLICATION.add_handler(
739
+ CallbackQueryHandler(handle_file_cancel, pattern="^no_f")
740
+ )
741
+ # 处理确认操作(确认删除、复制等)
742
+ TG_BOT_APPLICATION.add_handler(
743
+ CallbackQueryHandler(handle_file_confirmation, pattern="^yes_f")
744
+ )
745
+
746
+ TG_BOT_APPLICATION.add_handler(CommandHandler("start", start))
747
+ TG_BOT_APPLICATION.add_handler(CommandHandler("help", help))
748
+ TG_BOT_APPLICATION.add_handler(CommandHandler("quota", quota))
749
+ TG_BOT_APPLICATION.add_handler(CommandHandler("emptytrash", tg_emptytrash))
750
+ TG_BOT_APPLICATION.add_handler(CommandHandler("tasks", tg_show_task))
751
+ TG_BOT_APPLICATION.add_handler(CommandHandler("files", tg_show_files))
752
+ TG_BOT_APPLICATION.add_handler(CommandHandler("shares", tg_show_shares))
753
+ # Message 消息处理相关的命令!
754
+ TG_BOT_APPLICATION.add_handler(MessageHandler(filters.TEXT, handle_message))
755
+ # 处理取消任务操作
756
+ TG_BOT_APPLICATION.add_handler(
757
+ CallbackQueryHandler(handle_copy_text, pattern="^copy_text")
758
+ )
759
+ await TG_BOT_APPLICATION.initialize()
760
+
761
+
762
+ # FastAPI 路由:接收来自 Telegram 的 Webhook 回调
763
+ @app.post("/webhook")
764
+ async def webhook(request: Request):
765
+ # 从请求获取 JSON 数据
766
+ data = await request.json()
767
+
768
+ # 将 Telegram Update 转换为 Update 对象
769
+ update = Update.de_json(data, TG_BOT_APPLICATION.bot)
770
+
771
+ # 将 Update 对象传递给 Application 进行处理
772
+ await TG_BOT_APPLICATION.process_update(update)
773
+
774
+ return JSONResponse({"status": "ok"})
775
+
776
+
777
+ @front_router.get(
778
+ "/",
779
+ response_class=HTMLResponse,
780
+ summary="前台页面",
781
+ description="前台管理页面,需要在设置里设置SECRET_TOKEN才能正常请求",
782
+ tags=["前端"],
783
+ )
784
+ async def home(request: Request):
785
+ return templates.TemplateResponse("index.html", {"request": request})
786
+
787
+
788
+ @api_router.post(
789
+ "/files", summary="文件列表", description="获取文件列表", tags=["文件"]
790
+ )
791
+ async def get_files(item: FileRequest):
792
+ return await THUNDERX_CLIENT.file_list(
793
+ item.size, item.parent_id, item.next_page_token, item.additional_filters
794
+ )
795
+
796
+
797
+ @api_router.post(
798
+ "/file_star_list", summary="加星文件列表", description="加星文件列表", tags=["文件"]
799
+ )
800
+ async def file_star_list(
801
+ size: int = Query(default=100, title="显示数量", description="显示数量"),
802
+ next_page_token: str | None = Query(
803
+ default=None, title="分页Token", description="分页Token"
804
+ ),
805
+ ):
806
+ return await THUNDERX_CLIENT.file_star_list(size, next_page_token)
807
+
808
+
809
+ @api_router.get(
810
+ "/files/{file_id}", summary="文件信息", description="获取文件信息", tags=["文件"]
811
+ )
812
+ async def get_file_info(file_id: str = Path(..., title="文件ID", description="文件ID")):
813
+ return await THUNDERX_CLIENT.get_download_url(file_id)
814
+
815
+
816
+ @api_router.delete(
817
+ "/files/{file_id}", summary="删除文件", description="删除文件", tags=["文件"]
818
+ )
819
+ async def delete_file_info(
820
+ file_id: str = Path(..., title="文件ID", description="文件ID")
821
+ ):
822
+ return await THUNDERX_CLIENT.delete_forever([file_id])
823
+
824
+
825
+ @api_router.post(
826
+ "/file_rename", summary="重命名文件", description="重命名文件", tags=["文件"]
827
+ )
828
+ async def file_rename(
829
+ file_id: str = Query(title="文件ID", description="文件ID"),
830
+ new_file_name: str = Query(title="新文件名", description="新文件名"),
831
+ ):
832
+ return await THUNDERX_CLIENT.file_rename(file_id, new_file_name)
833
+
834
+
835
+ @api_router.post(
836
+ "/file_batch_copy",
837
+ summary="批量复制文件",
838
+ description="批量复制文件",
839
+ tags=["文件"],
840
+ )
841
+ async def file_batch_copy(
842
+ ids: List[str] = Body(title="文件ID列表", description="文件ID列表"),
843
+ to_parent_id: str = Query(
844
+ title="复制到的文件夹id, 默认为根目录",
845
+ description="复制到的文件夹id, 默认为根目录",
846
+ ),
847
+ ):
848
+ return await THUNDERX_CLIENT.file_batch_copy(ids, to_parent_id)
849
+
850
+
851
+ @api_router.post(
852
+ "/file_batch_move",
853
+ summary="批量移动文件",
854
+ description="批量移动文件",
855
+ tags=["文件"],
856
+ )
857
+ async def file_batch_move(
858
+ ids: List[str] = Body(title="文件ID列表", description="文件ID列表"),
859
+ to_parent_id: str = Query(
860
+ title="移动到的文件夹id, 默认为根目录",
861
+ description="移动到的文件夹id, 默认为根目录",
862
+ ),
863
+ ):
864
+ return await THUNDERX_CLIENT.file_batch_move(ids, to_parent_id)
865
+
866
+
867
+ @api_router.post(
868
+ "/create_folder", summary="新建文件夹", description="新建文件夹", tags=["文件"]
869
+ )
870
+ async def create_folder(
871
+ name: str = Query(title="文件夹名称", description="文件夹名称"),
872
+ parent_id: str = Query(
873
+ title="父文件夹id, 默认创建到根目录", description="父文件夹id, 默认创建到根目录"
874
+ ),
875
+ ):
876
+ return await THUNDERX_CLIENT.create_folder(name, parent_id)
877
+
878
+
879
+ @api_router.post(
880
+ "/delete_to_trash",
881
+ summary="将文件夹、文件移动到回收站",
882
+ description="将文件夹、文件移动到回收站",
883
+ tags=["文件"],
884
+ )
885
+ async def delete_to_trash(
886
+ ids: List[str] = Body(title="文件ID列表", description="文件ID列表")
887
+ ):
888
+ return await THUNDERX_CLIENT.delete_to_trash(ids)
889
+
890
+
891
+ @api_router.post(
892
+ "/delete_forever",
893
+ summary="将文件夹、文件彻底删除",
894
+ description="将文件夹、文件彻底删除",
895
+ tags=["文件"],
896
+ )
897
+ async def delete_forever(
898
+ ids: List[str] = Body(title="文件ID列表", description="文件ID列表")
899
+ ):
900
+ return await THUNDERX_CLIENT.delete_forever(ids)
901
+
902
+
903
+ @api_router.post(
904
+ "/untrash",
905
+ summary="将文件夹、文件移出回收站",
906
+ description="将文件夹、文件移出回收站",
907
+ tags=["文件"],
908
+ )
909
+ async def untrash(ids: List[str] = Body(title="文件ID列表", description="文件ID列表")):
910
+ return await THUNDERX_CLIENT.untrash(ids)
911
+
912
+
913
+ @api_router.post(
914
+ "/file_batch_star",
915
+ summary="批量给文件加星标",
916
+ description="批量给文件加星标",
917
+ tags=["文件"],
918
+ )
919
+ async def file_batch_star(
920
+ ids: List[str] = Body(title="文件ID列表", description="文件ID列表")
921
+ ):
922
+ return await THUNDERX_CLIENT.file_batch_star(ids)
923
+
924
+
925
+ @api_router.post(
926
+ "/file_batch_unstar",
927
+ summary="批量给文件加星标",
928
+ description="批量给文件加星标",
929
+ tags=["文件"],
930
+ )
931
+ async def file_batch_unstar(
932
+ ids: List[str] = Body(title="文件ID列表", description="文件ID列表")
933
+ ):
934
+ return await THUNDERX_CLIENT.file_batch_unstar(ids)
935
+
936
+
937
+ @api_router.post(
938
+ "/emptytrash", summary="清空回收站", description="清空回收站【慎用】", tags=["文件"]
939
+ )
940
+ async def emptytrash():
941
+ return await THUNDERX_CLIENT.emptytrash()
942
+
943
+
944
+ ############## 分享 ################
945
+ @api_router.post(
946
+ "/get_share_list",
947
+ summary="获取账号分享列表",
948
+ description="获取账号分享列表",
949
+ tags=["分享"],
950
+ )
951
+ async def get_share_list(
952
+ page_token: str | None = Query(
953
+ default=None, title="分页Token", description="分页Token"
954
+ )
955
+ ):
956
+ return await THUNDERX_CLIENT.get_share_list(page_token)
957
+
958
+
959
+ @api_router.post(
960
+ "/file_batch_share", summary="创建分享", description="创建分享", tags=["分享"]
961
+ )
962
+ async def file_batch_share(
963
+ ids: List[str] = Body(default=None, title="文件ID列表", description="文件ID列表"),
964
+ need_password: bool | None = Query(
965
+ default=False, title="是否需要密码", description="是否需要密码"
966
+ ),
967
+ expiration_days: int | None = Query(
968
+ default=-1, title="过期时间", description="过期时间【天数,默认永远】"
969
+ ),
970
+ ):
971
+ return await THUNDERX_CLIENT.file_batch_share(ids, need_password, expiration_days)
972
+
973
+
974
+ @api_router.post(
975
+ "/share_batch_delete", summary="取消分享", description="取消分享", tags=["分享"]
976
+ )
977
+ async def share_batch_delete(
978
+ ids: List[str] = Body(title="文件ID列表", description="文件ID列表")
979
+ ):
980
+ return await THUNDERX_CLIENT.share_batch_delete(ids)
981
+
982
+
983
+ @api_router.post(
984
+ "/get_share_folder",
985
+ summary="获取分享信息",
986
+ description="获取分享信息",
987
+ tags=["分享"],
988
+ )
989
+ async def get_share_folder(
990
+ share_id: str = Query(title="分享ID", description="分享ID"),
991
+ pass_code_token: str | None = Query(default=None, title="密码", description="密码"),
992
+ parent_id: str | None = Query(default=None, title="父ID", description="父ID"),
993
+ ):
994
+ return await THUNDERX_CLIENT.get_share_folder(share_id, pass_code_token, parent_id)
995
+
996
+
997
+ @api_router.post(
998
+ "/restore", summary="转存分享文件", description="转存分享文件", tags=["分享"]
999
+ )
1000
+ async def restore(
1001
+ share_id: str, pass_code_token: str | None = None, file_ids: List[str] | None = None
1002
+ ):
1003
+ return await THUNDERX_CLIENT.restore(share_id, pass_code_token, file_ids)
1004
+
1005
+
1006
+ ############## 离线任务 ################
1007
+
1008
+
1009
+ @api_router.get(
1010
+ "/offline", summary="离线任务列表", description="离线任务列表", tags=["离线任务"]
1011
+ )
1012
+ async def offline_list(size: int = 10000, next_page_token: str | None = None):
1013
+ return await THUNDERX_CLIENT.offline_list(
1014
+ size=size,
1015
+ next_page_token=next_page_token,
1016
+ phase=None,
1017
+ )
1018
+
1019
+
1020
+ @api_router.post(
1021
+ "/offline", summary="添加离线任务", description="添加离线任务", tags=["离线任务"]
1022
+ )
1023
+ async def offline(item: OfflineRequest):
1024
+ return await THUNDERX_CLIENT.offline_download(
1025
+ item.file_url, item.parent_id, item.name
1026
+ )
1027
+
1028
+
1029
+ @api_router.post(
1030
+ "/delete_tasks",
1031
+ summary="删除离线任务",
1032
+ description="删除离线任务",
1033
+ tags=["离线任务"],
1034
+ )
1035
+ async def delete_tasks(task_ids: List[str], delete_files: bool = False):
1036
+ return await THUNDERX_CLIENT.delete_tasks(task_ids, delete_files)
1037
+
1038
+
1039
+ ############## 账号 ################
1040
+ @api_router.get(
1041
+ "/userinfo", summary="用户信息", description="获取用户登陆信息", tags=["账号"]
1042
+ )
1043
+ async def userinfo():
1044
+ return THUNDERX_CLIENT.get_user_info()
1045
+
1046
+
1047
+ @api_router.get(
1048
+ "/quota", summary="空间使用信息", description="获取空间使用信息", tags=["账号"]
1049
+ )
1050
+ async def quota_info():
1051
+ return await THUNDERX_CLIENT.get_quota_info()
1052
+
1053
+
1054
+ @api_router.get(
1055
+ "/invite_code", summary="查看邀请码", description="查看邀请码", tags=["账号"]
1056
+ )
1057
+ async def get_invite_code():
1058
+ return await THUNDERX_CLIENT.get_invite_code()
1059
+
1060
+
1061
+ app.include_router(front_router)
1062
+ app.include_router(api_router)
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ python-telegram-bot
3
+ pydantic-settings
4
+ requests
5
+ jinja2
6
+ aiofiles
7
+ uvicorn
8
+ sqlalchemy
9
+ fastapi-utilities
10
+ bs4
11
+ httpx
12
+ datetime
13
+ cachelib
14
+ python-multipart
15
+ hashids
16
+ asyncio