mirror of
https://github.com/dnslin/aria2bot.git
synced 2026-01-11 04:02:20 +08:00
fix: 修复 多个存储策略的时候 自动删除冲突
This commit is contained in:
@@ -79,7 +79,7 @@ uv run main.py
|
|||||||
| 变量 | 说明 |
|
| 变量 | 说明 |
|
||||||
| -------------------------------------- | -------------------------- |
|
| -------------------------------------- | -------------------------- |
|
||||||
| `TELEGRAM_CHANNEL_ENABLED` | 启用频道存储(true/false) |
|
| `TELEGRAM_CHANNEL_ENABLED` | 启用频道存储(true/false) |
|
||||||
| `TELEGRAM_CHANNEL_ID` | 频道 ID 或 @username |
|
| `TELEGRAM_CHANNEL_ID` | 频道 ID |
|
||||||
| `TELEGRAM_CHANNEL_AUTO_UPLOAD` | 下载完成后自动发送 |
|
| `TELEGRAM_CHANNEL_AUTO_UPLOAD` | 下载完成后自动发送 |
|
||||||
| `TELEGRAM_CHANNEL_DELETE_AFTER_UPLOAD` | 发送后删除本地文件 |
|
| `TELEGRAM_CHANNEL_DELETE_AFTER_UPLOAD` | 发送后删除本地文件 |
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
"""Configuration dataclass for aria2bot."""
|
"""Configuration dataclass for aria2bot."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from src.core.constants import DOWNLOAD_DIR
|
from src.core.constants import DOWNLOAD_DIR
|
||||||
|
|
||||||
|
# 云存储配置持久化文件路径
|
||||||
|
CLOUD_CONFIG_FILE = Path.home() / ".config" / "aria2bot" / "cloud_config.json"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Aria2Config:
|
class Aria2Config:
|
||||||
@@ -111,3 +115,78 @@ class BotConfig:
|
|||||||
onedrive=onedrive,
|
onedrive=onedrive,
|
||||||
telegram_channel=telegram_channel,
|
telegram_channel=telegram_channel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def save_cloud_config(onedrive: OneDriveConfig, telegram: TelegramChannelConfig) -> bool:
|
||||||
|
"""保存云存储配置到文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
onedrive: OneDrive 配置
|
||||||
|
telegram: Telegram 频道配置
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否保存成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
CLOUD_CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
data = {
|
||||||
|
"onedrive": {
|
||||||
|
"auto_upload": onedrive.auto_upload,
|
||||||
|
"delete_after_upload": onedrive.delete_after_upload,
|
||||||
|
"remote_path": onedrive.remote_path,
|
||||||
|
},
|
||||||
|
"telegram_channel": {
|
||||||
|
"channel_id": telegram.channel_id,
|
||||||
|
"auto_upload": telegram.auto_upload,
|
||||||
|
"delete_after_upload": telegram.delete_after_upload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CLOUD_CONFIG_FILE.write_text(json.dumps(data, indent=2, ensure_ascii=False))
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def load_cloud_config() -> dict | None:
|
||||||
|
"""从文件加载云存储配置
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
配置字典,如果文件不存在或解析失败则返回 None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if CLOUD_CONFIG_FILE.exists():
|
||||||
|
return json.loads(CLOUD_CONFIG_FILE.read_text())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def apply_saved_config(onedrive: OneDriveConfig, telegram: TelegramChannelConfig) -> None:
|
||||||
|
"""将保存的配置应用到配置对象(文件配置优先级低于环境变量)
|
||||||
|
|
||||||
|
只有当环境变量未设置时,才使用文件中的配置
|
||||||
|
"""
|
||||||
|
saved = load_cloud_config()
|
||||||
|
if not saved:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 应用 OneDrive 配置(仅当环境变量未明确设置时)
|
||||||
|
if "onedrive" in saved:
|
||||||
|
od = saved["onedrive"]
|
||||||
|
# auto_upload: 如果环境变量未设置,使用文件配置
|
||||||
|
if not os.environ.get("ONEDRIVE_AUTO_UPLOAD"):
|
||||||
|
onedrive.auto_upload = od.get("auto_upload", False)
|
||||||
|
if not os.environ.get("ONEDRIVE_DELETE_AFTER_UPLOAD"):
|
||||||
|
onedrive.delete_after_upload = od.get("delete_after_upload", False)
|
||||||
|
if not os.environ.get("ONEDRIVE_REMOTE_PATH"):
|
||||||
|
onedrive.remote_path = od.get("remote_path", "/aria2bot")
|
||||||
|
|
||||||
|
# 应用 Telegram 频道配置
|
||||||
|
if "telegram_channel" in saved:
|
||||||
|
tg = saved["telegram_channel"]
|
||||||
|
if not os.environ.get("TELEGRAM_CHANNEL_ID"):
|
||||||
|
telegram.channel_id = tg.get("channel_id", "")
|
||||||
|
if not os.environ.get("TELEGRAM_CHANNEL_AUTO_UPLOAD"):
|
||||||
|
telegram.auto_upload = tg.get("auto_upload", False)
|
||||||
|
if not os.environ.get("TELEGRAM_CHANNEL_DELETE_AFTER_UPLOAD"):
|
||||||
|
telegram.delete_after_upload = tg.get("delete_after_upload", False)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from telegram import Bot, BotCommand
|
|||||||
from telegram.ext import Application
|
from telegram.ext import Application
|
||||||
|
|
||||||
from src.core import BotConfig, is_aria2_installed
|
from src.core import BotConfig, is_aria2_installed
|
||||||
|
from src.core.config import apply_saved_config
|
||||||
from src.aria2.service import Aria2ServiceManager, get_service_mode
|
from src.aria2.service import Aria2ServiceManager, get_service_mode
|
||||||
from src.telegram.handlers import Aria2BotAPI, build_handlers
|
from src.telegram.handlers import Aria2BotAPI, build_handlers
|
||||||
from src.utils import setup_logger
|
from src.utils import setup_logger
|
||||||
@@ -46,6 +47,9 @@ async def post_init(application: Application) -> None:
|
|||||||
|
|
||||||
def create_app(config: BotConfig) -> Application:
|
def create_app(config: BotConfig) -> Application:
|
||||||
"""创建 Telegram Application"""
|
"""创建 Telegram Application"""
|
||||||
|
# 应用保存的云存储配置
|
||||||
|
apply_saved_config(config.onedrive, config.telegram_channel)
|
||||||
|
|
||||||
builder = Application.builder().token(config.token).post_init(post_init)
|
builder = Application.builder().token(config.token).post_init(post_init)
|
||||||
if config.api_base_url:
|
if config.api_base_url:
|
||||||
builder = builder.base_url(config.api_base_url).base_file_url(config.api_base_url + "/file")
|
builder = builder.base_url(config.api_base_url).base_file_url(config.api_base_url + "/file")
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from src.core import (
|
|||||||
ARIA2_CONF,
|
ARIA2_CONF,
|
||||||
DOWNLOAD_DIR,
|
DOWNLOAD_DIR,
|
||||||
)
|
)
|
||||||
from src.core.config import OneDriveConfig, TelegramChannelConfig
|
from src.core.config import OneDriveConfig, TelegramChannelConfig, save_cloud_config
|
||||||
from src.cloud.base import UploadProgress, UploadStatus
|
from src.cloud.base import UploadProgress, UploadStatus
|
||||||
from src.aria2 import Aria2Installer, Aria2ServiceManager
|
from src.aria2 import Aria2Installer, Aria2ServiceManager
|
||||||
from src.aria2.rpc import Aria2RpcClient, DownloadTask, _format_size
|
from src.aria2.rpc import Aria2RpcClient, DownloadTask, _format_size
|
||||||
@@ -36,6 +36,9 @@ from src.telegram.keyboards import (
|
|||||||
build_cloud_menu_keyboard,
|
build_cloud_menu_keyboard,
|
||||||
build_cloud_settings_keyboard,
|
build_cloud_settings_keyboard,
|
||||||
build_detail_keyboard_with_upload,
|
build_detail_keyboard_with_upload,
|
||||||
|
build_onedrive_menu_keyboard,
|
||||||
|
build_telegram_channel_menu_keyboard,
|
||||||
|
build_telegram_channel_settings_keyboard,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reply Keyboard 按钮文本到命令的映射
|
# Reply Keyboard 按钮文本到命令的映射
|
||||||
@@ -109,6 +112,7 @@ class Aria2BotAPI:
|
|||||||
self._telegram_channel = None
|
self._telegram_channel = None
|
||||||
self._api_base_url = api_base_url
|
self._api_base_url = api_base_url
|
||||||
self._channel_uploaded_gids: set[str] = set() # 已上传到频道的 GID
|
self._channel_uploaded_gids: set[str] = set() # 已上传到频道的 GID
|
||||||
|
self._pending_channel_input: dict[int, bool] = {} # 等待用户输入频道ID
|
||||||
|
|
||||||
async def _check_permission(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
async def _check_permission(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
||||||
"""检查用户权限,返回 True 表示有权限"""
|
"""检查用户权限,返回 True 表示有权限"""
|
||||||
@@ -147,6 +151,34 @@ class Aria2BotAPI:
|
|||||||
self._telegram_channel = TelegramChannelClient(self._telegram_channel_config, bot, is_local_api)
|
self._telegram_channel = TelegramChannelClient(self._telegram_channel_config, bot, is_local_api)
|
||||||
return self._telegram_channel
|
return self._telegram_channel
|
||||||
|
|
||||||
|
def _recreate_telegram_channel_client(self, bot):
|
||||||
|
"""重新创建 Telegram 频道客户端(配置更新后调用)"""
|
||||||
|
self._telegram_channel = None
|
||||||
|
return self._get_telegram_channel_client(bot)
|
||||||
|
|
||||||
|
async def _delete_local_file(self, local_path, gid: str) -> tuple[bool, str]:
|
||||||
|
"""删除本地文件,返回 (成功, 消息)"""
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
if isinstance(local_path, str):
|
||||||
|
local_path = Path(local_path)
|
||||||
|
try:
|
||||||
|
if local_path.is_dir():
|
||||||
|
shutil.rmtree(local_path)
|
||||||
|
else:
|
||||||
|
local_path.unlink()
|
||||||
|
logger.info(f"已删除本地文件 GID={gid}: {local_path}")
|
||||||
|
return True, "🗑️ 本地文件已删除"
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"删除本地文件失败 GID={gid}: {e}")
|
||||||
|
return False, f"⚠️ 删除本地文件失败: {e}"
|
||||||
|
|
||||||
|
def _save_cloud_config(self) -> bool:
|
||||||
|
"""保存云存储配置"""
|
||||||
|
if self._onedrive_config and self._telegram_channel_config:
|
||||||
|
return save_cloud_config(self._onedrive_config, self._telegram_channel_config)
|
||||||
|
return False
|
||||||
|
|
||||||
async def _reply(self, update: Update, context: ContextTypes.DEFAULT_TYPE, text: str, **kwargs):
|
async def _reply(self, update: Update, context: ContextTypes.DEFAULT_TYPE, text: str, **kwargs):
|
||||||
if update.effective_message:
|
if update.effective_message:
|
||||||
return await update.effective_message.reply_text(text, **kwargs)
|
return await update.effective_message.reply_text(text, **kwargs)
|
||||||
@@ -688,15 +720,22 @@ class Aria2BotAPI:
|
|||||||
))
|
))
|
||||||
|
|
||||||
async def _do_auto_upload(
|
async def _do_auto_upload(
|
||||||
self, client, local_path, remote_path: str, task_name: str, chat_id: int, gid: str
|
self, client, local_path, remote_path: str, task_name: str, chat_id: int, gid: str,
|
||||||
) -> None:
|
skip_delete: bool = False
|
||||||
"""后台执行自动上传任务"""
|
) -> bool:
|
||||||
import shutil
|
"""后台执行自动上传任务
|
||||||
|
|
||||||
|
Args:
|
||||||
|
skip_delete: 是否跳过删除(用于并行上传协调)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
上传是否成功
|
||||||
|
"""
|
||||||
from .app import _bot_instance # 获取全局 bot 实例
|
from .app import _bot_instance # 获取全局 bot 实例
|
||||||
|
|
||||||
if _bot_instance is None:
|
if _bot_instance is None:
|
||||||
logger.error(f"自动上传失败:无法获取 bot 实例 GID={gid}")
|
logger.error(f"自动上传失败:无法获取 bot 实例 GID={gid}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
# 发送上传开始通知
|
# 发送上传开始通知
|
||||||
try:
|
try:
|
||||||
@@ -706,7 +745,7 @@ class Aria2BotAPI:
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"自动上传失败:发送消息失败 GID={gid}: {e}")
|
logger.error(f"自动上传失败:发送消息失败 GID={gid}: {e}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
@@ -734,26 +773,24 @@ class Aria2BotAPI:
|
|||||||
|
|
||||||
if success:
|
if success:
|
||||||
result_text = f"✅ 自动上传成功: {task_name}"
|
result_text = f"✅ 自动上传成功: {task_name}"
|
||||||
if self._onedrive_config and self._onedrive_config.delete_after_upload:
|
# 只有不跳过删除且配置了删除时才删除
|
||||||
try:
|
if not skip_delete and self._onedrive_config and self._onedrive_config.delete_after_upload:
|
||||||
if local_path.is_dir():
|
_, delete_msg = await self._delete_local_file(local_path, gid)
|
||||||
shutil.rmtree(local_path)
|
result_text += f"\n{delete_msg}"
|
||||||
else:
|
|
||||||
local_path.unlink()
|
|
||||||
result_text += "\n🗑️ 本地文件已删除"
|
|
||||||
except Exception as e:
|
|
||||||
result_text += f"\n⚠️ 删除本地文件失败: {e}"
|
|
||||||
await msg.edit_text(result_text)
|
await msg.edit_text(result_text)
|
||||||
logger.info(f"自动上传成功 GID={gid}")
|
logger.info(f"自动上传成功 GID={gid}")
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
await msg.edit_text(f"❌ 自动上传失败: {task_name}")
|
await msg.edit_text(f"❌ 自动上传失败: {task_name}")
|
||||||
logger.error(f"自动上传失败 GID={gid}")
|
logger.error(f"自动上传失败 GID={gid}")
|
||||||
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"自动上传异常 GID={gid}: {e}")
|
logger.error(f"自动上传异常 GID={gid}: {e}")
|
||||||
try:
|
try:
|
||||||
await msg.edit_text(f"❌ 自动上传失败: {task_name}\n错误: {e}")
|
await msg.edit_text(f"❌ 自动上传失败: {task_name}\n错误: {e}")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
async def _trigger_channel_auto_upload(self, chat_id: int, gid: str, bot) -> None:
|
async def _trigger_channel_auto_upload(self, chat_id: int, gid: str, bot) -> None:
|
||||||
"""触发频道自动上传"""
|
"""触发频道自动上传"""
|
||||||
@@ -793,40 +830,227 @@ class Aria2BotAPI:
|
|||||||
|
|
||||||
asyncio.create_task(self._do_channel_upload(client, local_path, task.name, chat_id, gid, bot))
|
asyncio.create_task(self._do_channel_upload(client, local_path, task.name, chat_id, gid, bot))
|
||||||
|
|
||||||
async def _do_channel_upload(self, client, local_path, task_name: str, chat_id: int, gid: str, bot) -> None:
|
async def _do_channel_upload(
|
||||||
"""执行频道上传"""
|
self, client, local_path, task_name: str, chat_id: int, gid: str, bot,
|
||||||
import shutil
|
skip_delete: bool = False
|
||||||
|
) -> bool:
|
||||||
|
"""执行频道上传
|
||||||
|
|
||||||
|
Args:
|
||||||
|
skip_delete: 是否跳过删除(用于并行上传协调)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
上传是否成功
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
msg = await bot.send_message(chat_id=chat_id, text=f"📢 正在发送到频道: {task_name}")
|
msg = await bot.send_message(chat_id=chat_id, text=f"📢 正在发送到频道: {task_name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"频道上传失败:发送消息失败 GID={gid}: {e}")
|
logger.error(f"频道上传失败:发送消息失败 GID={gid}: {e}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
success, result = await client.upload_file(local_path)
|
success, result = await client.upload_file(local_path)
|
||||||
if success:
|
if success:
|
||||||
result_text = f"✅ 已发送到频道: {task_name}"
|
result_text = f"✅ 已发送到频道: {task_name}"
|
||||||
if self._telegram_channel_config and self._telegram_channel_config.delete_after_upload:
|
# 只有不跳过删除且配置了删除时才删除
|
||||||
try:
|
if not skip_delete and self._telegram_channel_config and self._telegram_channel_config.delete_after_upload:
|
||||||
if local_path.is_dir():
|
_, delete_msg = await self._delete_local_file(local_path, gid)
|
||||||
shutil.rmtree(local_path)
|
result_text += f"\n{delete_msg}"
|
||||||
else:
|
|
||||||
local_path.unlink()
|
|
||||||
result_text += "\n🗑️ 本地文件已删除"
|
|
||||||
except Exception as e:
|
|
||||||
result_text += f"\n⚠️ 删除本地文件失败: {e}"
|
|
||||||
await msg.edit_text(result_text)
|
await msg.edit_text(result_text)
|
||||||
logger.info(f"频道上传成功 GID={gid}")
|
logger.info(f"频道上传成功 GID={gid}")
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
await msg.edit_text(f"❌ 发送到频道失败: {task_name}\n原因: {result}")
|
await msg.edit_text(f"❌ 发送到频道失败: {task_name}\n原因: {result}")
|
||||||
logger.error(f"频道上传失败 GID={gid}: {result}")
|
logger.error(f"频道上传失败 GID={gid}: {result}")
|
||||||
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"频道上传异常 GID={gid}: {e}")
|
logger.error(f"频道上传异常 GID={gid}: {e}")
|
||||||
try:
|
try:
|
||||||
await msg.edit_text(f"❌ 发送到频道失败: {task_name}\n错误: {e}")
|
await msg.edit_text(f"❌ 发送到频道失败: {task_name}\n错误: {e}")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _coordinated_auto_upload(self, chat_id: int, gid: str, task, bot) -> None:
|
||||||
|
"""协调多云存储并行上传
|
||||||
|
|
||||||
|
当 OneDrive 和 Telegram 频道都启用自动上传且都启用删除时,
|
||||||
|
并行执行上传,全部成功后才删除本地文件。
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
local_path = Path(task.dir) / task.name
|
||||||
|
if not local_path.exists():
|
||||||
|
logger.error(f"协调上传失败:本地文件不存在 GID={gid}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检测哪些云存储需要上传
|
||||||
|
need_onedrive = (
|
||||||
|
self._onedrive_config and
|
||||||
|
self._onedrive_config.enabled and
|
||||||
|
self._onedrive_config.auto_upload
|
||||||
|
)
|
||||||
|
need_telegram = (
|
||||||
|
self._telegram_channel_config and
|
||||||
|
self._telegram_channel_config.enabled and
|
||||||
|
self._telegram_channel_config.auto_upload
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检测是否需要协调删除(两个都启用删除)
|
||||||
|
onedrive_delete = need_onedrive and self._onedrive_config.delete_after_upload
|
||||||
|
telegram_delete = need_telegram and self._telegram_channel_config.delete_after_upload
|
||||||
|
need_coordinated_delete = onedrive_delete and telegram_delete
|
||||||
|
|
||||||
|
if need_coordinated_delete:
|
||||||
|
# 并行执行,跳过各自的删除,最后统一删除
|
||||||
|
logger.info(f"启动协调并行上传 GID={gid}")
|
||||||
|
await self._parallel_upload_with_coordinated_delete(
|
||||||
|
chat_id, gid, local_path, task.name, bot
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 独立执行(保持现有逻辑)
|
||||||
|
if need_onedrive and gid not in self._auto_uploaded_gids:
|
||||||
|
self._auto_uploaded_gids.add(gid)
|
||||||
|
asyncio.create_task(self._trigger_auto_upload(chat_id, gid))
|
||||||
|
|
||||||
|
if need_telegram and gid not in self._channel_uploaded_gids:
|
||||||
|
self._channel_uploaded_gids.add(gid)
|
||||||
|
asyncio.create_task(self._trigger_channel_auto_upload(chat_id, gid, bot))
|
||||||
|
|
||||||
|
async def _parallel_upload_with_coordinated_delete(
|
||||||
|
self, chat_id: int, gid: str, local_path, task_name: str, bot
|
||||||
|
) -> None:
|
||||||
|
"""并行上传到多个云存储,全部成功后才删除文件"""
|
||||||
|
from .app import _bot_instance
|
||||||
|
|
||||||
|
# 准备 OneDrive 上传参数
|
||||||
|
onedrive_client = self._get_onedrive_client()
|
||||||
|
onedrive_authenticated = onedrive_client and await onedrive_client.is_authenticated()
|
||||||
|
|
||||||
|
# 计算 OneDrive 远程路径
|
||||||
|
try:
|
||||||
|
download_dir = DOWNLOAD_DIR.resolve()
|
||||||
|
relative_path = local_path.resolve().relative_to(download_dir)
|
||||||
|
remote_path = f"{self._onedrive_config.remote_path}/{relative_path.parent}"
|
||||||
|
except ValueError:
|
||||||
|
remote_path = self._onedrive_config.remote_path
|
||||||
|
|
||||||
|
# 准备 Telegram 频道客户端
|
||||||
|
telegram_client = self._get_telegram_channel_client(bot)
|
||||||
|
|
||||||
|
# 检查文件大小是否超过 Telegram 限制
|
||||||
|
telegram_size_ok = True
|
||||||
|
if telegram_client:
|
||||||
|
file_size = local_path.stat().st_size
|
||||||
|
if file_size > telegram_client.get_max_size():
|
||||||
|
telegram_size_ok = False
|
||||||
|
limit_mb = telegram_client.get_max_size_mb()
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=chat_id,
|
||||||
|
text=f"⚠️ 文件 {task_name} 超过 {limit_mb}MB 限制,跳过频道上传"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 构建上传任务列表
|
||||||
|
tasks = []
|
||||||
|
task_names = []
|
||||||
|
|
||||||
|
if onedrive_authenticated:
|
||||||
|
tasks.append(self._do_auto_upload(
|
||||||
|
onedrive_client, local_path, remote_path, task_name, chat_id, gid,
|
||||||
|
skip_delete=True
|
||||||
|
))
|
||||||
|
task_names.append("onedrive")
|
||||||
|
|
||||||
|
if telegram_client and telegram_size_ok:
|
||||||
|
tasks.append(self._do_channel_upload(
|
||||||
|
telegram_client, local_path, task_name, chat_id, gid, bot,
|
||||||
|
skip_delete=True
|
||||||
|
))
|
||||||
|
task_names.append("telegram")
|
||||||
|
|
||||||
|
if not tasks:
|
||||||
|
logger.warning(f"协调上传跳过:没有可用的上传目标 GID={gid}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 并行执行上传
|
||||||
|
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
|
||||||
|
# 分析结果
|
||||||
|
all_success = True
|
||||||
|
for i, result in enumerate(results):
|
||||||
|
if isinstance(result, Exception):
|
||||||
|
logger.error(f"协调上传异常 ({task_names[i]}) GID={gid}: {result}")
|
||||||
|
all_success = False
|
||||||
|
elif result is not True:
|
||||||
|
all_success = False
|
||||||
|
|
||||||
|
# 只有全部成功才删除
|
||||||
|
if all_success and len(tasks) > 0:
|
||||||
|
_, delete_msg = await self._delete_local_file(local_path, gid)
|
||||||
|
if _bot_instance:
|
||||||
|
await _bot_instance.send_message(
|
||||||
|
chat_id=chat_id,
|
||||||
|
text=f"📦 所有上传完成: {task_name}\n{delete_msg}"
|
||||||
|
)
|
||||||
|
elif not all_success:
|
||||||
|
if _bot_instance:
|
||||||
|
await _bot_instance.send_message(
|
||||||
|
chat_id=chat_id,
|
||||||
|
text=f"⚠️ 部分上传失败,保留本地文件: {task_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def handle_channel_id_input(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
||||||
|
"""处理频道ID输入,返回 True 表示已处理"""
|
||||||
|
user_id = update.effective_user.id if update.effective_user else None
|
||||||
|
if not user_id or user_id not in self._pending_channel_input:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 清除等待状态
|
||||||
|
del self._pending_channel_input[user_id]
|
||||||
|
|
||||||
|
text = update.message.text.strip()
|
||||||
|
if not text:
|
||||||
|
await self._reply(update, context, "❌ 频道ID不能为空")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 验证格式
|
||||||
|
if not (text.startswith("@") or text.startswith("-100") or text.lstrip("-").isdigit()):
|
||||||
|
await self._reply(
|
||||||
|
update, context,
|
||||||
|
"❌ 无效的频道ID格式\n\n"
|
||||||
|
"请使用以下格式之一:\n"
|
||||||
|
"• `@channel_name`\n"
|
||||||
|
"• `-100xxxxxxxxxx`",
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 更新配置
|
||||||
|
if self._telegram_channel_config:
|
||||||
|
self._telegram_channel_config.channel_id = text
|
||||||
|
# 重新创建客户端
|
||||||
|
self._recreate_telegram_channel_client(context.bot)
|
||||||
|
# 保存配置
|
||||||
|
self._save_cloud_config()
|
||||||
|
await self._reply(
|
||||||
|
update, context,
|
||||||
|
f"✅ 频道ID已设置为: `{text}`\n\n"
|
||||||
|
"请确保 Bot 已被添加为频道管理员",
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self._reply(update, context, "❌ 频道配置未初始化")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def handle_text_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
"""处理文本消息(包括频道ID输入和按钮点击)"""
|
||||||
|
# 先检查是否是频道ID输入
|
||||||
|
if await self.handle_channel_id_input(update, context):
|
||||||
|
return
|
||||||
|
|
||||||
|
# 然后检查是否是按钮点击
|
||||||
|
await self.handle_button_text(update, context)
|
||||||
|
|
||||||
async def handle_button_text(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def handle_button_text(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
"""处理 Reply Keyboard 按钮点击"""
|
"""处理 Reply Keyboard 按钮点击"""
|
||||||
@@ -1209,14 +1433,25 @@ class Aria2BotAPI:
|
|||||||
|
|
||||||
# 任务完成或出错时停止刷新
|
# 任务完成或出错时停止刷新
|
||||||
if task.status in ("complete", "error", "removed"):
|
if task.status in ("complete", "error", "removed"):
|
||||||
# 任务完成时检查是否需要自动上传
|
# 任务完成时检查是否需要自动上传(使用协调上传)
|
||||||
if (task.status == "complete" and
|
if task.status == "complete" and gid not in self._auto_uploaded_gids:
|
||||||
gid not in self._auto_uploaded_gids and
|
from .app import _bot_instance
|
||||||
self._onedrive_config and
|
need_onedrive = (
|
||||||
self._onedrive_config.enabled and
|
self._onedrive_config and
|
||||||
self._onedrive_config.auto_upload):
|
self._onedrive_config.enabled and
|
||||||
self._auto_uploaded_gids.add(gid)
|
self._onedrive_config.auto_upload
|
||||||
asyncio.create_task(self._trigger_auto_upload(message.chat_id, gid))
|
)
|
||||||
|
need_telegram = (
|
||||||
|
self._telegram_channel_config and
|
||||||
|
self._telegram_channel_config.enabled and
|
||||||
|
self._telegram_channel_config.auto_upload
|
||||||
|
)
|
||||||
|
if need_onedrive or need_telegram:
|
||||||
|
self._auto_uploaded_gids.add(gid)
|
||||||
|
self._channel_uploaded_gids.add(gid)
|
||||||
|
asyncio.create_task(
|
||||||
|
self._coordinated_auto_upload(message.chat_id, gid, task, _bot_instance)
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(2)
|
||||||
@@ -1269,13 +1504,8 @@ class Aria2BotAPI:
|
|||||||
text = f"✅ *下载完成*\n📄 {safe_name}\n📦 大小: {task.size_str}\n🆔 GID: `{task.gid}`"
|
text = f"✅ *下载完成*\n📄 {safe_name}\n📦 大小: {task.size_str}\n🆔 GID: `{task.gid}`"
|
||||||
try:
|
try:
|
||||||
await _bot_instance.send_message(chat_id=chat_id, text=text, parse_mode="Markdown")
|
await _bot_instance.send_message(chat_id=chat_id, text=text, parse_mode="Markdown")
|
||||||
# 触发频道自动上传
|
# 注意:自动上传已在 _auto_refresh_task 中通过 _coordinated_auto_upload 处理
|
||||||
if (self._telegram_channel_config and
|
# 这里不再单独触发,避免重复上传
|
||||||
self._telegram_channel_config.enabled and
|
|
||||||
self._telegram_channel_config.auto_upload and
|
|
||||||
task.gid not in self._channel_uploaded_gids):
|
|
||||||
self._channel_uploaded_gids.add(task.gid)
|
|
||||||
asyncio.create_task(self._trigger_channel_auto_upload(chat_id, task.gid, _bot_instance))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"发送完成通知失败 (GID={task.gid}): {e}")
|
logger.warning(f"发送完成通知失败 (GID={task.gid}): {e}")
|
||||||
|
|
||||||
@@ -1316,11 +1546,43 @@ class Aria2BotAPI:
|
|||||||
|
|
||||||
sub_action = parts[1]
|
sub_action = parts[1]
|
||||||
|
|
||||||
if sub_action == "auth":
|
# 主菜单
|
||||||
# 认证请求
|
if sub_action == "menu":
|
||||||
|
keyboard = build_cloud_menu_keyboard()
|
||||||
|
await query.edit_message_text("☁️ *云存储管理*\n\n选择要配置的云存储:", parse_mode="Markdown", reply_markup=keyboard)
|
||||||
|
|
||||||
|
# OneDrive 相关
|
||||||
|
elif sub_action == "onedrive":
|
||||||
|
await self._handle_onedrive_callback(query, update, context, parts[2:] if len(parts) > 2 else [])
|
||||||
|
|
||||||
|
# Telegram 频道相关
|
||||||
|
elif sub_action == "telegram":
|
||||||
|
await self._handle_telegram_channel_callback(query, update, context, parts[2:] if len(parts) > 2 else [])
|
||||||
|
|
||||||
|
# 兼容旧的回调格式
|
||||||
|
elif sub_action == "auth":
|
||||||
await self.cloud_auth(update, context)
|
await self.cloud_auth(update, context)
|
||||||
elif sub_action == "status":
|
elif sub_action == "status":
|
||||||
# 状态查询
|
await self._handle_onedrive_callback(query, update, context, ["status"])
|
||||||
|
elif sub_action == "settings":
|
||||||
|
await self._handle_onedrive_callback(query, update, context, ["settings"])
|
||||||
|
elif sub_action == "logout":
|
||||||
|
await self._handle_onedrive_callback(query, update, context, ["logout"])
|
||||||
|
elif sub_action == "toggle":
|
||||||
|
await self._handle_onedrive_callback(query, update, context, ["toggle"] + parts[2:])
|
||||||
|
|
||||||
|
async def _handle_onedrive_callback(self, query, update: Update, context: ContextTypes.DEFAULT_TYPE, parts: list) -> None:
|
||||||
|
"""处理 OneDrive 相关回调"""
|
||||||
|
action = parts[0] if parts else "menu"
|
||||||
|
|
||||||
|
if action == "menu":
|
||||||
|
keyboard = build_onedrive_menu_keyboard()
|
||||||
|
await query.edit_message_text("☁️ *OneDrive 设置*", parse_mode="Markdown", reply_markup=keyboard)
|
||||||
|
|
||||||
|
elif action == "auth":
|
||||||
|
await self.cloud_auth(update, context)
|
||||||
|
|
||||||
|
elif action == "status":
|
||||||
client = self._get_onedrive_client()
|
client = self._get_onedrive_client()
|
||||||
if not client:
|
if not client:
|
||||||
await query.edit_message_text("❌ OneDrive 未配置")
|
await query.edit_message_text("❌ OneDrive 未配置")
|
||||||
@@ -1336,39 +1598,108 @@ class Aria2BotAPI:
|
|||||||
f"🗑️ 上传后删除: {'✅ 开启' if delete_after else '❌ 关闭'}\n"
|
f"🗑️ 上传后删除: {'✅ 开启' if delete_after else '❌ 关闭'}\n"
|
||||||
f"📁 远程路径: `{remote_path}`"
|
f"📁 远程路径: `{remote_path}`"
|
||||||
)
|
)
|
||||||
keyboard = build_cloud_menu_keyboard()
|
keyboard = build_onedrive_menu_keyboard()
|
||||||
await query.edit_message_text(text, parse_mode="Markdown", reply_markup=keyboard)
|
await query.edit_message_text(text, parse_mode="Markdown", reply_markup=keyboard)
|
||||||
elif sub_action == "settings":
|
|
||||||
# 设置页面
|
elif action == "settings":
|
||||||
auto_upload = self._onedrive_config.auto_upload if self._onedrive_config else False
|
auto_upload = self._onedrive_config.auto_upload if self._onedrive_config else False
|
||||||
delete_after = self._onedrive_config.delete_after_upload if self._onedrive_config else False
|
delete_after = self._onedrive_config.delete_after_upload if self._onedrive_config else False
|
||||||
keyboard = build_cloud_settings_keyboard(auto_upload, delete_after)
|
keyboard = build_cloud_settings_keyboard(auto_upload, delete_after)
|
||||||
await query.edit_message_text("⚙️ *云存储设置*\n\n点击切换设置:", parse_mode="Markdown", reply_markup=keyboard)
|
await query.edit_message_text("⚙️ *OneDrive 设置*\n\n点击切换设置:", parse_mode="Markdown", reply_markup=keyboard)
|
||||||
elif sub_action == "logout":
|
|
||||||
# 登出
|
elif action == "logout":
|
||||||
client = self._get_onedrive_client()
|
client = self._get_onedrive_client()
|
||||||
if client and await client.logout():
|
if client and await client.logout():
|
||||||
await query.edit_message_text("✅ 已登出 OneDrive")
|
await query.edit_message_text("✅ 已登出 OneDrive")
|
||||||
else:
|
else:
|
||||||
await query.edit_message_text("❌ 登出失败")
|
await query.edit_message_text("❌ 登出失败")
|
||||||
elif sub_action == "menu":
|
|
||||||
# 返回菜单
|
elif action == "toggle":
|
||||||
keyboard = build_cloud_menu_keyboard()
|
if len(parts) < 2:
|
||||||
await query.edit_message_text("☁️ *云存储管理*", parse_mode="Markdown", reply_markup=keyboard)
|
|
||||||
elif sub_action == "toggle":
|
|
||||||
# 切换设置(注意:运行时修改配置,重启后会重置)
|
|
||||||
if len(parts) < 3:
|
|
||||||
return
|
return
|
||||||
setting = parts[2]
|
setting = parts[1]
|
||||||
if self._onedrive_config:
|
if self._onedrive_config:
|
||||||
if setting == "auto_upload":
|
if setting == "auto_upload":
|
||||||
self._onedrive_config.auto_upload = not self._onedrive_config.auto_upload
|
self._onedrive_config.auto_upload = not self._onedrive_config.auto_upload
|
||||||
elif setting == "delete_after":
|
elif setting == "delete_after":
|
||||||
self._onedrive_config.delete_after_upload = not self._onedrive_config.delete_after_upload
|
self._onedrive_config.delete_after_upload = not self._onedrive_config.delete_after_upload
|
||||||
|
# 保存配置
|
||||||
|
self._save_cloud_config()
|
||||||
auto_upload = self._onedrive_config.auto_upload if self._onedrive_config else False
|
auto_upload = self._onedrive_config.auto_upload if self._onedrive_config else False
|
||||||
delete_after = self._onedrive_config.delete_after_upload if self._onedrive_config else False
|
delete_after = self._onedrive_config.delete_after_upload if self._onedrive_config else False
|
||||||
keyboard = build_cloud_settings_keyboard(auto_upload, delete_after)
|
keyboard = build_cloud_settings_keyboard(auto_upload, delete_after)
|
||||||
await query.edit_message_text("⚙️ *云存储设置*\n\n点击切换设置:", parse_mode="Markdown", reply_markup=keyboard)
|
await query.edit_message_text("⚙️ *OneDrive 设置*\n\n点击切换设置:", parse_mode="Markdown", reply_markup=keyboard)
|
||||||
|
|
||||||
|
async def _handle_telegram_channel_callback(self, query, update: Update, context: ContextTypes.DEFAULT_TYPE, parts: list) -> None:
|
||||||
|
"""处理 Telegram 频道相关回调"""
|
||||||
|
action = parts[0] if parts else "menu"
|
||||||
|
|
||||||
|
if action == "menu":
|
||||||
|
enabled = self._telegram_channel_config.enabled if self._telegram_channel_config else False
|
||||||
|
channel_id = self._telegram_channel_config.channel_id if self._telegram_channel_config else ""
|
||||||
|
keyboard = build_telegram_channel_menu_keyboard(enabled, channel_id)
|
||||||
|
await query.edit_message_text("📢 *Telegram 频道设置*", parse_mode="Markdown", reply_markup=keyboard)
|
||||||
|
|
||||||
|
elif action == "info":
|
||||||
|
# 显示频道信息
|
||||||
|
if not self._telegram_channel_config:
|
||||||
|
await query.answer("频道未配置")
|
||||||
|
return
|
||||||
|
channel_id = self._telegram_channel_config.channel_id
|
||||||
|
if channel_id:
|
||||||
|
await query.answer(f"当前频道: {channel_id}")
|
||||||
|
else:
|
||||||
|
await query.answer("频道ID未设置,请在设置中配置")
|
||||||
|
|
||||||
|
elif action == "settings":
|
||||||
|
auto_upload = self._telegram_channel_config.auto_upload if self._telegram_channel_config else False
|
||||||
|
delete_after = self._telegram_channel_config.delete_after_upload if self._telegram_channel_config else False
|
||||||
|
channel_id = self._telegram_channel_config.channel_id if self._telegram_channel_config else ""
|
||||||
|
keyboard = build_telegram_channel_settings_keyboard(auto_upload, delete_after, channel_id)
|
||||||
|
await query.edit_message_text("⚙️ *Telegram 频道设置*\n\n点击切换设置:", parse_mode="Markdown", reply_markup=keyboard)
|
||||||
|
|
||||||
|
elif action == "toggle":
|
||||||
|
if len(parts) < 2:
|
||||||
|
return
|
||||||
|
setting = parts[1]
|
||||||
|
if self._telegram_channel_config:
|
||||||
|
if setting == "enabled":
|
||||||
|
self._telegram_channel_config.enabled = not self._telegram_channel_config.enabled
|
||||||
|
# 重新创建客户端
|
||||||
|
self._recreate_telegram_channel_client(context.bot)
|
||||||
|
elif setting == "auto_upload":
|
||||||
|
self._telegram_channel_config.auto_upload = not self._telegram_channel_config.auto_upload
|
||||||
|
elif setting == "delete_after":
|
||||||
|
self._telegram_channel_config.delete_after_upload = not self._telegram_channel_config.delete_after_upload
|
||||||
|
# 保存配置
|
||||||
|
self._save_cloud_config()
|
||||||
|
|
||||||
|
# 根据来源返回不同页面
|
||||||
|
if setting == "enabled":
|
||||||
|
enabled = self._telegram_channel_config.enabled if self._telegram_channel_config else False
|
||||||
|
channel_id = self._telegram_channel_config.channel_id if self._telegram_channel_config else ""
|
||||||
|
keyboard = build_telegram_channel_menu_keyboard(enabled, channel_id)
|
||||||
|
await query.edit_message_text("📢 *Telegram 频道设置*", parse_mode="Markdown", reply_markup=keyboard)
|
||||||
|
else:
|
||||||
|
auto_upload = self._telegram_channel_config.auto_upload if self._telegram_channel_config else False
|
||||||
|
delete_after = self._telegram_channel_config.delete_after_upload if self._telegram_channel_config else False
|
||||||
|
channel_id = self._telegram_channel_config.channel_id if self._telegram_channel_config else ""
|
||||||
|
keyboard = build_telegram_channel_settings_keyboard(auto_upload, delete_after, channel_id)
|
||||||
|
await query.edit_message_text("⚙️ *Telegram 频道设置*\n\n点击切换设置:", parse_mode="Markdown", reply_markup=keyboard)
|
||||||
|
|
||||||
|
elif action == "set_channel":
|
||||||
|
# 提示用户输入频道ID
|
||||||
|
user_id = update.effective_user.id if update.effective_user else None
|
||||||
|
if user_id:
|
||||||
|
self._pending_channel_input = {user_id: True}
|
||||||
|
await query.edit_message_text(
|
||||||
|
"📝 *设置频道ID*\n\n"
|
||||||
|
"请发送频道ID或频道用户名:\n"
|
||||||
|
"• 频道ID格式: `-100xxxxxxxxxx`\n"
|
||||||
|
"• 用户名格式: `@channel_name`\n\n"
|
||||||
|
"注意:Bot 必须是频道管理员才能发送消息",
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
|
||||||
async def _handle_upload_callback(self, query, update: Update, context: ContextTypes.DEFAULT_TYPE, parts: list) -> None:
|
async def _handle_upload_callback(self, query, update: Update, context: ContextTypes.DEFAULT_TYPE, parts: list) -> None:
|
||||||
"""处理上传回调"""
|
"""处理上传回调"""
|
||||||
@@ -1470,8 +1801,10 @@ def build_handlers(api: Aria2BotAPI) -> list:
|
|||||||
CommandHandler("stats", wrap_with_permission(api.global_stats)),
|
CommandHandler("stats", wrap_with_permission(api.global_stats)),
|
||||||
# 云存储命令
|
# 云存储命令
|
||||||
CommandHandler("cloud", wrap_with_permission(api.cloud_command)),
|
CommandHandler("cloud", wrap_with_permission(api.cloud_command)),
|
||||||
# Reply Keyboard 按钮文本处理
|
# Reply Keyboard 按钮文本处理(也处理频道ID输入)
|
||||||
MessageHandler(filters.TEXT & filters.Regex(button_pattern), wrap_with_permission(api.handle_button_text)),
|
MessageHandler(filters.TEXT & filters.Regex(button_pattern), wrap_with_permission(api.handle_text_message)),
|
||||||
|
# 频道ID输入处理(捕获 @channel 或 -100xxx 格式)
|
||||||
|
MessageHandler(filters.TEXT & filters.Regex(r"^(@[\w]+|-?\d+)$"), wrap_with_permission(api.handle_channel_id_input)),
|
||||||
# OneDrive 认证回调 URL 处理
|
# OneDrive 认证回调 URL 处理
|
||||||
MessageHandler(filters.TEXT & filters.Regex(r"^https://login\.microsoftonline\.com"), wrap_with_permission(api.handle_auth_callback)),
|
MessageHandler(filters.TEXT & filters.Regex(r"^https://login\.microsoftonline\.com"), wrap_with_permission(api.handle_auth_callback)),
|
||||||
# 种子文件处理
|
# 种子文件处理
|
||||||
|
|||||||
@@ -121,14 +121,10 @@ def build_main_reply_keyboard() -> ReplyKeyboardMarkup:
|
|||||||
|
|
||||||
|
|
||||||
def build_cloud_menu_keyboard() -> InlineKeyboardMarkup:
|
def build_cloud_menu_keyboard() -> InlineKeyboardMarkup:
|
||||||
"""构建云存储管理菜单"""
|
"""构建云存储主菜单 - 选择配置哪个云存储"""
|
||||||
return InlineKeyboardMarkup([
|
return InlineKeyboardMarkup([
|
||||||
[InlineKeyboardButton("🔐 OneDrive 认证", callback_data="cloud:auth:onedrive")],
|
[InlineKeyboardButton("☁️ OneDrive 设置", callback_data="cloud:onedrive:menu")],
|
||||||
[
|
[InlineKeyboardButton("📢 Telegram 频道设置", callback_data="cloud:telegram:menu")],
|
||||||
InlineKeyboardButton("📊 状态", callback_data="cloud:status"),
|
|
||||||
InlineKeyboardButton("⚙️ 设置", callback_data="cloud:settings"),
|
|
||||||
],
|
|
||||||
[InlineKeyboardButton("🚪 登出", callback_data="cloud:logout")],
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@@ -141,16 +137,54 @@ def build_upload_choice_keyboard(gid: str) -> InlineKeyboardMarkup:
|
|||||||
|
|
||||||
|
|
||||||
def build_cloud_settings_keyboard(auto_upload: bool, delete_after: bool) -> InlineKeyboardMarkup:
|
def build_cloud_settings_keyboard(auto_upload: bool, delete_after: bool) -> InlineKeyboardMarkup:
|
||||||
"""构建云存储设置键盘"""
|
"""构建 OneDrive 设置键盘"""
|
||||||
auto_text = "✅ 自动上传" if auto_upload else "❌ 自动上传"
|
auto_text = "✅ 自动上传" if auto_upload else "❌ 自动上传"
|
||||||
delete_text = "✅ 上传后删除" if delete_after else "❌ 上传后删除"
|
delete_text = "✅ 上传后删除" if delete_after else "❌ 上传后删除"
|
||||||
return InlineKeyboardMarkup([
|
return InlineKeyboardMarkup([
|
||||||
[InlineKeyboardButton(auto_text, callback_data="cloud:toggle:auto_upload")],
|
[InlineKeyboardButton(auto_text, callback_data="cloud:onedrive:toggle:auto_upload")],
|
||||||
[InlineKeyboardButton(delete_text, callback_data="cloud:toggle:delete_after")],
|
[InlineKeyboardButton(delete_text, callback_data="cloud:onedrive:toggle:delete_after")],
|
||||||
[InlineKeyboardButton("🔙 返回", callback_data="cloud:menu")],
|
[InlineKeyboardButton("🔙 返回", callback_data="cloud:menu")],
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def build_onedrive_menu_keyboard() -> InlineKeyboardMarkup:
|
||||||
|
"""构建 OneDrive 菜单键盘"""
|
||||||
|
return InlineKeyboardMarkup([
|
||||||
|
[InlineKeyboardButton("🔐 认证", callback_data="cloud:onedrive:auth")],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton("📊 状态", callback_data="cloud:onedrive:status"),
|
||||||
|
InlineKeyboardButton("⚙️ 设置", callback_data="cloud:onedrive:settings"),
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton("🚪 登出", callback_data="cloud:onedrive:logout")],
|
||||||
|
[InlineKeyboardButton("🔙 返回", callback_data="cloud:menu")],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def build_telegram_channel_menu_keyboard(config_enabled: bool, channel_id: str) -> InlineKeyboardMarkup:
|
||||||
|
"""构建 Telegram 频道菜单键盘"""
|
||||||
|
status_text = f"📢 频道: {channel_id}" if channel_id else "📢 频道: 未设置"
|
||||||
|
enabled_text = "✅ 已启用" if config_enabled else "❌ 未启用"
|
||||||
|
return InlineKeyboardMarkup([
|
||||||
|
[InlineKeyboardButton(status_text, callback_data="cloud:telegram:info")],
|
||||||
|
[InlineKeyboardButton(enabled_text, callback_data="cloud:telegram:toggle:enabled")],
|
||||||
|
[InlineKeyboardButton("⚙️ 设置", callback_data="cloud:telegram:settings")],
|
||||||
|
[InlineKeyboardButton("🔙 返回", callback_data="cloud:menu")],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def build_telegram_channel_settings_keyboard(auto_upload: bool, delete_after: bool, channel_id: str) -> InlineKeyboardMarkup:
|
||||||
|
"""构建 Telegram 频道设置键盘"""
|
||||||
|
auto_text = "✅ 自动上传" if auto_upload else "❌ 自动上传"
|
||||||
|
delete_text = "✅ 上传后删除" if delete_after else "❌ 上传后删除"
|
||||||
|
channel_text = f"📝 频道ID: {channel_id}" if channel_id else "📝 设置频道ID"
|
||||||
|
return InlineKeyboardMarkup([
|
||||||
|
[InlineKeyboardButton(channel_text, callback_data="cloud:telegram:set_channel")],
|
||||||
|
[InlineKeyboardButton(auto_text, callback_data="cloud:telegram:toggle:auto_upload")],
|
||||||
|
[InlineKeyboardButton(delete_text, callback_data="cloud:telegram:toggle:delete_after")],
|
||||||
|
[InlineKeyboardButton("🔙 返回", callback_data="cloud:telegram:menu")],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
def build_detail_keyboard_with_upload(gid: str, status: str, show_onedrive: bool = False, show_channel: bool = False) -> InlineKeyboardMarkup:
|
def build_detail_keyboard_with_upload(gid: str, status: str, show_onedrive: bool = False, show_channel: bool = False) -> InlineKeyboardMarkup:
|
||||||
"""构建详情页面的操作按钮(含上传选项)"""
|
"""构建详情页面的操作按钮(含上传选项)"""
|
||||||
buttons = []
|
buttons = []
|
||||||
|
|||||||
Reference in New Issue
Block a user