TelegramPoster/plugins/commands/photos.py

357 lines
11 KiB
Python
Raw Normal View History

2023-03-20 13:03:03 +02:00
import asyncio
import logging
2023-03-20 13:03:03 +02:00
from glob import iglob
from io import BytesIO
2023-03-20 13:03:03 +02:00
from os import getcwd, makedirs, path, remove
from pathlib import Path
2023-03-20 13:03:03 +02:00
from shutil import disk_usage, rmtree
from traceback import format_exc
from uuid import uuid4
from zipfile import ZipFile
from convopyro import listen_message
from photosapi_client.errors import UnexpectedStatus
2023-02-17 17:46:13 +02:00
from pyrogram import filters
from pyrogram.client import Client
2023-06-28 09:53:15 +03:00
from pyrogram.types import (
KeyboardButton,
Message,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
from ujson import loads
2023-02-17 17:46:13 +02:00
from classes.pyroclient import PyroClient
from modules.api_client import (
BodyPhotoUpload,
File,
client,
photo_delete,
photo_upload,
2023-06-28 09:53:15 +03:00
video_delete,
video_upload,
)
from modules.utils import USERS_WITH_CONTEXT, extract_and_save
2023-02-17 17:46:13 +02:00
logger = logging.getLogger(__name__)
2023-02-17 17:46:13 +02:00
@Client.on_message(~filters.scheduled & filters.command(["import"], prefixes=["", "/"]))
async def cmd_import(app: PyroClient, msg: Message):
if msg.from_user.id not in app.admins:
return
global USERS_WITH_CONTEXT
if msg.from_user.id not in USERS_WITH_CONTEXT:
USERS_WITH_CONTEXT.append(msg.from_user.id)
else:
return
2023-06-28 11:37:18 +03:00
user = await app.find_user(msg.from_user)
await msg.reply_text(app._("import_request", "message", locale=user.locale))
answer = await listen_message(app, msg.chat.id, timeout=600)
USERS_WITH_CONTEXT.remove(msg.from_user.id)
if answer is None:
2023-03-20 13:03:03 +02:00
await msg.reply_text(
2023-06-28 11:37:18 +03:00
app._("import_ignored", "message", locale=user.locale),
quote=True,
2023-03-18 21:53:26 +02:00
)
return
if answer.text == "/cancel":
2023-06-28 11:37:18 +03:00
await answer.reply_text(app._("import_abort", "message", locale=user.locale))
return
if answer.document is None:
await answer.reply_text(
app._(
"import_invalid_media",
"message",
2023-06-28 11:37:18 +03:00
locale=user.locale,
),
2023-03-20 15:50:57 +02:00
quote=True,
)
return
if answer.document.mime_type != "application/zip":
await answer.reply_text(
2023-06-28 11:37:18 +03:00
app._("import_invalid_mime", "message", locale=user.locale),
quote=True,
2023-03-20 15:50:57 +02:00
)
return
if disk_usage(getcwd())[2] < (answer.document.file_size) * 3:
await msg.reply_text(
2023-06-28 11:37:18 +03:00
app._("import_too_big", "message", locale=user.locale).format(
answer.document.file_size // (2**30),
disk_usage(getcwd())[2] // (2**30),
2023-03-20 13:03:03 +02:00
)
2023-03-20 15:50:57 +02:00
)
return
tmp_dir = str(uuid4())
logging.info(
"Importing '%s' file %s bytes big (TMP ID %s)",
answer.document.file_name,
answer.document.file_size,
tmp_dir,
)
makedirs(Path(f"{app.config['locations']['tmp']}/{tmp_dir}"), exist_ok=True)
tmp_path = Path(f"{app.config['locations']['tmp']}/{answer.document.file_id}")
2023-03-20 13:03:03 +02:00
downloading = await answer.reply_text(
2023-06-28 11:37:18 +03:00
app._("import_downloading", "message", locale=user.locale),
quote=True,
)
await app.download_media(answer, file_name=str(tmp_path))
2023-06-28 11:37:18 +03:00
await downloading.edit(app._("import_unpacking", "message", locale=user.locale))
try:
with ZipFile(tmp_path, "r") as handle:
tasks = [
extract_and_save(
handle, name, Path(f"{app.config['locations']['tmp']}/{tmp_dir}")
)
for name in handle.namelist()
]
_ = await asyncio.gather(*tasks)
except Exception as exp:
logger.error(
"Could not import '%s' due to %s: %s",
answer.document.file_name,
exp,
format_exc(),
2023-03-20 13:03:03 +02:00
)
2023-03-20 15:50:57 +02:00
await answer.reply_text(
2023-06-28 11:37:18 +03:00
app._("import_unpack_error", "message", locale=user.locale).format(
exp, format_exc()
)
2023-03-20 15:50:57 +02:00
)
2023-03-18 21:53:26 +02:00
return
2023-02-17 17:46:13 +02:00
logger.info("Downloaded '%s' - awaiting upload", answer.document.file_name)
2023-02-17 17:46:13 +02:00
2023-06-28 11:37:18 +03:00
await downloading.edit(app._("import_uploading", "message", locale=user.locale))
2023-02-26 00:19:08 +02:00
remove(tmp_path)
2023-02-26 00:19:08 +02:00
for filename in iglob(
str(Path(f"{app.config['locations']['tmp']}/{tmp_dir}")) + "**/**",
recursive=True,
):
if not path.isfile(filename):
continue
with open(str(filename), "rb") as fh:
photo_bytes = BytesIO(fh.read())
try:
2023-06-28 09:53:15 +03:00
# VIDEO SUPPORT IS PLANNED HERE TOO
uploaded = await photo_upload(
app.config["posting"]["api"]["album"],
client=client,
multipart_data=BodyPhotoUpload(
File(photo_bytes, Path(filename).name, "image/jpeg")
),
ignore_duplicates=app.config["submission"]["allow_duplicates"],
compress=False,
caption="queue",
2023-03-21 15:34:25 +02:00
)
except UnexpectedStatus as exp:
logger.error(
"Could not upload '%s' from '%s': %s",
filename,
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
exp,
2023-03-21 15:34:25 +02:00
)
await msg.reply_text(
app._(
"import_upload_error_other",
"message",
2023-06-28 11:37:18 +03:00
locale=user.locale,
).format(path.basename(filename)),
disable_notification=True,
2023-03-21 15:34:25 +02:00
)
continue
uploaded_dict = loads(uploaded.content.decode("utf-8"))
if "duplicates" in uploaded_dict:
logger.warning(
"Could not upload '%s' from '%s'. Duplicates: %s",
filename,
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
str(uploaded_dict["duplicates"]),
2023-03-21 15:34:25 +02:00
)
if len(uploaded_dict["duplicates"]) > 0:
await msg.reply_text(
app._(
"import_upload_error_duplicate",
"message",
2023-06-28 11:37:18 +03:00
locale=user.locale,
).format(path.basename(filename)),
disable_notification=True,
)
else:
await msg.reply_text(
app._(
"import_upload_error_other",
"message",
2023-06-28 11:37:18 +03:00
locale=user.locale,
).format(path.basename(filename)),
disable_notification=True,
)
2023-03-21 15:34:25 +02:00
else:
logger.info(
"Uploaded '%s' from '%s' and got ID %s",
filename,
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
uploaded.parsed.id,
2023-03-21 15:34:25 +02:00
)
2023-02-26 00:19:08 +02:00
await downloading.delete()
logger.info(
"Removing '%s' after uploading",
Path(f"{app.config['locations']['tmp']}/{tmp_dir}"),
)
rmtree(Path(f"{app.config['locations']['tmp']}/{tmp_dir}"), ignore_errors=True)
await answer.reply_text(
2023-06-28 11:37:18 +03:00
app._("import_finished", "message", locale=user.locale),
quote=True,
)
return
@Client.on_message(~filters.scheduled & filters.command(["export"], prefixes=["", "/"]))
async def cmd_export(app: PyroClient, msg: Message):
if msg.from_user.id not in app.admins:
return
@Client.on_message(~filters.scheduled & filters.command(["remove"], prefixes=["", "/"]))
async def cmd_remove(app: PyroClient, msg: Message):
if msg.from_user.id not in app.admins:
return
global USERS_WITH_CONTEXT
if msg.from_user.id not in USERS_WITH_CONTEXT:
USERS_WITH_CONTEXT.append(msg.from_user.id)
else:
return
2023-06-28 11:37:18 +03:00
user = await app.find_user(msg.from_user)
await msg.reply_text(app._("remove_request", "message", locale=user.locale))
2023-02-26 00:19:08 +02:00
2023-06-28 09:56:21 +03:00
answer_id = await listen_message(app, msg.chat.id, timeout=600)
USERS_WITH_CONTEXT.remove(msg.from_user.id)
2023-06-28 09:56:21 +03:00
if answer_id is None:
await msg.reply_text(
2023-06-28 11:37:18 +03:00
app._("remove_ignored", "message", locale=user.locale),
quote=True,
)
return
2023-06-28 09:56:21 +03:00
if answer_id.text == "/cancel":
2023-06-28 11:37:18 +03:00
await answer_id.reply_text(app._("remove_abort", "message", locale=user.locale))
return
2023-06-28 09:53:15 +03:00
await msg.reply_text(
2023-06-28 11:37:18 +03:00
app._("remove_kind", "message", locale=user.locale),
2023-06-28 09:53:15 +03:00
reply_markup=ReplyKeyboardMarkup(
[
[
2023-06-28 11:37:18 +03:00
KeyboardButton(app._("photo", "button", locale=user.locale)),
KeyboardButton(app._("video", "button", locale=user.locale)),
2023-06-28 09:53:15 +03:00
]
],
resize_keyboard=True,
one_time_keyboard=True,
),
)
USERS_WITH_CONTEXT.append(msg.from_user.id)
2023-06-28 09:56:21 +03:00
answer_kind = await listen_message(app, msg.chat.id, timeout=600)
2023-06-28 09:53:15 +03:00
USERS_WITH_CONTEXT.remove(msg.from_user.id)
2023-06-28 09:56:21 +03:00
if answer_kind is None:
2023-06-28 09:53:15 +03:00
await msg.reply_text(
2023-06-28 11:37:18 +03:00
app._("remove_ignored", "message", locale=user.locale),
2023-06-28 09:53:15 +03:00
quote=True,
reply_markup=ReplyKeyboardRemove(),
)
return
2023-06-28 09:56:21 +03:00
if answer_kind.text == "/cancel":
await answer_kind.reply_text(
2023-06-28 11:37:18 +03:00
app._("remove_abort", "message", locale=user.locale),
2023-06-28 09:53:15 +03:00
reply_markup=ReplyKeyboardRemove(),
)
return
2023-06-28 09:56:21 +03:00
if answer_kind.text in app.in_all_locales("photo", "button"):
2023-06-28 09:53:15 +03:00
func = photo_delete
2023-06-28 09:56:21 +03:00
elif answer_kind.text in app.in_all_locales("video", "button"):
2023-06-28 09:53:15 +03:00
func = video_delete
else:
2023-06-28 09:56:21 +03:00
await answer_kind.reply_text(
2023-06-28 11:37:18 +03:00
app._("remove_unknown", "message", locale=user.locale).format(
app._("photo", "button", locale=user.locale),
app._("video", "button", locale=user.locale),
2023-06-28 09:53:15 +03:00
),
reply_markup=ReplyKeyboardRemove(),
)
return
2023-06-28 09:56:21 +03:00
response = await func(id=answer_id.text, client=client)
2023-06-28 09:59:40 +03:00
if response is None:
logger.info(
2023-06-28 09:56:21 +03:00
"Removed %s '%s' by request of user %s",
answer_kind.text,
answer_id.text,
answer_id.from_user.id,
)
2023-06-28 09:56:21 +03:00
await answer_kind.reply_text(
2023-06-28 11:37:18 +03:00
app._("remove_success", "message", locale=user.locale).format(
answer_id.text
),
2023-06-28 09:57:09 +03:00
reply_markup=ReplyKeyboardRemove(),
)
else:
logger.warning(
2023-06-28 09:56:21 +03:00
"Could not remove %s '%s' by request of user %s",
answer_kind.text,
answer_id.text,
answer_id.from_user.id,
)
2023-06-28 09:56:21 +03:00
await answer_kind.reply_text(
2023-06-28 11:37:18 +03:00
app._("remove_failure", "message", locale=user.locale).format(
answer_id.text
),
2023-06-28 09:57:09 +03:00
reply_markup=ReplyKeyboardRemove(),
)
@Client.on_message(~filters.scheduled & filters.command(["purge"], prefixes=["", "/"]))
async def cmd_purge(app: PyroClient, msg: Message):
if msg.from_user.id not in app.admins:
return