├── .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 |  
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 | 
101 |
102 | 
103 |
104 | 
105 |
106 | 
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 |
--------------------------------------------------------------------------------