├── .dockerignore ├── log └── keep ├── requirements.txt ├── images ├── 1.webp ├── 2.webp ├── 3.webp └── 4.webp ├── .gitignore ├── Dockerfile ├── .vscode └── settings.json ├── 源码 ├── 下载器.py ├── 配置 │ └── __init__.py ├── 日志.py ├── 技能 │ ├── 停止.py │ ├── 下载.py │ ├── 单独操作.py │ └── 查询.py └── 小工具.py ├── config.yaml ├── docker-compose.yml ├── LICENSE ├── .github └── workflows │ └── docker-image.yml ├── bot.py └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | *config* 2 | *images* -------------------------------------------------------------------------------- /log/keep: -------------------------------------------------------------------------------- 1 | 我也不知道为啥新建不了文件夹,所以放个文件keep住这个文件夹 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aioaria2 2 | dynaconf 3 | python-telegram-bot 4 | -------------------------------------------------------------------------------- /images/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krau/aria2bot/HEAD/images/1.webp -------------------------------------------------------------------------------- /images/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krau/aria2bot/HEAD/images/2.webp -------------------------------------------------------------------------------- /images/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krau/aria2bot/HEAD/images/3.webp -------------------------------------------------------------------------------- /images/4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krau/aria2bot/HEAD/images/4.webp -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore dynaconf secret files 3 | config.dev* 4 | *venv* 5 | __pycache__ 6 | *.log 7 | .vscode/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.6-slim-bookworm 2 | 3 | COPY . /aria2bot 4 | WORKDIR /aria2bot 5 | RUN pip install -r requirements.txt 6 | ENTRYPOINT [ "python","bot.py" ] -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "editor.defaultFormatter": "ms-python.black-formatter" 4 | }, 5 | "python.formatting.provider": "none" 6 | } -------------------------------------------------------------------------------- /源码/下载器.py: -------------------------------------------------------------------------------- 1 | from aioaria2 import Aria2HttpClient 2 | from contextlib import asynccontextmanager 3 | from .配置 import 配置 4 | 5 | 6 | @asynccontextmanager 7 | async def 获取下载器(): 8 | 下载器 = Aria2HttpClient(url=配置.下载器组[0].下载器地址, token=配置.下载器组[0].下载器密钥) 9 | try: 10 | yield 下载器 11 | finally: 12 | await 下载器.close() 13 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | # 必填 2 | 机器人密钥: "" 3 | # 主人id,必填,支持多个 4 | 主人: 5 | - 6 | 7 | # 代理地址,留空为不使用代理 8 | 代理地址: 9 | 10 | # 日志等级,可选: DEBUG INFO WARN ERROR 还有什么我忘了,建议保持默认 11 | 控制台日志等级: "INFO" 12 | 文件日志等级: "DEBUG" 13 | 14 | # 支持多下载器(wip) 15 | # 下载器名: 自定义,将作为标识 16 | # 下载器地址: 示例: http://127.0.0.1:6800/jsonrpc 17 | # 下载器密钥: 即你设置的rpc密钥 18 | 下载器组: 19 | - 下载器名: "" 20 | 下载器地址: "" 21 | 下载器密钥: "" 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | aria2bot: 4 | image: ghcr.io/krau/aria2bot:main 5 | container_name: aria2bot 6 | restart: unless-stopped 7 | volumes: 8 | - /path/to/config.yaml:/aria2bot/config.yaml #冒号前是你自己的配置文件路径 9 | - /path/to/log:/aria2bot/log #冒号前是你自己的日志文件路径 10 | environment: 11 | - TZ=Asia/Shanghai 12 | network_mode: host -------------------------------------------------------------------------------- /源码/配置/__init__.py: -------------------------------------------------------------------------------- 1 | from dynaconf import Dynaconf 2 | from pathlib import Path 3 | 4 | 基本目录 = Path(__file__).parent.parent.parent 5 | 6 | 配置文件列表 = [ 7 | "config.yaml", 8 | "config.dev.yaml", 9 | ] 10 | 11 | 配置 = Dynaconf(envvar_prefix="ARIA2BOT", settings_files=配置文件列表, base_dir=基本目录) 12 | 13 | # `envvar_prefix` = export envvars with `export DYNACONF_FOO=bar`. 14 | # `settings_files` = Load these files in the order. 15 | -------------------------------------------------------------------------------- /源码/日志.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from logging import handlers 3 | from .配置 import 配置 4 | 5 | 日志器 = logging.getLogger(name="ARIA2BOT") 6 | 日志格式 = logging.Formatter( 7 | "%(asctime)s - %(name)s - %(levelname)s: - %(message)s", datefmt="%Y-%m-%d %H:%M:%S" 8 | ) 9 | 流处理器 = logging.StreamHandler() 10 | 流处理器.setLevel(配置.控制台日志等级) 11 | 流处理器.setFormatter(日志格式) 12 | 日志器.addHandler(流处理器) 13 | 14 | 时旋文件处理器 = handlers.TimedRotatingFileHandler( 15 | filename="./log/aria2bot.log", when="D", interval=1, backupCount=7, encoding="utf-8" 16 | ) 17 | 18 | 时旋文件处理器.setLevel(配置.文件日志等级) 19 | 时旋文件处理器.setFormatter(日志格式) 20 | 日志器.addHandler(时旋文件处理器) 21 | 22 | 日志器.setLevel(logging.DEBUG) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Krau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish docker container 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | publish: 11 | name: Publish container image 12 | runs-on: ubuntu-latest 13 | env: 14 | TZ: Asia/Shanghai 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: OCI meta 20 | id: meta 21 | uses: docker/metadata-action@v4.3.0 22 | with: 23 | images: ghcr.io/${{ github.repository }} 24 | tags: | 25 | type=edge,branch=main 26 | type=ref,event=branch 27 | type=semver,pattern={{version}} 28 | type=semver,pattern={{major}}.{{minor}} 29 | type=semver,pattern={{major}} 30 | type=sha 31 | - name: Set up Docker Buildx 32 | uses: docker/setup-buildx-action@v2 33 | 34 | - name: Set up QEMU 35 | uses: docker/setup-qemu-action@v2 36 | 37 | - name: Login to GHCR 38 | uses: docker/login-action@v2 39 | with: 40 | registry: ghcr.io 41 | username: ${{ github.repository_owner }} 42 | password: ${{ secrets.GITHUB_TOKEN }} 43 | 44 | - name: Build and push 45 | uses: docker/build-push-action@v3 46 | with: 47 | context: . 48 | push: true 49 | tags: ${{ steps.meta.outputs.tags }} 50 | labels: ${{ steps.meta.outputs.labels }} 51 | platforms: linux/amd64,linux/arm64 52 | cache-from: type=gha 53 | cache-to: type=gha,mode=max -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from telegram import Update 4 | from telegram.ext import ( 5 | ApplicationBuilder, 6 | CallbackContext, 7 | CommandHandler, 8 | ContextTypes, 9 | ) 10 | 11 | from 源码.小工具 import 仅主人装饰器, 开始标记 12 | from 源码.技能.下载 import ( 13 | 强制添加下载任务处理器, 14 | 添加下载任务处理器, 15 | ) 16 | from 源码.技能.停止 import ( 17 | 恢复所有任务处理器, 18 | 暂停所有任务处理器, 19 | 确认清空任务处理器, 20 | 请求清空任务处理器, 21 | ) 22 | from 源码.技能.单独操作 import ( 23 | 删除单任务处理器, 24 | 刷新单任务处理器, 25 | 操作单任务对话处理器, 26 | 暂停单任务处理器, 27 | 继续单任务处理器, 28 | ) 29 | from 源码.技能.查询 import ( 30 | 刷新下载器状态处理器, 31 | 刷新活跃任务处理器, 32 | 刷新等待中任务处理器, 33 | 回主菜单处理器, 34 | 查询下载器状态处理器, 35 | 查询活跃任务处理器, 36 | 查询等待中任务处理器, 37 | ) 38 | from 源码.日志 import 日志器 39 | from 源码.配置 import 配置 40 | 41 | if not os.path.exists("./log"): 42 | os.mkdir("./log") 43 | 44 | 日志器.info("Aria2Bot启动中...") 45 | if 配置.代理地址: 46 | 日志器.debug(f"设置代理为: {配置.代理地址}") 47 | os.environ["HTTP_PROXY"] = 配置.代理地址 48 | os.environ["HTTPS_PROXY"] = 配置.代理地址 49 | 50 | 51 | @仅主人装饰器 52 | async def 开始(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 53 | 日志器.info(f"{更新.effective_user.name} 执行了/start") 54 | await 上下文.bot.send_message( 55 | chat_id=更新.effective_chat.id, 56 | text="欢迎使用Aria2Bot", 57 | reply_markup=开始标记, 58 | reply_to_message_id=更新.effective_message.id, 59 | ) 60 | 61 | 62 | async def 帮助(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 63 | 日志器.info(f"{更新.effective_user.name} 执行了/help") 64 | 帮助信息 = "/start - 开始使用\n/help - 帮助\n仓库地址: https://github.com/krau/aria2bot" 65 | await 上下文.bot.send_message(chat_id=更新.effective_chat.id, text=帮助信息) 66 | 67 | 68 | async def 错误回调(更新, 上下文: CallbackContext): 69 | 日志器.error(f"发生错误\n{更新}\n: {上下文.error}") 70 | 71 | 72 | def 跑(): 73 | 应用 = ApplicationBuilder().token(配置.机器人密钥).build() 74 | 应用._concurrent_updates = False 75 | 76 | 开始处理器 = CommandHandler("start", 开始) 77 | 帮助处理器 = CommandHandler("help", 帮助) 78 | 79 | 处理器组 = [ 80 | 开始处理器, 81 | 帮助处理器, 82 | 添加下载任务处理器, 83 | 强制添加下载任务处理器, 84 | 查询活跃任务处理器, 85 | 刷新活跃任务处理器, 86 | 回主菜单处理器, 87 | 查询下载器状态处理器, 88 | 查询等待中任务处理器, 89 | 刷新等待中任务处理器, 90 | 刷新下载器状态处理器, 91 | 恢复所有任务处理器, 92 | 暂停所有任务处理器, 93 | 确认清空任务处理器, 94 | 请求清空任务处理器, 95 | 操作单任务对话处理器, 96 | 暂停单任务处理器, 97 | 继续单任务处理器, 98 | 删除单任务处理器, 99 | 刷新单任务处理器, 100 | ] 101 | 应用.add_handlers(处理器组) 102 | 应用.add_error_handler(错误回调) 103 | 日志器.info("Aria2Bot已启动") 104 | 应用.run_polling() 105 | 106 | 107 | if __name__ == "__main__": 108 | 跑() 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Aria2bot 4 | 5 | 使用 Telegram bot 控制 Aria2 下载器。 6 | 7 | Control Aria2 downloader using Telegram bot. 8 | 9 | ![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) 10 | 11 |
12 | 13 | ## ⭐ 特性 14 | 15 | ⚡ 异步实现 16 | 17 | 📝 交互式添加下载任务,支持多链接 18 | 19 | 📊 查询下载器状态 20 | 21 | ✨ 内联键盘刷新 22 | 23 | 😋 中文编程(伪) 24 | 25 | ## 🖥 使用 26 | 27 | ### docker-compose 部署 28 | 29 | 下载 `docker-compose.yml` 文件: 30 | 31 | ```yml 32 | version: "3" 33 | services: 34 | aria2bot: 35 | image: ghcr.io/krau/aria2bot:main 36 | container_name: aria2bot 37 | restart: unless-stopped 38 | volumes: 39 | - /path/to/config.yaml:/aria2bot/config.yaml #冒号前是你自己的配置文件路径 40 | - /path/to/log:/aria2bot/log #冒号前是你自己的日志文件路径 41 | environment: 42 | - TZ=Asia/Shanghai 43 | network_mode: host 44 | ``` 45 | 46 | 在同一目录下新建并修改 `config.yaml` 中的各项配置 47 | 48 | ```yaml 49 | # 必填 50 | 机器人密钥: "" 51 | # 主人id,必填,支持多个 52 | 主人: 53 | - 54 | 55 | # 代理地址,留空为不使用代理 56 | 代理地址: 57 | 58 | # 日志等级,可选: DEBUG INFO WARN ERROR 还有什么我忘了,建议保持默认 59 | 控制台日志等级: "INFO" 60 | 文件日志等级: "DEBUG" 61 | 62 | # 支持多下载器(正在开发中,暂时只支持第一个) 63 | # 下载器名: 自定义,将作为标识 64 | # 下载器地址: 示例: http://127.0.0.1:6800/jsonrpc 65 | # 下载器密钥: 即你设置的rpc密钥 66 | 下载器组: 67 | - 下载器名: "" 68 | 下载器地址: "" 69 | 下载器密钥: "" 70 | - 下载器名: "" 71 | 下载器地址: "" 72 | 下载器密钥: "" 73 | ``` 74 | 75 | 运行 `docker-compose up -d` 启动容器 76 | 77 | ### ⚙️ 源码运行 78 | 79 | Python 版本: 3.10+ 80 | 81 | ```bash 82 | git clone https://github.com/krau/aria2bot 83 | cd aria2bot 84 | ``` 85 | 86 | 用你喜欢的工具创建并激活虚拟环境后安装依赖 87 | 88 | ```bash 89 | pip install -r requirements.txt 90 | ``` 91 | 92 | 修改 `config.yaml` 各项配置,然后运行 'bot.py' 即可: 93 | 94 | ```bash 95 | python bot.py 96 | ``` 97 | 98 | ## 🗄 Demo 99 | 100 | ![图 1](images/1.webp) 101 | 102 | ![图 2](images/2.webp) 103 | 104 | ![图 3](images/3.webp) 105 | 106 | ![图 4](images/4.webp) 107 | 108 | ## 📅 TODO 109 | 110 | | 🔔 未完成 | ✅ 已完成 | 111 | | -------- | -------- | 112 | | 多下载器支持 | 添加下载任务 | 113 | | 查询各项状态 | 交互式添加下载任务,支持多链接 | 114 | | 对每个任务进行查看与操作(内联键盘) | 下载器状态 | 115 | | 任务完成通知 | 任务队列 | 116 | | | 对每个任务进行查看与操作 | 117 | | | docker compose 部署 | 118 | 119 | 120 | ## 🔨 参与开发 121 | 122 | 欢迎提交 PR, 请使用 black 格式化代码 123 | 124 | ~~风格上希望保持伪中文编程~~ 125 | 126 | ### 配置 127 | 128 | 本项目使用了 [Dynaconf](https://github.com/dynaconf/dynaconf) 作为配置管理,开发时请在项目根目录下创建 `config.dev.yaml` 文件,它会覆盖 `config.yaml` 中的配置。 129 | 130 | ## ♥ 鸣谢 131 | 132 | - [aioaria2](https://github.com/synodriver/aioaria2) 133 | - [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) 134 | 135 | etc. 136 | 137 | ## 📖 License 138 | 139 | MIT 140 | -------------------------------------------------------------------------------- /源码/技能/停止.py: -------------------------------------------------------------------------------- 1 | from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update 2 | from telegram.ext import CallbackQueryHandler, ContextTypes, MessageHandler, filters 3 | 4 | from ..下载器 import 获取下载器 5 | from ..小工具 import 仅主人装饰器, 有机体可读统计结果 6 | from ..日志 import 日志器 7 | 8 | 9 | @仅主人装饰器 10 | async def 恢复所有任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 11 | 日志器.info(f"{更新.effective_user.name} 取消暂停所有任务") 12 | async with 获取下载器() as 下载器: 13 | await 下载器.unpauseAll() 14 | await 上下文.bot.send_message( 15 | chat_id=更新.effective_chat.id, 16 | text="已取消暂停所有任务", 17 | reply_to_message_id=更新.effective_message.id, 18 | ) 19 | 20 | 21 | @仅主人装饰器 22 | async def 暂停所有任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 23 | 日志器.info(f"{更新.effective_user.name} 暂停所有任务") 24 | async with 获取下载器() as 下载器: 25 | await 下载器.pauseAll() 26 | await 上下文.bot.send_message( 27 | chat_id=更新.effective_chat.id, 28 | text="已暂停所有任务", 29 | reply_to_message_id=更新.effective_message.id, 30 | ) 31 | 32 | 33 | 请求清空任务标记 = InlineKeyboardMarkup( 34 | [ 35 | [ 36 | InlineKeyboardButton("确认", callback_data="确认清空任务"), 37 | InlineKeyboardButton("取消", callback_data="回主菜单"), 38 | ] 39 | ] 40 | ) 41 | 42 | 43 | @仅主人装饰器 44 | async def 请求清空任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 45 | 日志器.warning(f"{更新.effective_user.name} 请求清空任务") 46 | async with 获取下载器() as 下载器: 47 | 统计结果 = await 下载器.getGlobalStat() 48 | 好结果 = await 有机体可读统计结果(统计结果) 49 | 提醒 = f""" 50 | *此操作将清空所有任务与记录*,包括正在下载,等待队列,已暂停和已完成的任务. 51 | 但默认情况下,已下载的文件不会被删除. 52 | 如果想删除已下载的文件,请通过在aria2的配置文件中设置 `on-download-complete` 选项来实现. 53 | 当前状态: 54 | {好结果} 55 | 56 | *请确认是否要清空任务* 57 | """ 58 | await 上下文.bot.send_message( 59 | chat_id=更新.effective_chat.id, 60 | text=提醒, 61 | reply_markup=请求清空任务标记, 62 | parse_mode="Markdown", 63 | reply_to_message_id=更新.effective_message.id, 64 | ) 65 | 66 | 67 | @仅主人装饰器 68 | async def 确认清空任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 69 | 日志器.warning(f"{更新.effective_user.name} 确认清空任务") 70 | async with 获取下载器() as 下载器: 71 | 活跃的任务 = await 下载器.tellActive() 72 | if 活跃的任务: 73 | for 任务 in 活跃的任务: 74 | await 下载器.forceRemove(任务["gid"]) 75 | 等待的任务 = await 下载器.tellWaiting(0, 114514) 76 | if 等待的任务: 77 | for 任务 in 等待的任务: 78 | await 下载器.forceRemove(任务["gid"]) 79 | await 下载器.forcePauseAll() 80 | await 下载器.purgeDownloadResult() 81 | await 上下文.bot.edit_message_text( 82 | chat_id=更新.effective_chat.id, 83 | message_id=更新.callback_query.message.message_id, 84 | text="已清空任务", 85 | ) 86 | 87 | 88 | 恢复所有任务处理器 = MessageHandler(filters=filters.Regex("恢复所有任务"), callback=恢复所有任务) 89 | 暂停所有任务处理器 = MessageHandler(filters=filters.Regex("暂停所有任务"), callback=暂停所有任务) 90 | 确认清空任务处理器 = CallbackQueryHandler(pattern="确认清空任务", callback=确认清空任务) 91 | 请求清空任务处理器 = MessageHandler(filters=filters.Regex("清空任务"), callback=请求清空任务) 92 | -------------------------------------------------------------------------------- /源码/小工具.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | from urllib.parse import urlparse 5 | from telegram import ( 6 | Update, 7 | ReplyKeyboardMarkup, 8 | InlineKeyboardMarkup, 9 | InlineKeyboardButton, 10 | ) 11 | from telegram.ext import ContextTypes 12 | from .配置 import 配置 13 | from .日志 import 日志器 14 | 15 | 16 | 开始键盘 = [ 17 | ["暂停所有任务", "添加下载任务", "恢复所有任务"], 18 | ["活跃任务", "下载器状态", "等待中任务"], 19 | ["⚠️清空任务", "操作单任务", "强制下载"], 20 | ] 21 | 开始标记 = ReplyKeyboardMarkup(keyboard=开始键盘, selective=True, resize_keyboard=True) 22 | 回主菜单标记 = InlineKeyboardMarkup([[InlineKeyboardButton("回主菜单", callback_data="回主菜单")]]) 23 | 24 | 25 | async def 从消息中获取链接列表(文字消息: str) -> list: 26 | 非磁力正则式 = r"(?:http[s]?|ftp|sftp)://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+" # noqa: E501 27 | 非磁力链接 = re.findall(非磁力正则式, 文字消息) 28 | 磁力链接 = re.findall("magnet:\?xt=urn:btih:[0-9a-fA-F]{40,}.*", 文字消息) 29 | return 非磁力链接 + 磁力链接 30 | 31 | 32 | def 仅主人装饰器(func): 33 | async def 包装器(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 34 | if 更新.effective_user.id in 配置.主人: 35 | await func(更新, 上下文) 36 | else: 37 | 日志器.info(f"{更新.effective_user.name} 试图使用 {func.__name__} 被拒绝") 38 | 39 | return 包装器 40 | 41 | 42 | async def 不是主人(用户id: int) -> None: 43 | if 用户id not in 配置.主人: 44 | 日志器.info(f"{用户id} 操作被拒绝") 45 | return None 46 | 47 | 48 | async def 文件单位转换(文件大小: str) -> str: 49 | if isinstance(文件大小, str): 50 | 文件大小 = int(文件大小) 51 | if 文件大小 < 1024: 52 | return f"{文件大小} B" 53 | elif 文件大小 < 1024**2: 54 | return f"{文件大小 / 1024:.2f} KB" 55 | elif 文件大小 < 1024**3: 56 | return f"{文件大小 / 1024 ** 2:.2f} MB" 57 | elif 文件大小 < 1024**4: 58 | return f"{文件大小 / 1024 ** 3:.2f} GB" 59 | else: 60 | return f"{文件大小 / 1024 ** 4:.2f} TB" 61 | 62 | 63 | async def 有机体可读下载任务详细结果(原始结果: dict) -> str: 64 | gid = 原始结果["gid"] 65 | 文件 = await 获取文件名(原始结果) 66 | try: 67 | 链接 = 原始结果["files"][0]["uris"][0]["uri"] 68 | except IndexError: 69 | 链接 = "无" 70 | 文件数量 = len(原始结果["files"]) 71 | 下载目录 = 原始结果["dir"] 72 | 下载速度 = await 文件单位转换(原始结果["downloadSpeed"]) 73 | 总大小 = await 文件单位转换(原始结果["totalLength"]) 74 | 已完成 = await 文件单位转换(原始结果["completedLength"]) 75 | try: 76 | 下载进度 = f'{int(原始结果["completedLength"]) / int(原始结果["totalLength"]) * 100:.2f}%' 77 | except ZeroDivisionError: 78 | 下载进度 = "0%" 79 | 状态 = 原始结果["status"] 80 | return f"任务ID: `{gid}`\n文件: *{文件}*\n链接: {链接}\n文件数量: {文件数量}\n下载目录: *{下载目录}*\n下载速度: *{下载速度}/s*\n总大小: *{总大小}*\n已完成: *{已完成}*\n下载进度: *{下载进度}*\n状态: *{状态}*" # noqa: E501 81 | 82 | 83 | async def 有机体可读下载任务简略结果(原始结果: dict) -> str: 84 | gid = 原始结果["gid"] 85 | 文件 = await 获取文件名(原始结果) 86 | 下载速度 = await 文件单位转换(原始结果["downloadSpeed"]) 87 | 总大小 = await 文件单位转换(原始结果["totalLength"]) 88 | 已完成 = await 文件单位转换(原始结果["completedLength"]) 89 | try: 90 | 下载进度 = f'{int(原始结果["completedLength"]) / int(原始结果["totalLength"]) * 100:.2f}%' 91 | except ZeroDivisionError: 92 | 下载进度 = "0%" 93 | return f"任务ID: `{gid}`\n文件: *{文件}*\n下载速度: *{下载速度}/s*\n总大小: *{总大小}*\n已完成: *{已完成}*\n下载进度: *{下载进度}*" # noqa: E501 94 | 95 | 96 | async def 有机体可读下载器状态结果(原始结果: dict) -> str: 97 | 已启用功能列表 = 原始结果["enabledFeatures"] 98 | 版本 = 原始结果["version"] 99 | 有机体可读功能列表 = "" 100 | for 功能 in 已启用功能列表: 101 | 有机体可读功能列表 += f"`{功能}`\n" 102 | return f"已启用功能: \n{有机体可读功能列表}版本: *{版本}*" 103 | 104 | 105 | async def 有机体可读统计结果(原始结果: dict) -> str: 106 | 活动下载数 = 原始结果["numActive"] 107 | 下载速度 = await 文件单位转换(原始结果["downloadSpeed"]) 108 | 上传速度 = await 文件单位转换(原始结果["uploadSpeed"]) 109 | 等待下载数 = 原始结果["numWaiting"] 110 | 已停止下载数 = 原始结果["numStopped"] 111 | return f"活动下载数: *{活动下载数}*\n下载速度: *{下载速度}/s*\n上传速度: *{上传速度}/s*\n等待下载数: *{等待下载数}*\n完成与停止下载数: *{已停止下载数}*" # noqa: E501 112 | 113 | 114 | async def 有机体可读等待任务结果(原始结果: dict) -> str: 115 | gid = 原始结果["gid"] 116 | 文件 = await 获取文件名(原始结果) 117 | 总大小 = await 文件单位转换(原始结果["totalLength"]) 118 | try: 119 | 链接 = 原始结果["files"][0]["uris"][0]["uri"] 120 | except IndexError: 121 | 链接 = "无" 122 | return f"任务ID: `{gid}`\n文件: *{文件}*\n总大小: *{总大小}*\n链接: {链接}" 123 | 124 | 125 | async def 获取文件名(任务: dict) -> str: 126 | if 任务.__contains__("bittorrent"): 127 | if 任务["bittorrent"].__contains__("info"): 128 | return 任务["bittorrent"]["info"]["name"] 129 | return 任务["files"][0]["path"] 130 | 文件名 = 任务["files"][0]["path"].split("/")[-1] 131 | if 文件名 == "": 132 | 啪 = urlparse(任务["files"][0]["uris"][0]["uri"]) 133 | 文件名 = os.path.basename(啪.path) 134 | return 文件名 135 | -------------------------------------------------------------------------------- /源码/技能/下载.py: -------------------------------------------------------------------------------- 1 | from warnings import filterwarnings 2 | 3 | from aioaria2 import Aria2rpcException 4 | from telegram import ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, Update 5 | from telegram.ext import ( 6 | CallbackQueryHandler, 7 | ContextTypes, 8 | ConversationHandler, 9 | MessageHandler, 10 | filters, 11 | ) 12 | from telegram.warnings import PTBUserWarning 13 | 14 | from ..下载器 import 获取下载器 15 | from ..小工具 import 不是主人, 从消息中获取链接列表, 回主菜单标记 16 | from ..日志 import 日志器 17 | 18 | filterwarnings( 19 | action="ignore", message=r".*CallbackQueryHandler", category=PTBUserWarning 20 | ) 21 | REPLY = range(1) 22 | 添加下载任务成功标记 = InlineKeyboardMarkup( 23 | [ 24 | [ 25 | InlineKeyboardButton("查看活跃任务详情", callback_data="刷新活跃任务"), 26 | InlineKeyboardButton("回主菜单", callback_data="回主菜单"), 27 | ] 28 | ] 29 | ) 30 | 31 | 32 | async def 添加下载任务回复中(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE) -> int: 33 | 日志器.info(f"{更新.effective_user.name} 请求添加下载任务") 34 | 强制回复标记 = ForceReply( 35 | input_field_placeholder="请发送链接,支持http(s)|ftp|sftp|magnet,支持一次发送多个链接", 36 | selective=True, 37 | ) 38 | await 上下文.bot.send_message( 39 | chat_id=更新.effective_chat.id, 40 | text="请发送下载链接", 41 | reply_markup=强制回复标记, 42 | reply_to_message_id=更新.effective_message.id, 43 | ) 44 | return REPLY 45 | 46 | 47 | 强制添加任务标记 = InlineKeyboardMarkup( 48 | [ 49 | [ 50 | InlineKeyboardButton("强制添加", callback_data="强制添加"), 51 | InlineKeyboardButton("回主菜单", callback_data="回主菜单"), 52 | ] 53 | ] 54 | ) 55 | 56 | 57 | async def 添加下载任务已回复(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE) -> int: 58 | 不是主人旗 = await 不是主人(更新.effective_user.id) 59 | if 不是主人旗: 60 | await 上下文.bot.send_message( 61 | chat_id=更新.effective_chat.id, 62 | text=不是主人旗, 63 | reply_to_message_id=更新.effective_message.id, 64 | ) 65 | return ConversationHandler.END 66 | 日志器.info(f"{更新.effective_user.name} 已回复添加下载任务") 67 | 下载链接列表 = await 从消息中获取链接列表(更新.effective_message.text) 68 | if not 下载链接列表: 69 | await 上下文.bot.send_message( 70 | chat_id=更新.effective_chat.id, 71 | text="未检测到下载链接", 72 | reply_markup=强制添加任务标记, 73 | reply_to_message_id=更新.effective_message.id, 74 | ) 75 | return ConversationHandler.END 76 | try: 77 | async with 获取下载器() as 下载器: 78 | for 单链接 in 下载链接列表: 79 | 单链接列表 = [单链接] 80 | await 下载器.addUri(uris=单链接列表) 81 | 有机体可读下载链接列表 = "\n".join(下载链接列表) 82 | await 上下文.bot.send_message( 83 | chat_id=更新.effective_chat.id, 84 | text=f"已添加到下载队列:\n\n{有机体可读下载链接列表}", 85 | parse_mode="Markdown", 86 | reply_markup=添加下载任务成功标记, 87 | reply_to_message_id=更新.effective_message.id, 88 | ) 89 | except Aria2rpcException as e: 90 | await 上下文.bot.send_message( 91 | chat_id=更新.effective_chat.id, 92 | text=f"添加下载任务失败,Aria2rpc异常:\n _{e}_", 93 | parse_mode="Markdown", 94 | reply_markup=回主菜单标记, 95 | reply_to_message_id=更新.effective_message.id, 96 | ) 97 | except Exception as e: 98 | await 上下文.bot.send_message( 99 | chat_id=更新.effective_chat.id, 100 | text=f"添加下载任务失败,未知异常:\n _{e}_", 101 | parse_mode="Markdown", 102 | reply_markup=回主菜单标记, 103 | reply_to_message_id=更新.effective_message.id, 104 | ) 105 | finally: 106 | return ConversationHandler.END 107 | 108 | 109 | FORECE_DL_REPLY = range(1) 110 | 111 | 112 | async def 强制添加下载任务回复中(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 113 | 强制回复标记 = ForceReply(input_field_placeholder="请发送链接,仅支持单个", selective=True) 114 | await 上下文.bot.send_message( 115 | chat_id=更新.effective_chat.id, 116 | text="请发送要强制添加的下载链接\nbot不会对链接进行任何检查,而直接尝试推送到 aria2", 117 | reply_markup=强制回复标记, 118 | reply_to_message_id=更新.effective_message.id, 119 | ) 120 | return FORECE_DL_REPLY 121 | 122 | 123 | async def 强制添加下载任务已回复(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE) -> int: 124 | 不是主人旗 = await 不是主人(更新.effective_user.id) 125 | if 不是主人旗: 126 | await 上下文.bot.send_message( 127 | chat_id=更新.effective_chat.id, 128 | text=不是主人旗, 129 | reply_to_message_id=更新.effective_message.id, 130 | ) 131 | return ConversationHandler.END 132 | 日志器.info(f"{更新.effective_user.name} 已回复强制添加下载任务") 133 | 下载链接 = 更新.effective_message.text 134 | try: 135 | async with 获取下载器() as 下载器: 136 | await 下载器.addUri(uris=[下载链接]) 137 | await 上下文.bot.send_message( 138 | chat_id=更新.effective_chat.id, 139 | text=f"已添加到下载队列:\n\n{下载链接}", 140 | parse_mode="Markdown", 141 | reply_markup=添加下载任务成功标记, 142 | reply_to_message_id=更新.effective_message.id, 143 | ) 144 | except Aria2rpcException as e: 145 | await 上下文.bot.send_message( 146 | chat_id=更新.effective_chat.id, 147 | text=f"添加下载任务失败,Aria2rpc异常:\n _{e}_", 148 | parse_mode="Markdown", 149 | reply_markup=回主菜单标记, 150 | reply_to_message_id=更新.effective_message.id, 151 | ) 152 | except Exception as e: 153 | await 上下文.bot.send_message( 154 | chat_id=更新.effective_chat.id, 155 | text=f"添加下载任务失败,未知异常:\n _{e}_", 156 | parse_mode="Markdown", 157 | reply_markup=回主菜单标记, 158 | reply_to_message_id=更新.effective_message.id, 159 | ) 160 | finally: 161 | return ConversationHandler.END 162 | 163 | 164 | 添加下载任务处理器 = ConversationHandler( 165 | per_chat=True, 166 | per_user=True, 167 | entry_points=[MessageHandler(filters.Regex("添加下载任务"), 添加下载任务回复中)], 168 | states={REPLY: [MessageHandler(~filters.COMMAND, 添加下载任务已回复)]}, 169 | fallbacks=[MessageHandler(~filters.COMMAND, 添加下载任务已回复)], 170 | ) 171 | 172 | 强制添加下载任务处理器 = ConversationHandler( 173 | per_chat=True, 174 | per_user=True, 175 | entry_points=[ 176 | CallbackQueryHandler(pattern="强制添加", callback=强制添加下载任务回复中), 177 | MessageHandler(filters.Regex("强制下载"), 强制添加下载任务回复中), 178 | ], 179 | states={FORECE_DL_REPLY: [MessageHandler(~filters.COMMAND, 强制添加下载任务已回复)]}, 180 | fallbacks=[MessageHandler(~filters.COMMAND, 强制添加下载任务已回复)], 181 | ) 182 | -------------------------------------------------------------------------------- /源码/技能/单独操作.py: -------------------------------------------------------------------------------- 1 | from aioaria2 import Aria2rpcException 2 | from telegram import ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, Update 3 | from telegram.error import BadRequest 4 | from telegram.ext import ( 5 | CallbackQueryHandler, 6 | ContextTypes, 7 | ConversationHandler, 8 | MessageHandler, 9 | filters, 10 | ) 11 | 12 | from ..下载器 import 获取下载器 13 | from ..小工具 import ( 14 | 不是主人, 15 | 仅主人装饰器, 16 | 有机体可读下载任务详细结果, 17 | ) 18 | from ..日志 import 日志器 19 | 20 | REPLY = range(1) 21 | 22 | 23 | async def 操作单任务回复中(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 24 | 强制回复标记 = ForceReply(selective=True, input_field_placeholder="请输入任务ID") 25 | await 上下文.bot.send_message( 26 | chat_id=更新.effective_chat.id, 27 | text="请输入任务ID", 28 | reply_markup=强制回复标记, 29 | reply_to_message_id=更新.effective_message.id, 30 | ) 31 | return REPLY 32 | 33 | 34 | 操作单任务内联键盘 = [ 35 | [ 36 | InlineKeyboardButton("暂停", callback_data="暂停单任务"), 37 | InlineKeyboardButton("继续", callback_data="继续单任务"), 38 | ], 39 | [InlineKeyboardButton("删除", callback_data="删除单任务")], 40 | [ 41 | InlineKeyboardButton("刷新", callback_data="刷新单任务"), 42 | InlineKeyboardButton("返回", callback_data="回主菜单"), 43 | ], 44 | ] 45 | 操作单任务回复标记 = InlineKeyboardMarkup(操作单任务内联键盘) 46 | 47 | 48 | async def 操作单任务回复(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 49 | 不是主人旗 = await 不是主人(更新.effective_user.id) 50 | if 不是主人旗: 51 | await 上下文.bot.send_message( 52 | chat_id=更新.effective_chat.id, 53 | text=不是主人旗, 54 | reply_to_message_id=更新.effective_message.id, 55 | ) 56 | return ConversationHandler.END 57 | 日志器.info(f"{更新.effective_user.name} 请求操作单任务 {更新.effective_message.text}") 58 | 任务id = 更新.effective_message.text 59 | 上下文.user_data["任务id"] = 任务id 60 | try: 61 | async with 获取下载器() as 下载器: 62 | 任务状态 = await 下载器.tellStatus(任务id) 63 | 好任务状态 = await 有机体可读下载任务详细结果(任务状态) 64 | await 上下文.bot.send_message( 65 | chat_id=更新.effective_chat.id, 66 | text=好任务状态, 67 | parse_mode="Markdown", 68 | reply_markup=操作单任务回复标记, 69 | reply_to_message_id=更新.effective_message.id, 70 | ) 71 | except Exception as e: 72 | await 上下文.bot.send_message( 73 | chat_id=更新.effective_chat.id, 74 | text=f"操作失败,未知异常:\n _{e}_", 75 | parse_mode="Markdown", 76 | reply_to_message_id=更新.effective_message.id, 77 | ) 78 | finally: 79 | return ConversationHandler.END 80 | 81 | 82 | 操作单任务对话处理器 = ConversationHandler( 83 | per_chat=True, 84 | per_user=True, 85 | entry_points=[MessageHandler(filters.Regex("操作单任务"), 操作单任务回复中)], 86 | states={ 87 | REPLY: [MessageHandler(~filters.COMMAND, 操作单任务回复)], 88 | }, 89 | fallbacks=[MessageHandler(~filters.COMMAND, 操作单任务回复)], 90 | allow_reentry=True, 91 | ) 92 | 93 | 94 | @仅主人装饰器 95 | async def 暂停单任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 96 | 任务id = 上下文.user_data["任务id"] 97 | 日志器.info(f"{更新.effective_user.name} 请求暂停单任务 {任务id}") 98 | try: 99 | async with 获取下载器() as 下载器: 100 | await 下载器.pause(任务id) 101 | 新任务状态 = await 下载器.tellStatus(任务id) 102 | 新好任务状态 = await 有机体可读下载任务详细结果(新任务状态) 103 | await 上下文.bot.edit_message_text( 104 | chat_id=更新.effective_chat.id, 105 | message_id=更新.callback_query.message.id, 106 | text=f"{新好任务状态}\n 已暂停任务", 107 | parse_mode="Markdown", 108 | reply_markup=操作单任务回复标记, 109 | ) 110 | except Aria2rpcException: 111 | pass 112 | except Exception as e: 113 | 日志器.error(f"暂停单任务失败: {e}") 114 | await 上下文.bot.send_message( 115 | chat_id=更新.effective_chat.id, 116 | text=f"操作失败,未知异常:\n _{e}_", 117 | parse_mode="Markdown", 118 | reply_to_message_id=更新.effective_message.id, 119 | ) 120 | 121 | 122 | @仅主人装饰器 123 | async def 继续单任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 124 | 任务id = 上下文.user_data["任务id"] 125 | 日志器.info(f"{更新.effective_user.name} 请求继续单任务 {任务id}") 126 | try: 127 | async with 获取下载器() as 下载器: 128 | await 下载器.unpause(任务id) 129 | 新任务状态 = await 下载器.tellStatus(任务id) 130 | 新好任务状态 = await 有机体可读下载任务详细结果(新任务状态) 131 | await 上下文.bot.edit_message_text( 132 | chat_id=更新.effective_chat.id, 133 | message_id=更新.callback_query.message.id, 134 | text=f"{新好任务状态}\n 已继续任务", 135 | parse_mode="Markdown", 136 | reply_markup=操作单任务回复标记, 137 | ) 138 | except Aria2rpcException: 139 | pass 140 | except Exception as e: 141 | 日志器.error(f"继续单任务失败: {e}") 142 | await 上下文.bot.send_message( 143 | chat_id=更新.effective_chat.id, 144 | text=f"操作失败,未知异常:\n _{e}_", 145 | parse_mode="Markdown", 146 | reply_to_message_id=更新.effective_message.id, 147 | ) 148 | 149 | 150 | 删除单任务回复标记 = InlineKeyboardMarkup([[InlineKeyboardButton("返回", callback_data="回主菜单")]]) 151 | 152 | 153 | @仅主人装饰器 154 | async def 删除单任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 155 | 任务id = 上下文.user_data["任务id"] 156 | 日志器.info(f"{更新.effective_user.name} 请求删除单任务 {任务id}") 157 | try: 158 | async with 获取下载器() as 下载器: 159 | await 下载器.remove(任务id) 160 | await 上下文.bot.edit_message_text( 161 | chat_id=更新.effective_chat.id, 162 | message_id=更新.callback_query.message.id, 163 | text="已删除任务", 164 | parse_mode="Markdown", 165 | reply_markup=删除单任务回复标记, 166 | ) 167 | except Exception as e: 168 | 日志器.error(f"删除单任务失败: {e}") 169 | await 上下文.bot.send_message( 170 | chat_id=更新.effective_chat.id, 171 | text=f"操作失败,未知异常:\n _{e}_", 172 | parse_mode="Markdown", 173 | reply_to_message_id=更新.effective_message.id, 174 | ) 175 | 176 | 177 | @仅主人装饰器 178 | async def 刷新单任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 179 | 任务id = 上下文.user_data["任务id"] 180 | 日志器.info(f"{更新.effective_user.name} 请求刷新单任务 {任务id}") 181 | try: 182 | async with 获取下载器() as 下载器: 183 | 任务状态 = await 下载器.tellStatus(任务id) 184 | 好任务状态 = await 有机体可读下载任务详细结果(任务状态) 185 | await 上下文.bot.edit_message_text( 186 | chat_id=更新.effective_chat.id, 187 | message_id=更新.callback_query.message.id, 188 | text=f"{好任务状态}", 189 | parse_mode="Markdown", 190 | reply_markup=操作单任务回复标记, 191 | ) 192 | except BadRequest: 193 | await 上下文.bot.edit_message_text( 194 | chat_id=更新.effective_chat.id, 195 | message_id=更新.callback_query.message.id, 196 | text=f"{好任务状态}\n_当前状态无变化_", 197 | parse_mode="Markdown", 198 | reply_markup=操作单任务回复标记, 199 | ) 200 | except Exception as e: 201 | 日志器.error(f"刷新单任务失败: {e}") 202 | await 上下文.bot.send_message( 203 | chat_id=更新.effective_chat.id, 204 | text=f"操作失败,未知异常:\n _{e}_", 205 | parse_mode="Markdown", 206 | reply_to_message_id=更新.effective_message.id, 207 | ) 208 | 209 | 210 | 暂停单任务处理器 = CallbackQueryHandler(暂停单任务, pattern="暂停单任务") 211 | 继续单任务处理器 = CallbackQueryHandler(继续单任务, pattern="继续单任务") 212 | 删除单任务处理器 = CallbackQueryHandler(删除单任务, pattern="删除单任务") 213 | 刷新单任务处理器 = CallbackQueryHandler(刷新单任务, pattern="刷新单任务") 214 | -------------------------------------------------------------------------------- /源码/技能/查询.py: -------------------------------------------------------------------------------- 1 | from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update 2 | from telegram.error import BadRequest 3 | from telegram.ext import CallbackQueryHandler, ContextTypes, MessageHandler, filters 4 | 5 | from ..下载器 import 获取下载器 6 | from ..小工具 import ( 7 | 仅主人装饰器, 8 | 开始标记, 9 | 有机体可读下载任务简略结果, 10 | 有机体可读下载器状态结果, 11 | 有机体可读等待任务结果, 12 | 有机体可读统计结果, 13 | ) 14 | from ..日志 import 日志器 15 | from ..配置 import 配置 16 | 17 | 查询活跃刷新标记 = InlineKeyboardMarkup( 18 | inline_keyboard=[ 19 | [ 20 | InlineKeyboardButton("刷新", callback_data="刷新活跃任务"), 21 | InlineKeyboardButton("回主菜单", callback_data="回主菜单"), 22 | ], 23 | ] 24 | ) 25 | 26 | 27 | @仅主人装饰器 28 | async def 查询活跃任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 29 | 日志器.info(f"{更新.effective_user.name} 请求查询活跃任务") 30 | try: 31 | async with 获取下载器() as 下载器: 32 | 结果: list = await 下载器.tellActive() 33 | if not 结果: 34 | await 上下文.bot.send_message( 35 | chat_id=更新.effective_chat.id, 36 | text="当前没有下载中的任务", 37 | reply_to_message_id=更新.effective_message.id, 38 | ) 39 | return 40 | 好结果 = "" 41 | for 任务 in 结果: 42 | 好结果 = 好结果 + await 有机体可读下载任务简略结果(任务) + "\n\n" 43 | await 上下文.bot.send_message( 44 | chat_id=更新.effective_chat.id, 45 | text=f"当前活跃任务:\n\n{好结果}", 46 | parse_mode="Markdown", 47 | reply_markup=查询活跃刷新标记, 48 | reply_to_message_id=更新.effective_message.id, 49 | ) 50 | except Exception as e: 51 | 日志器.error(f"查询活跃任务出错,错误信息:\n {e.__class__.__name__}: {e}") 52 | await 上下文.bot.send_message( 53 | chat_id=更新.effective_chat.id, 54 | text=f"查询活跃任务出错,错误信息:\n {e.__class__.__name__}: {e}", 55 | reply_to_message_id=更新.effective_message.id, 56 | ) 57 | 58 | 59 | @仅主人装饰器 60 | async def 刷新活跃任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 61 | 日志器.info(f"{更新.effective_user.name} 请求刷新活跃任务") 62 | try: 63 | async with 获取下载器() as 下载器: 64 | 结果: list = await 下载器.tellActive() 65 | if not 结果: 66 | await 上下文.bot.send_message( 67 | chat_id=更新.effective_chat.id, 68 | text="当前没有下载中的任务", 69 | reply_to_message_id=更新.effective_message.id, 70 | ) 71 | return 72 | 好结果 = "" 73 | for 任务 in 结果: 74 | 好结果 = 好结果 + await 有机体可读下载任务简略结果(任务) + "\n\n" 75 | await 上下文.bot.edit_message_text( 76 | chat_id=更新.effective_chat.id, 77 | message_id=更新.callback_query.message.message_id, 78 | text=f"当前活跃任务:\n\n{好结果} 共有{len(结果)}个活跃任务", 79 | parse_mode="Markdown", 80 | reply_markup=查询活跃刷新标记, 81 | ) 82 | except BadRequest: 83 | """状态无变化时的处理""" 84 | await 上下文.bot.edit_message_text( 85 | chat_id=更新.effective_chat.id, 86 | message_id=更新.callback_query.message.message_id, 87 | text=f"当前活跃任务:\n\n{好结果} _当前状态无变化_", 88 | parse_mode="Markdown", 89 | reply_markup=查询活跃刷新标记, 90 | ) 91 | except Exception as e: 92 | 日志器.error(f"刷新活跃任务出错,错误信息:\n {e.__class__.__name__}: {e}") 93 | await 上下文.bot.send_message( 94 | chat_id=更新.effective_chat.id, 95 | text=f"刷新活跃任务出错,错误信息:\n {e.__class__.__name__}: {e}", 96 | reply_to_message_id=更新.effective_message.id, 97 | ) 98 | 99 | 100 | 查询等待刷新标记 = InlineKeyboardMarkup( 101 | inline_keyboard=[ 102 | [ 103 | InlineKeyboardButton("刷新", callback_data="刷新等待中任务"), 104 | InlineKeyboardButton("回主菜单", callback_data="回主菜单"), 105 | ], 106 | ] 107 | ) 108 | 109 | 110 | @仅主人装饰器 111 | async def 查询等待中任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 112 | 日志器.info(f"{更新.effective_user.name} 请求查询等待中任务") 113 | try: 114 | async with 获取下载器() as 下载器: 115 | 结果: list = await 下载器.tellWaiting(offset=0, num=114514) 116 | if not 结果: 117 | await 上下文.bot.send_message( 118 | chat_id=更新.effective_chat.id, 119 | text="当前没有等待中任务", 120 | reply_to_message_id=更新.effective_message.id, 121 | ) 122 | return 123 | 好结果 = "" 124 | for 任务 in 结果[:10]: 125 | 好结果 = 好结果 + await 有机体可读等待任务结果(任务) + "\n\n" 126 | await 上下文.bot.send_message( 127 | chat_id=更新.effective_chat.id, 128 | text=f"当前等待中任务共{len(结果)}个,最多显示前10条:\n\n{好结果}", 129 | parse_mode="Markdown", 130 | reply_markup=查询等待刷新标记, 131 | reply_to_message_id=更新.effective_message.id, 132 | ) 133 | except Exception as e: 134 | 日志器.error(f"查询等待中任务出错,错误信息:\n {e.__class__.__name__}: {e}") 135 | await 上下文.bot.send_message( 136 | chat_id=更新.effective_chat.id, 137 | text=f"查询等待中任务出错,错误信息:\n {e.__class__.__name__}: {e}", 138 | reply_to_message_id=更新.effective_message.id, 139 | ) 140 | 141 | 142 | @仅主人装饰器 143 | async def 刷新等待中任务(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 144 | 日志器.info(f"{更新.effective_user.name} 请求刷新等待中任务") 145 | try: 146 | async with 获取下载器() as 下载器: 147 | 结果: list = await 下载器.tellWaiting(offset=0, num=114514) 148 | if not 结果: 149 | await 上下文.bot.send_message( 150 | chat_id=更新.effective_chat.id, 151 | text="当前没有等待中任务", 152 | reply_to_message_id=更新.effective_message.id, 153 | ) 154 | return 155 | 好结果 = "" 156 | for 任务 in 结果[:10]: 157 | 好结果 = 好结果 + await 有机体可读等待任务结果(任务) + "\n\n" 158 | await 上下文.bot.edit_message_text( 159 | chat_id=更新.effective_chat.id, 160 | message_id=更新.callback_query.message.message_id, 161 | text=f"最多显示前10条等待中任务:\n\n{好结果} 当前等待中任务共{len(结果)}个", 162 | parse_mode="Markdown", 163 | reply_markup=查询等待刷新标记, 164 | reply_to_message_id=更新.effective_message.id, 165 | ) 166 | except BadRequest: 167 | """状态无变化时的处理""" 168 | await 上下文.bot.edit_message_text( 169 | chat_id=更新.effective_chat.id, 170 | message_id=更新.callback_query.message.message_id, 171 | text=f"最多显示前10条等待中任务:\n\n{好结果} _当前状态无变化_", 172 | parse_mode="Markdown", 173 | reply_markup=查询等待刷新标记, 174 | reply_to_message_id=更新.effective_message.id, 175 | ) 176 | except Exception as e: 177 | 日志器.error(f"刷新等待中任务出错,错误信息:\n {e.__class__.__name__}: {e}") 178 | await 上下文.bot.send_message( 179 | chat_id=更新.effective_chat.id, 180 | text=f"刷新等待中任务出错,错误信息:\n {e.__class__.__name__}: {e}", 181 | reply_to_message_id=更新.effective_message.id, 182 | ) 183 | 184 | 185 | @仅主人装饰器 186 | async def 回主菜单(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 187 | await 上下文.bot.delete_message( 188 | chat_id=更新.effective_chat.id, message_id=更新.callback_query.message.message_id 189 | ) 190 | await 上下文.bot.send_message( 191 | chat_id=更新.effective_chat.id, text="请选择操作", reply_markup=开始标记 192 | ) 193 | 194 | 195 | 查询下载器刷新标记 = InlineKeyboardMarkup( 196 | [ 197 | [ 198 | InlineKeyboardButton("刷新", callback_data="刷新下载器状态"), 199 | InlineKeyboardButton("回主菜单", callback_data="回主菜单"), 200 | ] 201 | ] 202 | ) 203 | 204 | 205 | @仅主人装饰器 206 | async def 查询下载器状态(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 207 | 日志器.info(f"{更新.effective_user.name} 请求查询下载器状态") 208 | try: 209 | async with 获取下载器() as 下载器: 210 | 本体结果: dict = await 下载器.getVersion() 211 | 统计结果: dict = await 下载器.getGlobalStat() 212 | 好结果 = ( 213 | await 有机体可读下载器状态结果(本体结果) 214 | + "\n\n" 215 | + await 有机体可读统计结果(统计结果) 216 | + "\n\n" 217 | + f"RPC地址: {配置.下载器组[0].下载器地址}" 218 | ) 219 | await 上下文.bot.send_message( 220 | chat_id=更新.effective_chat.id, 221 | text=f"{好结果}", 222 | parse_mode="Markdown", 223 | reply_markup=查询下载器刷新标记, 224 | reply_to_message_id=更新.effective_message.id, 225 | ) 226 | except Exception as e: 227 | await 上下文.bot.send_message( 228 | chat_id=更新.effective_chat.id, 229 | text=f"查询下载器状态出错,错误信息:\n {e.__class__.__name__}: {e}", 230 | reply_to_message_id=更新.effective_message.id, 231 | ) 232 | 233 | 234 | @仅主人装饰器 235 | async def 刷新下载器状态(更新: Update, 上下文: ContextTypes.DEFAULT_TYPE): 236 | 日志器.info(f"{更新.effective_user.name} 请求刷新下载器状态") 237 | try: 238 | async with 获取下载器() as 下载器: 239 | 本体结果: dict = await 下载器.getVersion() 240 | 统计结果: dict = await 下载器.getGlobalStat() 241 | 好结果 = ( 242 | await 有机体可读下载器状态结果(本体结果) 243 | + "\n\n" 244 | + await 有机体可读统计结果(统计结果) 245 | + "\n\n" 246 | + f"RPC地址: {配置.下载器组[0].下载器地址}" 247 | ) 248 | await 上下文.bot.edit_message_text( 249 | chat_id=更新.effective_chat.id, 250 | message_id=更新.callback_query.message.message_id, 251 | text=f"{好结果}", 252 | parse_mode="Markdown", 253 | reply_markup=查询下载器刷新标记, 254 | ) 255 | except BadRequest: 256 | """状态无变化时的处理""" 257 | await 上下文.bot.edit_message_text( 258 | chat_id=更新.effective_chat.id, 259 | message_id=更新.callback_query.message.message_id, 260 | text=f"{好结果}\n_当前状态无变化_", 261 | parse_mode="Markdown", 262 | reply_markup=查询下载器刷新标记, 263 | ) 264 | except Exception as e: 265 | await 上下文.bot.send_message( 266 | chat_id=更新.effective_chat.id, 267 | text=f"刷新下载器状态出错,错误信息:\n {e.__class__.__name__}: {e}", 268 | reply_to_message_id=更新.effective_message.id, 269 | ) 270 | 271 | 272 | 查询活跃任务处理器 = MessageHandler(filters.Regex("活跃任务"), 查询活跃任务) 273 | 274 | 刷新活跃任务处理器 = CallbackQueryHandler(刷新活跃任务, pattern="刷新活跃任务") 275 | 276 | 查询下载器状态处理器 = MessageHandler(filters.Regex("下载器状态"), 查询下载器状态) 277 | 278 | 查询等待中任务处理器 = MessageHandler(filters.Regex("等待中任务"), 查询等待中任务) 279 | 280 | 刷新等待中任务处理器 = CallbackQueryHandler(刷新等待中任务, pattern="刷新等待中任务") 281 | 282 | 刷新下载器状态处理器 = CallbackQueryHandler(刷新下载器状态, pattern="刷新下载器状态") 283 | 284 | 回主菜单处理器 = CallbackQueryHandler(回主菜单, pattern="回主菜单") 285 | --------------------------------------------------------------------------------