Compare commits

..

8 Commits

Author SHA1 Message Date
Profitroll
642e17ee55 Image resize when too big 2023-02-17 16:46:44 +01:00
Profitroll
25af9b31f8 Changed Client to PosterClient 2023-02-17 16:46:33 +01:00
Profitroll
28fc359593 WIP: export and import 2023-02-17 16:46:13 +01:00
Profitroll
07203a9db9 Changed the way exceptions are handled 2023-02-17 16:45:51 +01:00
Profitroll
663a7b0db8 Submission without confirmation added 2023-02-17 16:44:56 +01:00
Profitroll
0d2e9fa6ec Using PosterClient instead of Client 2023-02-17 16:44:30 +01:00
Profitroll
c90e5eb697 Added Pillow to requirements 2023-02-17 16:44:03 +01:00
Profitroll
f4359aa6cd Added upload_pic method 2023-02-17 11:51:38 +01:00
16 changed files with 213 additions and 77 deletions

16
classes/exceptions.py Normal file
View File

@@ -0,0 +1,16 @@
from typing import Any
class SubmissionUnavailableError(Exception):
pass
class SubmissionUploadError(Exception):
def __init__(self, file_path: str, status_code: int, content: Any) -> None:
self.status_code = status_code
self.content = content
super().__init__(f"Could not upload photo '{file_path}' due to HTTP {self.status_code}: {self.content}")
class SubmissionDuplicatesError(Exception):
def __init__(self, file_path: str, duplicates: list) -> None:
self.duplicates = duplicates
super().__init__(f"Found duplicates of a photo '{file_path}': {self.duplicates}")

62
classes/poster_client.py Normal file
View File

