1169 lines
36 KiB
Python
1169 lines
36 KiB
Python
"""Bot for media downloader"""
|
|
|
|
import asyncio
|
|
import os
|
|
from datetime import datetime
|
|
from typing import Callable, List, Union
|
|
|
|
import pyrogram
|
|
from loguru import logger
|
|
from pyrogram import types
|
|
from pyrogram.handlers import CallbackQueryHandler, MessageHandler
|
|
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
|
from ruamel import yaml
|
|
|
|
import utils
|
|
from module.app import (
|
|
Application,
|
|
ChatDownloadConfig,
|
|
ForwardStatus,
|
|
QueryHandler,
|
|
QueryHandlerStr,
|
|
TaskNode,
|
|
TaskType,
|
|
UploadStatus,
|
|
)
|
|
from module.filter import Filter
|
|
from module.get_chat_history_v2 import get_chat_history_v2
|
|
from module.language import Language, _t
|
|
from module.pyrogram_extension import (
|
|
check_user_permission,
|
|
parse_link,
|
|
proc_cache_forward,
|
|
report_bot_forward_status,
|
|
report_bot_status,
|
|
retry,
|
|
set_meta_data,
|
|
upload_telegram_chat_message,
|
|
)
|
|
from utils.format import replace_date_time, validate_title
|
|
from utils.meta_data import MetaData
|
|
|
|
# pylint: disable = C0301, R0902
|
|
|
|
|
|
class DownloadBot:
|
|
"""Download bot"""
|
|
|
|
def __init__(self):
|
|
self.bot = None
|
|
self.client = None
|
|
self.add_download_task: Callable = None
|
|
self.download_chat_task: Callable = None
|
|
self.app = None
|
|
self.listen_forward_chat: dict = {}
|
|
self.config: dict = {}
|
|
self._yaml = yaml.YAML()
|
|
self.config_path = os.path.join(os.path.abspath("."), "bot.yaml")
|
|
self.download_command: dict = {}
|
|
self.filter = Filter()
|
|
self.bot_info = None
|
|
self.task_node: dict = {}
|
|
self.is_running = True
|
|
self.allowed_user_ids: List[Union[int, str]] = []
|
|
|
|
meta = MetaData(datetime(2022, 8, 5, 14, 35, 12), 0, "", 0, 0, 0, "", 0)
|
|
self.filter.set_meta_data(meta)
|
|
|
|
self.download_filter: List[str] = []
|
|
self.task_id: int = 0
|
|
self.reply_task = None
|
|
|
|
def gen_task_id(self) -> int:
|
|
"""Gen task id"""
|
|
self.task_id += 1
|
|
return self.task_id
|
|
|
|
def add_task_node(self, node: TaskNode):
|
|
"""Add task node"""
|
|
self.task_node[node.task_id] = node
|
|
|
|
def remove_task_node(self, task_id: int):
|
|
"""Remove task node"""
|
|
self.task_node.pop(task_id)
|
|
|
|
def stop_task(self, task_id: str):
|
|
"""Stop task"""
|
|
if task_id == "all":
|
|
for value in self.task_node.values():
|
|
value.stop_transmission()
|
|
else:
|
|
try:
|
|
task = self.task_node.get(int(task_id))
|
|
if task:
|
|
task.stop_transmission()
|
|
except Exception:
|
|
return
|
|
|
|
async def update_reply_message(self):
|
|
"""Update reply message"""
|
|
while self.is_running:
|
|
for key, value in self.task_node.copy().items():
|
|
if value.is_running:
|
|
await report_bot_status(self.bot, value)
|
|
|
|
for key, value in self.task_node.copy().items():
|
|
if value.is_running and value.is_finish():
|
|
self.remove_task_node(key)
|
|
await asyncio.sleep(3)
|
|
|
|
def assign_config(self, _config: dict):
|
|
"""assign config from str.
|
|
|
|
Parameters
|
|
----------
|
|
_config: dict
|
|
application config dict
|
|
|
|
Returns
|
|
-------
|
|
bool
|
|
"""
|
|
|
|
self.download_filter = _config.get("download_filter", self.download_filter)
|
|
|
|
return True
|
|
|
|
def update_config(self):
|
|
"""Update config from str."""
|
|
self.config["download_filter"] = self.download_filter
|
|
|
|
with open("d", "w", encoding="utf-8") as yaml_file:
|
|
self._yaml.dump(self.config, yaml_file)
|
|
|
|
async def start(
|
|
self,
|
|
app: Application,
|
|
client: pyrogram.Client,
|
|
add_download_task: Callable,
|
|
download_chat_task: Callable,
|
|
):
|
|
"""Start bot"""
|
|
self.bot = pyrogram.Client(
|
|
app.application_name + "_bot",
|
|
api_hash=app.api_hash,
|
|
api_id=app.api_id,
|
|
bot_token=app.bot_token,
|
|
workdir=app.session_file_path,
|
|
proxy=app.proxy,
|
|
)
|
|
|
|
# 命令列表
|
|
commands = [
|
|
types.BotCommand("help", _t("Help")),
|
|
types.BotCommand(
|
|
"get_info", _t("Get group and user info from message link")
|
|
),
|
|
types.BotCommand(
|
|
"download",
|
|
_t(
|
|
"To download the video, use the method to directly enter /download to view"
|
|
),
|
|
),
|
|
types.BotCommand(
|
|
"forward",
|
|
_t("Forward video, use the method to directly enter /forward to view"),
|
|
),
|
|
types.BotCommand(
|
|
"listen_forward",
|
|
_t(
|
|
"Listen forward, use the method to directly enter /listen_forward to view"
|
|
),
|
|
),
|
|
types.BotCommand(
|
|
"add_filter",
|
|
_t(
|
|
"Add download filter, use the method to directly enter /add_filter to view"
|
|
),
|
|
),
|
|
types.BotCommand("set_language", _t("Set language")),
|
|
types.BotCommand("stop", _t("Stop bot download or forward")),
|
|
]
|
|
|
|
self.app = app
|
|
self.client = client
|
|
self.add_download_task = add_download_task
|
|
self.download_chat_task = download_chat_task
|
|
|
|
# load config
|
|
if os.path.exists(self.config_path):
|
|
with open(self.config_path, encoding="utf-8") as f:
|
|
config = self._yaml.load(f.read())
|
|
if config:
|
|
self.config = config
|
|
self.assign_config(self.config)
|
|
|
|
await self.bot.start()
|
|
|
|
self.bot_info = await self.bot.get_me()
|
|
|
|
for allowed_user_id in self.app.allowed_user_ids:
|
|
try:
|
|
chat = await self.client.get_chat(allowed_user_id)
|
|
self.allowed_user_ids.append(chat.id)
|
|
except Exception as e:
|
|
logger.warning(f"set allowed_user_ids error: {e}")
|
|
|
|
admin = await self.client.get_me()
|
|
self.allowed_user_ids.append(admin.id)
|
|
|
|
await self.bot.set_bot_commands(commands)
|
|
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
download_from_bot,
|
|
filters=pyrogram.filters.command(["download"])
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
forward_messages,
|
|
filters=pyrogram.filters.command(["forward"])
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
download_forward_media,
|
|
filters=pyrogram.filters.media
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
download_from_link,
|
|
filters=pyrogram.filters.regex(r"^https://t.me.*")
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
set_listen_forward_msg,
|
|
filters=pyrogram.filters.command(["listen_forward"])
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
help_command,
|
|
filters=pyrogram.filters.command(["help"])
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
get_info,
|
|
filters=pyrogram.filters.command(["get_info"])
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
help_command,
|
|
filters=pyrogram.filters.command(["start"])
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
set_language,
|
|
filters=pyrogram.filters.command(["set_language"])
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
add_filter,
|
|
filters=pyrogram.filters.command(["add_filter"])
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
stop,
|
|
filters=pyrogram.filters.command(["stop"])
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
|
|
self.bot.add_handler(
|
|
CallbackQueryHandler(
|
|
on_query_handler, filters=pyrogram.filters.user(self.allowed_user_ids)
|
|
)
|
|
)
|
|
|
|
self.client.add_handler(MessageHandler(listen_forward_msg))
|
|
|
|
try:
|
|
await send_help_str(self.bot, admin.id)
|
|
except Exception:
|
|
pass
|
|
|
|
self.reply_task = _bot.app.loop.create_task(_bot.update_reply_message())
|
|
|
|
self.bot.add_handler(
|
|
MessageHandler(
|
|
forward_to_comments,
|
|
filters=pyrogram.filters.command(["forward_to_comments"])
|
|
& pyrogram.filters.user(self.allowed_user_ids),
|
|
)
|
|
)
|
|
|
|
|
|
_bot = DownloadBot()
|
|
|
|
|
|
async def start_download_bot(
|
|
app: Application,
|
|
client: pyrogram.Client,
|
|
add_download_task: Callable,
|
|
download_chat_task: Callable,
|
|
):
|
|
"""Start download bot"""
|
|
await _bot.start(app, client, add_download_task, download_chat_task)
|
|
|
|
|
|
async def stop_download_bot():
|
|
"""Stop download bot"""
|
|
_bot.update_config()
|
|
_bot.is_running = False
|
|
if _bot.reply_task:
|
|
_bot.reply_task.cancel()
|
|
_bot.stop_task("all")
|
|
if _bot.bot:
|
|
await _bot.bot.stop()
|
|
|
|
|
|
async def send_help_str(client: pyrogram.Client, chat_id):
|
|
"""
|
|
Sends a help string to the specified chat ID using the provided client.
|
|
|
|
Parameters:
|
|
client (pyrogram.Client): The Pyrogram client used to send the message.
|
|
chat_id: The ID of the chat to which the message will be sent.
|
|
|
|
Returns:
|
|
str: The help string that was sent.
|
|
|
|
Note:
|
|
The help string includes information about the Telegram Media Downloader bot,
|
|
its version, and the available commands.
|
|
"""
|
|
|
|
update_keyboard = InlineKeyboardMarkup(
|
|
[
|
|
[
|
|
InlineKeyboardButton(
|
|
"Github",
|
|
url="https://github.com/tangyoha/telegram_media_downloader/releases",
|
|
),
|
|
InlineKeyboardButton(
|
|
"Join us", url="https://t.me/TeegramMediaDownload"
|
|
),
|
|
]
|
|
]
|
|
)
|
|
latest_release_str = ""
|
|
# try:
|
|
# latest_release = get_latest_release(_bot.app.proxy)
|
|
|
|
# latest_release_str = (
|
|
# f"{_t('New Version')}: [{latest_release['name']}]({latest_release['html_url']})\an"
|
|
# if latest_release
|
|
# else ""
|
|
# )
|
|
# except Exception:
|
|
# latest_release_str = ""
|
|
|
|
msg = (
|
|
f"`\n🤖 {_t('Telegram Media Downloader')}\n"
|
|
f"🌐 {_t('Version')}: {utils.__version__}`\n"
|
|
f"{latest_release_str}\n"
|
|
f"{_t('Available commands:')}\n"
|
|
f"/help - {_t('Show available commands')}\n"
|
|
f"/get_info - {_t('Get group and user info from message link')}\n"
|
|
f"/download - {_t('Download messages')}\n"
|
|
f"/forward - {_t('Forward messages')}\n"
|
|
f"/listen_forward - {_t('Listen for forwarded messages')}\n"
|
|
f"/forward_to_comments - {_t('Forward a specific media to a comment section')}\n"
|
|
f"/set_language - {_t('Set language')}\n"
|
|
f"/stop - {_t('Stop bot download or forward')}\n\n"
|
|
f"{_t('**Note**: 1 means the start of the entire chat')},"
|
|
f"{_t('0 means the end of the entire chat')}\n"
|
|
f"`[` `]` {_t('means optional, not required')}\n"
|
|
)
|
|
|
|
await client.send_message(chat_id, msg, reply_markup=update_keyboard)
|
|
|
|
|
|
async def help_command(client: pyrogram.Client, message: pyrogram.types.Message):
|
|
"""
|
|
Sends a message with the available commands and their usage.
|
|
|
|
Parameters:
|
|
client (pyrogram.Client): The client instance.
|
|
message (pyrogram.types.Message): The message object.
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
|
|
await send_help_str(client, message.chat.id)
|
|
|
|
|
|
async def set_language(client: pyrogram.Client, message: pyrogram.types.Message):
|
|
"""
|
|
Set the language of the bot.
|
|
|
|
Parameters:
|
|
client (pyrogram.Client): The pyrogram client.
|
|
message (pyrogram.types.Message): The message containing the command.
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
|
|
if len(message.text.split()) != 2:
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
_t("Invalid command format. Please use /set_language en/ru/zh/ua"),
|
|
)
|
|
return
|
|
|
|
language = message.text.split()[1]
|
|
|
|
try:
|
|
language = Language[language.upper()]
|
|
_bot.app.set_language(language)
|
|
await client.send_message(
|
|
message.from_user.id, f"{_t('Language set to')} {language.name}"
|
|
)
|
|
except KeyError:
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
_t("Invalid command format. Please use /set_language en/ru/zh/ua"),
|
|
)
|
|
|
|
|
|
async def get_info(client: pyrogram.Client, message: pyrogram.types.Message):
|
|
"""
|
|
Async function that retrieves information from a group message link.
|
|
"""
|
|
|
|
msg = _t("Invalid command format. Please use /get_info group_message_link")
|
|
|
|
args = message.text.split()
|
|
if len(args) != 2:
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
msg,
|
|
)
|
|
return
|
|
|
|
chat_id, message_id, _ = await parse_link(_bot.client, args[1])
|
|
|
|
entity = None
|
|
if chat_id:
|
|
entity = await _bot.client.get_chat(chat_id)
|
|
|
|
if entity:
|
|
if message_id:
|
|
_message = await retry(_bot.client.get_messages, args=(chat_id, message_id))
|
|
if _message:
|
|
meta_data = MetaData()
|
|
set_meta_data(meta_data, _message)
|
|
msg = (
|
|
f"`\n"
|
|
f"{_t('Group/Channel')}\n"
|
|
f"├─ {_t('id')}: {entity.id}\n"
|
|
f"├─ {_t('first name')}: {entity.first_name}\n"
|
|
f"├─ {_t('last name')}: {entity.last_name}\n"
|
|
f"└─ {_t('name')}: {entity.username}\n"
|
|
f"{_t('Message')}\n"
|
|
)
|
|
|
|
for key, value in meta_data.data().items():
|
|
if key == "send_name":
|
|
msg += f"└─ {key}: {value or None}\n"
|
|
else:
|
|
msg += f"├─ {key}: {value or None}\n"
|
|
|
|
msg += "`"
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
msg,
|
|
)
|
|
|
|
|
|
async def add_filter(client: pyrogram.Client, message: pyrogram.types.Message):
|
|
"""
|
|
Set the download filter of the bot.
|
|
|
|
Parameters:
|
|
client (pyrogram.Client): The pyrogram client.
|
|
message (pyrogram.types.Message): The message containing the command.
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
|
|
args = message.text.split(maxsplit=1)
|
|
if len(args) != 2:
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
_t("Invalid command format. Please use /add_filter your filter"),
|
|
)
|
|
return
|
|
|
|
filter_str = replace_date_time(args[1])
|
|
res, err = _bot.filter.check_filter(filter_str)
|
|
if res:
|
|
_bot.app.down = args[1]
|
|
await client.send_message(
|
|
message.from_user.id, f"{_t('Add download filter')} : {args[1]}"
|
|
)
|
|
else:
|
|
await client.send_message(
|
|
message.from_user.id, f"{err}\n{_t('Check error, please add again!')}"
|
|
)
|
|
return
|
|
|
|
|
|
async def direct_download(
|
|
download_bot: DownloadBot,
|
|
chat_id: Union[str, int],
|
|
message: pyrogram.types.Message,
|
|
download_message: pyrogram.types.Message,
|
|
client: pyrogram.Client = None,
|
|
):
|
|
"""Direct Download"""
|
|
|
|
replay_message = "Direct download..."
|
|
last_reply_message = await download_bot.bot.send_message(
|
|
message.from_user.id, replay_message, reply_to_message_id=message.id
|
|
)
|
|
|
|
node = TaskNode(
|
|
chat_id=chat_id,
|
|
from_user_id=message.from_user.id,
|
|
reply_message_id=last_reply_message.id,
|
|
replay_message=replay_message,
|
|
limit=1,
|
|
bot=download_bot.bot,
|
|
task_id=_bot.gen_task_id(),
|
|
)
|
|
|
|
node.client = client
|
|
|
|
_bot.add_task_node(node)
|
|
|
|
await _bot.add_download_task(
|
|
download_message,
|
|
node,
|
|
)
|
|
|
|
node.is_running = True
|
|
|
|
|
|
async def download_forward_media(
|
|
client: pyrogram.Client, message: pyrogram.types.Message
|
|
):
|
|
"""
|
|
Downloads the media from a forwarded message.
|
|
|
|
Parameters:
|
|
client (pyrogram.Client): The client instance.
|
|
message (pyrogram.types.Message): The message object.
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
|
|
if message.media and getattr(message, message.media.value):
|
|
await direct_download(_bot, message.from_user.id, message, message, client)
|
|
return
|
|
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
f"1. {_t('Direct download, directly forward the message to your robot')}\n\n",
|
|
parse_mode=pyrogram.enums.ParseMode.HTML,
|
|
)
|
|
|
|
|
|
async def download_from_link(client: pyrogram.Client, message: pyrogram.types.Message):
|
|
"""
|
|
Downloads a single message from a Telegram link.
|
|
|
|
Parameters:
|
|
client (pyrogram.Client): The pyrogram client.
|
|
message (pyrogram.types.Message): The message containing the Telegram link.
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
|
|
if not message.text or not message.text.startswith("https://t.me"):
|
|
return
|
|
|
|
msg = (
|
|
f"1. {_t('Directly download a single message')}\n"
|
|
"<i>https://t.me/12000000/1</i>\n\n"
|
|
)
|
|
|
|
text = message.text.split()
|
|
if len(text) != 1:
|
|
await client.send_message(
|
|
message.from_user.id, msg, parse_mode=pyrogram.enums.ParseMode.HTML
|
|
)
|
|
|
|
chat_id, message_id, _ = await parse_link(_bot.client, text[0])
|
|
|
|
entity = None
|
|
if chat_id:
|
|
entity = await _bot.client.get_chat(chat_id)
|
|
if entity:
|
|
if message_id:
|
|
download_message = await retry(
|
|
_bot.client.get_messages, args=(chat_id, message_id)
|
|
)
|
|
if download_message:
|
|
await direct_download(_bot, entity.id, message, download_message)
|
|
else:
|
|
client.send_message(
|
|
message.from_user.id,
|
|
f"{_t('From')} {entity.title} {_t('download')} {message_id} {_t('error')}!",
|
|
reply_to_message_id=message.id,
|
|
)
|
|
return
|
|
|
|
await client.send_message(
|
|
message.from_user.id, msg, parse_mode=pyrogram.enums.ParseMode.HTML
|
|
)
|
|
|
|
|
|
# pylint: disable = R0912, R0915,R0914
|
|
|
|
|
|
async def download_from_bot(client: pyrogram.Client, message: pyrogram.types.Message):
|
|
"""Download from bot"""
|
|
|
|
msg = (
|
|
f"{_t('Parameter error, please enter according to the reference format')}:\n\n"
|
|
f"1. {_t('Download all messages of common group')}\n"
|
|
"<i>/download https://t.me/fkdhlg 1 0</i>\n\n"
|
|
f"{_t('The private group (channel) link is a random group message link')}\n\n"
|
|
f"2. {_t('The download starts from the N message to the end of the M message')}. "
|
|
f"{_t('When M is 0, it means the last message. The filter is optional')}\n"
|
|
f"<i>/download https://t.me/12000000 N M [filter]</i>\n\n"
|
|
)
|
|
|
|
args = message.text.split(maxsplit=4)
|
|
if not message.text or len(args) < 4:
|
|
await client.send_message(
|
|
message.from_user.id, msg, parse_mode=pyrogram.enums.ParseMode.HTML
|
|
)
|
|
return
|
|
|
|
url = args[1]
|
|
try:
|
|
start_offset_id = int(args[2])
|
|
end_offset_id = int(args[3])
|
|
except Exception:
|
|
await client.send_message(
|
|
message.from_user.id, msg, parse_mode=pyrogram.enums.ParseMode.HTML
|
|
)
|
|
return
|
|
|
|
limit = 0
|
|
if end_offset_id:
|
|
if end_offset_id < start_offset_id:
|
|
raise ValueError(
|
|
f"end_offset_id < start_offset_id, {end_offset_id} < {start_offset_id}"
|
|
)
|
|
|
|
limit = end_offset_id - start_offset_id + 1
|
|
|
|
download_filter = args[4] if len(args) > 4 else None
|
|
|
|
if download_filter:
|
|
download_filter = replace_date_time(download_filter)
|
|
res, err = _bot.filter.check_filter(download_filter)
|
|
if not res:
|
|
await client.send_message(
|
|
message.from_user.id, err, reply_to_message_id=message.id
|
|
)
|
|
return
|
|
try:
|
|
chat_id, _, _ = await parse_link(_bot.client, url)
|
|
if chat_id:
|
|
entity = await _bot.client.get_chat(chat_id)
|
|
if entity:
|
|
chat_title = entity.title
|
|
reply_message = f"from {chat_title} "
|
|
chat_download_config = ChatDownloadConfig()
|
|
chat_download_config.last_read_message_id = start_offset_id
|
|
chat_download_config.download_filter = download_filter
|
|
reply_message += (
|
|
f"download message id = {start_offset_id} - {end_offset_id} !"
|
|
)
|
|
last_reply_message = await client.send_message(
|
|
message.from_user.id, reply_message, reply_to_message_id=message.id
|
|
)
|
|
node = TaskNode(
|
|
chat_id=entity.id,
|
|
from_user_id=message.from_user.id,
|
|
reply_message_id=last_reply_message.id,
|
|
replay_message=reply_message,
|
|
limit=limit,
|
|
start_offset_id=start_offset_id,
|
|
end_offset_id=end_offset_id,
|
|
bot=_bot.bot,
|
|
task_id=_bot.gen_task_id(),
|
|
)
|
|
_bot.add_task_node(node)
|
|
_bot.app.loop.create_task(
|
|
_bot.download_chat_task(_bot.client, chat_download_config, node)
|
|
)
|
|
except Exception as e:
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
f"{_t('chat input error, please enter the channel or group link')}\n\n"
|
|
f"{_t('Error type')}: {e.__class__}"
|
|
f"{_t('Exception message')}: {e}",
|
|
)
|
|
return
|
|
|
|
|
|
async def get_forward_task_node(
|
|
client: pyrogram.Client,
|
|
message: pyrogram.types.Message,
|
|
task_type: TaskType,
|
|
src_chat_link: str,
|
|
dst_chat_link: str,
|
|
offset_id: int = 0,
|
|
end_offset_id: int = 0,
|
|
download_filter: str = None,
|
|
reply_comment: bool = False,
|
|
):
|
|
"""Get task node"""
|
|
limit: int = 0
|
|
|
|
if end_offset_id:
|
|
if end_offset_id < offset_id:
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
f" end_offset_id({end_offset_id}) < start_offset_id({offset_id}),"
|
|
f" end_offset_id{_t('must be greater than')} offset_id",
|
|
)
|
|
return None
|
|
|
|
limit = end_offset_id - offset_id + 1
|
|
|
|
src_chat_id, _, _ = await parse_link(_bot.client, src_chat_link)
|
|
dst_chat_id, target_msg_id, topic_id = await parse_link(_bot.client, dst_chat_link)
|
|
|
|
if not src_chat_id or not dst_chat_id:
|
|
logger.info(f"{src_chat_id} {dst_chat_id}")
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
_t("Invalid chat link") + f"{src_chat_id} {dst_chat_id}",
|
|
reply_to_message_id=message.id,
|
|
)
|
|
return None
|
|
|
|
try:
|
|
src_chat = await _bot.client.get_chat(src_chat_id)
|
|
dst_chat = await _bot.client.get_chat(dst_chat_id)
|
|
except Exception as e:
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
f"{_t('Invalid chat link')} {e}",
|
|
reply_to_message_id=message.id,
|
|
)
|
|
logger.exception(f"get chat error: {e}")
|
|
return None
|
|
|
|
me = await client.get_me()
|
|
if dst_chat.id == me.id:
|
|
# TODO: when bot receive message judge if download
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
_t("Cannot be forwarded to this bot, will cause an infinite loop"),
|
|
reply_to_message_id=message.id,
|
|
)
|
|
return None
|
|
|
|
if download_filter:
|
|
download_filter = replace_date_time(download_filter)
|
|
res, err = _bot.filter.check_filter(download_filter)
|
|
if not res:
|
|
await client.send_message(
|
|
message.from_user.id, err, reply_to_message_id=message.id
|
|
)
|
|
|
|
last_reply_message = await client.send_message(
|
|
message.from_user.id,
|
|
"Forwarding message, please wait...",
|
|
reply_to_message_id=message.id,
|
|
)
|
|
|
|
node = TaskNode(
|
|
chat_id=src_chat.id,
|
|
from_user_id=message.from_user.id,
|
|
upload_telegram_chat_id=dst_chat_id,
|
|
reply_message_id=last_reply_message.id,
|
|
replay_message=last_reply_message.text,
|
|
has_protected_content=src_chat.has_protected_content,
|
|
download_filter=download_filter,
|
|
limit=limit,
|
|
start_offset_id=offset_id,
|
|
end_offset_id=end_offset_id,
|
|
bot=_bot.bot,
|
|
task_id=_bot.gen_task_id(),
|
|
task_type=task_type,
|
|
topic_id=topic_id,
|
|
)
|
|
|
|
if target_msg_id and reply_comment:
|
|
node.reply_to_message = await _bot.client.get_discussion_message(
|
|
dst_chat_id, target_msg_id
|
|
)
|
|
|
|
_bot.add_task_node(node)
|
|
|
|
node.upload_user = _bot.client
|
|
if not dst_chat.type is pyrogram.enums.ChatType.BOT:
|
|
has_permission = await check_user_permission(_bot.client, me.id, dst_chat.id)
|
|
if has_permission:
|
|
node.upload_user = _bot.bot
|
|
|
|
if node.upload_user is _bot.client:
|
|
await client.edit_message_text(
|
|
message.from_user.id,
|
|
last_reply_message.id,
|
|
"Note that the robot may not be in the target group,"
|
|
" use the user account to forward",
|
|
)
|
|
|
|
return node
|
|
|
|
|
|
# pylint: disable = R0914
|
|
async def forward_message_impl(
|
|
client: pyrogram.Client, message: pyrogram.types.Message, reply_comment: bool
|
|
):
|
|
"""
|
|
Forward message
|
|
"""
|
|
|
|
async def report_error(client: pyrogram.Client, message: pyrogram.types.Message):
|
|
"""Report error"""
|
|
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
f"{_t('Invalid command format')}."
|
|
f"{_t('Please use')} "
|
|
"/forward https://t.me/c/src_chat https://t.me/c/dst_chat "
|
|
f"1 400 `[`{_t('Filter')}`]`\n",
|
|
)
|
|
|
|
args = message.text.split(maxsplit=5)
|
|
if len(args) < 5:
|
|
await report_error(client, message)
|
|
return
|
|
|
|
src_chat_link = args[1]
|
|
dst_chat_link = args[2]
|
|
|
|
try:
|
|
offset_id = int(args[3])
|
|
end_offset_id = int(args[4])
|
|
except Exception:
|
|
await report_error(client, message)
|
|
return
|
|
|
|
download_filter = args[5] if len(args) > 5 else None
|
|
|
|
node = await get_forward_task_node(
|
|
client,
|
|
message,
|
|
TaskType.Forward,
|
|
src_chat_link,
|
|
dst_chat_link,
|
|
offset_id,
|
|
end_offset_id,
|
|
download_filter,
|
|
reply_comment,
|
|
)
|
|
|
|
if not node:
|
|
return
|
|
|
|
if not node.has_protected_content:
|
|
try:
|
|
async for item in get_chat_history_v2( # type: ignore
|
|
_bot.client,
|
|
node.chat_id,
|
|
limit=node.limit,
|
|
max_id=node.end_offset_id,
|
|
offset_id=offset_id,
|
|
reverse=True,
|
|
):
|
|
await forward_normal_content(client, node, item)
|
|
if node.is_stop_transmission:
|
|
await client.edit_message_text(
|
|
message.from_user.id,
|
|
node.reply_message_id,
|
|
f"{_t('Stop Forward')}",
|
|
)
|
|
break
|
|
except Exception as e:
|
|
await client.edit_message_text(
|
|
message.from_user.id,
|
|
node.reply_message_id,
|
|
f"{_t('Error forwarding message')} {e}",
|
|
)
|
|
finally:
|
|
await report_bot_status(client, node, immediate_reply=True)
|
|
node.stop_transmission()
|
|
else:
|
|
await forward_msg(node, offset_id)
|
|
|
|
|
|
async def forward_messages(client: pyrogram.Client, message: pyrogram.types.Message):
|
|
"""
|
|
Forwards messages from one chat to another.
|
|
|
|
Parameters:
|
|
client (pyrogram.Client): The pyrogram client.
|
|
message (pyrogram.types.Message): The message containing the command.
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
return await forward_message_impl(client, message, False)
|
|
|
|
|
|
async def forward_normal_content(
|
|
client: pyrogram.Client, node: TaskNode, message: pyrogram.types.Message
|
|
):
|
|
"""Forward normal content"""
|
|
forward_ret = ForwardStatus.FailedForward
|
|
if node.download_filter:
|
|
meta_data = MetaData()
|
|
caption = message.caption
|
|
if caption:
|
|
caption = validate_title(caption)
|
|
_bot.app.set_caption_name(node.chat_id, message.media_group_id, caption)
|
|
else:
|
|
caption = _bot.app.get_caption_name(node.chat_id, message.media_group_id)
|
|
set_meta_data(meta_data, message, caption)
|
|
_bot.filter.set_meta_data(meta_data)
|
|
if not _bot.filter.exec(node.download_filter):
|
|
forward_ret = ForwardStatus.SkipForward
|
|
if message.media_group_id:
|
|
node.upload_status[message.id] = UploadStatus.SkipUpload
|
|
await proc_cache_forward(_bot.client, node, message, False)
|
|
await report_bot_forward_status(client, node, forward_ret)
|
|
return
|
|
|
|
await upload_telegram_chat_message(
|
|
_bot.client, node.upload_user, _bot.app, node, message
|
|
)
|
|
|
|
|
|
async def forward_msg(node: TaskNode, message_id: int):
|
|
"""Forward normal message"""
|
|
|
|
chat_download_config = ChatDownloadConfig()
|
|
chat_download_config.last_read_message_id = message_id
|
|
chat_download_config.download_filter = node.download_filter # type: ignore
|
|
|
|
await _bot.download_chat_task(_bot.client, chat_download_config, node)
|
|
|
|
|
|
async def set_listen_forward_msg(
|
|
client: pyrogram.Client, message: pyrogram.types.Message
|
|
):
|
|
"""
|
|
Set the chat to listen for forwarded messages.
|
|
|
|
Args:
|
|
client (pyrogram.Client): The pyrogram client.
|
|
message (pyrogram.types.Message): The message sent by the user.
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
args = message.text.split(maxsplit=3)
|
|
|
|
if len(args) < 3:
|
|
await client.send_message(
|
|
message.from_user.id,
|
|
f"{_t('Invalid command format')}. {_t('Please use')} /listen_forward "
|
|
f"https://t.me/c/src_chat https://t.me/c/dst_chat [{_t('Filter')}]\n",
|
|
)
|
|
return
|
|
|
|
src_chat_link = args[1]
|
|
dst_chat_link = args[2]
|
|
|
|
download_filter = args[3] if len(args) > 3 else None
|
|
|
|
node = await get_forward_task_node(
|
|
client,
|
|
message,
|
|
TaskType.ListenForward,
|
|
src_chat_link,
|
|
dst_chat_link,
|
|
download_filter=download_filter,
|
|
)
|
|
|
|
if not node:
|
|
return
|
|
|
|
if node.chat_id in _bot.listen_forward_chat:
|
|
_bot.remove_task_node(_bot.listen_forward_chat[node.chat_id].task_id)
|
|
|
|
node.is_running = True
|
|
_bot.listen_forward_chat[node.chat_id] = node
|
|
|
|
|
|
async def listen_forward_msg(client: pyrogram.Client, message: pyrogram.types.Message):
|
|
"""
|
|
Forwards messages from a chat to another chat if the message does not contain protected content.
|
|
If the message contains protected content, it will be downloaded and forwarded to the other chat.
|
|
|
|
Parameters:
|
|
client (pyrogram.Client): The pyrogram client.
|
|
message (pyrogram.types.Message): The message to be forwarded.
|
|
"""
|
|
|
|
if message.chat and message.chat.id in _bot.listen_forward_chat:
|
|
node = _bot.listen_forward_chat[message.chat.id]
|
|
|
|
# TODO(tangyoha):fix run time change protected content
|
|
if not node.has_protected_content:
|
|
await forward_normal_content(client, node, message)
|
|
await report_bot_status(client, node, immediate_reply=True)
|
|
else:
|
|
await _bot.add_download_task(
|
|
message,
|
|
node,
|
|
)
|
|
|
|
|
|
async def stop(client: pyrogram.Client, message: pyrogram.types.Message):
|
|
"""Stops listening for forwarded messages."""
|
|
|
|
await client.send_message(
|
|
message.chat.id,
|
|
_t("Please select:"),
|
|
reply_markup=InlineKeyboardMarkup(
|
|
[
|
|
[
|
|
InlineKeyboardButton(
|
|
_t("Stop Download"), callback_data="stop_download"
|
|
),
|
|
InlineKeyboardButton(
|
|
_t("Stop Forward"), callback_data="stop_forward"
|
|
),
|
|
],
|
|
[ # Second row
|
|
InlineKeyboardButton(
|
|
_t("Stop Listen Forward"), callback_data="stop_listen_forward"
|
|
)
|
|
],
|
|
]
|
|
),
|
|
)
|
|
|
|
|
|
async def stop_task(
|
|
client: pyrogram.Client,
|
|
query: pyrogram.types.CallbackQuery,
|
|
queryHandler: str,
|
|
task_type: TaskType,
|
|
):
|
|
"""Stop task"""
|
|
if query.data == queryHandler:
|
|
buttons: List[InlineKeyboardButton] = []
|
|
temp_buttons: List[InlineKeyboardButton] = []
|
|
for key, value in _bot.task_node.copy().items():
|
|
if not value.is_finish() and value.task_type is task_type:
|
|
if len(temp_buttons) == 3:
|
|
buttons.append(temp_buttons)
|
|
temp_buttons = []
|
|
temp_buttons.append(
|
|
InlineKeyboardButton(
|
|
f"{key}", callback_data=f"{queryHandler} task {key}"
|
|
)
|
|
)
|
|
if temp_buttons:
|
|
buttons.append(temp_buttons)
|
|
|
|
if buttons:
|
|
buttons.insert(
|
|
0,
|
|
[
|
|
InlineKeyboardButton(
|
|
_t("all"), callback_data=f"{queryHandler} task all"
|
|
)
|
|
],
|
|
)
|
|
await client.edit_message_text(
|
|
query.message.from_user.id,
|
|
query.message.id,
|
|
f"{_t('Stop')} {_t(task_type.name)}...",
|
|
reply_markup=InlineKeyboardMarkup(buttons),
|
|
)
|
|
else:
|
|
await client.edit_message_text(
|
|
query.message.from_user.id,
|
|
query.message.id,
|
|
f"{_t('No Task')}",
|
|
)
|
|
else:
|
|
task_id = query.data.split(" ")[2]
|
|
await client.edit_message_text(
|
|
query.message.from_user.id,
|
|
query.message.id,
|
|
f"{_t('Stop')} {_t(task_type.name)}...",
|
|
)
|
|
_bot.stop_task(task_id)
|
|
|
|
|
|
async def on_query_handler(
|
|
client: pyrogram.Client, query: pyrogram.types.CallbackQuery
|
|
):
|
|
"""
|
|
Asynchronous function that handles query callbacks.
|
|
|
|
Parameters:
|
|
client (pyrogram.Client): The Pyrogram client object.
|
|
query (pyrogram.types.CallbackQuery): The callback query object.
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
|
|
for it in QueryHandler:
|
|
queryHandler = QueryHandlerStr.get_str(it.value)
|
|
if queryHandler in query.data:
|
|
await stop_task(client, query, queryHandler, TaskType(it.value))
|
|
|
|
|
|
async def forward_to_comments(client: pyrogram.Client, message: pyrogram.types.Message):
|
|
"""
|
|
Forwards specified media to a designated comment section.
|
|
|
|
Usage: /forward_to_comments <source_chat_link> <destination_chat_link> <msg_start_id> <msg_end_id>
|
|
|
|
Parameters:
|
|
client (pyrogram.Client): The pyrogram client.
|
|
message (pyrogram.types.Message): The message containing the command.
|
|
"""
|
|
return await forward_message_impl(client, message, True)
|