This repository has been archived on 2024-08-21. You can view files and clone it, but cannot push or open issues or pull requests.
TelegramPoster/plugins/commands/photos.py

304 lines
9.1 KiB
Python
Raw Normal View History

2023-03-20 13:03:03 +02:00
import asyncio
2023-06-21 17:39:33 +03:00
import logging
2023-03-20 13:03:03 +02:00
from glob import iglob
2023-06-21 17:39:33 +03:00
from io import BytesIO
2023-03-20 13:03:03 +02:00
from os import getcwd, makedirs, path, remove
2023-06-21 17:39:33 +03:00
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
2023-06-21 17:39:33 +03:00
from photosapi_client.errors import UnexpectedStatus
2023-02-17 17:46:13 +02:00
from pyrogram import filters
2023-06-21 17:39:33 +03:00
from pyrogram.client import Client
2023-02-17 17:46:13 +02:00
from pyrogram.types import Message
2023-06-21 17:39:33 +03:00
from ujson import loads
2023-02-17 17:46:13 +02:00
2023-06-21 17:39:33 +03:00
from classes.pyroclient import PyroClient
from modules.api_client import (
2023-06-22 16:17:44 +03:00
BodyPhotoUpload,
2023-06-21 17:39:33 +03:00
File,
client,
photo_delete,
photo_upload,
)
from modules.utils import USERS_WITH_CONTEXT, extract_and_save
2023-02-17 17:46:13 +02:00
2023-06-21 17:39:33 +03:00
logger = logging.getLogger(__name__)
2023-02-17 17:46:13 +02:00
2023-06-21 17:39:33 +03: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
await msg.reply_text(
app._("import_request", "message", locale=msg.from_user.language_code)
)
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-21 17:39:33 +03:00
app._("import_ignored", "message", locale=msg.from_user.language_code),
quote=True,
2023-03-18 21:53:26 +02:00
)
2023-06-21 17:39:33 +03:00
return
if answer.text == "/cancel":
await answer.reply_text(
app._("import_abort", "message", locale=msg.from_user.language_code)
2023-03-20 13:03:03 +02:00
)
2023-06-21 17:39:33 +03:00
return
if answer.document is None:
await answer.reply_text(
app._(
"import_invalid_media",
"message",
locale=msg.from_user.language_code,
),
2023-03-20 15:50:57 +02:00
quote=True,
)
2023-06-21 17:39:33 +03:00
return
if answer.document.mime_type != "application/zip":
await answer.reply_text(
app._("import_invalid_mime", "message", locale=msg.from_user.language_code),
quote=True,
2023-03-20 15:50:57 +02:00
)
2023-06-21 17:39:33 +03:00
return
if disk_usage(getcwd())[2] < (answer.document.file_size) * 3:
await msg.reply_text(
app._(
"import_too_big", "message", locale=msg.from_user.language_code
).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
)
2023-06-21 17:39:33 +03: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
2023-06-21 17:39:33 +03:00
downloading = await answer.reply_text(
app._("import_downloading", "message", locale=msg.from_user.language_code),
quote=True,
)
await app.download_media(answer, file_name=str(tmp_path))
await downloading.edit(
app._("import_unpacking", "message", locale=msg.from_user.language_code)
)
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-21 17:39:33 +03:00
app._(
"import_unpack_error", "message", locale=msg.from_user.language_code
).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
2023-06-21 17:39:33 +03:00
logger.info("Downloaded '%s' - awaiting upload", answer.document.file_name)
2023-02-17 17:46:13 +02:00
2023-06-21 17:39:33 +03:00
await downloading.edit(
app._("import_uploading", "message", locale=msg.from_user.language_code)
)
2023-02-26 00:19:08 +02:00
2023-06-21 17:39:33 +03:00
remove(tmp_path)
2023-02-26 00:19:08 +02:00
2023-06-21 17:39:33 +03: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:
uploaded = await photo_upload(
app.config["posting"]["api"]["album"],
client=client,
2023-06-22 16:17:44 +03:00
multipart_data=BodyPhotoUpload(
2023-06-21 17:39:33 +03:00
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
)
2023-06-21 17:39:33 +03: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
)
2023-06-21 17:39:33 +03:00
await msg.reply_text(
app._(
"import_upload_error_other",
"message",
locale=msg.from_user.language_code,
).format(path.basename(filename)),
disable_notification=True,
2023-03-21 15:34:25 +02:00
)
2023-06-21 17:39:33 +03: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
)
2023-06-21 17:39:33 +03:00
if len(uploaded_dict["duplicates"]) > 0:
await msg.reply_text(
app._(
"import_upload_error_duplicate",
"message",
locale=msg.from_user.language_code,
).format(path.basename(filename)),
disable_notification=True,
)
else:
await msg.reply_text(
app._(
"import_upload_error_other",
"message",
locale=msg.from_user.language_code,
).format(path.basename(filename)),
disable_notification=True,
)
2023-03-21 15:34:25 +02:00
else:
2023-06-21 17:39:33 +03:00
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
2023-06-21 17:39:33 +03: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(
app._("import_finished", "message", locale=msg.from_user.language_code),
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
await msg.reply_text(
app._("remove_request", "message", locale=msg.from_user.language_code)
)
2023-02-26 00:19:08 +02:00
2023-06-21 17:39:33 +03:00
answer = await listen_message(app, msg.chat.id, timeout=600)
USERS_WITH_CONTEXT.remove(msg.from_user.id)
if answer is None:
await msg.reply_text(
app._("remove_ignored", "message", locale=msg.from_user.language_code),
quote=True,
)
return
if answer.text == "/cancel":
await answer.reply_text(
app._("remove_abort", "message", locale=msg.from_user.language_code)
)
return
response = await photo_delete(id=answer.text, client=client)
if response:
logger.info(
"Removed '%s' by request of user %s", answer.text, answer.from_user.id
)
await answer.reply_text(
app._(
"remove_success", "message", locale=msg.from_user.language_code
).format(answer.text)
)
else:
logger.warning(
"Could not remove '%s' by request of user %s",
answer.text,
answer.from_user.id,
)
await answer.reply_text(
app._(
"remove_failure", "message", locale=msg.from_user.language_code
).format(answer.text)
)
@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