@@ -0,0 +1,62 @@
from os import path, remove, sep
from shutil import rmtree
from typing import Union
from pyrogram.client import Client
from pyrogram.types import Message, CallbackQuery
from pyrogram.enums.parse_mode import ParseMode
from pyrogram.session.session import Session
from classes.exceptions import SubmissionDuplicatesError, SubmissionUnavailableError
from modules.api_client import upload_pic
from modules.database import col_submitted
from bson import ObjectId
from modules.logger import logWrite
from modules.utils import configGet
class PosterClient(Client):
def __init__(self, name: str, **kwargs): # type: ignore
super().__init__(name, **kwargs)
async def submit_photo(self, id: str) -> Union[Message, None]:
db_entry = col_submitted.find_one({"_id": ObjectId(id)})
submission = None
if db_entry is None:
raise SubmissionUnavailableError()
else:
if db_entry["temp"]["uuid"] is not None:
if not path.exists(path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"], db_entry["temp"]["file"])):
raise SubmissionUnavailableError()
else:
filepath = path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"], db_entry["temp"]["file"])
else:
try:
submission = await self.get_messages(db_entry["user"], db_entry["telegram"]["msg_id"])
filepath = await self.download_media(submission, file_name=configGet("tmp", "locations")+sep)
except:
raise SubmissionUnavailableError()
response = await upload_pic(str(filepath))
if response[0] is False:
raise SubmissionDuplicatesError(str(filepath), response[1])
col_submitted.find_one_and_update({"_id": ObjectId(id)}, {"$set": {"done": True}})
try:
if db_entry["temp"]["uuid"] is not None:
rmtree(path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"]), ignore_errors=True)
else:
remove(str(filepath))
except (FileNotFoundError, NotADirectoryError):
logWrite(f"Could not delete '{filepath}' on submission accepted", debug=True)
return submission
async def ban_user(self, id: int) -> None:
pass
async def unban_user(self, id: int) -> None:
pass

View File

@@ -88,6 +88,10 @@
"timeout": 30,
"file_size": 15728640,
"tmp_size": 15728640,
"require_confirmation": {
"users": true,
"admins": true
},
"mime_types": [
"image/png",
"image/gif",

View File

@@ -5,6 +5,8 @@
},
"commands_admin": {
"forwards": "Check post forwards",
"import": "Submit .zip archive with photos",
"export": "Get .zip archive with all photos",
"reboot": "Restart the bot"
},
"message": {

View File

@@ -5,6 +5,8 @@
},
"commands_admin": {
"forwards": "Переглянути репости",
"import": "Надати боту .zip архів з фотографіями",
"export": "Отримати .zip архів з усіма фотографіями",
"reboot": "Перезапустити бота"
},
"message": {

View File

@@ -1,3 +1,5 @@
"""This is only a temporary solution. Complete Photos API client is yet to be developed."""
import asyncio
from base64 import b64decode, b64encode
from os import makedirs, path, sep
@@ -5,6 +7,7 @@ from random import choice
from typing import Tuple, Union
from requests import get, patch, post
from classes.exceptions import SubmissionUploadError
from modules.logger import logWrite
from modules.utils import configGet
@@ -37,8 +40,7 @@ async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]:
### Returns:
* `Tuple[str, str]`: First value is an ID and the filename in the filesystem to be indexed.
"""
if token is None:
token = await authorize()
token = await authorize() if token is None else token
logWrite(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size=100&caption=queue')
resp = get(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size=100&caption=queue', headers={"Authorization": f"Bearer {token}"})
if resp.status_code != 200:
@@ -50,15 +52,15 @@ async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]:
pic = choice(resp.json()["results"])
return pic["id"], pic["filename"]
async def upload_pic(filepath: str) -> Tuple[bool, list]:
token = await authorize()
async def upload_pic(filepath: str, token: Union[str, None] = None) -> Tuple[bool, list]:
token = await authorize() if token is None else token
try:
pic_name = path.basename(filepath)
files = {'file': (pic_name, open(filepath, 'rb'), 'image/jpeg')}
response = post(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos', params={"caption": "queue", "compress": False}, headers={"Authorization": f"Bearer {token}"}, files=files)
if response.status_code != 200 and response.status_code != 409:
logWrite(f"Could not upload '{filepath}' to API: HTTP {response.status_code} with message '{response.content}'")
return False, []
raise SubmissionUploadError(str(filepath), response.status_code, response.content)
duplicates = []
if "duplicates" in response.json():
for duplicate in response.json()["duplicates"]:
@@ -67,8 +69,22 @@ async def upload_pic(filepath: str) -> Tuple[bool, list]:
except:
return False, []
async def move_pic(id: str) -> bool:
token = await authorize()
async def find_pic(name: str, caption: Union[str, None] = None, token: Union[str, None] = None) -> Union[dict, None]:
token = await authorize() if token is None else token
try:
response = get(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos', params={"q": name, "caption": caption}, headers={"Authorization": f"Bearer {token}"})
# logWrite(response.json())
if response.status_code != 200:
return None
if len(response.json()["results"]) == 0:
return None
return response.json()["results"]
except Exception as exp:
logWrite(f"Could not find image with name '{name}' and caption '{caption}' due to: {exp}")
return None
async def move_pic(id: str, token: Union[str, None] = None) -> bool:
token = await authorize() if token is None else token
try:
patch(f'{configGet("address", "posting", "api")}/photos/{id}?caption=sent', headers={"Authorization": f"Bearer {token}"})
return True

View File

@@ -1,4 +1,4 @@
from pyrogram.client import Client
from modules.utils import configGet
from classes.poster_client import PosterClient
app = Client("duptsiaposter", bot_token=configGet("bot_token", "bot"), api_id=configGet("api_id", "bot"), api_hash=configGet("api_hash", "bot"))
app = PosterClient("duptsiaposter", bot_token=configGet("bot_token", "bot"), api_id=configGet("api_id", "bot"), api_hash=configGet("api_hash", "bot"))

View File

@@ -1,9 +1,9 @@
from os import listdir
from pyrogram.client import Client
from classes.poster_client import PosterClient
from pyrogram.types import BotCommand, BotCommandScopeChat
from modules.utils import configGet, locale
async def register_commands(app: Client):
async def register_commands(app: PosterClient):
if configGet("submit", "mode"):
# Registering user commands

View File

@@ -3,9 +3,10 @@ from os import makedirs, path
from shutil import copyfileobj, rmtree
from traceback import format_exc
from uuid import uuid4
from PIL import Image
from bson import ObjectId
from pyrogram.client import Client
from classes.poster_client import PosterClient
from requests import get
from modules.api_client import authorize, move_pic, random_pic
@@ -14,7 +15,7 @@ from modules.logger import logWrite
from modules.utils import configGet, locale
async def send_content(app: Client):
async def send_content(app: PosterClient):
try:
@@ -52,6 +53,22 @@ async def send_content(app: Client):
with open(path.join(configGet("tmp", "locations"), tmp_path), 'wb') as out_file:
copyfileobj(response.raw, out_file)
logWrite(f'Candidate {pic[1]} ({pic[0]}) is {path.getsize(path.join(configGet("tmp", "locations"), tmp_path))} bytes big', debug=True)
if path.getsize(path.join(configGet("tmp", "locations"), tmp_path)) > 5242880:
image = Image.open(path.join(configGet("tmp", "locations"), tmp_path))
width, height = image.size
image = image.resize((int(width/2), int(height/2)), Image.ANTIALIAS)
if tmp_path.lower().endswith(".jpeg") or tmp_path.lower().endswith(".jpg"):
image.save(path.join(configGet("tmp", "locations"), tmp_path), "JPEG", optimize=True, quality=50)
elif tmp_path.lower().endswith(".png"):
image.save(path.join(configGet("tmp", "locations"), tmp_path), "PNG", optimize=True, compress_level=8)
image.close()
if path.getsize(path.join(configGet("tmp", "locations"), tmp_path)) > 5242880:
rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True)
raise BytesWarning
del response
submitted_caption = col_submitted.find_one( {"image": ObjectId(pic[0])} )
@@ -98,9 +115,13 @@ async def send_content(app: Client):
logWrite(locale("post_exception", "console", locale=configGet("locale")).format(str(exp), format_exc()))
if configGet("error", "reports"):
await app.send_message(configGet("admin"), locale("post_exception", "message", locale=configGet("locale")).format(exp, format_exc()))
try:
rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True)
except:
pass
# async def send_content_old(app: Client):
# async def send_content_old(app: PosterClient):
# # Send post to channel
# try:

View File

@@ -1,11 +1,11 @@
from modules.app import app
from pyrogram import filters
from pyrogram.types import CallbackQuery
from pyrogram.client import Client
from classes.poster_client import PosterClient
from modules.utils import locale
# Callback empty ===============================================================================================================
@app.on_callback_query(filters.regex("nothing"))
async def callback_query_nothing(app: Client, clb: CallbackQuery):
async def callback_query_nothing(app: PosterClient, clb: CallbackQuery):
await clb.answer(text=locale("nothing", "callback", locale=clb.from_user))
# ==============================================================================================================================

View File

@@ -1,70 +1,37 @@
from os import path, remove, sep
from pathlib import Path
from shutil import rmtree
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
from classes.exceptions import SubmissionDuplicatesError, SubmissionUnavailableError
from classes.poster_client import PosterClient
from modules.api_client import upload_pic
from modules.app import app
from modules.logger import logWrite
from modules.submissions import subBlock, subUnblock
from modules.utils import configGet, jsonLoad, jsonSave, locale
from modules.utils import configGet, locale
from modules.database import col_submitted
from bson import ObjectId
@app.on_callback_query(filters.regex("sub_yes_[\s\S]*"))
async def callback_query_yes(app: Client, clb: CallbackQuery):
async def callback_query_yes(app: PosterClient, clb: CallbackQuery):
fullclb = clb.data.split("_")
fullclb = str(clb.data).split("_")
user_locale = clb.from_user.language_code
db_entry = col_submitted.find_one({"_id": ObjectId(fullclb[2])})
submission = None
if db_entry is None:
await clb.answer(text=locale("sub_msg_unavail", "callback", locale=user_locale), show_alert=True)
return
else:
if db_entry["temp"]["uuid"] is not None:
if not path.exists(path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"], db_entry["temp"]["file"])):
await clb.answer(text=locale("sub_msg_unavail", "callback", locale=user_locale), show_alert=True)
return
else:
filepath = path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"], db_entry["temp"]["file"])
else:
try:
submission = await app.get_messages(db_entry["user"], db_entry["telegram"]["msg_id"])
filepath = await app.download_media(submission, file_name=configGet("tmp", "locations")+sep)
except:
submission = await app.submit_photo(fullclb[2])
except SubmissionUnavailableError:
await clb.answer(text=locale("sub_msg_unavail", "callback", locale=user_locale), show_alert=True)
return
response = await upload_pic(str(filepath))
if response[0] is False and len(response[1]) == 0:
await clb.answer(text=locale("sub_upload_failed", "callback", locale=user_locale), show_alert=True)
return
elif response[0] is False:
except SubmissionDuplicatesError as exp:
await clb.answer(text=locale("sub_duplicates_found", "callback", locale=user_locale), show_alert=True)
await clb.message.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n".join(response[1])))
await clb.message.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n".join(exp.duplicates)))
return
col_submitted.find_one_and_update({"_id": ObjectId(fullclb[2])}, {"$set": {"done": True}})
try:
if db_entry["temp"]["uuid"] is not None:
rmtree(path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"]), ignore_errors=True)
else:
remove(str(filepath))
except (FileNotFoundError, NotADirectoryError):
logWrite(f"Could not delete '{filepath}' on submission accepted", debug=True)
if submission is not None:
await submission.reply_text(locale("sub_yes", "message", locale=submission.from_user.language_code), quote=True)
else:
elif db_entry is not None:
await app.send_message(db_entry["user"], locale("sub_yes", "message"))
await clb.answer(text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]), show_alert=True)
@@ -72,10 +39,6 @@ async def callback_query_yes(app: Client, clb: CallbackQuery):
edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")], clb.message.reply_markup.inline_keyboard[1]] if len(clb.message.reply_markup.inline_keyboard) > 1 else [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]]
await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup))
# Change keyboard to a completed variant
# Send replies to both user and admin about accepting the application
# try:
# if configGet("api_based", "mode") is True:
# media = await app.download_media(submission, file_name=configGet("tmp", "locations")+sep)
@@ -107,7 +70,7 @@ async def callback_query_yes(app: Client, clb: CallbackQuery):
@app.on_callback_query(filters.regex("sub_no_[\s\S]*"))
async def callback_query_no(app: Client, clb: CallbackQuery):
async def callback_query_no(app: PosterClient, clb: CallbackQuery):
fullclb = clb.data.split("_")
user_locale = clb.from_user.language_code
try:
@@ -123,7 +86,7 @@ async def callback_query_no(app: Client, clb: CallbackQuery):
@app.on_callback_query(filters.regex("sub_block_[\s\S]*"))
async def callback_query_block(app: Client, clb: CallbackQuery):
async def callback_query_block(app: PosterClient, clb: CallbackQuery):
fullclb = clb.data.split("_")
user_locale = clb.from_user.language_code
await app.send_message(int(fullclb[2]), locale("sub_msg_unavail", "message", locale=configGet("locale")))
@@ -135,7 +98,7 @@ async def callback_query_block(app: Client, clb: CallbackQuery):
@app.on_callback_query(filters.regex("sub_unblock_[\s\S]*"))
async def callback_query_unblock(app: Client, clb: CallbackQuery):
async def callback_query_unblock(app: PosterClient, clb: CallbackQuery):
fullclb = clb.data.split("_")
user_locale = clb.from_user.language_code
await app.send_message(int(fullclb[2]), locale("sub_msg_unavail", "message", locale=configGet("locale")))

View File

@@ -1,7 +1,7 @@
from os import getpid
from pyrogram import filters
from pyrogram.client import Client
from classes.poster_client import PosterClient
from pyrogram.types import Message
from modules.app import app
@@ -10,7 +10,7 @@ from modules.utils import configGet, killProc, locale
@app.on_message(~ filters.scheduled & filters.command(["kill", "die", "reboot"], prefixes=["", "/"]))
async def cmd_kill(app: Client, msg: Message):
async def cmd_kill(app: PosterClient, msg: Message):
if msg.from_user.id == configGet("admin"):
pid = getpid()

View File

@@ -1,5 +1,5 @@
from pyrogram import filters
from pyrogram.client import Client
from classes.poster_client import PosterClient
from pyrogram.types import Message
from modules.app import app
@@ -8,11 +8,11 @@ from modules.utils import configGet, jsonLoad, locale
@app.on_message(~ filters.scheduled & filters.command(["start"], prefixes="/"))
async def cmd_start(app: Client, msg: Message):
async def cmd_start(app: PosterClient, msg: Message):
if subBlocked(msg.from_user) is False:
await msg.reply_text(locale("start", "message", locale=msg.from_user.language_code))
@app.on_message(~ filters.scheduled & filters.command(["rules", "help"], prefixes="/"))
async def cmd_rules(app: Client, msg: Message):
async def cmd_rules(app: PosterClient, msg: Message):
if subBlocked(msg.from_user) is False:
await msg.reply_text(locale("rules", "message", locale=msg.from_user.language_code))

View File

@@ -0,0 +1,23 @@
from os import getpid
from pyrogram import filters
from classes.poster_client import PosterClient
from pyrogram.types import Message
from modules.app import app
from modules.logger import logWrite
from modules.utils import configGet, killProc, locale
@app.on_message(~ filters.scheduled & filters.command(["import"], prefixes=["", "/"]))
async def cmd_import(app: PosterClient, msg: Message):
if msg.from_user.id == configGet("admin"):
pass
@app.on_message(~ filters.scheduled & filters.command(["export"], prefixes=["", "/"]))
async def cmd_export(app: PosterClient, msg: Message):
if msg.from_user.id == configGet("admin"):
pass

View File

@@ -1,10 +1,12 @@
from datetime import datetime, timezone
from os import makedirs, path, sep
from traceback import format_exc
from uuid import uuid4
from pyrogram import filters
from pyrogram.client import Client
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
from classes.exceptions import SubmissionDuplicatesError
from modules.app import app
from modules.database import col_banned, col_submitted
@@ -141,6 +143,30 @@ async def get_submission(_: Client, msg: Message):
if msg.from_user.phone_number is not None:
caption += f" ({msg.from_user.phone_number})"
if msg.from_user.id == configGet("admin") and configGet("admins", "submission", "require_confirmation") is False:
try:
await app.submit_photo(str(inserted.inserted_id))
await msg.copy(configGet("admin"), caption=caption)
return
except SubmissionDuplicatesError as exp:
await msg.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n".join(exp.duplicates)))
return
except Exception as exp:
await msg.reply_text(format_exc())
return
elif msg.from_user.id != configGet("admin") and configGet("users", "submission", "require_confirmation") is False:
try:
await app.submit_photo(str(inserted.inserted_id))
await msg.copy(configGet("admin"), caption=caption)
return
except SubmissionDuplicatesError as exp:
await msg.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n".join(exp.duplicates)))
return
except Exception as exp:
await app.send_message(configGet("admin"), f"User {msg.from_user.id} could not submit photo without additional confirmation due to:\n```\n{format_exc()}\n```")
await msg.reply_text("Could not upload this image. Admins are advised.")
return
if msg.from_user.id != configGet("admin"):
buttons += [
[

View File

@@ -3,3 +3,4 @@ pyrogram~=2.0.99
requests~=2.28.2
psutil~=5.9.4
pymongo~=4.3.3
pillow~=9.4.0