From 05042f01c615464276c122260e915111f3b595c8 Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 15 Feb 2023 14:07:40 +0100 Subject: [PATCH 01/98] Disabled WIP warning --- plugins/handlers/submission.py | 242 ++++++++++++++++----------------- 1 file changed, 120 insertions(+), 122 deletions(-) diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index b2c0278..7be2a21 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -17,146 +17,144 @@ from classes.enums.submission_types import SubmissionType @app.on_message(~ filters.scheduled & filters.photo | filters.video | filters.animation | filters.document) async def get_submission(_: Client, msg: Message): - locale("sub_wip", "message", locale=msg.from_user.language_code) + try: - # try: + if col_banned.find_one( {"user": msg.from_user.id} ) is not None: + return - # if col_banned.find_one( {"user": msg.from_user.id} ) is not None: - # return + user_locale = msg.from_user.language_code + save_tmp = True + contents = None - # user_locale = msg.from_user.language_code - # save_tmp = True - # contents = None + if subLimited(msg.from_user): + await msg.reply_text(locale("sub_cooldown", "message", locale=user_locale).format(str(configGet("timeout", "submission")))) + return - # if subLimited(msg.from_user): - # await msg.reply_text(locale("sub_cooldown", "message", locale=user_locale).format(str(configGet("timeout", "submission")))) - # return + if msg.document is not None: + if msg.document.mime_type not in configGet("mime_types", "submission"): + await msg.reply_text(locale("mime_not_allowed", "message", locale=user_locale), quote=True) + return + if msg.document.file_size > configGet("file_size", "submission"): + await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) + return + if msg.document.file_size > configGet("tmp_size", "submission"): + save_tmp = False + contents = msg.document.file_id, SubmissionType.DOCUMENT #, msg.document.file_name - # if msg.document is not None: - # if msg.document.mime_type not in configGet("mime_types", "submission"): - # await msg.reply_text(locale("mime_not_allowed", "message", locale=user_locale), quote=True) - # return - # if msg.document.file_size > configGet("file_size", "submission"): - # await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) - # return - # if msg.document.file_size > configGet("tmp_size", "submission"): - # save_tmp = False - # contents = msg.document.file_id, SubmissionType.DOCUMENT #, msg.document.file_name + if msg.video is not None: + if msg.video.file_size > configGet("file_size", "submission"): + await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) + return + if msg.video.file_size > configGet("tmp_size", "submission"): + save_tmp = False + contents = msg.video.file_id, SubmissionType.VIDEO #, msg.video.file_name - # if msg.video is not None: - # if msg.video.file_size > configGet("file_size", "submission"): - # await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) - # return - # if msg.video.file_size > configGet("tmp_size", "submission"): - # save_tmp = False - # contents = msg.video.file_id, SubmissionType.VIDEO #, msg.video.file_name + if msg.animation is not None: + if msg.animation.file_size > configGet("file_size", "submission"): + await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) + return + if msg.animation.file_size > configGet("tmp_size", "submission"): + save_tmp = False + contents = msg.animation.file_id, SubmissionType.ANIMATION #, msg.animation.file_name - # if msg.animation is not None: - # if msg.animation.file_size > configGet("file_size", "submission"): - # await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) - # return - # if msg.animation.file_size > configGet("tmp_size", "submission"): - # save_tmp = False - # contents = msg.animation.file_id, SubmissionType.ANIMATION #, msg.animation.file_name + if msg.photo is not None: + contents = msg.photo.file_id, SubmissionType.PHOTO #, "please_generate" - # if msg.photo is not None: - # contents = msg.photo.file_id, SubmissionType.PHOTO #, "please_generate" + if save_tmp is not None: - # if save_tmp is not None: + if contents is None: + return - # if contents is None: - # return - - # tmp_id = str(uuid4()) - # # filename = tmp_id if contents[1] == "please_generate" else contents[1] - # makedirs(path.join(configGet("data", "locations"), "submissions", tmp_id), exist_ok=True) - # downloaded = await app.download_media(msg, path.join(configGet("data", "locations"), "submissions", tmp_id)+sep) - # inserted = col_submitted.insert_one( - # { - # "user": msg.from_user.id, - # "date": datetime.now(tz=timezone.utc), - # "done": False, - # "type": contents[1].value, - # "temp": { - # "uuid": tmp_id, - # "file": path.basename(str(downloaded)) - # }, - # "telegram": { - # "msg_id": msg.id, - # "file_id": contents[0] - # }, - # "caption": str(msg.caption) if msg.caption is not None else None - # } - # ) + tmp_id = str(uuid4()) + # filename = tmp_id if contents[1] == "please_generate" else contents[1] + makedirs(path.join(configGet("data", "locations"), "submissions", tmp_id), exist_ok=True) + downloaded = await app.download_media(msg, path.join(configGet("data", "locations"), "submissions", tmp_id)+sep) + inserted = col_submitted.insert_one( + { + "user": msg.from_user.id, + "date": datetime.now(tz=timezone.utc), + "done": False, + "type": contents[1].value, + "temp": { + "uuid": tmp_id, + "file": path.basename(str(downloaded)) + }, + "telegram": { + "msg_id": msg.id, + "file_id": contents[0] + }, + "caption": str(msg.caption) if msg.caption is not None else None + } + ) - # else: + else: - # if contents is None: - # return + if contents is None: + return - # inserted = col_submitted.insert_one( - # { - # "user": msg.from_user.id, - # "date": datetime.now(tz=timezone.utc), - # "done": False, - # "type": contents[1].value, - # "temp": { - # "uuid": None, - # "file": None - # }, - # "telegram": { - # "msg_id": msg.id, - # "file_id": contents[0] - # }, - # "caption": str(msg.caption) if msg.caption is not None else None - # } - # ) + inserted = col_submitted.insert_one( + { + "user": msg.from_user.id, + "date": datetime.now(tz=timezone.utc), + "done": False, + "type": contents[1].value, + "temp": { + "uuid": None, + "file": None + }, + "telegram": { + "msg_id": msg.id, + "file_id": contents[0] + }, + "caption": str(msg.caption) if msg.caption is not None else None + } + ) - # buttons = [ - # [ - # InlineKeyboardButton(text=locale("sub_yes", "button", locale=configGet("locale")), callback_data=f"sub_yes_{str(inserted.inserted_id)}") - # ] - # ] + buttons = [ + [ + InlineKeyboardButton(text=locale("sub_yes", "button", locale=configGet("locale")), callback_data=f"sub_yes_{str(inserted.inserted_id)}") + ] + ] - # if msg.caption is not None: - # caption = str(msg.caption) - # buttons[0].append( - # InlineKeyboardButton(text=locale("sub_yes_caption", "button", locale=configGet("locale")), callback_data=f"sub_yes_{str(inserted.inserted_id)}_caption") - # ) - # buttons[0].append( - # InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{str(inserted.inserted_id)}") - # ) - # else: - # caption = "" - # buttons[0].append( - # InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{str(inserted.inserted_id)}") - # ) + if msg.caption is not None: + caption = str(msg.caption) + buttons[0].append( + InlineKeyboardButton(text=locale("sub_yes_caption", "button", locale=configGet("locale")), callback_data=f"sub_yes_{str(inserted.inserted_id)}_caption") + ) + buttons[0].append( + InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{str(inserted.inserted_id)}") + ) + else: + caption = "" + buttons[0].append( + InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{str(inserted.inserted_id)}") + ) - # caption += locale("sub_by", "message", locale=locale(configGet("locale"))) + caption += locale("sub_by", "message", locale=locale(configGet("locale"))) - # if msg.from_user.first_name is not None: - # caption += f" {msg.from_user.first_name}" - # if msg.from_user.last_name is not None: - # caption += f" {msg.from_user.last_name}" - # if msg.from_user.username is not None: - # caption += f" (@{msg.from_user.username})" - # if msg.from_user.phone_number is not None: - # caption += f" ({msg.from_user.phone_number})" + if msg.from_user.first_name is not None: + caption += f" {msg.from_user.first_name}" + if msg.from_user.last_name is not None: + caption += f" {msg.from_user.last_name}" + if msg.from_user.username is not None: + caption += f" (@{msg.from_user.username})" + if msg.from_user.phone_number is not None: + caption += f" ({msg.from_user.phone_number})" - # if msg.from_user.id != configGet("admin"): - # buttons += [ - # [ - # InlineKeyboardButton(text=locale("sub_block", "button", locale=configGet("locale")), callback_data=f"sub_block_{msg.from_user.id}") - # ] - # # [ - # # InlineKeyboardButton(text=locale("sub_unblock", "button", locale=configGet("locale")), callback_data=f"sub_unblock_{msg.from_user.id}") - # # ] - # ] + if msg.from_user.id != configGet("admin"): + buttons += [ + [ + InlineKeyboardButton(text=locale("sub_block", "button", locale=configGet("locale")), callback_data=f"sub_block_{msg.from_user.id}") + ] + # [ + # InlineKeyboardButton(text=locale("sub_unblock", "button", locale=configGet("locale")), callback_data=f"sub_unblock_{msg.from_user.id}") + # ] + ] - # await msg.reply_text(locale("sub_sent", "message", locale=user_locale), quote=True) - # subLimit(msg.from_user) + await msg.reply_text(locale("sub_sent", "message", locale=user_locale), quote=True) + subLimit(msg.from_user) - # await msg.copy(configGet("admin"), caption=caption, reply_markup=InlineKeyboardMarkup(buttons)) + await msg.copy(configGet("admin"), caption=caption, reply_markup=InlineKeyboardMarkup(buttons)) - # except AttributeError: - # logWrite(f"from_user in function get_submission does not seem to contain id") \ No newline at end of file + except AttributeError: + logWrite(f"from_user in function get_submission does not seem to contain id") \ No newline at end of file From 4cd37be5dcedb75176ae9af1b979d60e1f337eb1 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 16 Feb 2023 16:41:01 +0100 Subject: [PATCH 02/98] WIP: New submission system --- locale/en.json | 7 +- locale/uk.json | 7 +- modules/api_client.py | 10 +-- modules/submissions.py | 2 +- plugins/callbacks/submission.py | 124 ++++++++++++++++++++------------ 5 files changed, 94 insertions(+), 56 deletions(-) diff --git a/locale/en.json b/locale/en.json index eb56b16..8d0ba88 100644 --- a/locale/en.json +++ b/locale/en.json @@ -30,7 +30,8 @@ "api_queue_error": "__TO_BE_ADDED__", "post_low": "Low amount of content: `There are only {0} files left in the queue.`", "api_creds_invalid": "__TO_BE_ADDED__", - "sub_wip": "Post submission is now WIP. It will be available again in a few days. Thank you for your patience." + "sub_wip": "Post submission is now WIP. It will be available again in a few days. Thank you for your patience.", + "sub_duplicates_found": "__TO_BE_ADDED__" }, "button": { "sub_yes": "✅ Accept", @@ -49,7 +50,9 @@ "sub_unblock": "User {0} has been unblocked", "sub_msg_unavail": "Submission message no longer exist", "sub_media_unavail": "Could not download submission", - "sub_done": "You've already decided what to do with submission" + "sub_done": "You've already decided what to do with submission", + "sub_upload_failed": "__TO_BE_ADDED__", + "sub_duplicates_found": "__TO_BE_ADDED__" }, "console": { "shutdown": "Shutting down bot with pid {0}", diff --git a/locale/uk.json b/locale/uk.json index a644403..0ffab70 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -30,7 +30,8 @@ "api_queue_error": "__TO_BE_ADDED__", "post_low": "Мала кількість контенту: `Залишилось всього {0} файлів в черзі.`", "api_creds_invalid": "__TO_BE_ADDED__", - "sub_wip": "Подання постів зараз знаходиться у розробці. Він буде знову доступний через кілька днів. Дякуємо за ваше терпіння." + "sub_wip": "Подання постів зараз знаходиться у розробці. Він буде знову доступний через кілька днів. Дякуємо за ваше терпіння.", + "sub_duplicates_found": "__TO_BE_ADDED__" }, "button": { "sub_yes": "✅ Прийняти", @@ -49,7 +50,9 @@ "sub_unblock": "Користувача {0} розблоковано", "sub_msg_unavail": "Повідомлення більше не існує", "sub_media_unavail": "Не вдалося завантажити подання", - "sub_done": "Ви вже обрали що зробити з цим поданням" + "sub_done": "Ви вже обрали що зробити з цим поданням", + "sub_upload_failed": "__TO_BE_ADDED__", + "sub_duplicates_found": "__TO_BE_ADDED__" }, "console": { "shutdown": "Вимкнення бота з підом {0}", diff --git a/modules/api_client.py b/modules/api_client.py index 30ad602..d785a01 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -55,11 +55,13 @@ async def upload_pic(filepath: str) -> Tuple[bool, list]: 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&caption=queue', headers={"Authorization": f"Bearer {token}"}, files=files).json() - print(response, flush=True) + 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, [] duplicates = [] - if "duplicates" in response: - for duplicate in response["duplicates"]: + if "duplicates" in response.json(): + for duplicate in response.json()["duplicates"]: duplicates.append(f'{configGet("address", "posting", "api")}/photos/{duplicate["id"]}') return True, duplicates except: diff --git a/modules/submissions.py b/modules/submissions.py index c42749a..4d50214 100644 --- a/modules/submissions.py +++ b/modules/submissions.py @@ -14,7 +14,7 @@ def subLimited(user: User) -> bool: db_record = col_users.find_one({"user": user.id}) if db_record is None: return False - return True if (datetime.now(tz=timezone.utc) - db_record["cooldown"]).total_seconds() < configGet("timeout", "submission") else False + return True if (datetime.now(tz=timezone.utc) - db_record["cooldown"].astimezone(timezone.utc)).total_seconds() < configGet("timeout", "submission") else False def subBlocked(user: User) -> bool: return False if col_banned.find_one({"user": user.id}) is None else True diff --git a/plugins/callbacks/submission.py b/plugins/callbacks/submission.py index 185b0ab..127f0bd 100644 --- a/plugins/callbacks/submission.py +++ b/plugins/callbacks/submission.py @@ -1,12 +1,14 @@ -from os import path, sep +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 +from pyrogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton 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.database import col_submitted @@ -19,58 +21,86 @@ async def callback_query_yes(app: Client, clb: CallbackQuery): fullclb = clb.data.split("_") user_locale = clb.from_user.language_code - # Check if submission is in DB and really exists + db_entry = col_submitted.find_one({"_id": ObjectId(fullclb[2])}) + submission = None - # Upload the file to the API server + 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: + await clb.answer(text=locale("sub_msg_unavail", "callback", locale=user_locale), show_alert=True) + return - # Modify submission in DB to state that it's accepted (or so called "done") + 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: + 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]))) + 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: + 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) + + 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 - db_entry = col_submitted.find_one({"_id": ObjectId(fullclb[2])}) - - if db_entry is None: - await clb.answer(text=locale("sub_msg_unavail", "message", locale=user_locale), show_alert=True) - return - else: - if db_entry["tmp"]["uuid"] is not None: - if not path.exists(path.join(configGet("data", "locations"), "submissions", db_entry["tmp"]["uuid"], db_entry["tmp"]["file"])): - await clb.answer(text=locale("sub_msg_unavail", "message", locale=user_locale), show_alert=True) - return - else: - try: - submission = await app.get_messages(db_entry["user"], db_entry["telegram"]["msg_id"]) - except: - await clb.answer(text=locale("sub_msg_unavail", "message", locale=user_locale), show_alert=True) - return - - try: - if configGet("api_based", "mode") is True: - media = await app.download_media(submission, file_name=configGet("tmp", "locations")+sep) - upload = upload_pic(media) - if upload[0] is False: - await clb.answer(text=locale("sub_media_failed", "message", locale=user_locale), show_alert=True) - elif len(upload[1]) > 0: - await clb.answer(text=locale("sub_media_duplicates", "message", locale=user_locale)) - await clb.message.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n • ".join(upload[1]))) - else: - if clb.data.endswith("_caption"): - index = jsonLoad(configGet("index", "locations")) - index["captions"][Path(media).name] = submission.caption - jsonSave(index, configGet("index", "locations")) - else: - media = await app.download_media(submission, file_name=configGet("queue", "locations")+sep) - if clb.data.endswith("_caption"): - index = jsonLoad(configGet("index", "locations")) - index["captions"][Path(media).name] = submission.caption - jsonSave(index, configGet("index", "locations")) - except: - await clb.answer(text=locale("sub_media_unavail", "message", locale=user_locale), show_alert=True) - return - await submission.reply_text(locale("sub_yes", "message", locale=submission.from_user.language_code), quote=True) - await clb.answer(text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) + # try: + # if configGet("api_based", "mode") is True: + # media = await app.download_media(submission, file_name=configGet("tmp", "locations")+sep) + # upload = upload_pic(media) + # if upload[0] is False: + # await clb.answer(text=locale("sub_media_failed", "message", locale=user_locale), show_alert=True) + # elif len(upload[1]) > 0: + # await clb.answer(text=locale("sub_media_duplicates", "message", locale=user_locale)) + # await clb.message.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n • ".join(upload[1]))) + # else: + # if clb.data.endswith("_caption"): + # index = jsonLoad(configGet("index", "locations")) + # index["captions"][Path(media).name] = submission.caption + # jsonSave(index, configGet("index", "locations")) + # else: + # media = await app.download_media(submission, file_name=configGet("queue", "locations")+sep) + # if clb.data.endswith("_caption"): + # index = jsonLoad(configGet("index", "locations")) + # index["captions"][Path(media).name] = submission.caption + # jsonSave(index, configGet("index", "locations")) + # except: + # await clb.answer(text=locale("sub_media_unavail", "message", locale=user_locale), show_alert=True) + # return + # await submission.reply_text(locale("sub_yes", "message", locale=submission.from_user.language_code), quote=True) + # await clb.answer(text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) # edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]] # await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) From f4359aa6cdd24ad1eb3ba1fde551c50373f216bf Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 11:51:38 +0100 Subject: [PATCH 03/98] Added upload_pic method --- modules/api_client.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/modules/api_client.py b/modules/api_client.py index d785a01..6805f95 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -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 @@ -37,8 +39,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,8 +51,8 @@ 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')} @@ -66,9 +67,23 @@ async def upload_pic(filepath: str) -> Tuple[bool, list]: return True, duplicates except: return False, [] + +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) -> bool: - token = await authorize() +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 From c90e5eb697c0bfb4856e223bf307d7d4c0a1afa5 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 16:44:03 +0100 Subject: [PATCH 04/98] Added Pillow to requirements --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b3d20c8..3eb7777 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ apscheduler~=3.10.0 pyrogram~=2.0.99 requests~=2.28.2 psutil~=5.9.4 -pymongo~=4.3.3 \ No newline at end of file +pymongo~=4.3.3 +pillow~=9.4.0 \ No newline at end of file From 0d2e9fa6ec73f1196d9d80f60ab256bb90c21e13 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 16:44:30 +0100 Subject: [PATCH 05/98] Using PosterClient instead of Client --- classes/poster_client.py | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 classes/poster_client.py diff --git a/classes/poster_client.py b/classes/poster_client.py new file mode 100644 index 0000000..a664d84 --- /dev/null +++ b/classes/poster_client.py @@ -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 \ No newline at end of file From 663a7b0db8833ceb3c9d3e88b457d2dfe0669bb0 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 16:44:56 +0100 Subject: [PATCH 06/98] Submission without confirmation added --- config.json | 4 ++ plugins/callbacks/submission.py | 73 ++++++++------------------------- 2 files changed, 22 insertions(+), 55 deletions(-) diff --git a/config.json b/config.json index 8d4cf3c..87a4d5f 100644 --- a/config.json +++ b/config.json @@ -88,6 +88,10 @@ "timeout": 30, "file_size": 15728640, "tmp_size": 15728640, + "require_confirmation": { + "users": true, + "admins": true + }, "mime_types": [ "image/png", "image/gif", diff --git a/plugins/callbacks/submission.py b/plugins/callbacks/submission.py index 127f0bd..062f063 100644 --- a/plugins/callbacks/submission.py +++ b/plugins/callbacks/submission.py @@ -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: - 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: - 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]))) - 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) + 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 + 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(exp.duplicates))) + return 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"))) From 07203a9db928072a1e78bfdf0620c9f1db4793c9 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 16:45:51 +0100 Subject: [PATCH 07/98] Changed the way exceptions are handled --- classes/exceptions.py | 16 ++++++++++++++++ modules/api_client.py | 3 ++- modules/app.py | 4 ++-- plugins/handlers/submission.py | 26 ++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 classes/exceptions.py diff --git a/classes/exceptions.py b/classes/exceptions.py new file mode 100644 index 0000000..7c86e01 --- /dev/null +++ b/classes/exceptions.py @@ -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}") \ No newline at end of file diff --git a/modules/api_client.py b/modules/api_client.py index 6805f95..1e0e4c5 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -7,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 @@ -59,7 +60,7 @@ async def upload_pic(filepath: str, token: Union[str, None] = None) -> Tuple[boo 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"]: diff --git a/modules/app.py b/modules/app.py index 84ff5a9..99139df 100644 --- a/modules/app.py +++ b/modules/app.py @@ -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")) \ No newline at end of file +app = PosterClient("duptsiaposter", bot_token=configGet("bot_token", "bot"), api_id=configGet("api_id", "bot"), api_hash=configGet("api_hash", "bot")) \ No newline at end of file diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index 7be2a21..ddc1cee 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -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 += [ [ From 28fc35959324b2e428c2c0ae9a26b06255f5cfdf Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 16:46:13 +0100 Subject: [PATCH 08/98] WIP: export and import --- locale/en.json | 2 ++ locale/uk.json | 2 ++ plugins/commands/photos.py | 23 +++++++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 plugins/commands/photos.py diff --git a/locale/en.json b/locale/en.json index 8d0ba88..4ca5c1b 100644 --- a/locale/en.json +++ b/locale/en.json @@ -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": { diff --git a/locale/uk.json b/locale/uk.json index 0ffab70..e5305c9 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -5,6 +5,8 @@ }, "commands_admin": { "forwards": "Переглянути репости", + "import": "Надати боту .zip архів з фотографіями", + "export": "Отримати .zip архів з усіма фотографіями", "reboot": "Перезапустити бота" }, "message": { diff --git a/plugins/commands/photos.py b/plugins/commands/photos.py new file mode 100644 index 0000000..72a157e --- /dev/null +++ b/plugins/commands/photos.py @@ -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 \ No newline at end of file From 25af9b31f8c2243f1c963c03494d2ca40efe659a Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 16:46:33 +0100 Subject: [PATCH 09/98] Changed Client to PosterClient --- modules/commands_register.py | 4 ++-- plugins/callbacks/nothing.py | 4 ++-- plugins/commands/general.py | 4 ++-- plugins/commands/mode_submit.py | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/commands_register.py b/modules/commands_register.py index ed84b86..4132522 100644 --- a/modules/commands_register.py +++ b/modules/commands_register.py @@ -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 diff --git a/plugins/callbacks/nothing.py b/plugins/callbacks/nothing.py index 8af1e02..7052b06 100644 --- a/plugins/callbacks/nothing.py +++ b/plugins/callbacks/nothing.py @@ -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)) # ============================================================================================================================== \ No newline at end of file diff --git a/plugins/commands/general.py b/plugins/commands/general.py index 01b42eb..9df1e59 100644 --- a/plugins/commands/general.py +++ b/plugins/commands/general.py @@ -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() diff --git a/plugins/commands/mode_submit.py b/plugins/commands/mode_submit.py index ae81d60..5181355 100644 --- a/plugins/commands/mode_submit.py +++ b/plugins/commands/mode_submit.py @@ -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)) \ No newline at end of file From 642e17ee557cf7a9a3ef36592aa15ca837edbdda Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 16:46:44 +0100 Subject: [PATCH 10/98] Image resize when too big --- modules/sender.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/modules/sender.py b/modules/sender.py index 2055c39..6783687 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -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: From 68c887999ecdbcc2c812815fd881fe31d736a4ec Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 21:53:43 +0100 Subject: [PATCH 11/98] Divided admins and owner --- classes/poster_client.py | 4 +++- config.json | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/classes/poster_client.py b/classes/poster_client.py index a664d84..9ac38be 100644 --- a/classes/poster_client.py +++ b/classes/poster_client.py @@ -17,6 +17,8 @@ class PosterClient(Client): def __init__(self, name: str, **kwargs): # type: ignore super().__init__(name, **kwargs) + self.owner = configGet("owner") + self.admins = configGet("admins")+[configGet("owner")] async def submit_photo(self, id: str) -> Union[Message, None]: @@ -40,7 +42,7 @@ class PosterClient(Client): response = await upload_pic(str(filepath)) - if response[0] is False: + if len(response[1]) > 0: raise SubmissionDuplicatesError(str(filepath), response[1]) col_submitted.find_one_and_update({"_id": ObjectId(id)}, {"$set": {"done": True}}) diff --git a/config.json b/config.json index 87a4d5f..e8a78cd 100644 --- a/config.json +++ b/config.json @@ -3,7 +3,8 @@ "locale": "en", "locale_log": "en", "locale_fallback": "en", - "admin": 0, + "owner": 0, + "admins": [], "bot": { "api_id": 0, "api_hash": "", @@ -46,7 +47,8 @@ "channel": 0, "silent": false, "move_sent": false, - "interval": 1, + "use_interval": false, + "interval": "1h30m", "extensions": { "photo": [ "jpg", @@ -74,6 +76,7 @@ ], "api": { "address": "http://localhost:8054", + "address_external": "https://photos.domain.com", "username": "", "password": "", "album": "" @@ -105,6 +108,8 @@ "rules" ], "commands_admin": [ + "import", + "export", "reboot" ] } \ No newline at end of file From cf204577e47f4c147f8d0ee26275c0e7ca7a7ee9 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 21:53:57 +0100 Subject: [PATCH 12/98] Added external address for links --- modules/api_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api_client.py b/modules/api_client.py index 1e0e4c5..311acaf 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -64,7 +64,7 @@ async def upload_pic(filepath: str, token: Union[str, None] = None) -> Tuple[boo duplicates = [] if "duplicates" in response.json(): for duplicate in response.json()["duplicates"]: - duplicates.append(f'{configGet("address", "posting", "api")}/photos/{duplicate["id"]}') + duplicates.append(f'{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}') return True, duplicates except: return False, [] From d1813856d91ec4ec0c1c8047bb7002826d9e3754 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 21:54:14 +0100 Subject: [PATCH 13/98] Imported callback "nothing" --- poster.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/poster.py b/poster.py index 125450a..6e94d9d 100644 --- a/poster.py +++ b/poster.py @@ -119,6 +119,7 @@ pid = getpid() from plugins.commands.general import * if configGet("submit", "mode"): + from plugins.callbacks.nothing import * from plugins.callbacks.submission import * from plugins.commands.mode_submit import * from plugins.handlers.submission import * @@ -174,7 +175,7 @@ if __name__ == "__main__": app.start() if configGet("startup", "reports"): - app.send_message(configGet("admin"), locale("startup", "message", locale=configGet("locale")).format(str(pid))) + app.send_message(app.owner, locale("startup", "message", locale=configGet("locale")).format(str(pid))) if configGet("post", "mode"): scheduler.start() @@ -186,7 +187,7 @@ if __name__ == "__main__": idle() - app.send_message(configGet("admin"), locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) + app.send_message(app.owner, locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid))) killProc(pid) \ No newline at end of file From 68ea0879636438ae0f8093b98f4f9fb060cbab6f Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 21:54:30 +0100 Subject: [PATCH 14/98] Integrated interval-based schedule --- modules/scheduler.py | 12 +++++++----- requirements.txt | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/scheduler.py b/modules/scheduler.py index f95f2eb..1289608 100644 --- a/modules/scheduler.py +++ b/modules/scheduler.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta from apscheduler.schedulers.asyncio import AsyncIOScheduler +from pytimeparse.timeparse import timeparse from modules.utils import configGet from modules.sender import send_content from modules.commands_register import register_commands @@ -8,10 +9,11 @@ from modules.app import app scheduler = AsyncIOScheduler() if configGet("post", "mode"): - # for entry in configGet("time", "posting"): - # dt_obj = datetime.strptime(entry, "%H:%M") - # Is only used for debug now! - scheduler.add_job(send_content, "interval", seconds=30, args=[app]) - # scheduler.add_job(send_content, "cron", hour=dt_obj.hour, minute=dt_obj.minute, args=[app]) + if configGet("use_interval", "posting"): + scheduler.add_job(send_content, "interval", seconds=timeparse(configGet("interval", "posting")), args=[app]) + else: + for entry in configGet("time", "posting"): + dt_obj = datetime.strptime(entry, "%H:%M") + scheduler.add_job(send_content, "cron", hour=dt_obj.hour, minute=dt_obj.minute, args=[app]) scheduler.add_job(register_commands, "date", run_date=datetime.now()+timedelta(seconds=10), args=[app]) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3eb7777..7607775 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ pyrogram~=2.0.99 requests~=2.28.2 psutil~=5.9.4 pymongo~=4.3.3 -pillow~=9.4.0 \ No newline at end of file +pillow~=9.4.0 +pytimeparse~=1.1.8 \ No newline at end of file From bd9917fb1758a134edfec6a7a66cff0371b4382e Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 21:54:52 +0100 Subject: [PATCH 15/98] Replaced user block logic --- classes/user.py | 17 +++++++++++++++++ modules/submissions.py | 23 +++++++---------------- 2 files changed, 24 insertions(+), 16 deletions(-) create mode 100644 classes/user.py diff --git a/classes/user.py b/classes/user.py new file mode 100644 index 0000000..d2e7c77 --- /dev/null +++ b/classes/user.py @@ -0,0 +1,17 @@ +from datetime import datetime +from modules.database import col_banned + +class PosterUser(): + + def __init__(self, id: int): + self.id = id + + def is_blocked(self) -> bool: + return False if col_banned.find_one({"user": self.id}) is None else True + + def block(self) -> None: + if col_banned.find_one({"user": self.id}) is None: + col_banned.insert_one({"user": self.id, "date": datetime.now()}) + + def unblock(self) -> None: + col_banned.find_one_and_delete({"user": self.id}) \ No newline at end of file diff --git a/modules/submissions.py b/modules/submissions.py index 4d50214..e529835 100644 --- a/modules/submissions.py +++ b/modules/submissions.py @@ -1,27 +1,18 @@ -from datetime import datetime, timezone +from modules.app import app +from datetime import datetime from modules.utils import configGet -from modules.database import col_users, col_banned +from modules.database import col_users from pyrogram.types.user_and_chats import User def subLimit(user: User) -> None: - if col_users.find_one_and_update({"user": user.id}, {"$set": {"cooldown": datetime.now(tz=timezone.utc)}}) is None: - col_users.insert_one({"user": user.id, "cooldown": datetime.now(tz=timezone.utc)}) + if col_users.find_one_and_update({"user": user.id}, {"$set": {"cooldown": datetime.now()}}) is None: + col_users.insert_one({"user": user.id, "cooldown": datetime.now()}) def subLimited(user: User) -> bool: - if user.id == configGet("admin"): + if user.id in app.admins: return False else: db_record = col_users.find_one({"user": user.id}) if db_record is None: return False - return True if (datetime.now(tz=timezone.utc) - db_record["cooldown"].astimezone(timezone.utc)).total_seconds() < configGet("timeout", "submission") else False - -def subBlocked(user: User) -> bool: - return False if col_banned.find_one({"user": user.id}) is None else True - -def subBlock(user: User) -> None: - if col_banned.find_one({"user": user.id}) is None: - col_banned.insert_one({"user": user.id, "date": datetime.now(tz=timezone.utc)}) - -def subUnblock(user: User) -> None: - col_banned.find_one_and_delete({"user": user.id}) \ No newline at end of file + return True if (datetime.now() - db_record["cooldown"]).total_seconds() < configGet("timeout", "submission") else False \ No newline at end of file From 664284a6f8445e59853949effa9104d543ff40ec Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 21:55:38 +0100 Subject: [PATCH 16/98] Integrated previous commits --- modules/commands_register.py | 3 ++- modules/sender.py | 16 ++++++------- plugins/callbacks/submission.py | 41 ++++++++++++++++++++++----------- plugins/commands/general.py | 2 +- plugins/commands/mode_submit.py | 10 ++++---- plugins/commands/photos.py | 4 ++-- plugins/handlers/submission.py | 35 ++++++++++++++++------------ 7 files changed, 66 insertions(+), 45 deletions(-) diff --git a/modules/commands_register.py b/modules/commands_register.py index 4132522..3da2bf9 100644 --- a/modules/commands_register.py +++ b/modules/commands_register.py @@ -30,4 +30,5 @@ async def register_commands(app: PosterClient): for command in configGet("commands_admin"): commands_admin_list.append(BotCommand(command, locale(command, "commands_admin", locale=configGet("locale")))) - await app.set_bot_commands(commands_admin_list, scope=BotCommandScopeChat(chat_id=configGet("admin"))) \ No newline at end of file + for admin in app.admins: + await app.set_bot_commands(commands_admin_list, scope=BotCommandScopeChat(chat_id=admin)) \ No newline at end of file diff --git a/modules/sender.py b/modules/sender.py index 6783687..508ff25 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import datetime from os import makedirs, path from shutil import copyfileobj, rmtree from traceback import format_exc @@ -22,7 +22,7 @@ async def send_content(app: PosterClient): try: token = await authorize() except ValueError: - await app.send_message(configGet("admin"), locale("api_creds_invalid", "message", locale=configGet("locale"))) + await app.send_message(app.owner, locale("api_creds_invalid", "message", locale=configGet("locale"))) return try: @@ -30,11 +30,11 @@ async def send_content(app: PosterClient): except KeyError: logWrite(locale("post_empty", "console", locale=configGet("locale"))) if configGet("error", "reports"): - await app.send_message(configGet("admin"), locale("api_queue_empty", "message", locale=configGet("locale"))) + await app.send_message(app.owner, locale("api_queue_empty", "message", locale=configGet("locale"))) return except ValueError: if configGet("error", "reports"): - await app.send_message(configGet("admin"), locale("api_queue_error", "message", locale=configGet("locale"))) + await app.send_message(app.owner, locale("api_queue_error", "message", locale=configGet("locale"))) return response = get(f'{configGet("address", "posting", "api")}/photos/{pic[0]}', headers={"Authorization": f"Bearer {token}"}, stream=True) @@ -42,7 +42,7 @@ async def send_content(app: PosterClient): if response.status_code != 200: logWrite(locale("post_invalid_pic", "console", locale=configGet("locale")).format(str(response.json()))) if configGet("error", "reports"): - await app.send_message(configGet("admin"), locale("post_invalid_pic", "message", locale=configGet("locale")).format(response.json())) + await app.send_message(app.owner, locale("post_invalid_pic", "message", locale=configGet("locale")).format(response.json())) tmp_dir = str(uuid4()) @@ -91,13 +91,13 @@ async def send_content(app: PosterClient): except Exception as exp: logWrite(f"Could not send image {pic[1]} ({pic[0]}) due to {exp}") if configGet("error", "reports"): - await app.send_message(configGet("admin"), locale("post_exception", "message", locale=configGet("locale")).format(exp, format_exc())) + await app.send_message(app.owner, locale("post_exception", "message", locale=configGet("locale")).format(exp, format_exc())) # rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) return col_sent.insert_one( { - "date": datetime.now(tz=timezone.utc), + "date": datetime.now(), "image": pic[0], "filename": pic[1], "channel": configGet("channel", "posting"), @@ -114,7 +114,7 @@ async def send_content(app: PosterClient): except Exception as exp: 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())) + await app.send_message(app.owner, locale("post_exception", "message", locale=configGet("locale")).format(exp, format_exc())) try: rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) except: diff --git a/plugins/callbacks/submission.py b/plugins/callbacks/submission.py index 062f063..603588b 100644 --- a/plugins/callbacks/submission.py +++ b/plugins/callbacks/submission.py @@ -1,11 +1,13 @@ +from os import path +from shutil import rmtree from pyrogram import filters from pyrogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton from classes.exceptions import SubmissionDuplicatesError, SubmissionUnavailableError from classes.poster_client import PosterClient +from classes.user import PosterUser from modules.app import app -from modules.submissions import subBlock, subUnblock from modules.utils import configGet, locale from modules.database import col_submitted from bson import ObjectId @@ -26,7 +28,7 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): return 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(exp.duplicates))) + await clb.message.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n • ".join(exp.duplicates)), quote=True) return if submission is not None: @@ -37,7 +39,7 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): await clb.answer(text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) 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)) + await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) # try: # if configGet("api_based", "mode") is True: @@ -71,39 +73,52 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): @app.on_callback_query(filters.regex("sub_no_[\s\S]*")) async def callback_query_no(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_and_delete({"_id": ObjectId(fullclb[2])}) + + if db_entry["temp"]["uuid"] is not None: + if path.exists(path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"])): + rmtree(path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"]), ignore_errors=True) + try: - submission = await app.get_messages(int(fullclb[2]), int(fullclb[3])) + submission = await app.get_messages(db_entry["user"], db_entry["telegram"]["msg_id"]) except: await clb.answer(text=locale("sub_msg_unavail", "message", locale=user_locale), show_alert=True) return + await submission.reply_text(locale("sub_no", "message", locale=submission.from_user.language_code), quote=True) await clb.answer(text=locale("sub_no", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) - # edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]] - # await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) + edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "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("declined", "button")), callback_data="nothing")]] + await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) @app.on_callback_query(filters.regex("sub_block_[\s\S]*")) async def callback_query_block(app: PosterClient, clb: CallbackQuery): - fullclb = clb.data.split("_") + fullclb = str(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"))) - subBlock(int(fullclb[2])) + await app.send_message(int(fullclb[2]), locale("sub_blocked", "message", locale=configGet("locale"))) + PosterUser(int(fullclb[2])).block() await clb.answer(text=locale("sub_block", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) + edited_markup = [clb.message.reply_markup.inline_keyboard[0], [InlineKeyboardButton(text=str(locale("sub_unblock", "button")), callback_data=f"sub_unblock_{fullclb[2]}")]] + await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) # edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]] # await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) @app.on_callback_query(filters.regex("sub_unblock_[\s\S]*")) async def callback_query_unblock(app: PosterClient, clb: CallbackQuery): - fullclb = clb.data.split("_") + fullclb = str(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"))) - subUnblock(int(fullclb[2])) + await app.send_message(int(fullclb[2]), locale("sub_unblocked", "message", locale=configGet("locale"))) + PosterUser(int(fullclb[2])).unblock() await clb.answer(text=locale("sub_unblock", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) + edited_markup = [clb.message.reply_markup.inline_keyboard[0], [InlineKeyboardButton(text=str(locale("sub_block", "button")), callback_data=f"sub_block_{fullclb[2]}")]] + await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) # edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]] # await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) \ No newline at end of file diff --git a/plugins/commands/general.py b/plugins/commands/general.py index 9df1e59..6b11323 100644 --- a/plugins/commands/general.py +++ b/plugins/commands/general.py @@ -12,7 +12,7 @@ from modules.utils import configGet, killProc, locale @app.on_message(~ filters.scheduled & filters.command(["kill", "die", "reboot"], prefixes=["", "/"])) async def cmd_kill(app: PosterClient, msg: Message): - if msg.from_user.id == configGet("admin"): + if msg.from_user.id in app.admins: pid = getpid() logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid))) await msg.reply_text(locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) diff --git a/plugins/commands/mode_submit.py b/plugins/commands/mode_submit.py index 5181355..39f550c 100644 --- a/plugins/commands/mode_submit.py +++ b/plugins/commands/mode_submit.py @@ -1,18 +1,18 @@ from pyrogram import filters -from classes.poster_client import PosterClient from pyrogram.types import Message from modules.app import app -from modules.submissions import subBlocked -from modules.utils import configGet, jsonLoad, locale +from modules.utils import locale +from classes.user import PosterUser +from classes.poster_client import PosterClient @app.on_message(~ filters.scheduled & filters.command(["start"], prefixes="/")) async def cmd_start(app: PosterClient, msg: Message): - if subBlocked(msg.from_user) is False: + if PosterUser(msg.from_user.id).is_blocked() 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: PosterClient, msg: Message): - if subBlocked(msg.from_user) is False: + if PosterUser(msg.from_user.id).is_blocked() is False: await msg.reply_text(locale("rules", "message", locale=msg.from_user.language_code)) \ No newline at end of file diff --git a/plugins/commands/photos.py b/plugins/commands/photos.py index 72a157e..f6a37b7 100644 --- a/plugins/commands/photos.py +++ b/plugins/commands/photos.py @@ -12,12 +12,12 @@ 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"): + if msg.from_user.id in app.admins: 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"): + if msg.from_user.id in app.admins: pass \ No newline at end of file diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index ddc1cee..6b683af 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -4,9 +4,10 @@ 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 pyrogram.enums.chat_action import ChatAction from classes.exceptions import SubmissionDuplicatesError +from classes.poster_client import PosterClient from modules.app import app from modules.database import col_banned, col_submitted @@ -16,13 +17,15 @@ from modules.utils import configGet, locale from classes.enums.submission_types import SubmissionType -@app.on_message(~ filters.scheduled & filters.photo | filters.video | filters.animation | filters.document) -async def get_submission(_: Client, msg: Message): +@app.on_message(~filters.scheduled & filters.private & filters.photo | filters.video | filters.animation | filters.document) +async def get_submission(app: PosterClient, msg: Message): try: if col_banned.find_one( {"user": msg.from_user.id} ) is not None: return + + await app.send_chat_action(msg.chat.id, ChatAction.TYPING) user_locale = msg.from_user.language_code save_tmp = True @@ -74,7 +77,7 @@ async def get_submission(_: Client, msg: Message): inserted = col_submitted.insert_one( { "user": msg.from_user.id, - "date": datetime.now(tz=timezone.utc), + "date": datetime.now(), "done": False, "type": contents[1].value, "temp": { @@ -97,7 +100,7 @@ async def get_submission(_: Client, msg: Message): inserted = col_submitted.insert_one( { "user": msg.from_user.id, - "date": datetime.now(tz=timezone.utc), + "date": datetime.now(), "done": False, "type": contents[1].value, "temp": { @@ -143,31 +146,31 @@ 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: + if msg.from_user.id in app.admins and configGet("admins", "submission", "require_confirmation") is False: try: await app.submit_photo(str(inserted.inserted_id)) - await msg.copy(configGet("admin"), caption=caption) + await msg.copy(app.owner, 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))) + await msg.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n • ".join(exp.duplicates)), quote=True) 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: + elif msg.from_user.id not in app.admins and configGet("users", "submission", "require_confirmation") is False: try: await app.submit_photo(str(inserted.inserted_id)) - await msg.copy(configGet("admin"), caption=caption) + await msg.copy(app.owner, 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))) + await msg.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n • ".join(exp.duplicates)), quote=True) 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 app.send_message(app.owner, 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"): + if msg.from_user.id not in app.admins: buttons += [ [ InlineKeyboardButton(text=locale("sub_block", "button", locale=configGet("locale")), callback_data=f"sub_block_{msg.from_user.id}") @@ -177,10 +180,12 @@ async def get_submission(_: Client, msg: Message): # ] ] - await msg.reply_text(locale("sub_sent", "message", locale=user_locale), quote=True) subLimit(msg.from_user) - await msg.copy(configGet("admin"), caption=caption, reply_markup=InlineKeyboardMarkup(buttons)) + if msg.from_user.id != app.owner: + await msg.reply_text(locale("sub_sent", "message", locale=user_locale), quote=True) + + await msg.copy(app.owner, caption=caption, reply_markup=InlineKeyboardMarkup(buttons)) except AttributeError: logWrite(f"from_user in function get_submission does not seem to contain id") \ No newline at end of file From a54081a2aef090edae1b4051ee1f69a17aa233f8 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 21:58:46 +0100 Subject: [PATCH 17/98] Fixed some issues with locale --- plugins/callbacks/submission.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/plugins/callbacks/submission.py b/plugins/callbacks/submission.py index 603588b..e8420bb 100644 --- a/plugins/callbacks/submission.py +++ b/plugins/callbacks/submission.py @@ -38,7 +38,7 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): await clb.answer(text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) - 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")]] + edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button", locale=user_locale)), 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", locale=user_locale)), callback_data="nothing")]] await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) # try: @@ -64,11 +64,6 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): # except: # await clb.answer(text=locale("sub_media_unavail", "message", locale=user_locale), show_alert=True) # return - # await submission.reply_text(locale("sub_yes", "message", locale=submission.from_user.language_code), quote=True) - # await clb.answer(text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) - - # edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]] - # await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) @app.on_callback_query(filters.regex("sub_no_[\s\S]*")) @@ -92,7 +87,7 @@ async def callback_query_no(app: PosterClient, clb: CallbackQuery): await submission.reply_text(locale("sub_no", "message", locale=submission.from_user.language_code), quote=True) await clb.answer(text=locale("sub_no", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) - edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "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("declined", "button")), callback_data="nothing")]] + edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button", locale=user_locale)), callback_data="nothing")], clb.message.reply_markup.inline_keyboard[1]] if len(clb.message.reply_markup.inline_keyboard) > 1 else [[InlineKeyboardButton(text=str(locale("declined", "button", locale=user_locale)), callback_data="nothing")]] await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) @@ -104,10 +99,8 @@ async def callback_query_block(app: PosterClient, clb: CallbackQuery): PosterUser(int(fullclb[2])).block() await clb.answer(text=locale("sub_block", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) - edited_markup = [clb.message.reply_markup.inline_keyboard[0], [InlineKeyboardButton(text=str(locale("sub_unblock", "button")), callback_data=f"sub_unblock_{fullclb[2]}")]] + edited_markup = [clb.message.reply_markup.inline_keyboard[0], [InlineKeyboardButton(text=str(locale("sub_unblock", "button", locale=user_locale)), callback_data=f"sub_unblock_{fullclb[2]}")]] await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) - # edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]] - # await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) @app.on_callback_query(filters.regex("sub_unblock_[\s\S]*")) @@ -118,7 +111,5 @@ async def callback_query_unblock(app: PosterClient, clb: CallbackQuery): PosterUser(int(fullclb[2])).unblock() await clb.answer(text=locale("sub_unblock", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) - edited_markup = [clb.message.reply_markup.inline_keyboard[0], [InlineKeyboardButton(text=str(locale("sub_block", "button")), callback_data=f"sub_block_{fullclb[2]}")]] - await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) - # edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button")), callback_data="nothing")]] - # await clb.message.edit(text=clb.message.text, reply_markup=InlineKeyboardMarkup(edited_markup)) \ No newline at end of file + edited_markup = [clb.message.reply_markup.inline_keyboard[0], [InlineKeyboardButton(text=str(locale("sub_block", "button", locale=user_locale)), callback_data=f"sub_block_{fullclb[2]}")]] + await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) \ No newline at end of file From 87af9fd333d06b2b475b13acce170aeeefb90fd7 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:26:07 +0100 Subject: [PATCH 18/98] Fixed captions not getting detected --- modules/sender.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sender.py b/modules/sender.py index 508ff25..0b6aafd 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -71,7 +71,7 @@ async def send_content(app: PosterClient): del response - submitted_caption = col_submitted.find_one( {"image": ObjectId(pic[0])} ) + submitted_caption = col_submitted.find_one( {"temp.file": pic[1]} ) if submitted_caption is not None: caption = submitted_caption["caption"].strip() From 807e629ae7cd605231c4f361df039a3eccf5c7be Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:48:37 +0100 Subject: [PATCH 19/98] Moved cooldowns to PosterUser class --- classes/user.py | 19 +++++++++++++++++-- modules/submissions.py | 18 ------------------ plugins/handlers/submission.py | 8 ++++---- 3 files changed, 21 insertions(+), 24 deletions(-) delete mode 100644 modules/submissions.py diff --git a/classes/user.py b/classes/user.py index d2e7c77..7e94896 100644 --- a/classes/user.py +++ b/classes/user.py @@ -1,5 +1,7 @@ +from modules.app import app from datetime import datetime -from modules.database import col_banned +from modules.database import col_banned, col_users +from modules.utils import configGet class PosterUser(): @@ -14,4 +16,17 @@ class PosterUser(): col_banned.insert_one({"user": self.id, "date": datetime.now()}) def unblock(self) -> None: - col_banned.find_one_and_delete({"user": self.id}) \ No newline at end of file + col_banned.find_one_and_delete({"user": self.id}) + + def is_limited(self) -> bool: + if self.id in app.admins: + return False + else: + db_record = col_users.find_one({"user": self.id}) + if db_record is None: + return False + return True if (datetime.now() - db_record["cooldown"]).total_seconds() < configGet("timeout", "submission") else False + + def limit(self) -> None: + if col_users.find_one_and_update({"user": self.id}, {"$set": {"cooldown": datetime.now()}}) is None: + col_users.insert_one({"user": self.id, "cooldown": datetime.now()}) \ No newline at end of file diff --git a/modules/submissions.py b/modules/submissions.py deleted file mode 100644 index e529835..0000000 --- a/modules/submissions.py +++ /dev/null @@ -1,18 +0,0 @@ -from modules.app import app -from datetime import datetime -from modules.utils import configGet -from modules.database import col_users -from pyrogram.types.user_and_chats import User - -def subLimit(user: User) -> None: - if col_users.find_one_and_update({"user": user.id}, {"$set": {"cooldown": datetime.now()}}) is None: - col_users.insert_one({"user": user.id, "cooldown": datetime.now()}) - -def subLimited(user: User) -> bool: - if user.id in app.admins: - return False - else: - db_record = col_users.find_one({"user": user.id}) - if db_record is None: - return False - return True if (datetime.now() - db_record["cooldown"]).total_seconds() < configGet("timeout", "submission") else False \ No newline at end of file diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index 6b683af..e09937e 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import datetime from os import makedirs, path, sep from traceback import format_exc from uuid import uuid4 @@ -8,11 +8,11 @@ from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message from pyrogram.enums.chat_action import ChatAction from classes.exceptions import SubmissionDuplicatesError from classes.poster_client import PosterClient +from classes.user import PosterUser from modules.app import app from modules.database import col_banned, col_submitted from modules.logger import logWrite -from modules.submissions import subLimit, subLimited from modules.utils import configGet, locale from classes.enums.submission_types import SubmissionType @@ -31,7 +31,7 @@ async def get_submission(app: PosterClient, msg: Message): save_tmp = True contents = None - if subLimited(msg.from_user): + if PosterUser(msg.from_user.id).is_limited(): await msg.reply_text(locale("sub_cooldown", "message", locale=user_locale).format(str(configGet("timeout", "submission")))) return @@ -180,7 +180,7 @@ async def get_submission(app: PosterClient, msg: Message): # ] ] - subLimit(msg.from_user) + PosterUser(msg.from_user.id).limit() if msg.from_user.id != app.owner: await msg.reply_text(locale("sub_sent", "message", locale=user_locale), quote=True) From b766d0c52c5346ed27792a3d6030d7bd477e2550 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:59:03 +0100 Subject: [PATCH 20/98] Improved linting and removed unused imports --- classes/poster_client.py | 4 +- classes/user.py | 13 +++ modules/commands_register.py | 2 +- modules/logger.py | 6 +- modules/sender.py | 139 +-------------------------------- modules/utils.py | 15 ++-- plugins/commands/photos.py | 6 +- plugins/handlers/submission.py | 3 - 8 files changed, 31 insertions(+), 157 deletions(-) diff --git a/classes/poster_client.py b/classes/poster_client.py index 9ac38be..baebc7f 100644 --- a/classes/poster_client.py +++ b/classes/poster_client.py @@ -2,9 +2,7 @@ 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 pyrogram.types import Message from classes.exceptions import SubmissionDuplicatesError, SubmissionUnavailableError from modules.api_client import upload_pic from modules.database import col_submitted diff --git a/classes/user.py b/classes/user.py index 7e94896..ba00e17 100644 --- a/classes/user.py +++ b/classes/user.py @@ -9,16 +9,28 @@ class PosterUser(): self.id = id def is_blocked(self) -> bool: + """Check if user is banned from submitting content. + + ### Returns: + `bool`: Must be `True` if banned and `False` if not + """ return False if col_banned.find_one({"user": self.id}) is None else True def block(self) -> None: + """Ban user from using command and submitting content.""" if col_banned.find_one({"user": self.id}) is None: col_banned.insert_one({"user": self.id, "date": datetime.now()}) def unblock(self) -> None: + """Allow user to use command and submit posts again.""" col_banned.find_one_and_delete({"user": self.id}) def is_limited(self) -> bool: + """Check if user is on a cooldown after submitting something. + + ### Returns: + `bool`: Must be `True` if on the cooldown and `False` if not + """ if self.id in app.admins: return False else: @@ -28,5 +40,6 @@ class PosterUser(): return True if (datetime.now() - db_record["cooldown"]).total_seconds() < configGet("timeout", "submission") else False def limit(self) -> None: + """Restart user's cooldown. Used after post has been submitted.""" if col_users.find_one_and_update({"user": self.id}, {"$set": {"cooldown": datetime.now()}}) is None: col_users.insert_one({"user": self.id, "cooldown": datetime.now()}) \ No newline at end of file diff --git a/modules/commands_register.py b/modules/commands_register.py index 3da2bf9..0b736fc 100644 --- a/modules/commands_register.py +++ b/modules/commands_register.py @@ -3,7 +3,7 @@ from classes.poster_client import PosterClient from pyrogram.types import BotCommand, BotCommandScopeChat from modules.utils import configGet, locale -async def register_commands(app: PosterClient): +async def register_commands(app: PosterClient) -> None: if configGet("submit", "mode"): # Registering user commands diff --git a/modules/logger.py b/modules/logger.py index 3be942d..37cae1c 100644 --- a/modules/logger.py +++ b/modules/logger.py @@ -16,7 +16,7 @@ with open(getcwd()+path.sep+"config.json", "r", encoding='utf8') as file: file.close() # Check latest log size -def checkSize(debug=False): +def checkSize(debug=False) -> None: global log_folder @@ -39,7 +39,7 @@ def checkSize(debug=False): pass # Append string to log -def logAppend(message, debug=False): +def logAppend(message, debug=False) -> None: global log_folder @@ -56,7 +56,7 @@ def logAppend(message, debug=False): log.close() # Print to stdout and then to log -def logWrite(message, debug=False): +def logWrite(message, debug=False) -> None: # save to log file and rotation is to be done logAppend(f'{message}', debug=debug) print(f"{message}", flush=True) \ No newline at end of file diff --git a/modules/sender.py b/modules/sender.py index 0b6aafd..2e10218 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -5,7 +5,6 @@ from traceback import format_exc from uuid import uuid4 from PIL import Image -from bson import ObjectId from classes.poster_client import PosterClient from requests import get @@ -15,7 +14,7 @@ from modules.logger import logWrite from modules.utils import configGet, locale -async def send_content(app: PosterClient): +async def send_content(app: PosterClient) -> None: try: @@ -118,138 +117,4 @@ async def send_content(app: PosterClient): try: rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) except: - pass - - -# async def send_content_old(app: PosterClient): - -# # Send post to channel -# try: - -# index = jsonLoad(configGet("index", "locations")) - -# if configGet("api_based", "mode"): - -# try: -# pic = random_pic() -# except: -# logWrite(locale("post_empty", "console", locale=configGet("locale"))) -# if configGet("error", "reports"): -# await app.send_message(configGet("admin"), locale("post_empty", "message", locale=configGet("locale"))) -# return - -# token = authorize() - -# response = get(f'{configGet("address", "posting", "api")}/photos/{pic[0]}', headers={"Authorization": f"Bearer {token}"}, stream=True) - -# with open(configGet("tmp", "locations")+sep+pic[0]+".jpg", 'wb') as out_file: -# copyfileobj(response.raw, out_file) - -# del response - -# candidate = configGet("tmp", "locations")+sep+pic[0]+".jpg" -# candidate_file = pic[1] -# ext_type = "photo" - -# if not configGet("api_based", "mode"): - -# list_queue = listdir(configGet("queue", "locations")) - -# for file in list_queue: - -# if not file in index["sent"]: - -# ext_match = False - -# for ext in configGet("photo", "posting", "extensions"): -# if file.endswith(ext): -# ext_match = True -# ext_type = "photo" -# break - -# for ext in configGet("video", "posting", "extensions"): -# if file.endswith(ext): -# ext_match = True -# ext_type = "video" -# break - -# if not ext_match: -# list_queue.remove(file) - -# else: -# list_queue.remove(file) - -# if len(list_queue) > 0: -# candidate_file = choice(list_queue) -# candidate = configGet("queue", "locations")+sep+candidate_file -# else: -# logWrite(locale("post_empty", "console", locale=configGet("locale"))) -# if configGet("error", "reports"): -# await app.send_message(configGet("admin"), locale("post_empty", "message", locale=configGet("locale"))) -# return - -# if candidate_file in index["captions"]: -# caption = index["captions"][candidate_file] -# else: -# caption = "" - -# if configGet("enabled", "caption"): -# if configGet("link", "caption") != None: -# caption = f"{caption}\n\n[{configGet('text', 'caption')}]({configGet('link', 'caption')})" -# else: -# caption = f"{caption}\n\n{configGet('text', 'caption')}" -# else: -# caption = caption - -# if ext_type == "photo": - -# if configGet("enabled", "caption"): -# if configGet("link", "caption") != None: -# sent = await app.send_photo(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) -# else: -# sent = await app.send_photo(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) -# else: -# sent = await app.send_photo(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) - -# elif ext_type == "video": - -# if configGet("enabled", "caption"): -# if configGet("link", "caption") != None: -# sent = await app.send_video(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) -# else: -# sent = await app.send_video(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) -# else: -# sent = await app.send_video(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) - -# else: -# return - -# if configGet("api_based", "mode"): -# remove(configGet("tmp", "locations")+sep+pic[0]+".jpg") -# move_pic(pic[0]) - -# index["sent"].append(candidate_file) -# index["last_id"] = sent.id - -# jsonSave(index, configGet("index", "locations")) - -# if configGet("move_sent", "posting"): -# move(candidate, configGet("sent", "locations")+sep+candidate_file) - -# logWrite(locale("post_sent", "console", locale=configGet("locale")).format(candidate, ext_type, str(configGet("channel", "posting")), caption.replace("\n", "%n"), str(configGet("silent", "posting")))) - -# if configGet("sent", "reports"): -# await app.send_message(configGet("admin"), f"Posted `{candidate_file}`", disable_web_page_preview=True, reply_markup=InlineKeyboardMarkup([ -# [InlineKeyboardButton(locale("post_view", "button", locale=configGet("locale")), url=sent.link)] -# ])) - -# except Exception as exp: -# 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())) -# pass - - - # Work in progress - # Check last posts forwards - # check_forwards(app) \ No newline at end of file + pass \ No newline at end of file diff --git a/modules/utils.py b/modules/utils.py index a72d298..92253ab 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -9,10 +9,11 @@ from sys import exit from os import sep, kill from os import name as osname from traceback import print_exc +from typing import Any from modules.logger import logWrite -def jsonLoad(filename): +def jsonLoad(filename: str) -> Any: """Loads arg1 as json and returns its contents""" with open(filename, "r", encoding='utf8') as file: try: @@ -26,7 +27,7 @@ def jsonLoad(filename): file.close() return output -def jsonSave(contents, filename): +def jsonSave(contents: Any, filename: str) -> None: """Dumps dict/list arg1 to file arg2""" try: with open(filename, "w", encoding='utf8') as file: @@ -108,10 +109,14 @@ except ModuleNotFoundError: print(locale("deps_missing", "console", locale=configGet("locale")), flush=True) exit() -def killProc(pid): +def killProc(pid: int) -> None: + """Kill process by its PID. Meant to be used to kill the main process of bot itself. + + ### Args: + * pid (`int`): PID of the target + """ if osname == "posix": from signal import SIGKILL kill(pid, SIGKILL) else: - p = Process(pid) - p.kill() \ No newline at end of file + Process(pid).kill() \ No newline at end of file diff --git a/plugins/commands/photos.py b/plugins/commands/photos.py index f6a37b7..ff5111b 100644 --- a/plugins/commands/photos.py +++ b/plugins/commands/photos.py @@ -1,12 +1,8 @@ -from os import getpid - from pyrogram import filters -from classes.poster_client import PosterClient from pyrogram.types import Message +from classes.poster_client import PosterClient 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=["", "/"])) diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index e09937e..4b6c51b 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -175,9 +175,6 @@ async def get_submission(app: PosterClient, msg: Message): [ InlineKeyboardButton(text=locale("sub_block", "button", locale=configGet("locale")), callback_data=f"sub_block_{msg.from_user.id}") ] - # [ - # InlineKeyboardButton(text=locale("sub_unblock", "button", locale=configGet("locale")), callback_data=f"sub_unblock_{msg.from_user.id}") - # ] ] PosterUser(msg.from_user.id).limit() From 8c478072c635b6b57379b12d1870b2ebbd8de362 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 23:18:19 +0100 Subject: [PATCH 21/98] Fixed reply message not getting quoted --- classes/poster_client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/classes/poster_client.py b/classes/poster_client.py index baebc7f..51c43cd 100644 --- a/classes/poster_client.py +++ b/classes/poster_client.py @@ -31,6 +31,10 @@ class PosterClient(Client): raise SubmissionUnavailableError() else: filepath = path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"], db_entry["temp"]["file"]) + try: + submission = await self.get_messages(db_entry["user"], db_entry["telegram"]["msg_id"]) + except: + pass else: try: submission = await self.get_messages(db_entry["user"], db_entry["telegram"]["msg_id"]) From fcd59b7acaa70b88a5d77069756979a857b07e88 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 23:18:34 +0100 Subject: [PATCH 22/98] Fixed caption display --- modules/sender.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/sender.py b/modules/sender.py index 2e10218..7e6adfb 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -70,10 +70,10 @@ async def send_content(app: PosterClient) -> None: del response - submitted_caption = col_submitted.find_one( {"temp.file": pic[1]} ) + submitted = col_submitted.find_one({"temp.file": pic[1]}) - if submitted_caption is not None: - caption = submitted_caption["caption"].strip() + if submitted is not None and submitted["caption"] is not None: + caption = submitted["caption"].strip() else: caption = "" @@ -100,7 +100,7 @@ async def send_content(app: PosterClient) -> None: "image": pic[0], "filename": pic[1], "channel": configGet("channel", "posting"), - "caption": None if submitted_caption is None else submitted_caption["caption"].strip() + "caption": None if (submitted is None or submitted["caption"] is None) else submitted["caption"].strip() } ) From 7918049f49e0016bd0cc814bb42b0168448cfeba Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 23:18:54 +0100 Subject: [PATCH 23/98] Fixed language code typo --- plugins/callbacks/nothing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/callbacks/nothing.py b/plugins/callbacks/nothing.py index 7052b06..e771c8f 100644 --- a/plugins/callbacks/nothing.py +++ b/plugins/callbacks/nothing.py @@ -7,5 +7,5 @@ from modules.utils import locale # Callback empty =============================================================================================================== @app.on_callback_query(filters.regex("nothing")) async def callback_query_nothing(app: PosterClient, clb: CallbackQuery): - await clb.answer(text=locale("nothing", "callback", locale=clb.from_user)) + await clb.answer(text=locale("nothing", "callback", locale=clb.from_user.language_code)) # ============================================================================================================================== \ No newline at end of file From 7607003f55a21be6dc2e14f5b64c0bd2af2d122a Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Fri, 17 Feb 2023 23:45:55 +0100 Subject: [PATCH 24/98] Added callback message for "nothing" --- locale/en.json | 3 ++- locale/uk.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/locale/en.json b/locale/en.json index 4ca5c1b..09e9d9a 100644 --- a/locale/en.json +++ b/locale/en.json @@ -54,7 +54,8 @@ "sub_media_unavail": "Could not download submission", "sub_done": "You've already decided what to do with submission", "sub_upload_failed": "__TO_BE_ADDED__", - "sub_duplicates_found": "__TO_BE_ADDED__" + "sub_duplicates_found": "__TO_BE_ADDED__", + "nothing": "🏁 This action is already finished" }, "console": { "shutdown": "Shutting down bot with pid {0}", diff --git a/locale/uk.json b/locale/uk.json index e5305c9..8900bee 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -54,7 +54,8 @@ "sub_media_unavail": "Не вдалося завантажити подання", "sub_done": "Ви вже обрали що зробити з цим поданням", "sub_upload_failed": "__TO_BE_ADDED__", - "sub_duplicates_found": "__TO_BE_ADDED__" + "sub_duplicates_found": "__TO_BE_ADDED__", + "nothing": "🏁 Цю дію вже було завершено" }, "console": { "shutdown": "Вимкнення бота з підом {0}", From b3698cfa70b044a9fd2022ce4ad5046f82c055c1 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Sat, 18 Feb 2023 00:55:58 +0100 Subject: [PATCH 25/98] Added support for duplicates access tokens --- modules/api_client.py | 4 ++-- plugins/callbacks/submission.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/api_client.py b/modules/api_client.py index 311acaf..e653e0f 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -63,8 +63,8 @@ async def upload_pic(filepath: str, token: Union[str, None] = None) -> Tuple[boo raise SubmissionUploadError(str(filepath), response.status_code, response.content) duplicates = [] if "duplicates" in response.json(): - for duplicate in response.json()["duplicates"]: - duplicates.append(f'{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}') + for index, duplicate in enumerate(response.json()["duplicates"]): + duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{response.json()["access_token"]}?id={index}') return True, duplicates except: return False, [] diff --git a/plugins/callbacks/submission.py b/plugins/callbacks/submission.py index e8420bb..4e15b6b 100644 --- a/plugins/callbacks/submission.py +++ b/plugins/callbacks/submission.py @@ -28,7 +28,7 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): return 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(exp.duplicates)), quote=True) + await clb.message.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n • ".join(exp.duplicates)), disable_web_page_preview=True, quote=True) return if submission is not None: From 6b7b5c22f25e46d18b21e2154a84f20bcf6510bc Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Sat, 18 Feb 2023 00:58:09 +0100 Subject: [PATCH 26/98] Reverted web preview disable --- plugins/callbacks/submission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/callbacks/submission.py b/plugins/callbacks/submission.py index 4e15b6b..e8420bb 100644 --- a/plugins/callbacks/submission.py +++ b/plugins/callbacks/submission.py @@ -28,7 +28,7 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): return 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(exp.duplicates)), disable_web_page_preview=True, quote=True) + await clb.message.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n • ".join(exp.duplicates)), quote=True) return if submission is not None: From fd47217badc772b9bb65660149e494a9ab282fb2 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Sun, 19 Feb 2023 20:31:08 +0100 Subject: [PATCH 27/98] This commit closes #5 --- modules/api_client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/api_client.py b/modules/api_client.py index e653e0f..7e7c487 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -64,7 +64,10 @@ async def upload_pic(filepath: str, token: Union[str, None] = None) -> Tuple[boo duplicates = [] if "duplicates" in response.json(): for index, duplicate in enumerate(response.json()["duplicates"]): - duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{response.json()["access_token"]}?id={index}') + if response.json()["access_token"] is None: + duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}') + else: + duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{response.json()["access_token"]}?id={index}') return True, duplicates except: return False, [] From 8bafd0cb35c629aa34cad565e80a309e61274343 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Sun, 19 Feb 2023 20:44:00 +0100 Subject: [PATCH 28/98] This commit closes #3 --- classes/poster_client.py | 2 +- config.json | 1 + modules/api_client.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/classes/poster_client.py b/classes/poster_client.py index 51c43cd..9c1c876 100644 --- a/classes/poster_client.py +++ b/classes/poster_client.py @@ -42,7 +42,7 @@ class PosterClient(Client): except: raise SubmissionUnavailableError() - response = await upload_pic(str(filepath)) + response = await upload_pic(str(filepath), allow_duplicates=configGet("allow_duplicates", "submission")) if len(response[1]) > 0: raise SubmissionDuplicatesError(str(filepath), response[1]) diff --git a/config.json b/config.json index e8a78cd..cbf9518 100644 --- a/config.json +++ b/config.json @@ -91,6 +91,7 @@ "timeout": 30, "file_size": 15728640, "tmp_size": 15728640, + "allow_duplicates": false, "require_confirmation": { "users": true, "admins": true diff --git a/modules/api_client.py b/modules/api_client.py index 7e7c487..1dd6f6b 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -52,12 +52,12 @@ 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, token: Union[str, None] = None) -> Tuple[bool, list]: +async def upload_pic(filepath: str, allow_duplicates: bool = False, 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) + response = post(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos', params={"caption": "queue", "compress": False, "ignore_duplicates": allow_duplicates}, 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}'") raise SubmissionUploadError(str(filepath), response.status_code, response.content) From 056fc52353a1f833f158376abd3cfdb36b45b1b9 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Sun, 19 Feb 2023 20:54:58 +0100 Subject: [PATCH 29/98] This commit closes #4 and closes #6 --- locale/en.json | 6 +++++- locale/uk.json | 6 +++++- plugins/handlers/submission.py | 12 +++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/locale/en.json b/locale/en.json index 09e9d9a..56598a1 100644 --- a/locale/en.json +++ b/locale/en.json @@ -15,7 +15,9 @@ "shutdown": "Shutting down bot with pid `{0}`", "startup": "Starting with pid `{0}`", "sub_yes": "✅ Submission approved and accepted", + "sub_yes_auto": "✅ Submission automatically accepted", "sub_no": "❌ Submission reviewed and declined", + "sub_dup": "⚠️ Submission automatically declined because database already contains this photo", "sub_blocked": "You were blocked and you can't submit media anymore.", "sub_unblocked": "You were unblocked and you can now submit media.", "sub_by": "\n\nSubmitted by:", @@ -33,7 +35,9 @@ "post_low": "Low amount of content: `There are only {0} files left in the queue.`", "api_creds_invalid": "__TO_BE_ADDED__", "sub_wip": "Post submission is now WIP. It will be available again in a few days. Thank you for your patience.", - "sub_duplicates_found": "__TO_BE_ADDED__" + "sub_duplicates_found": "__TO_BE_ADDED__", + "sub_error": "⚠️ Could not upload this image due to bot error. Admins are advised.", + "sub_error_admin": "User {0} could not submit photo without additional confirmation due to:\n```\n{1}\n```" }, "button": { "sub_yes": "✅ Accept", diff --git a/locale/uk.json b/locale/uk.json index 8900bee..4fb202a 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -15,7 +15,9 @@ "shutdown": "Вимкнення бота з підом `{0}`", "startup": "Запуск бота з підом `{0}`", "sub_yes": "✅ Подання схвалено та прийнято", + "sub_yes_auto": "✅ Подання автоматично прийнято", "sub_no": "❌ Подання розглянуто та відхилено", + "sub_dup": "⚠️ Подання автоматично відхилено через наявність цього фото в базі даних", "sub_blocked": "Вас заблокували, ви більше не можете надсилати медіафайли.", "sub_unblocked": "Вас розблокували, тепер ви можете надсилати медіафайли.", "sub_by": "\n\nПредставлено:", @@ -33,7 +35,9 @@ "post_low": "Мала кількість контенту: `Залишилось всього {0} файлів в черзі.`", "api_creds_invalid": "__TO_BE_ADDED__", "sub_wip": "Подання постів зараз знаходиться у розробці. Він буде знову доступний через кілька днів. Дякуємо за ваше терпіння.", - "sub_duplicates_found": "__TO_BE_ADDED__" + "sub_duplicates_found": "__TO_BE_ADDED__", + "sub_error": "⚠️ Не вдалось завантажити фото через помилку бота. Адміністрацію повідомлено.", + "sub_error_admin": "Користувач {0} не зміг надіслати фото без додаткової перевірки через помилку:\n```\n{1}\n```" }, "button": { "sub_yes": "✅ Прийняти", diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index 4b6c51b..e3151d6 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -149,25 +149,27 @@ async def get_submission(app: PosterClient, msg: Message): if msg.from_user.id in app.admins and configGet("admins", "submission", "require_confirmation") is False: try: await app.submit_photo(str(inserted.inserted_id)) - await msg.copy(app.owner, caption=caption) + await msg.reply_text(locale("sub_yes_auto", "message", locale=user_locale), quote=True) + await msg.copy(app.owner, caption=caption, disable_notification=True) return except SubmissionDuplicatesError as exp: await msg.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n • ".join(exp.duplicates)), quote=True) return except Exception as exp: - await msg.reply_text(format_exc()) + await msg.reply_text(format_exc(), quote=True) return elif msg.from_user.id not in app.admins and configGet("users", "submission", "require_confirmation") is False: try: await app.submit_photo(str(inserted.inserted_id)) + await msg.reply_text(locale("sub_yes_auto", "message", locale=user_locale), quote=True) await msg.copy(app.owner, 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)), quote=True) + await msg.reply_text(locale("sub_dup", "message", locale=user_locale), quote=True) return except Exception as exp: - await app.send_message(app.owner, 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.") + await app.send_message(app.owner, locale("sub_error_admin", "message").format(msg.from_user.id, format_exc())) + await msg.reply_text("sub_error", quote=True) return if msg.from_user.id not in app.admins: From 64ae3fb04705b842678892982336f39b11709a17 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 24 Feb 2023 12:39:33 -0500 Subject: [PATCH 30/98] Added option to mention submissions --- config.json | 5 +++++ modules/sender.py | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index cbf9518..ad3bb99 100644 --- a/config.json +++ b/config.json @@ -49,6 +49,11 @@ "move_sent": false, "use_interval": false, "interval": "1h30m", + "submitted_caption": { + "enabled": true, + "ignore_admins": true, + "text": "#submitted" + }, "extensions": { "photo": [ "jpg", diff --git a/modules/sender.py b/modules/sender.py index 7e6adfb..2d90f30 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -77,11 +77,16 @@ async def send_content(app: PosterClient) -> None: else: caption = "" + if configGet("enabled", "posting", "submitted_caption") and ((submitted["user"] not in app.admins) or (configGet("ignore_admins", "posting", "submitted_caption") is True)): + caption = f"{caption}\n\n{configGet('text', 'posting', 'submitted_caption')}\n" + else: + caption = f"{caption}\n\n" + if configGet("enabled", "caption"): if configGet("link", "caption") != None: - caption = f"{caption}\n\n[{configGet('text', 'caption')}]({configGet('link', 'caption')})" + caption = f"{caption}[{configGet('text', 'caption')}]({configGet('link', 'caption')})" else: - caption = f"{caption}\n\n{configGet('text', 'caption')}" + caption = f"{caption}{configGet('text', 'caption')}" else: caption = caption From c7228a006b1c9dde56862a9515a73be6f54ac2e5 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 24 Feb 2023 13:25:36 -0500 Subject: [PATCH 31/98] Improved randomness --- modules/api_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/api_client.py b/modules/api_client.py index 1dd6f6b..9214e1d 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -41,11 +41,11 @@ async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: * `Tuple[str, str]`: First value is an ID and the filename in the filesystem to be indexed. """ 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}"}) + logWrite(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size=500&caption=queue') + resp = get(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size=500&caption=queue', headers={"Authorization": f"Bearer {token}"}) if resp.status_code != 200: logWrite(f'Could not get photos from album {configGet("album", "posting", "api")}: HTTP {resp.status_code}') - logWrite(f'Could not get photos from "{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size=100&caption=queue" using token "{token}": HTTP {resp.status_code}', debug=True) + logWrite(f'Could not get photos from "{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size=500&caption=queue" using token "{token}": HTTP {resp.status_code}', debug=True) raise ValueError if len(resp.json()["results"]) == 0: raise KeyError From c27b1c5a5bb2595dca7166c6fb169bdf5c51f2c1 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 24 Feb 2023 13:25:57 -0500 Subject: [PATCH 32/98] Temporarily disabled album creation on start --- poster.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poster.py b/poster.py index 6e94d9d..faab802 100644 --- a/poster.py +++ b/poster.py @@ -180,10 +180,10 @@ if __name__ == "__main__": if configGet("post", "mode"): scheduler.start() - if configGet("api_based", "mode"): - token = authorize() - if len(get(f'{configGet("address", "posting", "api")}/albums?q={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}).json()["results"]) == 0: - post(f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}) + #if configGet("api_based", "mode"): + # token = await authorize() + # if len(get(f'{configGet("address", "posting", "api")}/albums?q={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}).json()["results"]) == 0: + # post(f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}) idle() From 6e8b47cf4b6c26563b5a6d887143cfc0eb7e5976 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 24 Feb 2023 13:33:34 -0500 Subject: [PATCH 33/98] Improved submissions randomness --- config.json | 1 + modules/api_client.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index ad3bb99..bfccebc 100644 --- a/config.json +++ b/config.json @@ -49,6 +49,7 @@ "move_sent": false, "use_interval": false, "interval": "1h30m", + "page_size": 300, "submitted_caption": { "enabled": true, "ignore_admins": true, diff --git a/modules/api_client.py b/modules/api_client.py index 9214e1d..25e0a6d 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -41,11 +41,11 @@ async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: * `Tuple[str, str]`: First value is an ID and the filename in the filesystem to be indexed. """ token = await authorize() if token is None else token - logWrite(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size=500&caption=queue') - resp = get(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size=500&caption=queue', headers={"Authorization": f"Bearer {token}"}) + logWrite(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue') + resp = get(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue', headers={"Authorization": f"Bearer {token}"}) if resp.status_code != 200: logWrite(f'Could not get photos from album {configGet("album", "posting", "api")}: HTTP {resp.status_code}') - logWrite(f'Could not get photos from "{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size=500&caption=queue" using token "{token}": HTTP {resp.status_code}', debug=True) + logWrite(f'Could not get photos from "{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue" using token "{token}": HTTP {resp.status_code}', debug=True) raise ValueError if len(resp.json()["results"]) == 0: raise KeyError From 7b2534012df9bae07e7b7af926d6061fb0516cda Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 24 Feb 2023 13:48:06 -0500 Subject: [PATCH 34/98] Fixed typo --- modules/sender.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/sender.py b/modules/sender.py index 2d90f30..61df84b 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -77,7 +77,9 @@ async def send_content(app: PosterClient) -> None: else: caption = "" - if configGet("enabled", "posting", "submitted_caption") and ((submitted["user"] not in app.admins) or (configGet("ignore_admins", "posting", "submitted_caption") is True)): + if configGet("enabled", "posting", "submitted_caption") and ( + (submitted["user"] not in app.admins) or (configGet("ignore_admins", "posting", "submitted_caption") is False) + ): caption = f"{caption}\n\n{configGet('text', 'posting', 'submitted_caption')}\n" else: caption = f"{caption}\n\n" From 7810f3b7b90b50b81a05d58bbdc62aa9e17accce Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 24 Feb 2023 21:06:18 +0100 Subject: [PATCH 35/98] Fixed absence of None check --- modules/sender.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sender.py b/modules/sender.py index 61df84b..c5194b6 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -77,7 +77,7 @@ async def send_content(app: PosterClient) -> None: else: caption = "" - if configGet("enabled", "posting", "submitted_caption") and ( + if submitted is not None and configGet("enabled", "posting", "submitted_caption") and ( (submitted["user"] not in app.admins) or (configGet("ignore_admins", "posting", "submitted_caption") is False) ): caption = f"{caption}\n\n{configGet('text', 'posting', 'submitted_caption')}\n" From 0a309a9f5977a7c6bb45c6f162b52d79912132a5 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sat, 25 Feb 2023 23:10:57 +0100 Subject: [PATCH 36/98] Added one more rule --- locale/en.json | 2 +- locale/uk.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locale/en.json b/locale/en.json index 56598a1..ec35330 100644 --- a/locale/en.json +++ b/locale/en.json @@ -11,7 +11,7 @@ }, "message": { "start": "Hi and welcome!\n\nYou can submit your pictures and videos here. We'll review and add them, if we like them. Make sure you send your stuff one at a time and have chosen media that corresponds to our rules.\n\nYou can also write something to us in the description field. We'll send it with the submission itself, if needed.\n\nAlso, make sure you follow the /rules of submission, otherwise your submission will be declined. In case of spam/abuse you may even be blocked.\n\nHave fun and happy submitting!", - "rules": "Photos submission rules:\n1. No porn, only erotics and aesthetics\n2. Nipples are semi-allowed, should be either veiled or barely visible\n3. Genitalia strictly prohibited, but labia prints on clothes or nice pubes/panties/butts - are fine", + "rules": "Photos submission rules:\n1. No porn, only erotics and aesthetics\n2. Nipples are semi-allowed, should be either veiled or barely visible\n3. Genitalia strictly prohibited, but labia prints on clothes or nice pubes/panties/butts - are fine\n4. Submitting russians is forbidden", "shutdown": "Shutting down bot with pid `{0}`", "startup": "Starting with pid `{0}`", "sub_yes": "✅ Submission approved and accepted", diff --git a/locale/uk.json b/locale/uk.json index 4fb202a..3861e11 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -11,7 +11,7 @@ }, "message": { "start": "Привіт і ласкаво просимо!\n\nТут можна пропонувати свої фотографії та відео. Ми переглянемо та додамо їх, якщо вони нам сподобаються. Переконайтеся, що ви надсилаєте свої матеріали по одному та вибираєте медіа, які відповідають нашим правилам.\n\nВи також можете написати нам щось у полі опису. За потреби ми надішлемо це разом із самим фото.\n\nКрім того, переконайтеся, що ви дотримуєтеся /rules (правил) подання, інакше вашу пропозицію буде відхилено. У разі спаму/зловживань вас можуть навіть заблокувати.\n\nГарного дня та щасливого надсилання!", - "rules": "Правила пропонування фото:\n1. Ніякого порно, тільки еротика та естетика\n2. Соски можна, але або завуальовані, або зовсім ледь помітні\n3. Геніталії суворо ні, а ось відбитки статевих губ на одязі або гарні лобочки/трусики/попки - без проблем", + "rules": "Правила пропонування фото:\n1. Ніякого порно, тільки еротика та естетика\n2. Соски можна, але або завуальовані, або зовсім ледь помітні\n3. Геніталії суворо ні, а ось відбитки статевих губ на одязі або гарні лобочки/трусики/попки - без проблем\n4. Пропонувати русню заборонено", "shutdown": "Вимкнення бота з підом `{0}`", "startup": "Запуск бота з підом `{0}`", "sub_yes": "✅ Подання схвалено та прийнято", From cf6c1f03d7f1c05386f727e14a52aca9d81b8ad5 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sat, 25 Feb 2023 23:12:33 +0100 Subject: [PATCH 37/98] Disabled notification for accepted submissions --- plugins/handlers/submission.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index e3151d6..6638f3e 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -149,7 +149,7 @@ async def get_submission(app: PosterClient, msg: Message): if msg.from_user.id in app.admins and configGet("admins", "submission", "require_confirmation") is False: try: await app.submit_photo(str(inserted.inserted_id)) - await msg.reply_text(locale("sub_yes_auto", "message", locale=user_locale), quote=True) + await msg.reply_text(locale("sub_yes_auto", "message", locale=user_locale), disable_notification=True, quote=True) await msg.copy(app.owner, caption=caption, disable_notification=True) return except SubmissionDuplicatesError as exp: @@ -161,7 +161,7 @@ async def get_submission(app: PosterClient, msg: Message): elif msg.from_user.id not in app.admins and configGet("users", "submission", "require_confirmation") is False: try: await app.submit_photo(str(inserted.inserted_id)) - await msg.reply_text(locale("sub_yes_auto", "message", locale=user_locale), quote=True) + await msg.reply_text(locale("sub_yes_auto", "message", locale=user_locale), disable_notification=True, quote=True) await msg.copy(app.owner, caption=caption) return except SubmissionDuplicatesError as exp: @@ -182,7 +182,7 @@ async def get_submission(app: PosterClient, msg: Message): PosterUser(msg.from_user.id).limit() if msg.from_user.id != app.owner: - await msg.reply_text(locale("sub_sent", "message", locale=user_locale), quote=True) + await msg.reply_text(locale("sub_sent", "message", locale=user_locale), disable_notification=True, quote=True) await msg.copy(app.owner, caption=caption, reply_markup=InlineKeyboardMarkup(buttons)) From 88f8bb4a52f0ef8402ae5d5d3d8bfc34c5b03e5a Mon Sep 17 00:00:00 2001 From: profitroll Date: Sat, 25 Feb 2023 23:19:08 +0100 Subject: [PATCH 38/98] WIP: Photos and queue removal --- locale/en.json | 2 ++ locale/uk.json | 2 ++ plugins/commands/photos.py | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/locale/en.json b/locale/en.json index ec35330..ca4803f 100644 --- a/locale/en.json +++ b/locale/en.json @@ -7,6 +7,8 @@ "forwards": "Check post forwards", "import": "Submit .zip archive with photos", "export": "Get .zip archive with all photos", + "remove": "Delete photo by its ID", + "purge": "Completely purge bot's queue", "reboot": "Restart the bot" }, "message": { diff --git a/locale/uk.json b/locale/uk.json index 3861e11..4a138a3 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -7,6 +7,8 @@ "forwards": "Переглянути репости", "import": "Надати боту .zip архів з фотографіями", "export": "Отримати .zip архів з усіма фотографіями", + "remove": "Видалити фото за його ID", + "purge": "Повністю видалити всю чергу бота", "reboot": "Перезапустити бота" }, "message": { diff --git a/plugins/commands/photos.py b/plugins/commands/photos.py index ff5111b..6c35ae9 100644 --- a/plugins/commands/photos.py +++ b/plugins/commands/photos.py @@ -15,5 +15,19 @@ async def cmd_import(app: PosterClient, msg: Message): @app.on_message(~ filters.scheduled & filters.command(["export"], prefixes=["", "/"])) async def cmd_export(app: PosterClient, msg: Message): + if msg.from_user.id in app.admins: + pass + + +@app.on_message(~ filters.scheduled & filters.command(["remove"], prefixes=["", "/"])) +async def cmd_remove(app: PosterClient, msg: Message): + + if msg.from_user.id in app.admins: + pass + + +@app.on_message(~ filters.scheduled & filters.command(["purge"], prefixes=["", "/"])) +async def cmd_purge(app: PosterClient, msg: Message): + if msg.from_user.id in app.admins: pass \ No newline at end of file From 23878231510bb354eb55909f9ce6c08981f6472f Mon Sep 17 00:00:00 2001 From: profitroll Date: Sat, 25 Feb 2023 23:31:09 +0100 Subject: [PATCH 39/98] Added missing texts --- locale/en.json | 10 +++++----- locale/uk.json | 10 +++++----- modules/sender.py | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/locale/en.json b/locale/en.json index ca4803f..f795bf8 100644 --- a/locale/en.json +++ b/locale/en.json @@ -31,11 +31,11 @@ "document_too_large": "File you've sent is too large. Please submit files not bigger than {0} MB", "mime_not_allowed": "File type not allowed. Please, consider using one of these: {0}", "post_exception": "Could not send content due to `{0}`\n\nTraceback:\n```{1}```", - "post_invalid_pic": "__TO_BE_ADDED__", + "post_invalid_pic": "⚠️ Error {0} while sending photo\n```python\n{1}\n```", "api_queue_empty": "Could not send content: `Queue is empty or contains only unsupported files.`", - "api_queue_error": "__TO_BE_ADDED__", + "api_queue_error": "Could not get photo from API's queue. Check the log above or API's errors to get more info.", "post_low": "Low amount of content: `There are only {0} files left in the queue.`", - "api_creds_invalid": "__TO_BE_ADDED__", + "api_creds_invalid": "Could not authorize API access. Please check whether provided in config file are valid and update them if they're not.", "sub_wip": "Post submission is now WIP. It will be available again in a few days. Thank you for your patience.", "sub_duplicates_found": "__TO_BE_ADDED__", "sub_error": "⚠️ Could not upload this image due to bot error. Admins are advised.", @@ -60,7 +60,7 @@ "sub_media_unavail": "Could not download submission", "sub_done": "You've already decided what to do with submission", "sub_upload_failed": "__TO_BE_ADDED__", - "sub_duplicates_found": "__TO_BE_ADDED__", + "sub_duplicates_found": "There're duplicates in bot's database", "nothing": "🏁 This action is already finished" }, "console": { @@ -70,7 +70,7 @@ "exception_occured": "Exception {0} happened on task execution", "post_sent": "Sent {0} to {1} with caption {2} and silently {3}", "post_exception": "Could not send content due to {0}. Traceback: {1}", - "post_invalid_pic": "__TO_BE_ADDED__", + "post_invalid_pic": "Error while sending photo HTTP {0}: {1}", "post_empty": "Could not send content due to queue empty or contains only forbidden extensions", "sub_mime_not_allowed": "Got submission from {0} but type of {1} which is not allowed", "sub_document_too_large": "Got submission from {0} but but file is too large ({1} > {2})", diff --git a/locale/uk.json b/locale/uk.json index 4a138a3..507adf7 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -31,11 +31,11 @@ "document_too_large": "Надісланий файл завеликий. Будь ласка, надсилайте файли не більше {0} Мб", "mime_not_allowed": "Тип файлу не дозволений. Розгляньте можливість використання одного з цих: {0}", "post_exception": "Не вдалося надіслати контент через `{0}`\n\nTraceback:\n```{1}```", - "post_invalid_pic": "__TO_BE_ADDED__", + "post_invalid_pic": "⚠️ Помилка надсилання фото {0}\n```python\n{1}\n```", "api_queue_empty": "Не вдалося надіслати контент: `Черга порожня або містить лише непідтримувані файли`.", - "api_queue_error": "__TO_BE_ADDED__", + "api_queue_error": "Не вдалось отримати фото з черги API. Погляньте на логи вище а також на лог помилок API щоб дізнатись подробиці.", "post_low": "Мала кількість контенту: `Залишилось всього {0} файлів в черзі.`", - "api_creds_invalid": "__TO_BE_ADDED__", + "api_creds_invalid": "Не вдалося авторизувати запит до API. Будь ласка, перевірте чи дані авторизації в конфігураційному файлі вірні та оновіть їх, якщо це не так.", "sub_wip": "Подання постів зараз знаходиться у розробці. Він буде знову доступний через кілька днів. Дякуємо за ваше терпіння.", "sub_duplicates_found": "__TO_BE_ADDED__", "sub_error": "⚠️ Не вдалось завантажити фото через помилку бота. Адміністрацію повідомлено.", @@ -60,7 +60,7 @@ "sub_media_unavail": "Не вдалося завантажити подання", "sub_done": "Ви вже обрали що зробити з цим поданням", "sub_upload_failed": "__TO_BE_ADDED__", - "sub_duplicates_found": "__TO_BE_ADDED__", + "sub_duplicates_found": "Знайдено дублікати в базі даних бота", "nothing": "🏁 Цю дію вже було завершено" }, "console": { @@ -70,7 +70,7 @@ "exception_occured": "Помилка {0} сталась під час виконання", "post_sent": "Надіслано {0} у {1} з підписом {2} та без звуку {3}", "post_exception": "Не вдалося надіслати контент через {0}. Traceback: {1}", - "post_invalid_pic": "__TO_BE_ADDED__", + "post_invalid_pic": "Помилка надсилання фото HTTP {0}: {1}", "post_empty": "Не вдалося надіслати контент адже черга з дозволеними розширеннями порожня", "sub_mime_not_allowed": "Отримано подання від {0} але типу {1} який не є дозволеним", "sub_document_too_large": "Отримано подання від {0} але файл завеликий({1} > {2})", diff --git a/modules/sender.py b/modules/sender.py index c5194b6..ca5c104 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -39,9 +39,9 @@ async def send_content(app: PosterClient) -> None: response = get(f'{configGet("address", "posting", "api")}/photos/{pic[0]}', headers={"Authorization": f"Bearer {token}"}, stream=True) if response.status_code != 200: - logWrite(locale("post_invalid_pic", "console", locale=configGet("locale")).format(str(response.json()))) + logWrite(locale("post_invalid_pic", "console", locale=configGet("locale")).format(response.status_code, str(response.json()))) if configGet("error", "reports"): - await app.send_message(app.owner, locale("post_invalid_pic", "message", locale=configGet("locale")).format(response.json())) + await app.send_message(app.owner, locale("post_invalid_pic", "message", locale=configGet("locale")).format(response.status_code, response.json())) tmp_dir = str(uuid4()) From a913ba19c60008b67bb88927b247e894fc70da90 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 2 Mar 2023 16:39:59 +0100 Subject: [PATCH 40/98] WIP: aiohttp migration --- modules/api_client.py | 62 +++++++++++++++++++++++++------------------ modules/sender.py | 13 +++++---- poster.py | 1 - requirements.txt | 4 +-- 4 files changed, 44 insertions(+), 36 deletions(-) diff --git a/modules/api_client.py b/modules/api_client.py index 25e0a6d..e8fba39 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -1,24 +1,33 @@ """This is only a temporary solution. Complete Photos API client is yet to be developed.""" +try: + from ujson import dumps +except ModuleNotFoundError: + from json import dumps + import asyncio from base64 import b64decode, b64encode +from ujson import dumps from os import makedirs, path, sep from random import choice from typing import Tuple, Union +from aiohttp import ClientSession -from requests import get, patch, post from classes.exceptions import SubmissionUploadError from modules.logger import logWrite from modules.utils import configGet +http_session = ClientSession(json_serialize=dumps, ) + + async def authorize() -> str: makedirs(configGet("cache", "locations"), exist_ok=True) if path.exists(configGet("cache", "locations")+sep+"api_access") is True: with open(configGet("cache", "locations")+sep+"api_access", "rb") as file: token = b64decode(file.read()).decode("utf-8") - if get(configGet("address", "posting", "api")+"/users/me/", headers={"Authorization": f"Bearer {token}"}).status_code == 200: + if (await http_session.get(configGet("address", "posting", "api")+"/users/me/", headers={"Authorization": f"Bearer {token}"})).status == 200: return token payload = { "grant_type": "password", @@ -26,13 +35,13 @@ async def authorize() -> str: "username": configGet("username", "posting", "api"), "password": configGet("password", "posting", "api") } - response = post(configGet("address", "posting", "api")+"/token", data=payload) - if response.status_code != 200: - logWrite(f'Incorrect API credentials! Could not login into "{configGet("address", "posting", "api")}" using login "{configGet("username", "posting", "api")}": HTTP {response.status_code}') + response = await http_session.post(configGet("address", "posting", "api")+"/token", data=payload) + if response.status != 200: + logWrite(f'Incorrect API credentials! Could not login into "{configGet("address", "posting", "api")}" using login "{configGet("username", "posting", "api")}": HTTP {response.status}') raise ValueError with open(configGet("cache", "locations")+sep+"api_access", "wb") as file: - file.write(b64encode(response.json()["access_token"].encode("utf-8"))) - return response.json()["access_token"] + file.write(b64encode((await response.json())["access_token"].encode("utf-8"))) + return (await response.json())["access_token"] async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: """Returns random image id and filename from the queue. @@ -42,14 +51,15 @@ async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: """ token = await authorize() if token is None else token logWrite(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue') - resp = get(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue', headers={"Authorization": f"Bearer {token}"}) - if resp.status_code != 200: - logWrite(f'Could not get photos from album {configGet("album", "posting", "api")}: HTTP {resp.status_code}') - logWrite(f'Could not get photos from "{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue" using token "{token}": HTTP {resp.status_code}', debug=True) + resp = await http_session.get(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue', headers={"Authorization": f"Bearer {token}"}) + print(await resp.json(), flush=True) + if resp.status != 200: + logWrite(f'Could not get photos from album {configGet("album", "posting", "api")}: HTTP {resp.status}') + logWrite(f'Could not get photos from "{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue" using token "{token}": HTTP {resp.status}', debug=True) raise ValueError - if len(resp.json()["results"]) == 0: + if len((await resp.json())["results"]) == 0: raise KeyError - pic = choice(resp.json()["results"]) + pic = choice((await resp.json())["results"]) return pic["id"], pic["filename"] async def upload_pic(filepath: str, allow_duplicates: bool = False, token: Union[str, None] = None) -> Tuple[bool, list]: @@ -57,17 +67,17 @@ async def upload_pic(filepath: str, allow_duplicates: bool = False, token: Union 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, "ignore_duplicates": allow_duplicates}, 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}'") - raise SubmissionUploadError(str(filepath), response.status_code, response.content) + response = await http_session.post(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos', params={"caption": "queue", "compress": False, "ignore_duplicates": allow_duplicates}, headers={"Authorization": f"Bearer {token}"}, files=files) + if response.status != 200 and response.status != 409: + logWrite(f"Could not upload '{filepath}' to API: HTTP {response.status} with message '{response.content}'") + raise SubmissionUploadError(str(filepath), response.status, response.content) duplicates = [] - if "duplicates" in response.json(): - for index, duplicate in enumerate(response.json()["duplicates"]): - if response.json()["access_token"] is None: + if "duplicates" in (await response.json()): + for index, duplicate in enumerate((await response.json())["duplicates"]): + if (await response.json())["access_token"] is None: duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}') else: - duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{response.json()["access_token"]}?id={index}') + duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{(await response.json())["access_token"]}?id={index}') return True, duplicates except: return False, [] @@ -75,13 +85,13 @@ async def upload_pic(filepath: str, allow_duplicates: bool = False, token: Union 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}"}) + response = await http_session.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: + if response.status != 200: return None - if len(response.json()["results"]) == 0: + if len((await response.json())["results"]) == 0: return None - return response.json()["results"] + return (await response.json())["results"] except Exception as exp: logWrite(f"Could not find image with name '{name}' and caption '{caption}' due to: {exp}") return None @@ -89,7 +99,7 @@ async def find_pic(name: str, caption: Union[str, None] = None, token: Union[str 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}"}) + await http_session.patch(f'{configGet("address", "posting", "api")}/photos/{id}?caption=sent', headers={"Authorization": f"Bearer {token}"}) return True except: return False diff --git a/modules/sender.py b/modules/sender.py index ca5c104..bd6b425 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -6,9 +6,8 @@ from uuid import uuid4 from PIL import Image from classes.poster_client import PosterClient -from requests import get -from modules.api_client import authorize, move_pic, random_pic +from modules.api_client import authorize, move_pic, random_pic, http_session from modules.database import col_sent, col_submitted from modules.logger import logWrite from modules.utils import configGet, locale @@ -36,12 +35,12 @@ async def send_content(app: PosterClient) -> None: await app.send_message(app.owner, locale("api_queue_error", "message", locale=configGet("locale"))) return - response = get(f'{configGet("address", "posting", "api")}/photos/{pic[0]}', headers={"Authorization": f"Bearer {token}"}, stream=True) + response = await http_session.get(f'{configGet("address", "posting", "api")}/photos/{pic[0]}', headers={"Authorization": f"Bearer {token}"}, stream=True) - if response.status_code != 200: - logWrite(locale("post_invalid_pic", "console", locale=configGet("locale")).format(response.status_code, str(response.json()))) + if response.status != 200: + logWrite(locale("post_invalid_pic", "console", locale=configGet("locale")).format(response.status, str(response.json()))) if configGet("error", "reports"): - await app.send_message(app.owner, locale("post_invalid_pic", "message", locale=configGet("locale")).format(response.status_code, response.json())) + await app.send_message(app.owner, locale("post_invalid_pic", "message", locale=configGet("locale")).format(response.status, response.json())) tmp_dir = str(uuid4()) @@ -50,7 +49,7 @@ async def send_content(app: PosterClient) -> None: tmp_path = path.join(tmp_dir, pic[1]) with open(path.join(configGet("tmp", "locations"), tmp_path), 'wb') as out_file: - copyfileobj(response.raw, out_file) + out_file.write(await response.read()) logWrite(f'Candidate {pic[1]} ({pic[0]}) is {path.getsize(path.join(configGet("tmp", "locations"), tmp_path))} bytes big', debug=True) diff --git a/poster.py b/poster.py index faab802..992c440 100644 --- a/poster.py +++ b/poster.py @@ -70,7 +70,6 @@ if args.norun: try: from modules.app import app from pyrogram.sync import idle - from requests import get, post except ModuleNotFoundError: print(locale("deps_missing", "console", locale=configGet("locale")), flush=True) exit() diff --git a/requirements.txt b/requirements.txt index 7607775..3bab7d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ apscheduler~=3.10.0 -pyrogram~=2.0.99 -requests~=2.28.2 +pyrogram~=2.0.100 +aiohttp~=3.8.4 psutil~=5.9.4 pymongo~=4.3.3 pillow~=9.4.0 From 4331af415e68e9b58169a2c4fafc6b77b4a3b0d9 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 2 Mar 2023 22:38:48 +0100 Subject: [PATCH 41/98] Moved to aiohttp and aiofiles --- modules/api_client.py | 30 +++++++++++++++++++++--------- modules/sender.py | 9 +++++---- requirements.txt | 1 + 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/modules/api_client.py b/modules/api_client.py index e8fba39..f5ac191 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -7,11 +7,13 @@ except ModuleNotFoundError: import asyncio from base64 import b64decode, b64encode +from traceback import print_exc +import aiofiles from ujson import dumps from os import makedirs, path, sep from random import choice from typing import Tuple, Union -from aiohttp import ClientSession +from aiohttp import ClientSession, FormData from classes.exceptions import SubmissionUploadError @@ -25,8 +27,8 @@ http_session = ClientSession(json_serialize=dumps, ) async def authorize() -> str: makedirs(configGet("cache", "locations"), exist_ok=True) if path.exists(configGet("cache", "locations")+sep+"api_access") is True: - with open(configGet("cache", "locations")+sep+"api_access", "rb") as file: - token = b64decode(file.read()).decode("utf-8") + async with aiofiles.open(configGet("cache", "locations")+sep+"api_access", "rb") as file: + token = b64decode(await file.read()).decode("utf-8") if (await http_session.get(configGet("address", "posting", "api")+"/users/me/", headers={"Authorization": f"Bearer {token}"})).status == 200: return token payload = { @@ -36,11 +38,11 @@ async def authorize() -> str: "password": configGet("password", "posting", "api") } response = await http_session.post(configGet("address", "posting", "api")+"/token", data=payload) - if response.status != 200: + if not response.ok: logWrite(f'Incorrect API credentials! Could not login into "{configGet("address", "posting", "api")}" using login "{configGet("username", "posting", "api")}": HTTP {response.status}') raise ValueError - with open(configGet("cache", "locations")+sep+"api_access", "wb") as file: - file.write(b64encode((await response.json())["access_token"].encode("utf-8"))) + async with aiofiles.open(configGet("cache", "locations")+sep+"api_access", "wb") as file: + await file.write(b64encode((await response.json())["access_token"].encode("utf-8"))) return (await response.json())["access_token"] async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: @@ -66,8 +68,17 @@ async def upload_pic(filepath: str, allow_duplicates: bool = False, token: Union 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 = await http_session.post(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos', params={"caption": "queue", "compress": False, "ignore_duplicates": allow_duplicates}, headers={"Authorization": f"Bearer {token}"}, files=files) + logWrite(f"Uploading {pic_name} to the API...", debug=True) + async with aiofiles.open(filepath, "rb") as f: + file_bytes = await f.read() + formdata = FormData() + formdata.add_field('file', file_bytes, filename=pic_name, content_type='image/jpeg') + response = await http_session.post( + f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos', + params={"caption": "queue", "compress": "false", "ignore_duplicates": str(allow_duplicates).lower()}, + headers={"Authorization": f"Bearer {token}"}, + data=formdata + ) if response.status != 200 and response.status != 409: logWrite(f"Could not upload '{filepath}' to API: HTTP {response.status} with message '{response.content}'") raise SubmissionUploadError(str(filepath), response.status, response.content) @@ -79,7 +90,8 @@ async def upload_pic(filepath: str, allow_duplicates: bool = False, token: Union else: duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{(await response.json())["access_token"]}?id={index}') return True, duplicates - except: + except Exception as exp: + print_exc() return False, [] async def find_pic(name: str, caption: Union[str, None] = None, token: Union[str, None] = None) -> Union[dict, None]: diff --git a/modules/sender.py b/modules/sender.py index bd6b425..16136c4 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -1,9 +1,10 @@ from datetime import datetime from os import makedirs, path -from shutil import copyfileobj, rmtree +from shutil import rmtree from traceback import format_exc from uuid import uuid4 from PIL import Image +import aiofiles from classes.poster_client import PosterClient @@ -35,7 +36,7 @@ async def send_content(app: PosterClient) -> None: await app.send_message(app.owner, locale("api_queue_error", "message", locale=configGet("locale"))) return - response = await http_session.get(f'{configGet("address", "posting", "api")}/photos/{pic[0]}', headers={"Authorization": f"Bearer {token}"}, stream=True) + response = await http_session.get(f'{configGet("address", "posting", "api")}/photos/{pic[0]}', headers={"Authorization": f"Bearer {token}"}) if response.status != 200: logWrite(locale("post_invalid_pic", "console", locale=configGet("locale")).format(response.status, str(response.json()))) @@ -48,8 +49,8 @@ async def send_content(app: PosterClient) -> None: tmp_path = path.join(tmp_dir, pic[1]) - with open(path.join(configGet("tmp", "locations"), tmp_path), 'wb') as out_file: - out_file.write(await response.read()) + async with aiofiles.open(path.join(configGet("tmp", "locations"), tmp_path), 'wb') as out_file: + await out_file.write(await response.read()) logWrite(f'Candidate {pic[1]} ({pic[0]}) is {path.getsize(path.join(configGet("tmp", "locations"), tmp_path))} bytes big', debug=True) diff --git a/requirements.txt b/requirements.txt index 3bab7d8..27f8e1a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ apscheduler~=3.10.0 pyrogram~=2.0.100 aiohttp~=3.8.4 +aiofiles~=23.1.0 psutil~=5.9.4 pymongo~=4.3.3 pillow~=9.4.0 From 88692ebc858d7c26e08e5cf588e4353dab3d61c2 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 9 Mar 2023 11:33:02 +0100 Subject: [PATCH 42/98] Now using black for formatting --- classes/enums/submission_types.py | 2 +- classes/exceptions.py | 10 +- classes/poster_client.py | 62 ++++++++--- classes/user.py | 32 ++++-- modules/api_client.py | 115 ++++++++++++++----- modules/app.py | 7 +- modules/commands_register.py | 39 +++++-- modules/database.py | 12 +- modules/logger.py | 33 ++++-- modules/scheduler.py | 18 ++- modules/sender.py | 127 ++++++++++++++++----- modules/utils.py | 41 ++++--- plugins/callbacks/nothing.py | 7 +- plugins/callbacks/submission.py | 164 ++++++++++++++++++++++----- plugins/commands/general.py | 15 ++- plugins/commands/mode_submit.py | 13 ++- plugins/commands/photos.py | 14 +-- plugins/handlers/submission.py | 178 +++++++++++++++++++++--------- poster.py | 62 +++++++---- 19 files changed, 701 insertions(+), 250 deletions(-) diff --git a/classes/enums/submission_types.py b/classes/enums/submission_types.py index 20494f9..265ce7a 100644 --- a/classes/enums/submission_types.py +++ b/classes/enums/submission_types.py @@ -5,4 +5,4 @@ class SubmissionType(Enum): DOCUMENT = "document" VIDEO = "video" ANIMATION = "animation" - PHOTO = "photo" \ No newline at end of file + PHOTO = "photo" diff --git a/classes/exceptions.py b/classes/exceptions.py index 7c86e01..4a0a0ad 100644 --- a/classes/exceptions.py +++ b/classes/exceptions.py @@ -4,13 +4,19 @@ 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}") + 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}") \ No newline at end of file + super().__init__( + f"Found duplicates of a photo '{file_path}': {self.duplicates}" + ) diff --git a/classes/poster_client.py b/classes/poster_client.py index 9c1c876..642bede 100644 --- a/classes/poster_client.py +++ b/classes/poster_client.py @@ -11,15 +11,14 @@ from modules.logger import logWrite from modules.utils import configGet -class PosterClient(Client): - def __init__(self, name: str, **kwargs): # type: ignore +class PosterClient(Client): + def __init__(self, name: str, **kwargs): # type: ignore super().__init__(name, **kwargs) self.owner = configGet("owner") - self.admins = configGet("admins")+[configGet("owner")] + self.admins = configGet("admins") + [configGet("owner")] async def submit_photo(self, id: str) -> Union[Message, None]: - db_entry = col_submitted.find_one({"_id": ObjectId(id)}) submission = None @@ -27,35 +26,66 @@ class PosterClient(Client): 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"])): + 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"]) + filepath = path.join( + configGet("data", "locations"), + "submissions", + db_entry["temp"]["uuid"], + db_entry["temp"]["file"], + ) try: - submission = await self.get_messages(db_entry["user"], db_entry["telegram"]["msg_id"]) + submission = await self.get_messages( + db_entry["user"], db_entry["telegram"]["msg_id"] + ) except: pass 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) + 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), allow_duplicates=configGet("allow_duplicates", "submission")) + + response = await upload_pic( + str(filepath), allow_duplicates=configGet("allow_duplicates", "submission") + ) if len(response[1]) > 0: raise SubmissionDuplicatesError(str(filepath), response[1]) - - col_submitted.find_one_and_update({"_id": ObjectId(id)}, {"$set": {"done": True}}) + + 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) + 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) + logWrite( + f"Could not delete '{filepath}' on submission accepted", debug=True + ) return submission @@ -63,4 +93,4 @@ class PosterClient(Client): pass async def unban_user(self, id: int) -> None: - pass \ No newline at end of file + pass diff --git a/classes/user.py b/classes/user.py index ba00e17..8042908 100644 --- a/classes/user.py +++ b/classes/user.py @@ -3,8 +3,8 @@ from datetime import datetime from modules.database import col_banned, col_users from modules.utils import configGet -class PosterUser(): +class PosterUser: def __init__(self, id: int): self.id = id @@ -13,16 +13,16 @@ class PosterUser(): ### Returns: `bool`: Must be `True` if banned and `False` if not - """ + """ return False if col_banned.find_one({"user": self.id}) is None else True - + def block(self) -> None: - """Ban user from using command and submitting content.""" + """Ban user from using command and submitting content.""" if col_banned.find_one({"user": self.id}) is None: col_banned.insert_one({"user": self.id, "date": datetime.now()}) def unblock(self) -> None: - """Allow user to use command and submit posts again.""" + """Allow user to use command and submit posts again.""" col_banned.find_one_and_delete({"user": self.id}) def is_limited(self) -> bool: @@ -30,16 +30,26 @@ class PosterUser(): ### Returns: `bool`: Must be `True` if on the cooldown and `False` if not - """ + """ if self.id in app.admins: return False else: db_record = col_users.find_one({"user": self.id}) if db_record is None: return False - return True if (datetime.now() - db_record["cooldown"]).total_seconds() < configGet("timeout", "submission") else False - + return ( + True + if (datetime.now() - db_record["cooldown"]).total_seconds() + < configGet("timeout", "submission") + else False + ) + def limit(self) -> None: - """Restart user's cooldown. Used after post has been submitted.""" - if col_users.find_one_and_update({"user": self.id}, {"$set": {"cooldown": datetime.now()}}) is None: - col_users.insert_one({"user": self.id, "cooldown": datetime.now()}) \ No newline at end of file + """Restart user's cooldown. Used after post has been submitted.""" + if ( + col_users.find_one_and_update( + {"user": self.id}, {"$set": {"cooldown": datetime.now()}} + ) + is None + ): + col_users.insert_one({"user": self.id, "cooldown": datetime.now()}) diff --git a/modules/api_client.py b/modules/api_client.py index f5ac191..d364e7d 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -21,50 +21,81 @@ from modules.logger import logWrite from modules.utils import configGet -http_session = ClientSession(json_serialize=dumps, ) +http_session = ClientSession( + json_serialize=dumps, +) async def authorize() -> str: makedirs(configGet("cache", "locations"), exist_ok=True) - if path.exists(configGet("cache", "locations")+sep+"api_access") is True: - async with aiofiles.open(configGet("cache", "locations")+sep+"api_access", "rb") as file: + if path.exists(configGet("cache", "locations") + sep + "api_access") is True: + async with aiofiles.open( + configGet("cache", "locations") + sep + "api_access", "rb" + ) as file: token = b64decode(await file.read()).decode("utf-8") - if (await http_session.get(configGet("address", "posting", "api")+"/users/me/", headers={"Authorization": f"Bearer {token}"})).status == 200: + if ( + await http_session.get( + configGet("address", "posting", "api") + "/users/me/", + headers={"Authorization": f"Bearer {token}"}, + ) + ).status == 200: return token payload = { "grant_type": "password", "scope": "me albums.list albums.read albums.write photos.list photos.read photos.write videos.list videos.read videos.write", "username": configGet("username", "posting", "api"), - "password": configGet("password", "posting", "api") + "password": configGet("password", "posting", "api"), } - response = await http_session.post(configGet("address", "posting", "api")+"/token", data=payload) + response = await http_session.post( + configGet("address", "posting", "api") + "/token", data=payload + ) if not response.ok: - logWrite(f'Incorrect API credentials! Could not login into "{configGet("address", "posting", "api")}" using login "{configGet("username", "posting", "api")}": HTTP {response.status}') + logWrite( + f'Incorrect API credentials! Could not login into "{configGet("address", "posting", "api")}" using login "{configGet("username", "posting", "api")}": HTTP {response.status}' + ) raise ValueError - async with aiofiles.open(configGet("cache", "locations")+sep+"api_access", "wb") as file: - await file.write(b64encode((await response.json())["access_token"].encode("utf-8"))) + async with aiofiles.open( + configGet("cache", "locations") + sep + "api_access", "wb" + ) as file: + await file.write( + b64encode((await response.json())["access_token"].encode("utf-8")) + ) return (await response.json())["access_token"] + async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: """Returns random image id and filename from the queue. ### Returns: * `Tuple[str, str]`: First value is an ID and the filename in the filesystem to be indexed. - """ + """ token = await authorize() if token is None else token - logWrite(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue') - resp = await http_session.get(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue', headers={"Authorization": f"Bearer {token}"}) + logWrite( + f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue' + ) + resp = await http_session.get( + f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue', + headers={"Authorization": f"Bearer {token}"}, + ) print(await resp.json(), flush=True) if resp.status != 200: - logWrite(f'Could not get photos from album {configGet("album", "posting", "api")}: HTTP {resp.status}') - logWrite(f'Could not get photos from "{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue" using token "{token}": HTTP {resp.status}', debug=True) + logWrite( + f'Could not get photos from album {configGet("album", "posting", "api")}: HTTP {resp.status}' + ) + logWrite( + f'Could not get photos from "{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue" using token "{token}": HTTP {resp.status}', + debug=True, + ) raise ValueError if len((await resp.json())["results"]) == 0: raise KeyError pic = choice((await resp.json())["results"]) return pic["id"], pic["filename"] -async def upload_pic(filepath: str, allow_duplicates: bool = False, token: Union[str, None] = None) -> Tuple[bool, list]: + +async def upload_pic( + filepath: str, allow_duplicates: bool = False, token: Union[str, None] = None +) -> Tuple[bool, list]: token = await authorize() if token is None else token try: pic_name = path.basename(filepath) @@ -72,32 +103,53 @@ async def upload_pic(filepath: str, allow_duplicates: bool = False, token: Union async with aiofiles.open(filepath, "rb") as f: file_bytes = await f.read() formdata = FormData() - formdata.add_field('file', file_bytes, filename=pic_name, content_type='image/jpeg') + formdata.add_field( + "file", file_bytes, filename=pic_name, content_type="image/jpeg" + ) response = await http_session.post( f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos', - params={"caption": "queue", "compress": "false", "ignore_duplicates": str(allow_duplicates).lower()}, + params={ + "caption": "queue", + "compress": "false", + "ignore_duplicates": str(allow_duplicates).lower(), + }, headers={"Authorization": f"Bearer {token}"}, - data=formdata + data=formdata, ) if response.status != 200 and response.status != 409: - logWrite(f"Could not upload '{filepath}' to API: HTTP {response.status} with message '{response.content}'") - raise SubmissionUploadError(str(filepath), response.status, response.content) + logWrite( + f"Could not upload '{filepath}' to API: HTTP {response.status} with message '{response.content}'" + ) + raise SubmissionUploadError( + str(filepath), response.status, response.content + ) duplicates = [] if "duplicates" in (await response.json()): for index, duplicate in enumerate((await response.json())["duplicates"]): if (await response.json())["access_token"] is None: - duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}') + duplicates.append( + f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}' + ) else: - duplicates.append(f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{(await response.json())["access_token"]}?id={index}') + duplicates.append( + f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{(await response.json())["access_token"]}?id={index}' + ) return True, duplicates except Exception as exp: print_exc() return False, [] - -async def find_pic(name: str, caption: Union[str, None] = None, token: Union[str, None] = None) -> Union[dict, None]: + + +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 = await http_session.get(f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos', params={"q": name, "caption": caption}, headers={"Authorization": f"Bearer {token}"}) + response = await http_session.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 != 200: return None @@ -105,16 +157,23 @@ async def find_pic(name: str, caption: Union[str, None] = None, token: Union[str return None return (await response.json())["results"] except Exception as exp: - logWrite(f"Could not find image with name '{name}' and caption '{caption}' due to: {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: - await http_session.patch(f'{configGet("address", "posting", "api")}/photos/{id}?caption=sent', headers={"Authorization": f"Bearer {token}"}) + await http_session.patch( + f'{configGet("address", "posting", "api")}/photos/{id}?caption=sent', + headers={"Authorization": f"Bearer {token}"}, + ) return True except: return False + if __name__ == "__main__": - print(asyncio.run(authorize())) \ No newline at end of file + print(asyncio.run(authorize())) diff --git a/modules/app.py b/modules/app.py index 99139df..91b6ecd 100644 --- a/modules/app.py +++ b/modules/app.py @@ -1,4 +1,9 @@ from modules.utils import configGet from classes.poster_client import PosterClient -app = PosterClient("duptsiaposter", bot_token=configGet("bot_token", "bot"), api_id=configGet("api_id", "bot"), api_hash=configGet("api_hash", "bot")) \ No newline at end of file +app = PosterClient( + "duptsiaposter", + bot_token=configGet("bot_token", "bot"), + api_id=configGet("api_id", "bot"), + api_hash=configGet("api_hash", "bot"), +) diff --git a/modules/commands_register.py b/modules/commands_register.py index 0b736fc..de1bcbe 100644 --- a/modules/commands_register.py +++ b/modules/commands_register.py @@ -3,21 +3,35 @@ from classes.poster_client import PosterClient from pyrogram.types import BotCommand, BotCommandScopeChat from modules.utils import configGet, locale -async def register_commands(app: PosterClient) -> None: +async def register_commands(app: PosterClient) -> None: if configGet("submit", "mode"): # Registering user commands for entry in listdir(configGet("locale", "locations")): if entry.endswith(".json"): commands_list = [] for command in configGet("commands"): - commands_list.append(BotCommand(command, locale(command, "commands", locale=entry.replace(".json", "")))) - await app.set_bot_commands(commands_list, language_code=entry.replace(".json", "")) + commands_list.append( + BotCommand( + command, + locale( + command, "commands", locale=entry.replace(".json", "") + ), + ) + ) + await app.set_bot_commands( + commands_list, language_code=entry.replace(".json", "") + ) # Registering user commands for fallback locale commands_list = [] for command in configGet("commands"): - commands_list.append(BotCommand(command, locale(command, "commands", locale=configGet("locale_fallback")))) + commands_list.append( + BotCommand( + command, + locale(command, "commands", locale=configGet("locale_fallback")), + ) + ) await app.set_bot_commands(commands_list) # Registering admin commands @@ -25,10 +39,19 @@ async def register_commands(app: PosterClient) -> None: if configGet("submit", "mode"): for command in configGet("commands"): - - commands_admin_list.append(BotCommand(command, locale(command, "commands", locale=configGet("locale")))) + commands_admin_list.append( + BotCommand( + command, locale(command, "commands", locale=configGet("locale")) + ) + ) for command in configGet("commands_admin"): - commands_admin_list.append(BotCommand(command, locale(command, "commands_admin", locale=configGet("locale")))) + commands_admin_list.append( + BotCommand( + command, locale(command, "commands_admin", locale=configGet("locale")) + ) + ) for admin in app.admins: - await app.set_bot_commands(commands_admin_list, scope=BotCommandScopeChat(chat_id=admin)) \ No newline at end of file + await app.set_bot_commands( + commands_admin_list, scope=BotCommandScopeChat(chat_id=admin) + ) diff --git a/modules/database.py b/modules/database.py index 97e5dc1..dbfa7df 100644 --- a/modules/database.py +++ b/modules/database.py @@ -8,18 +8,16 @@ with open("config.json", "r", encoding="utf-8") as f: f.close() if db_config["user"] is not None and db_config["password"] is not None: - con_string = 'mongodb://{0}:{1}@{2}:{3}/{4}'.format( + con_string = "mongodb://{0}:{1}@{2}:{3}/{4}".format( db_config["user"], db_config["password"], db_config["host"], db_config["port"], - db_config["name"] + db_config["name"], ) else: - con_string = 'mongodb://{0}:{1}/{2}'.format( - db_config["host"], - db_config["port"], - db_config["name"] + con_string = "mongodb://{0}:{1}/{2}".format( + db_config["host"], db_config["port"], db_config["name"] ) db_client = MongoClient(con_string) @@ -34,4 +32,4 @@ for collection in ["sent", "users", "banned", "submitted"]: col_sent = db.get_collection("sent") col_users = db.get_collection("users") col_banned = db.get_collection("banned") -col_submitted = db.get_collection("submitted") \ No newline at end of file +col_submitted = db.get_collection("submitted") diff --git a/modules/logger.py b/modules/logger.py index 37cae1c..a69104d 100644 --- a/modules/logger.py +++ b/modules/logger.py @@ -9,15 +9,15 @@ from shutil import copyfileobj from datetime import datetime -with open(getcwd()+path.sep+"config.json", "r", encoding='utf8') as file: +with open(getcwd() + path.sep + "config.json", "r", encoding="utf8") as file: json_contents = loads(file.read()) log_size = json_contents["logging"]["size"] log_folder = json_contents["logging"]["location"] file.close() + # Check latest log size def checkSize(debug=False) -> None: - global log_folder if debug: @@ -29,18 +29,26 @@ def checkSize(debug=False) -> None: makedirs(log_folder, exist_ok=True) log = stat(path.join(log_folder, log_file)) if (log.st_size / 1024) > log_size: - with open(path.join(log_folder, log_file), 'rb') as f_in: - with gzipopen(path.join(log_folder, f'{datetime.now().strftime("%d.%m.%Y_%H:%M:%S")}.log.gz'), 'wb') as f_out: + with open(path.join(log_folder, log_file), "rb") as f_in: + with gzipopen( + path.join( + log_folder, + f'{datetime.now().strftime("%d.%m.%Y_%H:%M:%S")}.log.gz', + ), + "wb", + ) as f_out: copyfileobj(f_in, f_out) - print(f'Copied {path.join(log_folder, datetime.now().strftime("%d.%m.%Y_%H:%M:%S"))}.log.gz') - open(path.join(log_folder, log_file), 'w').close() + print( + f'Copied {path.join(log_folder, datetime.now().strftime("%d.%m.%Y_%H:%M:%S"))}.log.gz' + ) + open(path.join(log_folder, log_file), "w").close() except FileNotFoundError: - print(f'Log file {path.join(log_folder, log_file)} does not exist') + print(f"Log file {path.join(log_folder, log_file)} does not exist") pass + # Append string to log def logAppend(message, debug=False) -> None: - global log_folder message_formatted = f'[{datetime.now().strftime("%d.%m.%Y")}] [{datetime.now().strftime("%H:%M:%S")}] {message}' @@ -51,12 +59,13 @@ def logAppend(message, debug=False) -> None: else: log_file = "latest.log" - log = open(path.join(log_folder, log_file), 'a') - log.write(f'{message_formatted}\n') + log = open(path.join(log_folder, log_file), "a") + log.write(f"{message_formatted}\n") log.close() + # Print to stdout and then to log def logWrite(message, debug=False) -> None: # save to log file and rotation is to be done - logAppend(f'{message}', debug=debug) - print(f"{message}", flush=True) \ No newline at end of file + logAppend(f"{message}", debug=debug) + print(f"{message}", flush=True) diff --git a/modules/scheduler.py b/modules/scheduler.py index 1289608..ba8fe06 100644 --- a/modules/scheduler.py +++ b/modules/scheduler.py @@ -10,10 +10,22 @@ scheduler = AsyncIOScheduler() if configGet("post", "mode"): if configGet("use_interval", "posting"): - scheduler.add_job(send_content, "interval", seconds=timeparse(configGet("interval", "posting")), args=[app]) + scheduler.add_job( + send_content, + "interval", + seconds=timeparse(configGet("interval", "posting")), + args=[app], + ) else: for entry in configGet("time", "posting"): dt_obj = datetime.strptime(entry, "%H:%M") - scheduler.add_job(send_content, "cron", hour=dt_obj.hour, minute=dt_obj.minute, args=[app]) + scheduler.add_job( + send_content, "cron", hour=dt_obj.hour, minute=dt_obj.minute, args=[app] + ) -scheduler.add_job(register_commands, "date", run_date=datetime.now()+timedelta(seconds=10), args=[app]) \ No newline at end of file +scheduler.add_job( + register_commands, + "date", + run_date=datetime.now() + timedelta(seconds=10), + args=[app], +) diff --git a/modules/sender.py b/modules/sender.py index 16136c4..e57dab6 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -15,13 +15,14 @@ from modules.utils import configGet, locale async def send_content(app: PosterClient) -> None: - try: - try: token = await authorize() except ValueError: - await app.send_message(app.owner, locale("api_creds_invalid", "message", locale=configGet("locale"))) + await app.send_message( + app.owner, + locale("api_creds_invalid", "message", locale=configGet("locale")), + ) return try: @@ -29,19 +30,37 @@ async def send_content(app: PosterClient) -> None: except KeyError: logWrite(locale("post_empty", "console", locale=configGet("locale"))) if configGet("error", "reports"): - await app.send_message(app.owner, locale("api_queue_empty", "message", locale=configGet("locale"))) + await app.send_message( + app.owner, + locale("api_queue_empty", "message", locale=configGet("locale")), + ) return except ValueError: if configGet("error", "reports"): - await app.send_message(app.owner, locale("api_queue_error", "message", locale=configGet("locale"))) + await app.send_message( + app.owner, + locale("api_queue_error", "message", locale=configGet("locale")), + ) return - - response = await http_session.get(f'{configGet("address", "posting", "api")}/photos/{pic[0]}', headers={"Authorization": f"Bearer {token}"}) + + response = await http_session.get( + f'{configGet("address", "posting", "api")}/photos/{pic[0]}', + headers={"Authorization": f"Bearer {token}"}, + ) if response.status != 200: - logWrite(locale("post_invalid_pic", "console", locale=configGet("locale")).format(response.status, str(response.json()))) + logWrite( + locale( + "post_invalid_pic", "console", locale=configGet("locale") + ).format(response.status, str(response.json())) + ) if configGet("error", "reports"): - await app.send_message(app.owner, locale("post_invalid_pic", "message", locale=configGet("locale")).format(response.status, response.json())) + await app.send_message( + app.owner, + locale( + "post_invalid_pic", "message", locale=configGet("locale") + ).format(response.status, response.json()), + ) tmp_dir = str(uuid4()) @@ -49,23 +68,40 @@ async def send_content(app: PosterClient) -> None: tmp_path = path.join(tmp_dir, pic[1]) - async with aiofiles.open(path.join(configGet("tmp", "locations"), tmp_path), 'wb') as out_file: + async with aiofiles.open( + path.join(configGet("tmp", "locations"), tmp_path), "wb" + ) as out_file: await out_file.write(await response.read()) - logWrite(f'Candidate {pic[1]} ({pic[0]}) is {path.getsize(path.join(configGet("tmp", "locations"), tmp_path))} bytes big', debug=True) + 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) + 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) + 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.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) + rmtree( + path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True + ) raise BytesWarning del response @@ -77,10 +113,17 @@ async def send_content(app: PosterClient) -> None: else: caption = "" - if submitted is not None and configGet("enabled", "posting", "submitted_caption") and ( - (submitted["user"] not in app.admins) or (configGet("ignore_admins", "posting", "submitted_caption") is False) + if ( + submitted is not None + and configGet("enabled", "posting", "submitted_caption") + and ( + (submitted["user"] not in app.admins) + or (configGet("ignore_admins", "posting", "submitted_caption") is False) + ) ): - caption = f"{caption}\n\n{configGet('text', 'posting', 'submitted_caption')}\n" + caption = ( + f"{caption}\n\n{configGet('text', 'posting', 'submitted_caption')}\n" + ) else: caption = f"{caption}\n\n" @@ -93,11 +136,21 @@ async def send_content(app: PosterClient) -> None: caption = caption try: - sent = await app.send_photo(configGet("channel", "posting"), path.join(configGet("tmp", "locations"), tmp_path), caption=caption, disable_notification=configGet("silent", "posting")) + sent = await app.send_photo( + configGet("channel", "posting"), + path.join(configGet("tmp", "locations"), tmp_path), + caption=caption, + disable_notification=configGet("silent", "posting"), + ) except Exception as exp: logWrite(f"Could not send image {pic[1]} ({pic[0]}) due to {exp}") if configGet("error", "reports"): - await app.send_message(app.owner, locale("post_exception", "message", locale=configGet("locale")).format(exp, format_exc())) + await app.send_message( + app.owner, + locale( + "post_exception", "message", locale=configGet("locale") + ).format(exp, format_exc()), + ) # rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) return @@ -107,7 +160,9 @@ async def send_content(app: PosterClient) -> None: "image": pic[0], "filename": pic[1], "channel": configGet("channel", "posting"), - "caption": None if (submitted is None or submitted["caption"] is None) else submitted["caption"].strip() + "caption": None + if (submitted is None or submitted["caption"] is None) + else submitted["caption"].strip(), } ) @@ -115,13 +170,31 @@ async def send_content(app: PosterClient) -> None: rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) - logWrite(locale("post_sent", "console", locale=configGet("locale")).format(pic[0], str(configGet("channel", "posting")), caption.replace("\n", "%n"), str(configGet("silent", "posting")))) + logWrite( + locale("post_sent", "console", locale=configGet("locale")).format( + pic[0], + str(configGet("channel", "posting")), + caption.replace("\n", "%n"), + str(configGet("silent", "posting")), + ) + ) except Exception as exp: - logWrite(locale("post_exception", "console", locale=configGet("locale")).format(str(exp), format_exc())) + logWrite( + locale("post_exception", "console", locale=configGet("locale")).format( + str(exp), format_exc() + ) + ) if configGet("error", "reports"): - await app.send_message(app.owner, locale("post_exception", "message", locale=configGet("locale")).format(exp, format_exc())) + await app.send_message( + app.owner, + locale("post_exception", "message", locale=configGet("locale")).format( + exp, format_exc() + ), + ) try: - rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) + rmtree( + path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True + ) except: - pass \ No newline at end of file + pass diff --git a/modules/utils.py b/modules/utils.py index 92253ab..0f7f809 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -13,24 +13,30 @@ from typing import Any from modules.logger import logWrite + def jsonLoad(filename: str) -> Any: """Loads arg1 as json and returns its contents""" - with open(filename, "r", encoding='utf8') as file: + with open(filename, "r", encoding="utf8") as file: try: output = loads(file.read()) except JSONDecodeError: - logWrite(f"Could not load json file {filename}: file seems to be incorrect!\n{print_exc()}") + logWrite( + f"Could not load json file {filename}: file seems to be incorrect!\n{print_exc()}" + ) raise except FileNotFoundError: - logWrite(f"Could not load json file {filename}: file does not seem to exist!\n{print_exc()}") + logWrite( + f"Could not load json file {filename}: file does not seem to exist!\n{print_exc()}" + ) raise file.close() return output + def jsonSave(contents: Any, filename: str) -> None: """Dumps dict/list arg1 to file arg2""" try: - with open(filename, "w", encoding='utf8') as file: + with open(filename, "w", encoding="utf8") as file: file.write(dumps(contents, ensure_ascii=False, indent=4)) file.close() except Exception as exp: @@ -44,7 +50,7 @@ def configSet(key: str, value, *args: str): * key (str): The last key of the keys path. * value (str/int/float/list/dict/None): Some needed value. * *args (str): Path to key like: dict[args][key]. - """ + """ this_dict = jsonLoad("config.json") string = "this_dict" for arg in args: @@ -57,6 +63,7 @@ def configSet(key: str, value, *args: str): jsonSave(this_dict, "config.json") return + def configGet(key: str, *args: str): """Get value of the config key Args: @@ -64,13 +71,14 @@ def configGet(key: str, *args: str): * *args (str): Path to key like: dict[args][key]. Returns: * any: Value of provided key - """ + """ this_dict = jsonLoad("config.json") this_key = this_dict for dict_key in args: this_key = this_key[dict_key] return this_key[key] + def locale(key: str, *args: str, locale=configGet("locale")): """Get value of locale string Args: @@ -79,36 +87,42 @@ def locale(key: str, *args: str, locale=configGet("locale")): * locale (str): Locale to looked up in. Defaults to config's locale value. Returns: * any: Value of provided locale key - """ - if (locale == None): + """ + if locale == None: locale = configGet("locale") - + try: this_dict = jsonLoad(f'{configGet("locale", "locations")}{sep}{locale}.json') except FileNotFoundError: try: - this_dict = jsonLoad(f'{configGet("locale", "locations")}{sep}{configGet("locale")}.json') + this_dict = jsonLoad( + f'{configGet("locale", "locations")}{sep}{configGet("locale")}.json' + ) except FileNotFoundError: try: - this_dict = jsonLoad(f'{configGet("locale_fallback", "locations")}{sep}{configGet("locale")}.json') + this_dict = jsonLoad( + f'{configGet("locale_fallback", "locations")}{sep}{configGet("locale")}.json' + ) except: return f'⚠️ Locale in config is invalid: could not get "{key}" in {str(args)} from locale "{locale}"' this_key = this_dict for dict_key in args: this_key = this_key[dict_key] - + try: return this_key[key] except KeyError: return f'⚠️ Locale in config is invalid: could not get "{key}" in {str(args)} from locale "{locale}"' + try: from psutil import Process except ModuleNotFoundError: print(locale("deps_missing", "console", locale=configGet("locale")), flush=True) exit() + def killProc(pid: int) -> None: """Kill process by its PID. Meant to be used to kill the main process of bot itself. @@ -117,6 +131,7 @@ def killProc(pid: int) -> None: """ if osname == "posix": from signal import SIGKILL + kill(pid, SIGKILL) else: - Process(pid).kill() \ No newline at end of file + Process(pid).kill() diff --git a/plugins/callbacks/nothing.py b/plugins/callbacks/nothing.py index e771c8f..a2ccaa0 100644 --- a/plugins/callbacks/nothing.py +++ b/plugins/callbacks/nothing.py @@ -4,8 +4,9 @@ from pyrogram.types import CallbackQuery 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: PosterClient, clb: CallbackQuery): - await clb.answer(text=locale("nothing", "callback", locale=clb.from_user.language_code)) -# ============================================================================================================================== \ No newline at end of file + await clb.answer( + text=locale("nothing", "callback", locale=clb.from_user.language_code) + ) diff --git a/plugins/callbacks/submission.py b/plugins/callbacks/submission.py index e8420bb..0f902c8 100644 --- a/plugins/callbacks/submission.py +++ b/plugins/callbacks/submission.py @@ -1,4 +1,3 @@ - from os import path from shutil import rmtree from pyrogram import filters @@ -15,7 +14,6 @@ from bson import ObjectId @app.on_callback_query(filters.regex("sub_yes_[\s\S]*")) async def callback_query_yes(app: PosterClient, clb: CallbackQuery): - fullclb = str(clb.data).split("_") user_locale = clb.from_user.language_code @@ -24,22 +22,60 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): try: submission = await app.submit_photo(fullclb[2]) except SubmissionUnavailableError: - await clb.answer(text=locale("sub_msg_unavail", "callback", locale=user_locale), show_alert=True) + await clb.answer( + text=locale("sub_msg_unavail", "callback", locale=user_locale), + show_alert=True, + ) return 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(exp.duplicates)), quote=True) + 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(exp.duplicates) + ), + quote=True, + ) return if submission is not None: - await submission.reply_text(locale("sub_yes", "message", locale=submission.from_user.language_code), quote=True) + await submission.reply_text( + locale("sub_yes", "message", locale=submission.from_user.language_code), + quote=True, + ) 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) + await clb.answer( + text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]), + show_alert=True, + ) - edited_markup = [[InlineKeyboardButton(text=str(locale("accepted", "button", locale=user_locale)), 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", locale=user_locale)), callback_data="nothing")]] - await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) + edited_markup = ( + [ + [ + InlineKeyboardButton( + text=str(locale("accepted", "button", locale=user_locale)), + 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", locale=user_locale)), + callback_data="nothing", + ) + ] + ] + ) + await clb.message.edit_reply_markup( + reply_markup=InlineKeyboardMarkup(edited_markup) + ) # try: # if configGet("api_based", "mode") is True: @@ -68,48 +104,120 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): @app.on_callback_query(filters.regex("sub_no_[\s\S]*")) async def callback_query_no(app: PosterClient, clb: CallbackQuery): - fullclb = str(clb.data).split("_") user_locale = clb.from_user.language_code db_entry = col_submitted.find_one_and_delete({"_id": ObjectId(fullclb[2])}) if db_entry["temp"]["uuid"] is not None: - if path.exists(path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"])): - rmtree(path.join(configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"]), ignore_errors=True) + if path.exists( + path.join( + configGet("data", "locations"), "submissions", db_entry["temp"]["uuid"] + ) + ): + rmtree( + path.join( + configGet("data", "locations"), + "submissions", + db_entry["temp"]["uuid"], + ), + ignore_errors=True, + ) try: - submission = await app.get_messages(db_entry["user"], db_entry["telegram"]["msg_id"]) + submission = await app.get_messages( + db_entry["user"], db_entry["telegram"]["msg_id"] + ) except: - await clb.answer(text=locale("sub_msg_unavail", "message", locale=user_locale), show_alert=True) + await clb.answer( + text=locale("sub_msg_unavail", "message", locale=user_locale), + show_alert=True, + ) return - - await submission.reply_text(locale("sub_no", "message", locale=submission.from_user.language_code), quote=True) - await clb.answer(text=locale("sub_no", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) - edited_markup = [[InlineKeyboardButton(text=str(locale("declined", "button", locale=user_locale)), callback_data="nothing")], clb.message.reply_markup.inline_keyboard[1]] if len(clb.message.reply_markup.inline_keyboard) > 1 else [[InlineKeyboardButton(text=str(locale("declined", "button", locale=user_locale)), callback_data="nothing")]] - await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) + await submission.reply_text( + locale("sub_no", "message", locale=submission.from_user.language_code), + quote=True, + ) + await clb.answer( + text=locale("sub_no", "callback", locale=user_locale).format(fullclb[2]), + show_alert=True, + ) + + edited_markup = ( + [ + [ + InlineKeyboardButton( + text=str(locale("declined", "button", locale=user_locale)), + callback_data="nothing", + ) + ], + clb.message.reply_markup.inline_keyboard[1], + ] + if len(clb.message.reply_markup.inline_keyboard) > 1 + else [ + [ + InlineKeyboardButton( + text=str(locale("declined", "button", locale=user_locale)), + callback_data="nothing", + ) + ] + ] + ) + await clb.message.edit_reply_markup( + reply_markup=InlineKeyboardMarkup(edited_markup) + ) @app.on_callback_query(filters.regex("sub_block_[\s\S]*")) async def callback_query_block(app: PosterClient, clb: CallbackQuery): fullclb = str(clb.data).split("_") user_locale = clb.from_user.language_code - await app.send_message(int(fullclb[2]), locale("sub_blocked", "message", locale=configGet("locale"))) + await app.send_message( + int(fullclb[2]), locale("sub_blocked", "message", locale=configGet("locale")) + ) PosterUser(int(fullclb[2])).block() - await clb.answer(text=locale("sub_block", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) + await clb.answer( + text=locale("sub_block", "callback", locale=user_locale).format(fullclb[2]), + show_alert=True, + ) - edited_markup = [clb.message.reply_markup.inline_keyboard[0], [InlineKeyboardButton(text=str(locale("sub_unblock", "button", locale=user_locale)), callback_data=f"sub_unblock_{fullclb[2]}")]] - await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) + edited_markup = [ + clb.message.reply_markup.inline_keyboard[0], + [ + InlineKeyboardButton( + text=str(locale("sub_unblock", "button", locale=user_locale)), + callback_data=f"sub_unblock_{fullclb[2]}", + ) + ], + ] + await clb.message.edit_reply_markup( + reply_markup=InlineKeyboardMarkup(edited_markup) + ) -@app.on_callback_query(filters.regex("sub_unblock_[\s\S]*")) +@app.on_callback_query(filters.regex("sub_unblock_[\s\S]*")) async def callback_query_unblock(app: PosterClient, clb: CallbackQuery): fullclb = str(clb.data).split("_") user_locale = clb.from_user.language_code - await app.send_message(int(fullclb[2]), locale("sub_unblocked", "message", locale=configGet("locale"))) + await app.send_message( + int(fullclb[2]), locale("sub_unblocked", "message", locale=configGet("locale")) + ) PosterUser(int(fullclb[2])).unblock() - await clb.answer(text=locale("sub_unblock", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) + await clb.answer( + text=locale("sub_unblock", "callback", locale=user_locale).format(fullclb[2]), + show_alert=True, + ) - edited_markup = [clb.message.reply_markup.inline_keyboard[0], [InlineKeyboardButton(text=str(locale("sub_block", "button", locale=user_locale)), callback_data=f"sub_block_{fullclb[2]}")]] - await clb.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(edited_markup)) \ No newline at end of file + edited_markup = [ + clb.message.reply_markup.inline_keyboard[0], + [ + InlineKeyboardButton( + text=str(locale("sub_block", "button", locale=user_locale)), + callback_data=f"sub_block_{fullclb[2]}", + ) + ], + ] + await clb.message.edit_reply_markup( + reply_markup=InlineKeyboardMarkup(edited_markup) + ) diff --git a/plugins/commands/general.py b/plugins/commands/general.py index 6b11323..7b489ba 100644 --- a/plugins/commands/general.py +++ b/plugins/commands/general.py @@ -9,11 +9,16 @@ from modules.logger import logWrite from modules.utils import configGet, killProc, locale -@app.on_message(~ filters.scheduled & filters.command(["kill", "die", "reboot"], prefixes=["", "/"])) +@app.on_message( + ~filters.scheduled & filters.command(["kill", "die", "reboot"], prefixes=["", "/"]) +) async def cmd_kill(app: PosterClient, msg: Message): - if msg.from_user.id in app.admins: pid = getpid() - logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid))) - await msg.reply_text(locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) - killProc(pid) \ No newline at end of file + logWrite( + locale("shutdown", "console", locale=configGet("locale")).format(str(pid)) + ) + await msg.reply_text( + locale("shutdown", "message", locale=configGet("locale")).format(str(pid)) + ) + killProc(pid) diff --git a/plugins/commands/mode_submit.py b/plugins/commands/mode_submit.py index 39f550c..016801d 100644 --- a/plugins/commands/mode_submit.py +++ b/plugins/commands/mode_submit.py @@ -7,12 +7,17 @@ from classes.user import PosterUser from classes.poster_client import PosterClient -@app.on_message(~ filters.scheduled & filters.command(["start"], prefixes="/")) +@app.on_message(~filters.scheduled & filters.command(["start"], prefixes="/")) async def cmd_start(app: PosterClient, msg: Message): if PosterUser(msg.from_user.id).is_blocked() is False: - await msg.reply_text(locale("start", "message", locale=msg.from_user.language_code)) + await msg.reply_text( + locale("start", "message", locale=msg.from_user.language_code) + ) -@app.on_message(~ filters.scheduled & filters.command(["rules", "help"], prefixes="/")) + +@app.on_message(~filters.scheduled & filters.command(["rules", "help"], prefixes="/")) async def cmd_rules(app: PosterClient, msg: Message): if PosterUser(msg.from_user.id).is_blocked() is False: - await msg.reply_text(locale("rules", "message", locale=msg.from_user.language_code)) \ No newline at end of file + await msg.reply_text( + locale("rules", "message", locale=msg.from_user.language_code) + ) diff --git a/plugins/commands/photos.py b/plugins/commands/photos.py index 6c35ae9..7b7dbc0 100644 --- a/plugins/commands/photos.py +++ b/plugins/commands/photos.py @@ -5,29 +5,25 @@ from classes.poster_client import PosterClient from modules.app import app -@app.on_message(~ filters.scheduled & filters.command(["import"], prefixes=["", "/"])) +@app.on_message(~filters.scheduled & filters.command(["import"], prefixes=["", "/"])) async def cmd_import(app: PosterClient, msg: Message): - if msg.from_user.id in app.admins: pass -@app.on_message(~ filters.scheduled & filters.command(["export"], prefixes=["", "/"])) +@app.on_message(~filters.scheduled & filters.command(["export"], prefixes=["", "/"])) async def cmd_export(app: PosterClient, msg: Message): - if msg.from_user.id in app.admins: pass -@app.on_message(~ filters.scheduled & filters.command(["remove"], prefixes=["", "/"])) +@app.on_message(~filters.scheduled & filters.command(["remove"], prefixes=["", "/"])) async def cmd_remove(app: PosterClient, msg: Message): - if msg.from_user.id in app.admins: pass -@app.on_message(~ filters.scheduled & filters.command(["purge"], prefixes=["", "/"])) +@app.on_message(~filters.scheduled & filters.command(["purge"], prefixes=["", "/"])) async def cmd_purge(app: PosterClient, msg: Message): - if msg.from_user.id in app.admins: - pass \ No newline at end of file + pass diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index 6638f3e..759a2c0 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -17,14 +17,17 @@ from modules.utils import configGet, locale from classes.enums.submission_types import SubmissionType -@app.on_message(~filters.scheduled & filters.private & filters.photo | filters.video | filters.animation | filters.document) +@app.on_message( + ~filters.scheduled & filters.private & filters.photo + | filters.video + | filters.animation + | filters.document +) async def get_submission(app: PosterClient, msg: Message): - try: - - if col_banned.find_one( {"user": msg.from_user.id} ) is not None: + if col_banned.find_one({"user": msg.from_user.id}) is not None: return - + await app.send_chat_action(msg.chat.id, ChatAction.TYPING) user_locale = msg.from_user.language_code @@ -32,68 +35,94 @@ async def get_submission(app: PosterClient, msg: Message): contents = None if PosterUser(msg.from_user.id).is_limited(): - await msg.reply_text(locale("sub_cooldown", "message", locale=user_locale).format(str(configGet("timeout", "submission")))) + await msg.reply_text( + locale("sub_cooldown", "message", locale=user_locale).format( + str(configGet("timeout", "submission")) + ) + ) return if msg.document is not None: if msg.document.mime_type not in configGet("mime_types", "submission"): - await msg.reply_text(locale("mime_not_allowed", "message", locale=user_locale), quote=True) + await msg.reply_text( + locale("mime_not_allowed", "message", locale=user_locale), + quote=True, + ) return if msg.document.file_size > configGet("file_size", "submission"): - await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) + await msg.reply_text( + locale("document_too_large", "message", locale=user_locale).format( + str(configGet("file_size", "submission") / 1024 / 1024) + ), + quote=True, + ) return if msg.document.file_size > configGet("tmp_size", "submission"): save_tmp = False - contents = msg.document.file_id, SubmissionType.DOCUMENT #, msg.document.file_name + contents = ( + msg.document.file_id, + SubmissionType.DOCUMENT, + ) # , msg.document.file_name if msg.video is not None: if msg.video.file_size > configGet("file_size", "submission"): - await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) + await msg.reply_text( + locale("document_too_large", "message", locale=user_locale).format( + str(configGet("file_size", "submission") / 1024 / 1024) + ), + quote=True, + ) return if msg.video.file_size > configGet("tmp_size", "submission"): save_tmp = False - contents = msg.video.file_id, SubmissionType.VIDEO #, msg.video.file_name + contents = msg.video.file_id, SubmissionType.VIDEO # , msg.video.file_name if msg.animation is not None: if msg.animation.file_size > configGet("file_size", "submission"): - await msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) + await msg.reply_text( + locale("document_too_large", "message", locale=user_locale).format( + str(configGet("file_size", "submission") / 1024 / 1024) + ), + quote=True, + ) return if msg.animation.file_size > configGet("tmp_size", "submission"): save_tmp = False - contents = msg.animation.file_id, SubmissionType.ANIMATION #, msg.animation.file_name + contents = ( + msg.animation.file_id, + SubmissionType.ANIMATION, + ) # , msg.animation.file_name if msg.photo is not None: - contents = msg.photo.file_id, SubmissionType.PHOTO #, "please_generate" + contents = msg.photo.file_id, SubmissionType.PHOTO # , "please_generate" if save_tmp is not None: - if contents is None: return tmp_id = str(uuid4()) # filename = tmp_id if contents[1] == "please_generate" else contents[1] - makedirs(path.join(configGet("data", "locations"), "submissions", tmp_id), exist_ok=True) - downloaded = await app.download_media(msg, path.join(configGet("data", "locations"), "submissions", tmp_id)+sep) + makedirs( + path.join(configGet("data", "locations"), "submissions", tmp_id), + exist_ok=True, + ) + downloaded = await app.download_media( + msg, + path.join(configGet("data", "locations"), "submissions", tmp_id) + sep, + ) inserted = col_submitted.insert_one( { "user": msg.from_user.id, "date": datetime.now(), "done": False, "type": contents[1].value, - "temp": { - "uuid": tmp_id, - "file": path.basename(str(downloaded)) - }, - "telegram": { - "msg_id": msg.id, - "file_id": contents[0] - }, - "caption": str(msg.caption) if msg.caption is not None else None + "temp": {"uuid": tmp_id, "file": path.basename(str(downloaded))}, + "telegram": {"msg_id": msg.id, "file_id": contents[0]}, + "caption": str(msg.caption) if msg.caption is not None else None, } ) - - else: + else: if contents is None: return @@ -103,36 +132,44 @@ async def get_submission(app: PosterClient, msg: Message): "date": datetime.now(), "done": False, "type": contents[1].value, - "temp": { - "uuid": None, - "file": None - }, - "telegram": { - "msg_id": msg.id, - "file_id": contents[0] - }, - "caption": str(msg.caption) if msg.caption is not None else None + "temp": {"uuid": None, "file": None}, + "telegram": {"msg_id": msg.id, "file_id": contents[0]}, + "caption": str(msg.caption) if msg.caption is not None else None, } ) - + buttons = [ [ - InlineKeyboardButton(text=locale("sub_yes", "button", locale=configGet("locale")), callback_data=f"sub_yes_{str(inserted.inserted_id)}") + InlineKeyboardButton( + text=locale("sub_yes", "button", locale=configGet("locale")), + callback_data=f"sub_yes_{str(inserted.inserted_id)}", + ) ] ] if msg.caption is not None: caption = str(msg.caption) buttons[0].append( - InlineKeyboardButton(text=locale("sub_yes_caption", "button", locale=configGet("locale")), callback_data=f"sub_yes_{str(inserted.inserted_id)}_caption") + InlineKeyboardButton( + text=locale( + "sub_yes_caption", "button", locale=configGet("locale") + ), + callback_data=f"sub_yes_{str(inserted.inserted_id)}_caption", + ) ) buttons[0].append( - InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{str(inserted.inserted_id)}") + InlineKeyboardButton( + text=locale("sub_no", "button", locale=configGet("locale")), + callback_data=f"sub_no_{str(inserted.inserted_id)}", + ) ) else: caption = "" buttons[0].append( - InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{str(inserted.inserted_id)}") + InlineKeyboardButton( + text=locale("sub_no", "button", locale=configGet("locale")), + callback_data=f"sub_no_{str(inserted.inserted_id)}", + ) ) caption += locale("sub_by", "message", locale=locale(configGet("locale"))) @@ -146,45 +183,80 @@ async def get_submission(app: PosterClient, msg: Message): if msg.from_user.phone_number is not None: caption += f" ({msg.from_user.phone_number})" - if msg.from_user.id in app.admins and configGet("admins", "submission", "require_confirmation") is False: + if ( + msg.from_user.id in app.admins + and configGet("admins", "submission", "require_confirmation") is False + ): try: await app.submit_photo(str(inserted.inserted_id)) - await msg.reply_text(locale("sub_yes_auto", "message", locale=user_locale), disable_notification=True, quote=True) + await msg.reply_text( + locale("sub_yes_auto", "message", locale=user_locale), + disable_notification=True, + quote=True, + ) await msg.copy(app.owner, caption=caption, disable_notification=True) return except SubmissionDuplicatesError as exp: - await msg.reply_text(locale("sub_media_duplicates_list", "message", locale=user_locale).format("\n • ".join(exp.duplicates)), quote=True) + await msg.reply_text( + locale( + "sub_media_duplicates_list", "message", locale=user_locale + ).format("\n • ".join(exp.duplicates)), + quote=True, + ) return except Exception as exp: await msg.reply_text(format_exc(), quote=True) return - elif msg.from_user.id not in app.admins and configGet("users", "submission", "require_confirmation") is False: + elif ( + msg.from_user.id not in app.admins + and configGet("users", "submission", "require_confirmation") is False + ): try: await app.submit_photo(str(inserted.inserted_id)) - await msg.reply_text(locale("sub_yes_auto", "message", locale=user_locale), disable_notification=True, quote=True) + await msg.reply_text( + locale("sub_yes_auto", "message", locale=user_locale), + disable_notification=True, + quote=True, + ) await msg.copy(app.owner, caption=caption) return except SubmissionDuplicatesError as exp: - await msg.reply_text(locale("sub_dup", "message", locale=user_locale), quote=True) + await msg.reply_text( + locale("sub_dup", "message", locale=user_locale), quote=True + ) return except Exception as exp: - await app.send_message(app.owner, locale("sub_error_admin", "message").format(msg.from_user.id, format_exc())) + await app.send_message( + app.owner, + locale("sub_error_admin", "message").format( + msg.from_user.id, format_exc() + ), + ) await msg.reply_text("sub_error", quote=True) return if msg.from_user.id not in app.admins: buttons += [ [ - InlineKeyboardButton(text=locale("sub_block", "button", locale=configGet("locale")), callback_data=f"sub_block_{msg.from_user.id}") + InlineKeyboardButton( + text=locale("sub_block", "button", locale=configGet("locale")), + callback_data=f"sub_block_{msg.from_user.id}", + ) ] ] PosterUser(msg.from_user.id).limit() if msg.from_user.id != app.owner: - await msg.reply_text(locale("sub_sent", "message", locale=user_locale), disable_notification=True, quote=True) + await msg.reply_text( + locale("sub_sent", "message", locale=user_locale), + disable_notification=True, + quote=True, + ) - await msg.copy(app.owner, caption=caption, reply_markup=InlineKeyboardMarkup(buttons)) + await msg.copy( + app.owner, caption=caption, reply_markup=InlineKeyboardMarkup(buttons) + ) except AttributeError: - logWrite(f"from_user in function get_submission does not seem to contain id") \ No newline at end of file + logWrite(f"from_user in function get_submission does not seem to contain id") diff --git a/poster.py b/poster.py index 992c440..67c9851 100644 --- a/poster.py +++ b/poster.py @@ -9,8 +9,8 @@ from modules.utils import configGet, jsonLoad, jsonSave, killProc, locale # Args ===================================================================================================================================== parser = ArgumentParser( - prog = "Telegram Poster", - description = "Bot for posting some of your stuff and also receiving submissions." + prog="Telegram Poster", + description="Bot for posting some of your stuff and also receiving submissions.", ) parser.add_argument("-m", "--move-sent", action="store_true") @@ -24,11 +24,22 @@ args = parser.parse_args() if args.move_sent: for entry in jsonLoad(configGet("index", "locations"))["sent"]: try: - move(configGet("queue", "locations")+sep+entry, configGet("sent", "locations")+sep+entry) + move( + configGet("queue", "locations") + sep + entry, + configGet("sent", "locations") + sep + entry, + ) except FileNotFoundError: - logWrite(locale("move_sent_doesnt_exist", "console", locale=configGet("locale")).format(entry)) + logWrite( + locale( + "move_sent_doesnt_exist", "console", locale=configGet("locale") + ).format(entry) + ) except Exception as exp: - logWrite(locale("move_sent_doesnt_exception", "console", locale=configGet("locale")).format(entry, exp)) + logWrite( + locale( + "move_sent_doesnt_exception", "console", locale=configGet("locale") + ).format(entry, exp) + ) logWrite(locale("move_sent_completed", "console", locale=configGet("locale"))) if args.cleanup: @@ -37,15 +48,19 @@ if args.cleanup: for entry in index["sent"]: try: try: - remove(configGet("queue", "locations")+sep+entry) + remove(configGet("queue", "locations") + sep + entry) except FileNotFoundError: pass try: - remove(configGet("sent", "locations")+sep+entry) + remove(configGet("sent", "locations") + sep + entry) except FileNotFoundError: pass except Exception as exp: - logWrite(locale("cleanup_exception", "console", locale=configGet("locale")).format(entry, exp)) + logWrite( + locale( + "cleanup_exception", "console", locale=configGet("locale") + ).format(entry, exp) + ) jsonSave(index, jsonLoad(configGet("index", "locations"))) logWrite(locale("cleanup_completed", "console", locale=configGet("locale"))) else: @@ -56,14 +71,18 @@ if args.cleanup_index: index = jsonLoad(configGet("index", "locations")) index["sent"] = [] jsonSave(index, jsonLoad(configGet("index", "locations"))) - logWrite(locale("cleanup_index_completed", "console", locale=configGet("locale"))) + logWrite( + locale("cleanup_index_completed", "console", locale=configGet("locale")) + ) else: - logWrite(locale("cleanup_index_unathorized", "console", locale=configGet("locale"))) + logWrite( + locale("cleanup_index_unathorized", "console", locale=configGet("locale")) + ) if args.norun: logWrite(locale("passed_norun", "console", locale=configGet("locale_log"))) exit() -#=========================================================================================================================================== +# =========================================================================================================================================== # Import =================================================================================================================================== @@ -73,7 +92,7 @@ try: except ModuleNotFoundError: print(locale("deps_missing", "console", locale=configGet("locale")), flush=True) exit() -#=========================================================================================================================================== +# =========================================================================================================================================== pid = getpid() @@ -125,7 +144,7 @@ if configGet("submit", "mode"): if configGet("api_based", "mode"): from modules.api_client import authorize -#=========================================================================================================================================== +# =========================================================================================================================================== # Work in progress # Handle new forwards @@ -168,25 +187,30 @@ if configGet("api_based", "mode"): # asyncio.run(main()) if __name__ == "__main__": - logWrite(locale("startup", "console", locale=configGet("locale")).format(str(pid))) app.start() - + if configGet("startup", "reports"): - app.send_message(app.owner, locale("startup", "message", locale=configGet("locale")).format(str(pid))) + app.send_message( + app.owner, + locale("startup", "message", locale=configGet("locale")).format(str(pid)), + ) if configGet("post", "mode"): scheduler.start() - #if configGet("api_based", "mode"): + # if configGet("api_based", "mode"): # token = await authorize() # if len(get(f'{configGet("address", "posting", "api")}/albums?q={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}).json()["results"]) == 0: # post(f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}) idle() - app.send_message(app.owner, locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) + app.send_message( + app.owner, + locale("shutdown", "message", locale=configGet("locale")).format(str(pid)), + ) logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid))) - killProc(pid) \ No newline at end of file + killProc(pid) From c2fb88ed65a134913c92872a3fd67714bf499822 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 9 Mar 2023 11:35:02 +0100 Subject: [PATCH 43/98] Bumped APScheduler to 3.10.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 27f8e1a..420578d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -apscheduler~=3.10.0 +apscheduler~=3.10.1 pyrogram~=2.0.100 aiohttp~=3.8.4 aiofiles~=23.1.0 From 3f22340852935c0b807ab54c6f8b5a58f8ac8f9f Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 9 Mar 2023 11:48:50 +0100 Subject: [PATCH 44/98] Added license and code style badges --- README.md | 12 +++++++++++- README_uk.md | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 71c7bf6..22cb21c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,15 @@ # TelegramPoster + +

+License: GPL +Code style: black +

> Шукаєш інструкцію українською? А вона [ось тут](https://git.end-play.xyz/profitroll/TelegramPoster/src/branch/master/README_uk.md) знаходиться) This bot is used for one and only task - post pictures from my personal archive. Here's its source code so you can also host a bot and have fun with it. Just don't exepect it to be brilliant. It is not. But hey, you can always fork it ;) ## Installation + To make this bot run at first you need to have a Python interpreter and git. Google is your friend finding it. You can also ignore git and simply download source code, should also work fine. After that you're ready to go. > In this README I assume that you're using default python in your @@ -42,19 +48,23 @@ To make this bot run at first you need to have a Python interpreter and git. Goo Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. ## Command line arguments + Of course bot also has them. You can perform some actions with them. + * `--move-sent` - allows you to move all sent files from queue to sent directories * `--cleanup` - purge files in both `queue` and `sent` folders if they're sent. Requires `--confirm` argument * `--cleanup-index` - purge all sent entries from index. Requires `--confirm` argument * `--norun` - allows you to execute above arguments without triggering the bot start itself Examples: + * `python3 ./main.py --move-sent --norun` * `python3 ./main.py --cleanup --confirm` ## Localization + Bot is capable of using custom locales. There are some that are pre-installed (English and Ukrainian), however you can add your own locales too. All localization files are located in the `locale` folder, otherwise in folder specified in config file. Just copy locale file of your choice, name it in accordance to [IETF language tags](https://en.wikipedia.org/wiki/IETF_language_tag) (if you want your locale to be compatible with Telegram's locales) or define your own name. Save it as json and you're good to go. If you want to change default locale for messages, that cannot determine admin's locale - edit `"locale"` parameter in the `config.json`. If this locale is not available - `"locale_fallback"` will be used instead. If both are not available - error will be shown. For console output and logging locale you should edit `"locale_log"`. -We recommend to only make changes to your custom locale. Or at least always have your backup of for example `en.json` as your fallback. \ No newline at end of file +We recommend to only make changes to your custom locale. Or at least always have your backup of for example `en.json` as your fallback. diff --git a/README_uk.md b/README_uk.md index 88c03c2..d410f9c 100644 --- a/README_uk.md +++ b/README_uk.md @@ -1,7 +1,13 @@ # TelegramPoster + +

+License: GPL +Code style: black +

Цей бот використовується для однієї-єдиної задачі - розміщувати фотографії з мого особистого архіву. Ось його код, тож Ви також можете захостити бота самостійно та розважитися з ним. Тільки не очікуйте, що він ідеальним. Не буде. Але гей, Ви завжди можете його доробити під себе ;) ## Установка + Для запуску цього бота спочатку потрібно мати інтерпретатор Python та встановлений git. Google — Ваш друг у пошуках. Ви також можете ігнорувати git і просто завантажити код, також має спрацювати добре. Після цього Ви готові до встановлення. > У цьому README я вважаю, що Ви використовуєте python за замовчуванням у своїй @@ -40,19 +46,23 @@ Крім того, доступні `loop.sh` і `loop.bat`, якщо ви хочете, щоб ваш бот запускався знову після зупинки або після використання команди `/reboot`. ## Аргументи командного рядка + Звичайно, у бота вони також є. З ними можна виконувати деякі дії. + * `--move-sent` - дозволяє перемістити всі надіслані файли з черги до папки надісланих * `--cleanup` - очистити файли в папках `queue` і `sent`, якщо вони вже надіслані. Потрібен аргумент `--confirm` * `--cleanup-index` - видалити всі надіслані записи з індексу. Потрібен аргумент `--confirm` * `--norun` - дозволяє виконувати наведені вище аргументи, не запускаючи самого бота Приклади: + * `python3 ./main.py --move-sent --norun` * `python3 ./main.py --cleanup --confirm` ## Локалізація + Бот може використовувати різні мови. Є деякі попередньо встановлені (Англійська та Українська), однак Ви можете додавати свої власні локалізації теж. Всі файли локалізації знаходяться у папці `locale`, якщо в конфігураційному файлі не вказано іншу. Просто скопіюйте цікавлячий Вас файл, назвіть його відповідно до [тегів мови IETF](https://en.wikipedia.org/wiki/IETF_language_tag) (якщо Ви хочете, щоб переклад був сумісним з перекладами Telegram) або просто вкажіть свою власну назву. Збережіть свій переклад як json файл і все готово. Якщо ви хочете змінити мову за замовчуванням для повідомлень самого бота, які не можуть визначити мову адміністратора, відредагуйте параметр `"locale"` у `config.json`. Якщо ця мова недоступна, замість неї буде використано `"locale_fallback"`. Якщо обидві мови недоступні - буде показано помилку. Для зміни мови виведення консолі та логування вам слід відредагувати `"locale_log"`. -Ми рекомендуємо вносити будь-які зміни лише до вашої окремої мови. Або, принаймні, завжди мати резервну копію, наприклад, `en.json` як запасний варіант. \ No newline at end of file +Ми рекомендуємо вносити будь-які зміни лише до вашої окремої мови. Або, принаймні, завжди мати резервну копію, наприклад, `en.json` як запасний варіант. From a8545dd097cd20f400e001a48039b6580f04e507 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 9 Mar 2023 16:16:38 +0100 Subject: [PATCH 45/98] Fixed project name align --- README.md | 2 +- README_uk.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 22cb21c..140b5d8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# TelegramPoster +

TelegramPoster

License: GPL diff --git a/README_uk.md b/README_uk.md index d410f9c..1b281ad 100644 --- a/README_uk.md +++ b/README_uk.md @@ -1,4 +1,4 @@ -# TelegramPoster +

TelegramPoster

License: GPL From 98e9c5f5a216ea8fbe28e62e483e83f39cbcc0c8 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 9 Mar 2023 16:17:35 +0100 Subject: [PATCH 46/98] Fixed line being displayed incorrectly --- README.md | 3 ++- README_uk.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 140b5d8..81fedc7 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@

License: GPL Code style: black -

+

+ > Шукаєш інструкцію українською? А вона [ось тут](https://git.end-play.xyz/profitroll/TelegramPoster/src/branch/master/README_uk.md) знаходиться) This bot is used for one and only task - post pictures from my personal archive. Here's its source code so you can also host a bot and have fun with it. Just don't exepect it to be brilliant. It is not. But hey, you can always fork it ;) diff --git a/README_uk.md b/README_uk.md index 1b281ad..f349de3 100644 --- a/README_uk.md +++ b/README_uk.md @@ -3,7 +3,8 @@

License: GPL Code style: black -

+

+ Цей бот використовується для однієї-єдиної задачі - розміщувати фотографії з мого особистого архіву. Ось його код, тож Ви також можете захостити бота самостійно та розважитися з ним. Тільки не очікуйте, що він ідеальним. Не буде. Але гей, Ви завжди можете його доробити під себе ;) ## Установка From 244173e55627e3056211ea620c1ddec84d9585cd Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 14:25:22 +0100 Subject: [PATCH 47/98] Bumped Pyrogram to 2.0.101 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 420578d..feb0c35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ apscheduler~=3.10.1 -pyrogram~=2.0.100 +pyrogram~=2.0.101 aiohttp~=3.8.4 aiofiles~=23.1.0 psutil~=5.9.4 From 0073120bf29b9667c89aca139767e723a7953053 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 14:54:06 +0100 Subject: [PATCH 48/98] Moved ujson to required dependencies --- modules/api_client.py | 14 ++++---------- modules/logger.py | 10 +++------- modules/utils.py | 14 +++++--------- requirements-optional.txt | 1 - requirements.txt | 5 +++-- 5 files changed, 15 insertions(+), 29 deletions(-) diff --git a/modules/api_client.py b/modules/api_client.py index d364e7d..14d4123 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -1,26 +1,20 @@ """This is only a temporary solution. Complete Photos API client is yet to be developed.""" -try: - from ujson import dumps -except ModuleNotFoundError: - from json import dumps - import asyncio from base64 import b64decode, b64encode -from traceback import print_exc -import aiofiles -from ujson import dumps from os import makedirs, path, sep from random import choice +from traceback import print_exc from typing import Tuple, Union + +import aiofiles from aiohttp import ClientSession, FormData +from ujson import dumps from classes.exceptions import SubmissionUploadError - from modules.logger import logWrite from modules.utils import configGet - http_session = ClientSession( json_serialize=dumps, ) diff --git a/modules/logger.py b/modules/logger.py index a69104d..0f32b14 100644 --- a/modules/logger.py +++ b/modules/logger.py @@ -1,13 +1,9 @@ -try: - from ujson import loads -except ModuleNotFoundError: - from json import loads - -from os import stat, makedirs, path, getcwd +from datetime import datetime from gzip import open as gzipopen +from os import getcwd, makedirs, path, stat from shutil import copyfileobj -from datetime import datetime +from ujson import loads with open(getcwd() + path.sep + "config.json", "r", encoding="utf8") as file: json_contents = loads(file.read()) diff --git a/modules/utils.py b/modules/utils.py index 0f7f809..34edf99 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -1,16 +1,12 @@ -try: - from ujson import JSONDecodeError as JSONDecodeError - from ujson import loads, dumps -except ModuleNotFoundError: - from json import JSONDecodeError as JSONDecodeError - from json import loads, dumps - -from sys import exit -from os import sep, kill +from os import kill from os import name as osname +from os import sep +from sys import exit from traceback import print_exc from typing import Any +from ujson import JSONDecodeError, dumps, loads + from modules.logger import logWrite diff --git a/requirements-optional.txt b/requirements-optional.txt index e67bbe1..717d475 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -1,2 +1 @@ -ujson~=5.7.0 tgcrypto~=1.2.5 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index feb0c35..fd2b3d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ apscheduler~=3.10.1 pyrogram~=2.0.101 -aiohttp~=3.8.4 +pytimeparse~=1.1.8 aiofiles~=23.1.0 +aiohttp~=3.8.4 psutil~=5.9.4 pymongo~=4.3.3 pillow~=9.4.0 -pytimeparse~=1.1.8 \ No newline at end of file +ujson~=5.7.0 \ No newline at end of file From ad281ba981f8e98a496fe0801c7bdfd0a722cdae Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 19:25:35 +0100 Subject: [PATCH 49/98] WIP: New readme --- README.md | 54 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 81fedc7..0653588 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,72 @@

TelegramPoster

-License: GPL +License: GPL Code style: black

-> Шукаєш інструкцію українською? А вона [ось тут](https://git.end-play.xyz/profitroll/TelegramPoster/src/branch/master/README_uk.md) знаходиться) +> Шукаєш інструкцію українською? А вона [ось тут](https://git.end-play.xyz/profitroll/TelegramPoster/src/branch/dev/README_uk.md) знаходиться) This bot is used for one and only task - post pictures from my personal archive. Here's its source code so you can also host a bot and have fun with it. Just don't exepect it to be brilliant. It is not. But hey, you can always fork it ;) +## Dependencies + +For now bot requires MongoDB and [PhotosAPI](https://git.end-play.xyz/profitroll/PhotosAPI) in order to function properly. Use [MongoDB's installation manual](https://www.mongodb.com/docs/manual/installation/) and [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md) to install install them. + +Please note that Photos API also requires MongoDB so it makes sense to install and configure Mongo first. + ## Installation -To make this bot run at first you need to have a Python interpreter and git. Google is your friend finding it. You can also ignore git and simply download source code, should also work fine. After that you're ready to go. +To make this bot run at first you need to have a Python interpreter, Photos API, MongoDB and optionally git. You can also ignore git and simply download source code, should also work fine. After that you're ready to go. > In this README I assume that you're using default python in your > system and your system's PATH contains it. If your default python > is `python3` or for example `/home/user/.local/bin/python3.9` - use it instead. -> If it's non-standart executable path - you should also change +> If it's non-standard executable path - you should also change > it in scripts you will use (`loop.sh`, `loop.bat`, `start.sh` and `start.bat`). -1. Download the bot: - 1. `git clone https://git.end-play.xyz/profitroll/TelegramSender.git` (if you want to use git) +1. Install Mongo and Photos API: + 1. Install MongoDB by following [official installation manual](https://www.mongodb.com/docs/manual/installation/) + 2. Install Photos API by following [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md) + +2. Download the bot: + 1. `git clone -b dev https://git.end-play.xyz/profitroll/TelegramSender.git` (if you want to use git) 2. `cd ./TelegramSender` -2. Install dependencies: +3. Install project's dependencies: `python -m pip install -r requirements.txt` Without installing those - bot cannot work at all -3. Install optional dependencies [Not required]: +4. Install optional dependencies [Not required]: `python -m pip install -r requirements-optional.txt` These are not required but can make the bot run a bit faster -4. Configure your bot with a favorite text editor: +5. Configure your bot with a favorite text editor: `nano config.json` You can edit with vim, nano, on Windows it's Notepad or Notepad++. Whatever. If you don't know where to find bot_token and your id - here you can find some hints: [get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [get your id](https://www.alphr.com/telegram-find-user-id/), [get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id). - Also don't forget to change bot's working mode. Dict key `"mode"` contains keys `"post"` and `"submit"`, each of those can be either `true` or `false`. -5. Add bot to the channel: - To use your bot of course you need to have a channel or group otherwise makes no sense to have such a bot. [Here](https://stackoverflow.com/a/33497769) you can find a quick guide how to add your bot to a channel. +6. Configure database and API: + 1. Configure database: + 1. Change database host and port in keys `"database.host"` and `"database.port"`; + 2. Change database name to the one you like in `"database.name"`; + 3. If you've changed user and password to access the db you should also change `"database.user"` and `"database.password"` keys, otherwise leave it `null`. + 2. Configure Photos API: + 1. Register new user using POST /users request on the page "docs#/default/user_create_users_post" of the API. Please make sure you're using a valid email address if you've configured you API to use email confirmation; + 2. If you have email confirmation - activate your new user by using "docs#/default/user_confirm_users__user__confirm_patch" page of the docs or by using the link from the email; + 3. Now create a new album for your bot using your user. Click "Authorize" on the docs page of your API, enter your login and password, select "select all" in scopes section and click "Authorize". Now you can execute requests as your user; + 4. Go to POST /albums (/docs#/default/album_create_albums_post) and create a friendly name for your album. No worries, it will be only used internally by the bot; + 5. Now you can configure your bot to use all of that. Change key `"posting.api.address"` to the http/https address accessible by the bot. Avoid using external address without a reason if API and the bot are on the same machine. If you want to see duplicates from the outside you can also change `"posting.api.address_external"` to the one accessible from the internet; + 6. Configure API user/password and your album name using `"posting.api.username"`, `"posting.api.password"` and `"posting.api.album"` keys. -6. Fill your contents folder: - Of course bot cannot post something from nothing. Configure your `config.json` what media types bot should post (`"posting", "extensions"`), when to post them (`"posting", "time"`) and also where to find them (`"locations"`). You can also move them when sent by setting `"posting", "move_sent"` to `true`. +7. Add bot to the channel: + To use your bot of course you need to have a channel or group otherwise makes no sense to have such a bot. [Here](https://stackoverflow.com/a/33497769) you can find a quick guide how to add your bot to a channel. After that simply set `"posting.channel"` to your channel's ID. -6. Good to go, run it! - `python ./main.py` +8. Configure posting time: + To make your bot post random content you need to configure `"posting.time"` with a list of "DD:MM" formatted strings or use `"posting.interval"` formatted as "XdXhXmXs". To use interval instead of selected time set `"posting.use_interval"` to `true`. + +9. Good to go, run it! + Make sure MongoDB and Photos API are running and use `python ./main.py` to start it. Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux. Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. From 59b504e6d98b1e238dd51ca3d04467928976f8fb Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 19:34:27 +0100 Subject: [PATCH 50/98] Temp: removed strings about API based in config --- config.json | 6 +----- poster.py | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/config.json b/config.json index bfccebc..96ebd2f 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,4 @@ { - "module": null, "locale": "en", "locale_log": "en", "locale_fallback": "en", @@ -19,8 +18,7 @@ }, "mode": { "post": true, - "submit": true, - "api_based": false + "submit": true }, "reports": { "sent": false, @@ -39,8 +37,6 @@ "sent": "data/sent", "queue": "data/queue", "index": "data/index.json", - "submit": "data/submit.json", - "blocked": "data/blocked.json", "locale": "locale" }, "posting": { diff --git a/poster.py b/poster.py index 67c9851..8ad473c 100644 --- a/poster.py +++ b/poster.py @@ -142,8 +142,8 @@ if configGet("submit", "mode"): from plugins.commands.mode_submit import * from plugins.handlers.submission import * -if configGet("api_based", "mode"): - from modules.api_client import authorize +# if configGet("api_based", "mode"): +# from modules.api_client import authorize # =========================================================================================================================================== # Work in progress From 62f076148c041b086e45a3fa6be5875c551c5d31 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 19:43:51 +0100 Subject: [PATCH 51/98] Wiki pages init --- docs/config_api.md | 1 + docs/config_channel.md | 1 + docs/config_posts.md | 1 + docs/config_systemd.md | 1 + docs/config_time.md | 1 + docs/config_venv.md | 1 + 6 files changed, 6 insertions(+) create mode 100644 docs/config_api.md create mode 100644 docs/config_channel.md create mode 100644 docs/config_posts.md create mode 100644 docs/config_systemd.md create mode 100644 docs/config_time.md create mode 100644 docs/config_venv.md diff --git a/docs/config_api.md b/docs/config_api.md new file mode 100644 index 0000000..f7026c3 --- /dev/null +++ b/docs/config_api.md @@ -0,0 +1 @@ +# Configuring API connection diff --git a/docs/config_channel.md b/docs/config_channel.md new file mode 100644 index 0000000..47e90c5 --- /dev/null +++ b/docs/config_channel.md @@ -0,0 +1 @@ +# Configuring channel and adding your bot diff --git a/docs/config_posts.md b/docs/config_posts.md new file mode 100644 index 0000000..02ab8d7 --- /dev/null +++ b/docs/config_posts.md @@ -0,0 +1 @@ +# Configuring posts and duplicates checker diff --git a/docs/config_systemd.md b/docs/config_systemd.md new file mode 100644 index 0000000..08b6d30 --- /dev/null +++ b/docs/config_systemd.md @@ -0,0 +1 @@ +# Configuring as systemd service diff --git a/docs/config_time.md b/docs/config_time.md new file mode 100644 index 0000000..9fca0c6 --- /dev/null +++ b/docs/config_time.md @@ -0,0 +1 @@ +# Configuring posting time diff --git a/docs/config_venv.md b/docs/config_venv.md new file mode 100644 index 0000000..ecdabd1 --- /dev/null +++ b/docs/config_venv.md @@ -0,0 +1 @@ +# Configuring virtual environment From 48acab2d5e37b37be2674c012ce4e58636933640 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 19:57:22 +0100 Subject: [PATCH 52/98] WIP: New readme --- README.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0653588..5d80bca 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This bot is used for one and only task - post pictures from my personal archive. ## Dependencies -For now bot requires MongoDB and [PhotosAPI](https://git.end-play.xyz/profitroll/PhotosAPI) in order to function properly. Use [MongoDB's installation manual](https://www.mongodb.com/docs/manual/installation/) and [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md) to install install them. +For now bot requires [MongoDB](https://www.mongodb.com) and [PhotosAPI](https://git.end-play.xyz/profitroll/PhotosAPI) in order to function properly. Use [MongoDB's installation manual](https://www.mongodb.com/docs/manual/installation) and [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md) to install install them. Please note that Photos API also requires MongoDB so it makes sense to install and configure Mongo first. @@ -26,7 +26,7 @@ To make this bot run at first you need to have a Python interpreter, Photos API, > it in scripts you will use (`loop.sh`, `loop.bat`, `start.sh` and `start.bat`). 1. Install Mongo and Photos API: - 1. Install MongoDB by following [official installation manual](https://www.mongodb.com/docs/manual/installation/) + 1. Install MongoDB by following [official installation manual](https://www.mongodb.com/docs/manual/installation) 2. Install Photos API by following [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md) 2. Download the bot: @@ -44,20 +44,22 @@ To make this bot run at first you need to have a Python interpreter, Photos API, 5. Configure your bot with a favorite text editor: `nano config.json` You can edit with vim, nano, on Windows it's Notepad or Notepad++. Whatever. - If you don't know where to find bot_token and your id - here you can find some hints: [get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [get your id](https://www.alphr.com/telegram-find-user-id/), [get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id). + If you don't know where to find bot_token and your id - here you can find some hints: [get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [get your id](https://www.alphr.com/telegram-find-user-id), [get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id). 6. Configure database and API: 1. Configure database: 1. Change database host and port in keys `"database.host"` and `"database.port"`; 2. Change database name to the one you like in `"database.name"`; - 3. If you've changed user and password to access the db you should also change `"database.user"` and `"database.password"` keys, otherwise leave it `null`. + 3. If you've changed user and password to access the db you should also change `"database.user"` and `"database.password"` keys, otherwise leave them `null` (default). 2. Configure Photos API: - 1. Register new user using POST /users request on the page "docs#/default/user_create_users_post" of the API. Please make sure you're using a valid email address if you've configured you API to use email confirmation; - 2. If you have email confirmation - activate your new user by using "docs#/default/user_confirm_users__user__confirm_patch" page of the docs or by using the link from the email; - 3. Now create a new album for your bot using your user. Click "Authorize" on the docs page of your API, enter your login and password, select "select all" in scopes section and click "Authorize". Now you can execute requests as your user; - 4. Go to POST /albums (/docs#/default/album_create_albums_post) and create a friendly name for your album. No worries, it will be only used internally by the bot; - 5. Now you can configure your bot to use all of that. Change key `"posting.api.address"` to the http/https address accessible by the bot. Avoid using external address without a reason if API and the bot are on the same machine. If you want to see duplicates from the outside you can also change `"posting.api.address_external"` to the one accessible from the internet; - 6. Configure API user/password and your album name using `"posting.api.username"`, `"posting.api.password"` and `"posting.api.album"` keys. + 1. Create new user and album + 1. Register new user using API's `POST /users` request (`/docs#/default/user_create_users_post`). Please make sure you're using a valid email address if you've configured you API to use email confirmation; + 2. If you have email confirmation - activate your new user by using `PATCH /users/{user}/confirm` (`/docs#/default/user_confirm_users__user__confirm_patch`) or by using the link from the email; + 3. Now create a new album for your bot using your user. Click "Authorize" on the docs page of your API, enter your login and password, select "select all" in scopes section and click "Authorize". Now you can execute requests as your user; + 4. Go to `POST /albums` (`docs#/default/album_create_albums_post`) and create a friendly name for your album. No worries, only bot can see it so it doesn't matter what name you use; + 2. Configure bot to use that album + 1. Now you can configure your bot to use all of that. Change key `"posting.api.address"` to the http/https address accessible by the bot. Avoid using external address without a reason if API and the bot are on the same machine. If you want to see duplicates from the outside you can also change `"posting.api.address_external"` to the one accessible from the internet; + 2. Configure API user/password and your album name using `"posting.api.username"`, `"posting.api.password"` and `"posting.api.album"` keys. 7. Add bot to the channel: To use your bot of course you need to have a channel or group otherwise makes no sense to have such a bot. [Here](https://stackoverflow.com/a/33497769) you can find a quick guide how to add your bot to a channel. After that simply set `"posting.channel"` to your channel's ID. @@ -70,6 +72,8 @@ To make this bot run at first you need to have a Python interpreter, Photos API, Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux. Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. +If you need any further instructions on how to configure your bot or you had any difficulties doing so - please use [wiki in this repository](https://git.end-play.xyz/profitroll/TelegramPoster/wiki) to get more detailed instructions. + ## Command line arguments Of course bot also has them. You can perform some actions with them. From c670db72faae951a6896aefca1687602646e63d3 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 22:14:03 +0100 Subject: [PATCH 53/98] Added user-friendly CLI --- classes/exceptions.py | 36 ++++++++++++++++++ modules/api_client.py | 37 +++++++++++++++++- modules/cli.py | 77 +++++++++++++++++++++++++++++++++++++ poster.py | 88 +++---------------------------------------- 4 files changed, 154 insertions(+), 84 deletions(-) create mode 100644 modules/cli.py diff --git a/classes/exceptions.py b/classes/exceptions.py index 4a0a0ad..4ca8e58 100644 --- a/classes/exceptions.py +++ b/classes/exceptions.py @@ -20,3 +20,39 @@ class SubmissionDuplicatesError(Exception): super().__init__( f"Found duplicates of a photo '{file_path}': {self.duplicates}" ) + + +class UserCreationError(Exception): + def __init__(self, code: int, data: str) -> None: + self.code = code + self.data = data + super().__init__( + f"Could not create a new user. API returned HTTP {self.code} with content: {self.data}" + ) + + +class UserCreationDuplicateError(Exception): + def __init__(self, username: str) -> None: + self.username = username + super().__init__(f"User '{self.username} already exists.'") + + +class AlbumCreationError(Exception): + def __init__(self, code: int, data: str) -> None: + self.code = code + self.data = data + super().__init__( + f"Could not create a new album. API returned HTTP {self.code} with content: {self.data}" + ) + + +class AlbumCreationDuplicateError(Exception): + def __init__(self, name: str) -> None: + self.name = name + super().__init__(f"Album '{self.name} already exists.'") + + +class AlbumCreationNameError(Exception): + def __init__(self, data: dict) -> None: + self.data = data + super().__init__(data["detail"]) diff --git a/modules/api_client.py b/modules/api_client.py index 14d4123..22c772b 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -11,7 +11,14 @@ import aiofiles from aiohttp import ClientSession, FormData from ujson import dumps -from classes.exceptions import SubmissionUploadError +from classes.exceptions import ( + AlbumCreationDuplicateError, + AlbumCreationError, + AlbumCreationNameError, + SubmissionUploadError, + UserCreationDuplicateError, + UserCreationError, +) from modules.logger import logWrite from modules.utils import configGet @@ -169,5 +176,33 @@ async def move_pic(id: str, token: Union[str, None] = None) -> bool: return False +async def create_user(username: str, email: str, password: str) -> None: + response = await http_session.post( + f'{configGet("address", "posting", "api")}/users', + data={"user": username, "email": email, "password": password}, + ) + if response.status == 409: + raise UserCreationDuplicateError(username) + elif response.status != 204: + raise UserCreationError(response.status, await response.text(encoding="utf-8")) + return None + + +async def create_album(name: str, title: str) -> None: + token = await authorize() + response = await http_session.post( + f'{configGet("address", "posting", "api")}/albums', + params={"name": name, "title": title}, + headers={"Authorization": f"Bearer {token}"}, + ) + if response.status == 409: + raise AlbumCreationDuplicateError(name) + elif response.status == 406: + raise AlbumCreationNameError(await response.json()) + elif response.status != 200: + raise AlbumCreationError(response.status, await response.text(encoding="utf-8")) + return None + + if __name__ == "__main__": print(asyncio.run(authorize())) diff --git a/modules/cli.py b/modules/cli.py new file mode 100644 index 0000000..2ea0be4 --- /dev/null +++ b/modules/cli.py @@ -0,0 +1,77 @@ +import asyncio +from sys import exit +from traceback import print_exc +from modules.api_client import create_album, create_user, http_session +from argparse import ArgumentParser + +from modules.utils import configSet + +parser = ArgumentParser( + prog="Telegram Poster", + description="Bot for posting some of your stuff and also receiving submissions.", +) + +parser.add_argument("--create-user", action="store_true") +parser.add_argument("--create-album", action="store_true") + +args = parser.parse_args() + + +async def cli_create_user() -> None: + print( + "To set up Photos API connection you need to create a new user.\nIf you have email confirmation enabled in your Photos API config - you need to use a real email that will get a confirmation code afterwards.", + flush=True, + ) + username = input("Choose username for new Photos API user: ").strip() + email = input(f"Choose email for user '{username}': ").strip() + password = input(f"Choose password for user '{username}': ").strip() + try: + result = await create_user(username, email, password) + # asyncio.run(create_user(username, email, password)) + configSet("username", username, "posting", "api") + configSet("password", password, "posting", "api") + none = input( + "Alright. If you have email confirmation enabled - please confirm registration by using the link in your email. After that press Enter. Otherwise just press Enter." + ) + except Exception as exp: + print(f"Could not create a user due to {exp}", flush=True) + print_exc() + exit() + if not args.create_album: + print("You're done!", flush=True) + await http_session.close() + exit() + + +async def cli_create_album() -> None: + print( + "To use Photos API your user needs to have an album to store its data.\nThis wizard will help you to create a new album with its name and title.", + flush=True, + ) + name = input("Choose a name for your album: ").strip() + title = input(f"Choose a title for album '{name}': ").strip() + try: + result = await create_album(name, title) + # asyncio.run(create_album(name, title)) + configSet("album", name, "posting", "api") + except Exception as exp: + print(f"Could not create an album due to {exp}", flush=True) + print_exc() + exit() + print("You're done!", flush=True) + await http_session.close() + exit() + + +if args.create_user or args.create_album: + loop = asyncio.get_event_loop() + tasks = [] + + if args.create_user: + tasks.append(loop.create_task(cli_create_user())) + + if args.create_album: + tasks.append(loop.create_task(cli_create_album())) + + loop.run_until_complete(asyncio.wait(tasks)) + loop.close() diff --git a/poster.py b/poster.py index 8ad473c..faa6624 100644 --- a/poster.py +++ b/poster.py @@ -1,94 +1,16 @@ -from os import sep, remove, getpid -from shutil import move +from os import getpid from sys import exit -from argparse import ArgumentParser +from modules.cli import * from modules.logger import logWrite from modules.scheduler import scheduler -from modules.utils import configGet, jsonLoad, jsonSave, killProc, locale - -# Args ===================================================================================================================================== -parser = ArgumentParser( - prog="Telegram Poster", - description="Bot for posting some of your stuff and also receiving submissions.", -) - -parser.add_argument("-m", "--move-sent", action="store_true") -parser.add_argument("-c", "--cleanup", action="store_true") -parser.add_argument("--confirm", action="store_true") -parser.add_argument("-i", "--cleanup-index", action="store_true") -parser.add_argument("-n", "--norun", action="store_true") - -args = parser.parse_args() - -if args.move_sent: - for entry in jsonLoad(configGet("index", "locations"))["sent"]: - try: - move( - configGet("queue", "locations") + sep + entry, - configGet("sent", "locations") + sep + entry, - ) - except FileNotFoundError: - logWrite( - locale( - "move_sent_doesnt_exist", "console", locale=configGet("locale") - ).format(entry) - ) - except Exception as exp: - logWrite( - locale( - "move_sent_doesnt_exception", "console", locale=configGet("locale") - ).format(entry, exp) - ) - logWrite(locale("move_sent_completed", "console", locale=configGet("locale"))) - -if args.cleanup: - if args.confirm: - index = jsonLoad(configGet("index", "locations")) - for entry in index["sent"]: - try: - try: - remove(configGet("queue", "locations") + sep + entry) - except FileNotFoundError: - pass - try: - remove(configGet("sent", "locations") + sep + entry) - except FileNotFoundError: - pass - except Exception as exp: - logWrite( - locale( - "cleanup_exception", "console", locale=configGet("locale") - ).format(entry, exp) - ) - jsonSave(index, jsonLoad(configGet("index", "locations"))) - logWrite(locale("cleanup_completed", "console", locale=configGet("locale"))) - else: - logWrite(locale("cleanup_unathorized", "console", locale=configGet("locale"))) - -if args.cleanup_index: - if args.confirm: - index = jsonLoad(configGet("index", "locations")) - index["sent"] = [] - jsonSave(index, jsonLoad(configGet("index", "locations"))) - logWrite( - locale("cleanup_index_completed", "console", locale=configGet("locale")) - ) - else: - logWrite( - locale("cleanup_index_unathorized", "console", locale=configGet("locale")) - ) - -if args.norun: - logWrite(locale("passed_norun", "console", locale=configGet("locale_log"))) - exit() -# =========================================================================================================================================== - +from modules.utils import configGet, killProc, locale # Import =================================================================================================================================== try: - from modules.app import app from pyrogram.sync import idle + + from modules.app import app except ModuleNotFoundError: print(locale("deps_missing", "console", locale=configGet("locale")), flush=True) exit() From 6684c4d75038e71b33ae0b58c211dc4d4866802b Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 22:14:23 +0100 Subject: [PATCH 54/98] Bump Pyrogram to 2.0.102 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fd2b3d1..6ca7494 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ apscheduler~=3.10.1 -pyrogram~=2.0.101 +pyrogram~=2.0.102 pytimeparse~=1.1.8 aiofiles~=23.1.0 aiohttp~=3.8.4 From 8cc7808afbbaab99540929b4b0119a3422cc135b Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 22:14:41 +0100 Subject: [PATCH 55/98] Disabled slash-escape for .json --- modules/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/utils.py b/modules/utils.py index 34edf99..ab518d8 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -33,7 +33,11 @@ def jsonSave(contents: Any, filename: str) -> None: """Dumps dict/list arg1 to file arg2""" try: with open(filename, "w", encoding="utf8") as file: - file.write(dumps(contents, ensure_ascii=False, indent=4)) + file.write( + dumps( + contents, ensure_ascii=False, indent=4, escape_forward_slashes=False + ) + ) file.close() except Exception as exp: logWrite(f"Could not save json file {filename}: {exp}\n{print_exc()}") From bd2a1f0b12c70f6ab8066a9e1dbedcdf561817fb Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 22:23:02 +0100 Subject: [PATCH 56/98] WIP: New readme --- README.md | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5d80bca..ed5186b 100644 --- a/README.md +++ b/README.md @@ -48,18 +48,12 @@ To make this bot run at first you need to have a Python interpreter, Photos API, 6. Configure database and API: 1. Configure database: - 1. Change database host and port in keys `"database.host"` and `"database.port"`; - 2. Change database name to the one you like in `"database.name"`; - 3. If you've changed user and password to access the db you should also change `"database.user"` and `"database.password"` keys, otherwise leave them `null` (default). + 1. Change database host and port in keys `"database.host"` and `"database.port"`. For default local installation those will be `127.0.0.1` and `27017` respectively; + 2. Change database name to the one you like in `"database.name"`. It will be automatically created on start; + 3. If you've changed user and password to access the db, you should also change `"database.user"` and `"database.password"` keys, otherwise leave them `null` (default). 2. Configure Photos API: - 1. Create new user and album - 1. Register new user using API's `POST /users` request (`/docs#/default/user_create_users_post`). Please make sure you're using a valid email address if you've configured you API to use email confirmation; - 2. If you have email confirmation - activate your new user by using `PATCH /users/{user}/confirm` (`/docs#/default/user_confirm_users__user__confirm_patch`) or by using the link from the email; - 3. Now create a new album for your bot using your user. Click "Authorize" on the docs page of your API, enter your login and password, select "select all" in scopes section and click "Authorize". Now you can execute requests as your user; - 4. Go to `POST /albums` (`docs#/default/album_create_albums_post`) and create a friendly name for your album. No worries, only bot can see it so it doesn't matter what name you use; - 2. Configure bot to use that album - 1. Now you can configure your bot to use all of that. Change key `"posting.api.address"` to the http/https address accessible by the bot. Avoid using external address without a reason if API and the bot are on the same machine. If you want to see duplicates from the outside you can also change `"posting.api.address_external"` to the one accessible from the internet; - 2. Configure API user/password and your album name using `"posting.api.username"`, `"posting.api.password"` and `"posting.api.album"` keys. + 1. Change `"posting.api.address"` to the one your API servers uses; + 2. Run your bot using `python ./poster.py --create-user --create-album` to configure its new user and album. You can also use manual user and album creation described [in the wiki](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-API). You can also change username, password and album in`"posting.api"` to the user and album you have if you already have Photos API album and user set up. In that case you don't need to create a new one. 7. Add bot to the channel: To use your bot of course you need to have a channel or group otherwise makes no sense to have such a bot. [Here](https://stackoverflow.com/a/33497769) you can find a quick guide how to add your bot to a channel. After that simply set `"posting.channel"` to your channel's ID. @@ -68,7 +62,7 @@ To make this bot run at first you need to have a Python interpreter, Photos API, To make your bot post random content you need to configure `"posting.time"` with a list of "DD:MM" formatted strings or use `"posting.interval"` formatted as "XdXhXmXs". To use interval instead of selected time set `"posting.use_interval"` to `true`. 9. Good to go, run it! - Make sure MongoDB and Photos API are running and use `python ./main.py` to start it. + Make sure MongoDB and Photos API are running and use `python ./poster.py` to start it. Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux. Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. @@ -78,15 +72,13 @@ If you need any further instructions on how to configure your bot or you had any Of course bot also has them. You can perform some actions with them. -* `--move-sent` - allows you to move all sent files from queue to sent directories -* `--cleanup` - purge files in both `queue` and `sent` folders if they're sent. Requires `--confirm` argument -* `--cleanup-index` - purge all sent entries from index. Requires `--confirm` argument -* `--norun` - allows you to execute above arguments without triggering the bot start itself +* `--create-user` - create new API user. Requires config key `"posting.api.address"` to be set; +* `--create-album` - create new API album. Requires API address and user config (`"posting.api"`) to be complete. Examples: -* `python3 ./main.py --move-sent --norun` -* `python3 ./main.py --cleanup --confirm` +* `python ./poster.py --create-user` +* `python ./poster.py --create-user --create-album` ## Localization From e1c1b58ffbdd8537bc2b6773aec4d347c2242746 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 22:26:16 +0100 Subject: [PATCH 57/98] Added warning about readme not being ready yet --- README_uk.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README_uk.md b/README_uk.md index f349de3..28e7a29 100644 --- a/README_uk.md +++ b/README_uk.md @@ -5,6 +5,8 @@ Code style: black

+## ⚠️ Українська версія README dev гілки ще не готова! Користуйтесь англійською! ⚠️ + Цей бот використовується для однієї-єдиної задачі - розміщувати фотографії з мого особистого архіву. Ось його код, тож Ви також можете захостити бота самостійно та розважитися з ним. Тільки не очікуйте, що він ідеальним. Не буде. Але гей, Ви завжди можете його доробити під себе ;) ## Установка From c96f98560cc776155211392b0b3228ffb33111f4 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 22:29:53 +0100 Subject: [PATCH 58/98] WIP: New readme --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ed5186b..2a9a85b 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,12 @@ To make this bot run at first you need to have a Python interpreter, Photos API, 9. Good to go, run it! Make sure MongoDB and Photos API are running and use `python ./poster.py` to start it. Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux. - Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. + Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. + +## Tips and improvements + +* It's better to use virtual environments in most of production cases so there's also [a tutorial how to put your bot into venv](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-Virtual-Environment) and use it inside the virtual environment; +* You may also want to configure your bot to work as a systemd service instead. There's [a tutorial for that](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-Service) in the wiki. If you need any further instructions on how to configure your bot or you had any difficulties doing so - please use [wiki in this repository](https://git.end-play.xyz/profitroll/TelegramPoster/wiki) to get more detailed instructions. From c593be156d10ac20c1efb8beecf584e66cfb8c7c Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 22:30:35 +0100 Subject: [PATCH 59/98] Rearranged content --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2a9a85b..c6d3b3e 100644 --- a/README.md +++ b/README.md @@ -66,12 +66,7 @@ To make this bot run at first you need to have a Python interpreter, Photos API, Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux. Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. -## Tips and improvements - -* It's better to use virtual environments in most of production cases so there's also [a tutorial how to put your bot into venv](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-Virtual-Environment) and use it inside the virtual environment; -* You may also want to configure your bot to work as a systemd service instead. There's [a tutorial for that](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-Service) in the wiki. - -If you need any further instructions on how to configure your bot or you had any difficulties doing so - please use [wiki in this repository](https://git.end-play.xyz/profitroll/TelegramPoster/wiki) to get more detailed instructions. +If you need any further instructions on how to configure your bot or you had any difficulties doing so - please use [wiki in this repository](https://git.end-play.xyz/profitroll/TelegramPoster/wiki) to get more detailed instructions. ## Command line arguments @@ -85,6 +80,11 @@ Examples: * `python ./poster.py --create-user` * `python ./poster.py --create-user --create-album` +## Tips and improvements + +* It's better to use virtual environments in most of production cases so there's also [a tutorial how to put your bot into venv](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-Virtual-Environment) and use it inside the virtual environment; +* You may also want to configure your bot to work as a systemd service instead. There's [a tutorial for that](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-Service) in the wiki. + ## Localization Bot is capable of using custom locales. There are some that are pre-installed (English and Ukrainian), however you can add your own locales too. From 6ed367368287af8ebf375216a6550d40a11e5017 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 22:45:06 +0100 Subject: [PATCH 60/98] Fixed a typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c6d3b3e..4d6dce3 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ To make this bot run at first you need to have a Python interpreter, Photos API, 2. Install Photos API by following [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md) 2. Download the bot: - 1. `git clone -b dev https://git.end-play.xyz/profitroll/TelegramSender.git` (if you want to use git) - 2. `cd ./TelegramSender` + 1. `git clone -b dev https://git.end-play.xyz/profitroll/TelegramPoster.git` (if you want to use git) + 2. `cd ./TelegramPoster` 3. Install project's dependencies: `python -m pip install -r requirements.txt` From b74ce9ec89fab7a0ddf04ecc78aa1b2e8180f10d Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 22:58:41 +0100 Subject: [PATCH 61/98] Small fixes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d6dce3..18e7acb 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ To make this bot run at first you need to have a Python interpreter, Photos API, `python -m pip install -r requirements-optional.txt` These are not required but can make the bot run a bit faster -5. Configure your bot with a favorite text editor: +5. Configure "bot" and "owner" with your favorite text editor: `nano config.json` You can edit with vim, nano, on Windows it's Notepad or Notepad++. Whatever. If you don't know where to find bot_token and your id - here you can find some hints: [get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [get your id](https://www.alphr.com/telegram-find-user-id), [get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id). From 9bf4663b9e537edefa8de06a5670a1e2ff3071d1 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sun, 12 Mar 2023 22:58:55 +0100 Subject: [PATCH 62/98] Made execution order correct --- modules/cli.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/cli.py b/modules/cli.py index 2ea0be4..0e4768e 100644 --- a/modules/cli.py +++ b/modules/cli.py @@ -26,7 +26,7 @@ async def cli_create_user() -> None: email = input(f"Choose email for user '{username}': ").strip() password = input(f"Choose password for user '{username}': ").strip() try: - result = await create_user(username, email, password) + result_1 = await create_user(username, email, password) # asyncio.run(create_user(username, email, password)) configSet("username", username, "posting", "api") configSet("password", password, "posting", "api") @@ -41,6 +41,7 @@ async def cli_create_user() -> None: print("You're done!", flush=True) await http_session.close() exit() + return None async def cli_create_album() -> None: @@ -51,7 +52,7 @@ async def cli_create_album() -> None: name = input("Choose a name for your album: ").strip() title = input(f"Choose a title for album '{name}': ").strip() try: - result = await create_album(name, title) + result_2 = await create_album(name, title) # asyncio.run(create_album(name, title)) configSet("album", name, "posting", "api") except Exception as exp: @@ -61,6 +62,7 @@ async def cli_create_album() -> None: print("You're done!", flush=True) await http_session.close() exit() + return None if args.create_user or args.create_album: @@ -68,10 +70,9 @@ if args.create_user or args.create_album: tasks = [] if args.create_user: - tasks.append(loop.create_task(cli_create_user())) + loop.run_until_complete(asyncio.wait([loop.create_task(cli_create_user())])) if args.create_album: - tasks.append(loop.create_task(cli_create_album())) + loop.run_until_complete(asyncio.wait([loop.create_task(cli_create_album())])) - loop.run_until_complete(asyncio.wait(tasks)) loop.close() From bc6080e832fcac5efef081097067a7e9489d933f Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 13 Mar 2023 11:33:30 +0100 Subject: [PATCH 63/98] Added venv guide to README --- README.md | 44 ++++++++++++++++++++++++-------------------- docs/config_venv.md | 1 - 2 files changed, 24 insertions(+), 21 deletions(-) delete mode 100644 docs/config_venv.md diff --git a/README.md b/README.md index 18e7acb..9da5b42 100644 --- a/README.md +++ b/README.md @@ -30,39 +30,44 @@ To make this bot run at first you need to have a Python interpreter, Photos API, 2. Install Photos API by following [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md) 2. Download the bot: - 1. `git clone -b dev https://git.end-play.xyz/profitroll/TelegramPoster.git` (if you want to use git) - 2. `cd ./TelegramPoster` + 1. `git clone -b dev https://git.end-play.xyz/profitroll/TelegramPoster.git` (if you want to use git) + 2. `cd TelegramPoster` -3. Install project's dependencies: +3. Create virtual environment [Optional]: + 1. Install virtualenv module: `pip install virtualenv` + 2. Create venv: `python -m venv env` + 3. Activate it using `source venv/bin/activate` on Linux, `venv\Scripts\activate.bat` in CMD or `venv\Scripts\Activate.ps1` in PowerShell. + +4. Install project's dependencies: `python -m pip install -r requirements.txt` - Without installing those - bot cannot work at all + Without installing those - bot cannot work at all. -4. Install optional dependencies [Not required]: +5. Install optional dependencies [Optional]: `python -m pip install -r requirements-optional.txt` - These are not required but can make the bot run a bit faster + These are not required but can make the bot run a bit faster. -5. Configure "bot" and "owner" with your favorite text editor: +6. Configure "bot" and "owner" with your favorite text editor: `nano config.json` You can edit with vim, nano, on Windows it's Notepad or Notepad++. Whatever. If you don't know where to find bot_token and your id - here you can find some hints: [get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [get your id](https://www.alphr.com/telegram-find-user-id), [get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id). -6. Configure database and API: +7. Configure database and API: 1. Configure database: - 1. Change database host and port in keys `"database.host"` and `"database.port"`. For default local installation those will be `127.0.0.1` and `27017` respectively; - 2. Change database name to the one you like in `"database.name"`. It will be automatically created on start; + 1. Change database host and port in keys `"database.host"` and `"database.port"`. For default local installation those will be `127.0.0.1` and `27017` respectively + 2. Change database name to the one you like in `"database.name"`. It will be automatically created on start 3. If you've changed user and password to access the db, you should also change `"database.user"` and `"database.password"` keys, otherwise leave them `null` (default). 2. Configure Photos API: - 1. Change `"posting.api.address"` to the one your API servers uses; - 2. Run your bot using `python ./poster.py --create-user --create-album` to configure its new user and album. You can also use manual user and album creation described [in the wiki](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-API). You can also change username, password and album in`"posting.api"` to the user and album you have if you already have Photos API album and user set up. In that case you don't need to create a new one. + 1. Change `"posting.api.address"` to the one your API servers uses + 2. Run your bot using `python poster.py --create-user --create-album` to configure its new user and album. You can also use manual user and album creation described [in the wiki](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-API). You can also change username, password and album in`"posting.api"` to the user and album you have if you already have Photos API album and user set up. In that case you don't need to create a new one. -7. Add bot to the channel: +8. Add bot to the channel: To use your bot of course you need to have a channel or group otherwise makes no sense to have such a bot. [Here](https://stackoverflow.com/a/33497769) you can find a quick guide how to add your bot to a channel. After that simply set `"posting.channel"` to your channel's ID. -8. Configure posting time: +9. Configure posting time: To make your bot post random content you need to configure `"posting.time"` with a list of "DD:MM" formatted strings or use `"posting.interval"` formatted as "XdXhXmXs". To use interval instead of selected time set `"posting.use_interval"` to `true`. -9. Good to go, run it! - Make sure MongoDB and Photos API are running and use `python ./poster.py` to start it. +10. Good to go, run it! + Make sure MongoDB and Photos API are running and use `python poster.py` to start it. Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux. Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. @@ -77,13 +82,12 @@ Of course bot also has them. You can perform some actions with them. Examples: -* `python ./poster.py --create-user` -* `python ./poster.py --create-user --create-album` +* `python poster.py --create-user` +* `python poster.py --create-user --create-album` ## Tips and improvements -* It's better to use virtual environments in most of production cases so there's also [a tutorial how to put your bot into venv](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-Virtual-Environment) and use it inside the virtual environment; -* You may also want to configure your bot to work as a systemd service instead. There's [a tutorial for that](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-Service) in the wiki. +* You may want to configure your bot to work as a systemd service instead. There's [a tutorial for that](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-Service) in the wiki. ## Localization diff --git a/docs/config_venv.md b/docs/config_venv.md deleted file mode 100644 index ecdabd1..0000000 --- a/docs/config_venv.md +++ /dev/null @@ -1 +0,0 @@ -# Configuring virtual environment From 87d2a2c6d35a97c756eb1b93b11be06400d44fe6 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 13 Mar 2023 13:02:06 +0100 Subject: [PATCH 64/98] Improved README --- README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9da5b42..9949438 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,11 @@ This bot is used for one and only task - post pictures from my personal archive. ## Dependencies -For now bot requires [MongoDB](https://www.mongodb.com) and [PhotosAPI](https://git.end-play.xyz/profitroll/PhotosAPI) in order to function properly. Use [MongoDB's installation manual](https://www.mongodb.com/docs/manual/installation) and [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md) to install install them. +* [Python 3.7+](https://www.python.org) (3.9+ recommended) +* [MongoDB](https://www.mongodb.com) +* [PhotosAPI](https://git.end-play.xyz/profitroll/PhotosAPI) + +Use [MongoDB's installation manual](https://www.mongodb.com/docs/manual/installation) and [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md). Please note that Photos API also requires MongoDB so it makes sense to install and configure Mongo first. @@ -26,47 +30,58 @@ To make this bot run at first you need to have a Python interpreter, Photos API, > it in scripts you will use (`loop.sh`, `loop.bat`, `start.sh` and `start.bat`). 1. Install Mongo and Photos API: + 1. Install MongoDB by following [official installation manual](https://www.mongodb.com/docs/manual/installation) 2. Install Photos API by following [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md) 2. Download the bot: - 1. `git clone -b dev https://git.end-play.xyz/profitroll/TelegramPoster.git` (if you want to use git) + + 1. `git clone -b dev https://git.end-play.xyz/profitroll/TelegramPoster.git` (if you're using git) 2. `cd TelegramPoster` 3. Create virtual environment [Optional]: + 1. Install virtualenv module: `pip install virtualenv` 2. Create venv: `python -m venv env` 3. Activate it using `source venv/bin/activate` on Linux, `venv\Scripts\activate.bat` in CMD or `venv\Scripts\Activate.ps1` in PowerShell. 4. Install project's dependencies: + `python -m pip install -r requirements.txt` Without installing those - bot cannot work at all. 5. Install optional dependencies [Optional]: + `python -m pip install -r requirements-optional.txt` These are not required but can make the bot run a bit faster. 6. Configure "bot" and "owner" with your favorite text editor: + `nano config.json` You can edit with vim, nano, on Windows it's Notepad or Notepad++. Whatever. If you don't know where to find bot_token and your id - here you can find some hints: [get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [get your id](https://www.alphr.com/telegram-find-user-id), [get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id). 7. Configure database and API: + 1. Configure database: 1. Change database host and port in keys `"database.host"` and `"database.port"`. For default local installation those will be `127.0.0.1` and `27017` respectively 2. Change database name to the one you like in `"database.name"`. It will be automatically created on start 3. If you've changed user and password to access the db, you should also change `"database.user"` and `"database.password"` keys, otherwise leave them `null` (default). + 2. Configure Photos API: 1. Change `"posting.api.address"` to the one your API servers uses 2. Run your bot using `python poster.py --create-user --create-album` to configure its new user and album. You can also use manual user and album creation described [in the wiki](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-API). You can also change username, password and album in`"posting.api"` to the user and album you have if you already have Photos API album and user set up. In that case you don't need to create a new one. 8. Add bot to the channel: + To use your bot of course you need to have a channel or group otherwise makes no sense to have such a bot. [Here](https://stackoverflow.com/a/33497769) you can find a quick guide how to add your bot to a channel. After that simply set `"posting.channel"` to your channel's ID. 9. Configure posting time: + To make your bot post random content you need to configure `"posting.time"` with a list of "DD:MM" formatted strings or use `"posting.interval"` formatted as "XdXhXmXs". To use interval instead of selected time set `"posting.use_interval"` to `true`. 10. Good to go, run it! + Make sure MongoDB and Photos API are running and use `python poster.py` to start it. Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux. Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. From e85adeafd062d7a0ab4745571785d5081d5467ba Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Wed, 15 Mar 2023 21:50:06 +0100 Subject: [PATCH 65/98] Added info about venv to the README --- README.md | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 18e7acb..06f7e9e 100644 --- a/README.md +++ b/README.md @@ -26,42 +26,58 @@ To make this bot run at first you need to have a Python interpreter, Photos API, > it in scripts you will use (`loop.sh`, `loop.bat`, `start.sh` and `start.bat`). 1. Install Mongo and Photos API: + 1. Install MongoDB by following [official installation manual](https://www.mongodb.com/docs/manual/installation) 2. Install Photos API by following [Photos API's README](https://git.end-play.xyz/profitroll/PhotosAPI/src/branch/master/README.md) 2. Download the bot: + 1. `git clone -b dev https://git.end-play.xyz/profitroll/TelegramPoster.git` (if you want to use git) 2. `cd ./TelegramPoster` -3. Install project's dependencies: +3. Create virtual environment [Optional yet recommended]: + + 1. Install virtualenv module: `pip install virtualenv` + 2. Create venv: `python -m venv env` + 3. Activate it using `source venv/bin/activate` on Linux, `venv\Scripts\activate.bat` in CMD or `venv\Scripts\Activate.ps1` in PowerShell. + +4. Install project's dependencies: + `python -m pip install -r requirements.txt` Without installing those - bot cannot work at all -4. Install optional dependencies [Not required]: +5. Install optional dependencies [Not required]: + `python -m pip install -r requirements-optional.txt` These are not required but can make the bot run a bit faster -5. Configure "bot" and "owner" with your favorite text editor: +6. Configure "bot" and "owner" with your favorite text editor: + `nano config.json` You can edit with vim, nano, on Windows it's Notepad or Notepad++. Whatever. If you don't know where to find bot_token and your id - here you can find some hints: [get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [get your id](https://www.alphr.com/telegram-find-user-id), [get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id). -6. Configure database and API: +7. Configure database and API: + 1. Configure database: 1. Change database host and port in keys `"database.host"` and `"database.port"`. For default local installation those will be `127.0.0.1` and `27017` respectively; 2. Change database name to the one you like in `"database.name"`. It will be automatically created on start; 3. If you've changed user and password to access the db, you should also change `"database.user"` and `"database.password"` keys, otherwise leave them `null` (default). + 2. Configure Photos API: 1. Change `"posting.api.address"` to the one your API servers uses; 2. Run your bot using `python ./poster.py --create-user --create-album` to configure its new user and album. You can also use manual user and album creation described [in the wiki](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-API). You can also change username, password and album in`"posting.api"` to the user and album you have if you already have Photos API album and user set up. In that case you don't need to create a new one. -7. Add bot to the channel: +8. Add bot to the channel: + To use your bot of course you need to have a channel or group otherwise makes no sense to have such a bot. [Here](https://stackoverflow.com/a/33497769) you can find a quick guide how to add your bot to a channel. After that simply set `"posting.channel"` to your channel's ID. -8. Configure posting time: +9. Configure posting time: + To make your bot post random content you need to configure `"posting.time"` with a list of "DD:MM" formatted strings or use `"posting.interval"` formatted as "XdXhXmXs". To use interval instead of selected time set `"posting.use_interval"` to `true`. -9. Good to go, run it! +10. Good to go, run it! + Make sure MongoDB and Photos API are running and use `python ./poster.py` to start it. Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux. Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. From be8f0262d043ef1f07d0f0e6858758dfdef34207 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Wed, 15 Mar 2023 21:58:14 +0100 Subject: [PATCH 66/98] This commit closes #11 --- config.json | 6 ++++-- modules/sender.py | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index 96ebd2f..191f736 100644 --- a/config.json +++ b/config.json @@ -86,8 +86,10 @@ }, "caption": { "enabled": false, - "text": "sample text", - "link": null + "link": null, + "text": [ + "sample text" + ] }, "submission": { "timeout": 30, diff --git a/modules/sender.py b/modules/sender.py index e57dab6..ed4de5a 100644 --- a/modules/sender.py +++ b/modules/sender.py @@ -1,5 +1,6 @@ from datetime import datetime from os import makedirs, path +from random import choice from shutil import rmtree from traceback import format_exc from uuid import uuid4 @@ -129,9 +130,9 @@ async def send_content(app: PosterClient) -> None: if configGet("enabled", "caption"): if configGet("link", "caption") != None: - caption = f"{caption}[{configGet('text', 'caption')}]({configGet('link', 'caption')})" + caption = f"{caption}[{choice(configGet('text', 'caption'))}]({configGet('link', 'caption')})" else: - caption = f"{caption}{configGet('text', 'caption')}" + caption = f"{caption}{choice(configGet('text', 'caption'))}" else: caption = caption From 62e0a4986c56bfb1c8515645d00b4e502cb981ff Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Thu, 16 Mar 2023 12:58:57 +0100 Subject: [PATCH 67/98] WIP: ID returns --- modules/api_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/api_client.py b/modules/api_client.py index 22c772b..cb7e750 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -96,7 +96,7 @@ async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: async def upload_pic( filepath: str, allow_duplicates: bool = False, token: Union[str, None] = None -) -> Tuple[bool, list]: +) -> Tuple[bool, list, str]: token = await authorize() if token is None else token try: pic_name = path.basename(filepath) @@ -135,10 +135,10 @@ async def upload_pic( duplicates.append( f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{(await response.json())["access_token"]}?id={index}' ) - return True, duplicates + return True, duplicates, (await response.json())["id"] except Exception as exp: print_exc() - return False, [] + return False, [], "" async def find_pic( From 4ec69c2a05d87dd8232cf99e4b7f59ce22d6a54a Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 16 Mar 2023 15:03:14 +0100 Subject: [PATCH 68/98] This commit closes #9 --- classes/poster_client.py | 8 +++++--- config.json | 1 + modules/api_client.py | 16 +++++++++------- plugins/callbacks/submission.py | 12 +++++++++--- plugins/handlers/submission.py | 8 ++++++-- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/classes/poster_client.py b/classes/poster_client.py index 642bede..ba0aace 100644 --- a/classes/poster_client.py +++ b/classes/poster_client.py @@ -1,6 +1,6 @@ from os import path, remove, sep from shutil import rmtree -from typing import Union +from typing import Tuple, Union from pyrogram.client import Client from pyrogram.types import Message from classes.exceptions import SubmissionDuplicatesError, SubmissionUnavailableError @@ -18,7 +18,9 @@ class PosterClient(Client): self.owner = configGet("owner") self.admins = configGet("admins") + [configGet("owner")] - async def submit_photo(self, id: str) -> Union[Message, None]: + async def submit_photo( + self, id: str + ) -> Tuple[Union[Message, None], Union[str, None]]: db_entry = col_submitted.find_one({"_id": ObjectId(id)}) submission = None @@ -87,7 +89,7 @@ class PosterClient(Client): f"Could not delete '{filepath}' on submission accepted", debug=True ) - return submission + return submission, response[2] async def ban_user(self, id: int) -> None: pass diff --git a/config.json b/config.json index 191f736..692e452 100644 --- a/config.json +++ b/config.json @@ -96,6 +96,7 @@ "file_size": 15728640, "tmp_size": 15728640, "allow_duplicates": false, + "send_uploaded_id": false, "require_confirmation": { "users": true, "admins": true diff --git a/modules/api_client.py b/modules/api_client.py index cb7e750..cd8af06 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -96,7 +96,7 @@ async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: async def upload_pic( filepath: str, allow_duplicates: bool = False, token: Union[str, None] = None -) -> Tuple[bool, list, str]: +) -> Tuple[bool, list, Union[str, None]]: token = await authorize() if token is None else token try: pic_name = path.basename(filepath) @@ -117,6 +117,7 @@ async def upload_pic( headers={"Authorization": f"Bearer {token}"}, data=formdata, ) + response_json = await response.json() if response.status != 200 and response.status != 409: logWrite( f"Could not upload '{filepath}' to API: HTTP {response.status} with message '{response.content}'" @@ -124,21 +125,22 @@ async def upload_pic( raise SubmissionUploadError( str(filepath), response.status, response.content ) + id = response_json["id"] if "id" in await response.json() else None duplicates = [] - if "duplicates" in (await response.json()): - for index, duplicate in enumerate((await response.json())["duplicates"]): - if (await response.json())["access_token"] is None: + if "duplicates" in response_json: + for index, duplicate in enumerate(response_json)["duplicates"]: # type: ignore + if response_json["access_token"] is None: duplicates.append( f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}' ) else: duplicates.append( - f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{(await response.json())["access_token"]}?id={index}' + f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/token/photo/{response_json["access_token"]}?id={index}' ) - return True, duplicates, (await response.json())["id"] + return True, duplicates, id except Exception as exp: print_exc() - return False, [], "" + return False, [], None async def find_pic( diff --git a/plugins/callbacks/submission.py b/plugins/callbacks/submission.py index 0f902c8..f182b76 100644 --- a/plugins/callbacks/submission.py +++ b/plugins/callbacks/submission.py @@ -40,9 +40,9 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): ) return - if submission is not None: - await submission.reply_text( - locale("sub_yes", "message", locale=submission.from_user.language_code), + if submission[0] is not None: + await submission[0].reply_text( + locale("sub_yes", "message", locale=submission[0].from_user.language_code), quote=True, ) elif db_entry is not None: @@ -73,6 +73,12 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): ] ] ) + + if configGet("send_uploaded_id", "submission"): + await clb.message.edit_caption( + clb.message.caption + f"\n\nID: `{submission[1]}`" + ) + await clb.message.edit_reply_markup( reply_markup=InlineKeyboardMarkup(edited_markup) ) diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index 759a2c0..112f767 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -188,12 +188,14 @@ async def get_submission(app: PosterClient, msg: Message): and configGet("admins", "submission", "require_confirmation") is False ): try: - await app.submit_photo(str(inserted.inserted_id)) + submitted = await app.submit_photo(str(inserted.inserted_id)) await msg.reply_text( locale("sub_yes_auto", "message", locale=user_locale), disable_notification=True, quote=True, ) + if configGet("send_uploaded_id", "submission"): + caption += f"\n\nID: `{submitted[1]}`" await msg.copy(app.owner, caption=caption, disable_notification=True) return except SubmissionDuplicatesError as exp: @@ -212,12 +214,14 @@ async def get_submission(app: PosterClient, msg: Message): and configGet("users", "submission", "require_confirmation") is False ): try: - await app.submit_photo(str(inserted.inserted_id)) + submitted = await app.submit_photo(str(inserted.inserted_id)) await msg.reply_text( locale("sub_yes_auto", "message", locale=user_locale), disable_notification=True, quote=True, ) + if configGet("send_uploaded_id", "submission"): + caption += f"\n\nID: `{submitted[1]}`" await msg.copy(app.owner, caption=caption) return except SubmissionDuplicatesError as exp: From b5e3abd4adc94a52f5ce1c7fba3a647f79f0f878 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 16 Mar 2023 16:32:26 +0100 Subject: [PATCH 69/98] WIP: default config replacement for (#12) --- .gitignore | 3 +- README.md | 6 +- config.json => config_example.json | 0 modules/default_config.json | 121 +++++++++++++++++++++++++++++ modules/utils.py | 19 ++++- 5 files changed, 142 insertions(+), 7 deletions(-) rename config.json => config_example.json (100%) create mode 100644 modules/default_config.json diff --git a/.gitignore b/.gitignore index d4b6024..9ac0792 100644 --- a/.gitignore +++ b/.gitignore @@ -168,4 +168,5 @@ cython_debug/ # Project cache data -logs \ No newline at end of file +logs +config.json \ No newline at end of file diff --git a/README.md b/README.md index 9949438..cbf728c 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,10 @@ To make this bot run at first you need to have a Python interpreter, Photos API, 6. Configure "bot" and "owner" with your favorite text editor: - `nano config.json` - You can edit with vim, nano, on Windows it's Notepad or Notepad++. Whatever. + 1. Copy file `config_example.json` to `config.json` + 2. Open `config.json` using your favorite text editor. For example `nano config.json`, but you can edit with vim, nano, on Windows it's Notepad or Notepad++. Whatever + 3. Change `"owner"`, `"bot.api_id"`, `"bot.api_hash"` and `"bot.bot_token"` keys' values. + If you don't know where to find bot_token and your id - here you can find some hints: [get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [get your id](https://www.alphr.com/telegram-find-user-id), [get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id). 7. Configure database and API: diff --git a/config.json b/config_example.json similarity index 100% rename from config.json rename to config_example.json diff --git a/modules/default_config.json b/modules/default_config.json new file mode 100644 index 0000000..692e452 --- /dev/null +++ b/modules/default_config.json @@ -0,0 +1,121 @@ +{ + "locale": "en", + "locale_log": "en", + "locale_fallback": "en", + "owner": 0, + "admins": [], + "bot": { + "api_id": 0, + "api_hash": "", + "bot_token": "" + }, + "database": { + "user": null, + "password": null, + "host": "127.0.0.1", + "port": 27017, + "name": "tgposter" + }, + "mode": { + "post": true, + "submit": true + }, + "reports": { + "sent": false, + "error": true, + "startup": true, + "shutdown": true + }, + "logging": { + "size": 512, + "location": "logs" + }, + "locations": { + "tmp": "tmp", + "data": "data", + "cache": "cache", + "sent": "data/sent", + "queue": "data/queue", + "index": "data/index.json", + "locale": "locale" + }, + "posting": { + "channel": 0, + "silent": false, + "move_sent": false, + "use_interval": false, + "interval": "1h30m", + "page_size": 300, + "submitted_caption": { + "enabled": true, + "ignore_admins": true, + "text": "#submitted" + }, + "extensions": { + "photo": [ + "jpg", + "png", + "gif", + "jpeg" + ], + "video": [ + "mp4", + "avi", + "mkv", + "webm", + "mov" + ] + }, + "time": [ + "08:00", + "10:00", + "12:00", + "14:00", + "16:00", + "18:00", + "20:00", + "22:00" + ], + "api": { + "address": "http://localhost:8054", + "address_external": "https://photos.domain.com", + "username": "", + "password": "", + "album": "" + } + }, + "caption": { + "enabled": false, + "link": null, + "text": [ + "sample text" + ] + }, + "submission": { + "timeout": 30, + "file_size": 15728640, + "tmp_size": 15728640, + "allow_duplicates": false, + "send_uploaded_id": false, + "require_confirmation": { + "users": true, + "admins": true + }, + "mime_types": [ + "image/png", + "image/gif", + "image/jpeg", + "video/mp4", + "video/quicktime" + ] + }, + "commands": [ + "start", + "rules" + ], + "commands_admin": [ + "import", + "export", + "reboot" + ] +} \ No newline at end of file diff --git a/modules/utils.py b/modules/utils.py index ab518d8..9c863f4 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -1,4 +1,4 @@ -from os import kill +from os import kill, path from os import name as osname from os import sep from sys import exit @@ -73,9 +73,20 @@ def configGet(key: str, *args: str): * any: Value of provided key """ this_dict = jsonLoad("config.json") - this_key = this_dict - for dict_key in args: - this_key = this_key[dict_key] + try: + this_key = this_dict + for dict_key in args: + this_key = this_key[dict_key] + except KeyError: + print( + f"Could not find config key '{key}' under path {args}: falling back to default config", + flush=True, + ) + fallback_dict = jsonLoad(path.join("modules", "default_config.json")) + this_key = fallback_dict + for dict_key in args: + this_key = this_key[dict_key] + configSet(key, this_key[key], args) return this_key[key] From 95351f247c735b1397e138baa20a29e5dad943e5 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 17 Mar 2023 13:52:16 +0100 Subject: [PATCH 70/98] This commit closed #12 --- modules/default_config.json | 121 ------------------------------------ modules/utils.py | 86 ++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 124 deletions(-) delete mode 100644 modules/default_config.json diff --git a/modules/default_config.json b/modules/default_config.json deleted file mode 100644 index 692e452..0000000 --- a/modules/default_config.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "locale": "en", - "locale_log": "en", - "locale_fallback": "en", - "owner": 0, - "admins": [], - "bot": { - "api_id": 0, - "api_hash": "", - "bot_token": "" - }, - "database": { - "user": null, - "password": null, - "host": "127.0.0.1", - "port": 27017, - "name": "tgposter" - }, - "mode": { - "post": true, - "submit": true - }, - "reports": { - "sent": false, - "error": true, - "startup": true, - "shutdown": true - }, - "logging": { - "size": 512, - "location": "logs" - }, - "locations": { - "tmp": "tmp", - "data": "data", - "cache": "cache", - "sent": "data/sent", - "queue": "data/queue", - "index": "data/index.json", - "locale": "locale" - }, - "posting": { - "channel": 0, - "silent": false, - "move_sent": false, - "use_interval": false, - "interval": "1h30m", - "page_size": 300, - "submitted_caption": { - "enabled": true, - "ignore_admins": true, - "text": "#submitted" - }, - "extensions": { - "photo": [ - "jpg", - "png", - "gif", - "jpeg" - ], - "video": [ - "mp4", - "avi", - "mkv", - "webm", - "mov" - ] - }, - "time": [ - "08:00", - "10:00", - "12:00", - "14:00", - "16:00", - "18:00", - "20:00", - "22:00" - ], - "api": { - "address": "http://localhost:8054", - "address_external": "https://photos.domain.com", - "username": "", - "password": "", - "album": "" - } - }, - "caption": { - "enabled": false, - "link": null, - "text": [ - "sample text" - ] - }, - "submission": { - "timeout": 30, - "file_size": 15728640, - "tmp_size": 15728640, - "allow_duplicates": false, - "send_uploaded_id": false, - "require_confirmation": { - "users": true, - "admins": true - }, - "mime_types": [ - "image/png", - "image/gif", - "image/jpeg", - "video/mp4", - "video/quicktime" - ] - }, - "commands": [ - "start", - "rules" - ], - "commands_admin": [ - "import", - "export", - "reboot" - ] -} \ No newline at end of file diff --git a/modules/utils.py b/modules/utils.py index 9c863f4..9ba6585 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -9,6 +9,86 @@ from ujson import JSONDecodeError, dumps, loads from modules.logger import logWrite +default_config = { + "locale": "en", + "locale_log": "en", + "locale_fallback": "en", + "owner": 0, + "admins": [], + "bot": {"api_id": 0, "api_hash": "", "bot_token": ""}, + "database": { + "user": None, + "password": None, + "host": "127.0.0.1", + "port": 27017, + "name": "tgposter", + }, + "mode": {"post": True, "submit": True}, + "reports": {"sent": False, "error": True, "startup": True, "shutdown": True}, + "logging": {"size": 512, "location": "logs"}, + "locations": { + "tmp": "tmp", + "data": "data", + "cache": "cache", + "sent": "data/sent", + "queue": "data/queue", + "index": "data/index.json", + "locale": "locale", + }, + "posting": { + "channel": 0, + "silent": False, + "move_sent": False, + "use_interval": False, + "interval": "1h30m", + "page_size": 300, + "submitted_caption": { + "enabled": True, + "ignore_admins": True, + "text": "#submitted", + }, + "extensions": { + "photo": ["jpg", "png", "gif", "jpeg"], + "video": ["mp4", "avi", "mkv", "webm", "mov"], + }, + "time": [ + "08:00", + "10:00", + "12:00", + "14:00", + "16:00", + "18:00", + "20:00", + "22:00", + ], + "api": { + "address": "http://localhost:8054", + "address_external": "https://photos.domain.com", + "username": "", + "password": "", + "album": "", + }, + }, + "caption": {"enabled": False, "link": None, "text": ["sample text"]}, + "submission": { + "timeout": 30, + "file_size": 15728640, + "tmp_size": 15728640, + "allow_duplicates": False, + "send_uploaded_id": False, + "require_confirmation": {"users": True, "admins": True}, + "mime_types": [ + "image/png", + "image/gif", + "image/jpeg", + "video/mp4", + "video/quicktime", + ], + }, + "commands": ["start", "rules"], + "commands_admin": ["import", "export", "reboot"], +} + def jsonLoad(filename: str) -> Any: """Loads arg1 as json and returns its contents""" @@ -77,16 +157,16 @@ def configGet(key: str, *args: str): this_key = this_dict for dict_key in args: this_key = this_key[dict_key] + this_key[key] except KeyError: print( f"Could not find config key '{key}' under path {args}: falling back to default config", flush=True, ) - fallback_dict = jsonLoad(path.join("modules", "default_config.json")) - this_key = fallback_dict + this_key = default_config for dict_key in args: this_key = this_key[dict_key] - configSet(key, this_key[key], args) + configSet(key, this_key[key], *args) return this_key[key] From 717586a9f07f4b481f18961c0f44af878f91a0c3 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 17 Mar 2023 14:13:28 +0100 Subject: [PATCH 71/98] Improved logging --- modules/api_client.py | 2 +- plugins/callbacks/submission.py | 22 ++++++++++++++++++++++ plugins/handlers/submission.py | 16 ++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/modules/api_client.py b/modules/api_client.py index cd8af06..4a6720f 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -78,7 +78,7 @@ async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue', headers={"Authorization": f"Bearer {token}"}, ) - print(await resp.json(), flush=True) + logWrite("Random pic response: " + str(await resp.json()), debug=True) if resp.status != 200: logWrite( f'Could not get photos from album {configGet("album", "posting", "api")}: HTTP {resp.status}' diff --git a/plugins/callbacks/submission.py b/plugins/callbacks/submission.py index f182b76..7727f25 100644 --- a/plugins/callbacks/submission.py +++ b/plugins/callbacks/submission.py @@ -7,6 +7,7 @@ from classes.poster_client import PosterClient from classes.user import PosterUser from modules.app import app +from modules.logger import logWrite from modules.utils import configGet, locale from modules.database import col_submitted from bson import ObjectId @@ -38,6 +39,10 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): ), quote=True, ) + logWrite( + f"Submission with ID '{fullclb[2]}' could not be accepted because of the duplicates: {str(exp.duplicates)}", + debug=True, + ) return if submission[0] is not None: @@ -83,6 +88,11 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): reply_markup=InlineKeyboardMarkup(edited_markup) ) + logWrite( + f"Submission with ID '{fullclb[2]}' accepted and uploaded with ID '{submission[1]}'", + debug=True, + ) + # try: # if configGet("api_based", "mode") is True: # media = await app.download_media(submission, file_name=configGet("tmp", "locations")+sep) @@ -173,6 +183,10 @@ async def callback_query_no(app: PosterClient, clb: CallbackQuery): await clb.message.edit_reply_markup( reply_markup=InlineKeyboardMarkup(edited_markup) ) + logWrite( + f"Submission with ID '{fullclb[2]}' rejected", + debug=True, + ) @app.on_callback_query(filters.regex("sub_block_[\s\S]*")) @@ -200,6 +214,10 @@ async def callback_query_block(app: PosterClient, clb: CallbackQuery): await clb.message.edit_reply_markup( reply_markup=InlineKeyboardMarkup(edited_markup) ) + logWrite( + f"User {fullclb[2]} has been blocked", + debug=True, + ) @app.on_callback_query(filters.regex("sub_unblock_[\s\S]*")) @@ -227,3 +245,7 @@ async def callback_query_unblock(app: PosterClient, clb: CallbackQuery): await clb.message.edit_reply_markup( reply_markup=InlineKeyboardMarkup(edited_markup) ) + logWrite( + f"User {fullclb[2]} has been unblocked", + debug=True, + ) diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index 112f767..fd06049 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -43,6 +43,10 @@ async def get_submission(app: PosterClient, msg: Message): return if msg.document is not None: + logWrite( + f"User {msg.from_user.id} is trying to submit a file of type '{msg.document.mime_type}' with name '{msg.document.file_name}' and size of {msg.document.file_size / 1024 / 1024} MB", + debug=True, + ) if msg.document.mime_type not in configGet("mime_types", "submission"): await msg.reply_text( locale("mime_not_allowed", "message", locale=user_locale), @@ -65,6 +69,10 @@ async def get_submission(app: PosterClient, msg: Message): ) # , msg.document.file_name if msg.video is not None: + logWrite( + f"User {msg.from_user.id} is trying to submit a video with name '{msg.video.file_name}' and size of {msg.video.file_size / 1024 / 1024} MB", + debug=True, + ) if msg.video.file_size > configGet("file_size", "submission"): await msg.reply_text( locale("document_too_large", "message", locale=user_locale).format( @@ -78,6 +86,10 @@ async def get_submission(app: PosterClient, msg: Message): contents = msg.video.file_id, SubmissionType.VIDEO # , msg.video.file_name if msg.animation is not None: + logWrite( + f"User {msg.from_user.id} is trying to submit an animation with name '{msg.animation.file_name}' and size of {msg.animation.file_size / 1024 / 1024} MB", + debug=True, + ) if msg.animation.file_size > configGet("file_size", "submission"): await msg.reply_text( locale("document_too_large", "message", locale=user_locale).format( @@ -94,6 +106,10 @@ async def get_submission(app: PosterClient, msg: Message): ) # , msg.animation.file_name if msg.photo is not None: + logWrite( + f"User {msg.from_user.id} is trying to submit a photo with ID '{msg.photo.file_id}' and size of {msg.photo.file_size / 1024 / 1024} MB", + debug=True, + ) contents = msg.photo.file_id, SubmissionType.PHOTO # , "please_generate" if save_tmp is not None: From 1f345e87f71c4971a647e5519826210ec7267063 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 17 Mar 2023 14:44:37 +0100 Subject: [PATCH 72/98] Improved logging --- locale/en.json | 10 ++++++-- locale/uk.json | 8 ++++++- modules/api_client.py | 53 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/locale/en.json b/locale/en.json index f795bf8..6bb4bac 100644 --- a/locale/en.json +++ b/locale/en.json @@ -65,7 +65,7 @@ }, "console": { "shutdown": "Shutting down bot with pid {0}", - "startup":"Starting with pid {0}", + "startup": "Starting with pid {0}", "keyboard_interrupt": "\nShutting down...", "exception_occured": "Exception {0} happened on task execution", "post_sent": "Sent {0} to {1} with caption {2} and silently {3}", @@ -94,6 +94,12 @@ "cleanup_completed": "Performed cleanup of the sent files", "cleanup_unathorized": "Requested cleanup of sent files but not authorized. Please pass '--confirm' to perform that", "cleanup_index_completed": "Performed cleanup of sent files index", - "cleanup_index_unathorized": "Requested cleanup of sent files index but not authorized. Please pass '--confirm' to perform that" + "cleanup_index_unathorized": "Requested cleanup of sent files index but not authorized. Please pass '--confirm' to perform that", + "random_pic_response": "Random pic response: {0}", + "random_pic_error_code": "Could not get photos from album {0}: HTTP {1}", + "random_pic_error_debug": "Could not get photos from '{0}/albums/{1}/photos?q=&page_size={2}&caption=queue' using token '{3}': HTTP {4}", + "find_pic_error": "Could not find image with name '{0}' and caption '{1}' due to: {2}", + "pic_upload_error": "Could not upload '{0}' to API: HTTP {1} with message '{2}'", + "api_creds_invalid": "Incorrect API credentials! Could not login into '{0}' using login '{1}': HTTP {2}" } } \ No newline at end of file diff --git a/locale/uk.json b/locale/uk.json index 507adf7..d199753 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -94,6 +94,12 @@ "cleanup_completed": "Виконано очищення надісланих файлів", "cleanup_unathorized": "Надіслано запит на очищення надісланих файлів, але не авторизовано. Для цього надайте аргумент '--confirm'", "cleanup_index_completed": "Виконано очищення індексу надісланих файлів", - "cleanup_index_unathorized": "Надіслано запит на очищення індексу надісланих файлів, але не авторизовано. Для цього надайте аргумент '--confirm'" + "cleanup_index_unathorized": "Надіслано запит на очищення індексу надісланих файлів, але не авторизовано. Для цього надайте аргумент '--confirm'", + "random_pic_response": "Відповідь на пошук випадкової картинки: {0}", + "random_pic_error_code": "Не вдалося отримати фото з альбому {0}: HTTP {1}", + "random_pic_error_debug": "Не вдалося отримати фотографії з '{0}/albums/{1}/photos?q=&page_size={2}&caption=queue', використовуючи токен '{3}': HTTP {4}", + "find_pic_error": "Не вдалося знайти зображення з назвою '{0}' та підписом '{1}' через: {2}", + "pic_upload_error": "Не вдалося завантажити '{0}' до API: HTTP {1} з повідомленням '{2}'", + "api_creds_invalid": "Невірні облікові дані API! Не вдалося увійти в '{0}' за допомогою логіна '{1}': HTTP {2}" } } \ No newline at end of file diff --git a/modules/api_client.py b/modules/api_client.py index 4a6720f..4ae6b41 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -20,7 +20,7 @@ from classes.exceptions import ( UserCreationError, ) from modules.logger import logWrite -from modules.utils import configGet +from modules.utils import configGet, locale http_session = ClientSession( json_serialize=dumps, @@ -52,7 +52,15 @@ async def authorize() -> str: ) if not response.ok: logWrite( - f'Incorrect API credentials! Could not login into "{configGet("address", "posting", "api")}" using login "{configGet("username", "posting", "api")}": HTTP {response.status}' + locale( + "api_creds_invalid", + "console", + locale=configGet("locale_log").format( + configGet("address", "posting", "api"), + configGet("username", "posting", "api"), + response.status, + ), + ) ) raise ValueError async with aiofiles.open( @@ -78,13 +86,34 @@ async def random_pic(token: Union[str, None] = None) -> Tuple[str, str]: f'{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue', headers={"Authorization": f"Bearer {token}"}, ) - logWrite("Random pic response: " + str(await resp.json()), debug=True) + logWrite( + locale("random_pic_response", "console", locale=configGet("locale_log")).format( + await resp.json() + ), + debug=True, + ) if resp.status != 200: logWrite( - f'Could not get photos from album {configGet("album", "posting", "api")}: HTTP {resp.status}' + locale( + "random_pic_error_code", + "console", + locale=configGet("locale_log").format( + configGet("album", "posting", "api"), resp.status + ), + ), ) logWrite( - f'Could not get photos from "{configGet("address", "posting", "api")}/albums/{configGet("album", "posting", "api")}/photos?q=&page_size={configGet("page_size", "posting")}&caption=queue" using token "{token}": HTTP {resp.status}', + locale( + "random_pic_error_debug", + "console", + locale=configGet("locale_log").format( + configGet("address", "posting", "api"), + configGet("album", "posting", "api"), + configGet("page_size", "posting"), + token, + resp.status, + ), + ), debug=True, ) raise ValueError @@ -120,7 +149,13 @@ async def upload_pic( response_json = await response.json() if response.status != 200 and response.status != 409: logWrite( - f"Could not upload '{filepath}' to API: HTTP {response.status} with message '{response.content}'" + locale( + "pic_upload_error", + "console", + locale=configGet("locale_log").format( + filepath, response.status, response.content + ), + ), ) raise SubmissionUploadError( str(filepath), response.status, response.content @@ -161,7 +196,11 @@ async def find_pic( return (await response.json())["results"] except Exception as exp: logWrite( - f"Could not find image with name '{name}' and caption '{caption}' due to: {exp}" + locale( + "find_pic_error", + "console", + locale=configGet("locale_log").format(name, caption, exp), + ), ) return None From 06b6b49f43b967ac280eea03bb0d83e1decfffb5 Mon Sep 17 00:00:00 2001 From: profitroll Date: Fri, 17 Mar 2023 14:51:41 +0100 Subject: [PATCH 73/98] This commit closes #13 --- locale/en.json | 7 ++++++- locale/uk.json | 7 ++++++- plugins/callbacks/submission.py | 33 ++++++++++++++++++++++++++++----- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/locale/en.json b/locale/en.json index 6bb4bac..6be4d9c 100644 --- a/locale/en.json +++ b/locale/en.json @@ -100,6 +100,11 @@ "random_pic_error_debug": "Could not get photos from '{0}/albums/{1}/photos?q=&page_size={2}&caption=queue' using token '{3}': HTTP {4}", "find_pic_error": "Could not find image with name '{0}' and caption '{1}' due to: {2}", "pic_upload_error": "Could not upload '{0}' to API: HTTP {1} with message '{2}'", - "api_creds_invalid": "Incorrect API credentials! Could not login into '{0}' using login '{1}': HTTP {2}" + "api_creds_invalid": "Incorrect API credentials! Could not login into '{0}' using login '{1}': HTTP {2}", + "user_blocked": "User {0} has been blocked", + "user_unblocked": "User {0} has been unblocked", + "submission_accepted": "Submission with ID '{0}' accepted and uploaded with ID '{1}'", + "submission_rejected": "Submission with ID '{0}' rejected", + "submission_duplicate": "Submission with ID '{0}' could not be accepted because of the duplicates: {1}" } } \ No newline at end of file diff --git a/locale/uk.json b/locale/uk.json index d199753..2e88c54 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -100,6 +100,11 @@ "random_pic_error_debug": "Не вдалося отримати фотографії з '{0}/albums/{1}/photos?q=&page_size={2}&caption=queue', використовуючи токен '{3}': HTTP {4}", "find_pic_error": "Не вдалося знайти зображення з назвою '{0}' та підписом '{1}' через: {2}", "pic_upload_error": "Не вдалося завантажити '{0}' до API: HTTP {1} з повідомленням '{2}'", - "api_creds_invalid": "Невірні облікові дані API! Не вдалося увійти в '{0}' за допомогою логіна '{1}': HTTP {2}" + "api_creds_invalid": "Невірні облікові дані API! Не вдалося увійти в '{0}' за допомогою логіна '{1}': HTTP {2}", + "user_blocked": "Користувача {0} було заблоковано", + "user_unblocked": "Користувача {0} було розблоковано", + "submission_accepted": "Подання з ID '{0}' прийнято та завантажено з ID '{1}'", + "submission_rejected": "Подання з ID '{0}' відхилено", + "submission_duplicate": "Подання з ID '{0}' не може бути прийнято через наявність дублікатів: {1}" } } \ No newline at end of file diff --git a/plugins/callbacks/submission.py b/plugins/callbacks/submission.py index 7727f25..33178b1 100644 --- a/plugins/callbacks/submission.py +++ b/plugins/callbacks/submission.py @@ -40,7 +40,14 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): quote=True, ) logWrite( - f"Submission with ID '{fullclb[2]}' could not be accepted because of the duplicates: {str(exp.duplicates)}", + locale( + "submission_duplicate", + "console", + locale=configGet("locale_log").format( + fullclb[2], + str(exp.duplicates), + ), + ), debug=True, ) return @@ -89,7 +96,11 @@ async def callback_query_yes(app: PosterClient, clb: CallbackQuery): ) logWrite( - f"Submission with ID '{fullclb[2]}' accepted and uploaded with ID '{submission[1]}'", + locale( + "submission_accepted", + "console", + locale=configGet("locale_log").format(fullclb[2], submission[1]), + ), debug=True, ) @@ -184,7 +195,11 @@ async def callback_query_no(app: PosterClient, clb: CallbackQuery): reply_markup=InlineKeyboardMarkup(edited_markup) ) logWrite( - f"Submission with ID '{fullclb[2]}' rejected", + locale( + "submission_rejected", + "console", + locale=configGet("locale_log").format(fullclb[2]), + ), debug=True, ) @@ -215,7 +230,11 @@ async def callback_query_block(app: PosterClient, clb: CallbackQuery): reply_markup=InlineKeyboardMarkup(edited_markup) ) logWrite( - f"User {fullclb[2]} has been blocked", + locale( + "user_blocked", + "console", + locale=configGet("locale_log").format(fullclb[2]), + ), debug=True, ) @@ -246,6 +265,10 @@ async def callback_query_unblock(app: PosterClient, clb: CallbackQuery): reply_markup=InlineKeyboardMarkup(edited_markup) ) logWrite( - f"User {fullclb[2]} has been unblocked", + locale( + "user_unblocked", + "console", + locale=configGet("locale_log").format(fullclb[2]), + ), debug=True, ) From a4323981fbb953fca2f299c0c60387ad1767e822 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sat, 18 Mar 2023 20:53:26 +0100 Subject: [PATCH 74/98] WIP: Media import/export --- modules/app.py | 3 +++ plugins/commands/photos.py | 11 ++++++++++- plugins/handlers/submission.py | 4 +++- poster.py | 3 +++ requirements-optional.txt | 2 +- requirements.txt | 3 ++- 6 files changed, 22 insertions(+), 4 deletions(-) diff --git a/modules/app.py b/modules/app.py index 91b6ecd..4216f27 100644 --- a/modules/app.py +++ b/modules/app.py @@ -1,5 +1,6 @@ from modules.utils import configGet from classes.poster_client import PosterClient +from convopyro import Conversation app = PosterClient( "duptsiaposter", @@ -7,3 +8,5 @@ app = PosterClient( api_id=configGet("api_id", "bot"), api_hash=configGet("api_hash", "bot"), ) + +Conversation(app) diff --git a/plugins/commands/photos.py b/plugins/commands/photos.py index 7b7dbc0..20963fb 100644 --- a/plugins/commands/photos.py +++ b/plugins/commands/photos.py @@ -1,5 +1,6 @@ from pyrogram import filters from pyrogram.types import Message +from convopyro import listen_message from classes.poster_client import PosterClient from modules.app import app @@ -8,7 +9,15 @@ from modules.app import app @app.on_message(~filters.scheduled & filters.command(["import"], prefixes=["", "/"])) async def cmd_import(app: PosterClient, msg: Message): if msg.from_user.id in app.admins: - pass + print("Listening to file...", flush=True) + answer = await app.listen.Message( + filters.document, id=filters.user(msg.from_user.id), timeout=None + ) + if answer is None: + return + print("Gotcha", flush=True) + await answer.reply_text("Gotcha") + return @app.on_message(~filters.scheduled & filters.command(["export"], prefixes=["", "/"])) diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index fd06049..244bf67 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -49,7 +49,9 @@ async def get_submission(app: PosterClient, msg: Message): ) if msg.document.mime_type not in configGet("mime_types", "submission"): await msg.reply_text( - locale("mime_not_allowed", "message", locale=user_locale), + locale("mime_not_allowed", "message", locale=user_locale).format( + ", ".join(configGet("mime_types", "submission")) + ), quote=True, ) return diff --git a/poster.py b/poster.py index faa6624..f314a48 100644 --- a/poster.py +++ b/poster.py @@ -64,6 +64,9 @@ if configGet("submit", "mode"): from plugins.commands.mode_submit import * from plugins.handlers.submission import * +if configGet("post", "mode"): + from plugins.commands.photos import * + # if configGet("api_based", "mode"): # from modules.api_client import authorize # =========================================================================================================================================== diff --git a/requirements-optional.txt b/requirements-optional.txt index 717d475..c5a65b8 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -1 +1 @@ -tgcrypto~=1.2.5 \ No newline at end of file +tgcrypto==1.2.5 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 6ca7494..87314de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ apscheduler~=3.10.1 -pyrogram~=2.0.102 pytimeparse~=1.1.8 +convopyro==0.5 +pyrogram~=2.0.102 aiofiles~=23.1.0 aiohttp~=3.8.4 psutil~=5.9.4 From 1749d49a5235db97e00222e0cb7956837e772428 Mon Sep 17 00:00:00 2001 From: profitroll Date: Sat, 18 Mar 2023 21:17:04 +0100 Subject: [PATCH 75/98] Fixed enumerate error --- modules/api_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api_client.py b/modules/api_client.py index 4ae6b41..9cb6dc0 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -163,7 +163,7 @@ async def upload_pic( id = response_json["id"] if "id" in await response.json() else None duplicates = [] if "duplicates" in response_json: - for index, duplicate in enumerate(response_json)["duplicates"]: # type: ignore + for index, duplicate in enumerate(response_json["duplicates"]): # type: ignore if response_json["access_token"] is None: duplicates.append( f'`{duplicate["id"]}`:\n{configGet("address_external", "posting", "api")}/photos/{duplicate["id"]}' From 03b6bbe03993c7233715e3ac4b3421f48d51f042 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 20 Mar 2023 12:03:03 +0100 Subject: [PATCH 76/98] Prototype: Files import --- modules/app.py | 2 + modules/utils.py | 28 +++++++- plugins/commands/photos.py | 118 ++++++++++++++++++++++++++++++--- plugins/handlers/submission.py | 11 +-- 4 files changed, 144 insertions(+), 15 deletions(-) diff --git a/modules/app.py b/modules/app.py index 4216f27..2724747 100644 --- a/modules/app.py +++ b/modules/app.py @@ -10,3 +10,5 @@ app = PosterClient( ) Conversation(app) + +users_with_context = [] diff --git a/modules/utils.py b/modules/utils.py index 9ba6585..1a68b8d 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -1,10 +1,12 @@ -from os import kill, path +from os import kill, makedirs from os import name as osname -from os import sep +from os import path, sep from sys import exit from traceback import print_exc from typing import Any +from zipfile import ZipFile +import aiofiles from ujson import JSONDecodeError, dumps, loads from modules.logger import logWrite @@ -207,6 +209,28 @@ def locale(key: str, *args: str, locale=configGet("locale")): return f'⚠️ Locale in config is invalid: could not get "{key}" in {str(args)} from locale "{locale}"' +async def extract_and_save(handle: ZipFile, filename: str, destpath: str): + """Extract and save file from archive + + Args: + * handle (ZipFile): ZipFile handler + * filename (str): File base name + * path (str): Path where to store + """ + data = handle.read(filename) + filepath = path.join(destpath, filename) + try: + makedirs(path.dirname(filepath), exist_ok=True) + async with aiofiles.open(filepath, "wb") as fd: + await fd.write(data) + logWrite(f"Unzipped {filename}", debug=True) + except IsADirectoryError: + makedirs(filepath, exist_ok=True) + except FileNotFoundError: + pass + return + + try: from psutil import Process except ModuleNotFoundError: diff --git a/plugins/commands/photos.py b/plugins/commands/photos.py index 20963fb..9c38d2a 100644 --- a/plugins/commands/photos.py +++ b/plugins/commands/photos.py @@ -1,22 +1,122 @@ +import asyncio +from glob import iglob +from os import getcwd, makedirs, path, remove +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 pyrogram import filters from pyrogram.types import Message -from convopyro import listen_message from classes.poster_client import PosterClient -from modules.app import app +from modules.api_client import upload_pic +from modules.app import app, users_with_context +from modules.logger import logWrite +from modules.utils import configGet, extract_and_save @app.on_message(~filters.scheduled & filters.command(["import"], prefixes=["", "/"])) async def cmd_import(app: PosterClient, msg: Message): if msg.from_user.id in app.admins: - print("Listening to file...", flush=True) - answer = await app.listen.Message( - filters.document, id=filters.user(msg.from_user.id), timeout=None - ) - if answer is None: + global users_with_context + if msg.from_user.id not in users_with_context: + users_with_context.append(msg.from_user.id) + else: return - print("Gotcha", flush=True) - await answer.reply_text("Gotcha") + await msg.reply_text( + f"Alright, please send me a zip archive with your media to be imported. Use /cancel if you want to abort this operation." + ) + 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("No response, aborting import.", quote=True) + return + if answer.text == "/cancel": + await answer.reply_text("Okay, aborting.") + return + if answer.document is None: + await answer.reply_text( + "File to import must be a zip archive. Aborting.", quote=True + ) + return + if answer.document.mime_type != "application/zip": + await answer.reply_text( + "Provided file is not supported. Please send `application/zip`. Aborting.", + quote=True, + ) + return + if disk_usage(getcwd())[2] < (answer.document.file_size) * 3: + await msg.reply_text( + f"You archive is `{answer.document.file_size//(2**30)} GiB` big, but system has only `{disk_usage(getcwd())[2]//(2**30)} GiB` free. Unpacking may take even more space. Aborting." + ) + return + tmp_dir = str(uuid4()) + logWrite( + f"Importing '{answer.document.file_name}' file {answer.document.file_size} bytes big (TMP ID {tmp_dir})" + ) + makedirs(path.join(configGet("tmp", "locations"), tmp_dir), exist_ok=True) + tmp_path = path.join(configGet("tmp", "locations"), answer.document.file_id) + downloading = await answer.reply_text("Okay, downloading...", quote=True) + await app.download_media(answer, file_name=tmp_path) + await downloading.edit("Downloaded, unpacking...") + try: + with ZipFile(tmp_path, "r") as handle: + tasks = [ + extract_and_save( + handle, name, path.join(configGet("tmp", "locations"), tmp_dir) + ) + for name in handle.namelist() + ] + _ = await asyncio.gather(*tasks) + except Exception as exp: + logWrite( + f"Could not import '{answer.document.file_name}' due to {exp}: {format_exc}" + ) + await answer.reply_text( + f"Could not unpack the archive\n\nException: {exp}\n\nTraceback:\n```python\n{format_exc}\n```" + ) + return + logWrite(f"Downloaded '{answer.document.file_name}' - awaiting upload") + await downloading.edit("Unpacked, uploading...") + remove(tmp_path) + + for filename in iglob( + path.join(configGet("tmp", "locations"), tmp_dir) + "**/**", recursive=True + ): + if not path.isfile(filename): + continue + # upload filename + uploaded = await upload_pic(filename) + if uploaded[0] is False: + logWrite( + f"Could not upload '{filename}' from '{path.join(configGet('tmp', 'locations'), tmp_dir)}'. Duplicates: {str(uploaded[1])}", + debug=True, + ) + if len(uploaded[1]) > 0: + await msg.reply_text( + f"Could not upload `{path.basename(filename)}` because there're duplicates on server.", + disable_notification=True, + ) + else: + await msg.reply_text( + f"Could not upload `{path.basename(filename)}`. Probably disallowed filetype", + disable_notification=True, + ) + else: + logWrite( + f"Uploaded '{filename}' from '{path.join(configGet('tmp', 'locations'), tmp_dir)}' and got ID {uploaded[2]}", + debug=True, + ) + + await downloading.delete() + logWrite( + f"Removing '{path.join(configGet('tmp', 'locations'), tmp_dir)}' after uploading", + debug=True, + ) + rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) + await answer.reply_text("Done.", quote=True) return diff --git a/plugins/handlers/submission.py b/plugins/handlers/submission.py index 244bf67..c28c67d 100644 --- a/plugins/handlers/submission.py +++ b/plugins/handlers/submission.py @@ -4,17 +4,17 @@ from traceback import format_exc from uuid import uuid4 from pyrogram import filters -from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message from pyrogram.enums.chat_action import ChatAction +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message + +from classes.enums.submission_types import SubmissionType from classes.exceptions import SubmissionDuplicatesError from classes.poster_client import PosterClient from classes.user import PosterUser - -from modules.app import app +from modules.app import app, users_with_context from modules.database import col_banned, col_submitted from modules.logger import logWrite from modules.utils import configGet, locale -from classes.enums.submission_types import SubmissionType @app.on_message( @@ -24,6 +24,9 @@ from classes.enums.submission_types import SubmissionType | filters.document ) async def get_submission(app: PosterClient, msg: Message): + global users_with_context + if msg.from_user.id in users_with_context: + return try: if col_banned.find_one({"user": msg.from_user.id}) is not None: return From 20666fc0f8726ddc360f3312ca8540a5af5523dd Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 20 Mar 2023 13:55:42 +0100 Subject: [PATCH 77/98] Improved shutdown --- config_example.json | 2 +- plugins/callbacks/shutdown.py | 29 +++++++++++++ plugins/commands/general.py | 47 ++++++++++++++------ poster.py | 81 ++++++++++++++++++++++++++++++----- 4 files changed, 134 insertions(+), 25 deletions(-) create mode 100644 plugins/callbacks/shutdown.py diff --git a/config_example.json b/config_example.json index 692e452..70d9612 100644 --- a/config_example.json +++ b/config_example.json @@ -116,6 +116,6 @@ "commands_admin": [ "import", "export", - "reboot" + "shutdown" ] } \ No newline at end of file diff --git a/plugins/callbacks/shutdown.py b/plugins/callbacks/shutdown.py new file mode 100644 index 0000000..595471a --- /dev/null +++ b/plugins/callbacks/shutdown.py @@ -0,0 +1,29 @@ +from os import getpid, makedirs, path +from time import time +from modules.app import app +from pyrogram import filters +from pyrogram.types import CallbackQuery +from classes.poster_client import PosterClient +from modules.scheduler import scheduler +from modules.logger import logWrite +from modules.utils import configGet, jsonSave, locale + + +@app.on_callback_query(filters.regex("shutdown")) +async def callback_query_nothing(app: PosterClient, clb: CallbackQuery): + if clb.from_user.id in app.admins: + pid = getpid() + logWrite(f"Shutting down bot with pid {pid}") + await clb.answer() + await clb.message.reply_text( + locale("shutdown", "message", locale=clb.from_user.language_code).format( + pid + ), + ) + scheduler.shutdown() + makedirs(configGet("cache", "locations"), exist_ok=True) + jsonSave( + {"timestamp": time()}, + path.join(configGet("cache", "locations"), "shutdown_time"), + ) + exit() diff --git a/plugins/commands/general.py b/plugins/commands/general.py index 7b489ba..e839482 100644 --- a/plugins/commands/general.py +++ b/plugins/commands/general.py @@ -1,24 +1,45 @@ -from os import getpid +from os import getpid, makedirs, path +from time import time from pyrogram import filters +from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton + from classes.poster_client import PosterClient -from pyrogram.types import Message - -from modules.app import app +from modules.app import app, users_with_context from modules.logger import logWrite -from modules.utils import configGet, killProc, locale +from modules.scheduler import scheduler +from modules.utils import configGet, jsonSave, locale -@app.on_message( - ~filters.scheduled & filters.command(["kill", "die", "reboot"], prefixes=["", "/"]) -) +@app.on_message(~filters.scheduled & filters.command(["shutdown"], prefixes=["", "/"])) async def cmd_kill(app: PosterClient, msg: Message): if msg.from_user.id in app.admins: + global users_with_context + if len(users_with_context) > 0: + await msg.reply_text( + f"There're {len(users_with_context)} unfinished users' contexts. If you turn off the bot, those will be lost. Please confirm shutdown using a button below.", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + "Confirm shutdown", callback_data="shutdown" + ) + ] + ] + ), + ) + return pid = getpid() - logWrite( - locale("shutdown", "console", locale=configGet("locale")).format(str(pid)) - ) + logWrite(f"Shutting down bot with pid {pid}") await msg.reply_text( - locale("shutdown", "message", locale=configGet("locale")).format(str(pid)) + locale("shutdown", "message", locale=msg.from_user.language_code).format( + pid + ), ) - killProc(pid) + scheduler.shutdown() + makedirs(configGet("cache", "locations"), exist_ok=True) + jsonSave( + {"timestamp": time()}, + path.join(configGet("cache", "locations"), "shutdown_time"), + ) + exit() diff --git a/poster.py b/poster.py index f314a48..ea43709 100644 --- a/poster.py +++ b/poster.py @@ -1,14 +1,18 @@ -from os import getpid +from os import getpid, path +from datetime import datetime from sys import exit +from time import time from modules.cli import * from modules.logger import logWrite from modules.scheduler import scheduler -from modules.utils import configGet, killProc, locale +from modules.utils import configGet, jsonLoad, jsonSave, killProc, locale # Import =================================================================================================================================== try: from pyrogram.sync import idle + from pyrogram.errors import bad_request_400 + from dateutil.relativedelta import relativedelta from modules.app import app except ModuleNotFoundError: @@ -57,6 +61,7 @@ pid = getpid() # Imports ================================================================================================================================== from plugins.commands.general import * +from plugins.callbacks.shutdown import * if configGet("submit", "mode"): from plugins.callbacks.nothing import * @@ -112,15 +117,55 @@ if configGet("post", "mode"): # asyncio.run(main()) if __name__ == "__main__": - logWrite(locale("startup", "console", locale=configGet("locale")).format(str(pid))) + logWrite(locale("startup", "console").format(str(pid))) app.start() if configGet("startup", "reports"): - app.send_message( - app.owner, - locale("startup", "message", locale=configGet("locale")).format(str(pid)), - ) + try: + if path.exists(path.join(configGet("cache", "locations"), "shutdown_time")): + downtime = relativedelta( + datetime.now(), + datetime.fromtimestamp( + jsonLoad( + path.join(configGet("cache", "locations"), "shutdown_time") + )["timestamp"] + ), + ) + if downtime.days >= 1: + app.send_message( + configGet("owner"), + locale( + "startup_downtime_days", + "message", + ).format(pid, downtime.days), + ) + elif downtime.hours >= 1: + app.send_message( + configGet("owner"), + locale( + "startup_downtime_hours", + "message", + ).format(pid, downtime.hours), + ) + else: + app.send_message( + configGet("owner"), + locale( + "startup_downtime_minutes", + "message", + locale=configGet("locale"), + ).format(pid, downtime.minutes), + ) + else: + app.send_message( + configGet("owner"), + locale("startup", "message").format(pid), + ) + except bad_request_400.PeerIdInvalid: + logWrite( + f"Could not send startup message to bot owner. Perhaps user has not started the bot yet." + ) if configGet("post", "mode"): scheduler.start() @@ -132,10 +177,24 @@ if __name__ == "__main__": idle() - app.send_message( - app.owner, - locale("shutdown", "message", locale=configGet("locale")).format(str(pid)), + try: + app.send_message( + configGet("owner"), + locale("shutdown", "message").format(pid), + ) + except bad_request_400.PeerIdInvalid: + logWrite( + f"Could not send shutdown message to bot owner. Perhaps user has not started the bot yet." + ) + + makedirs(configGet("cache", "locations"), exist_ok=True) + jsonSave( + {"timestamp": time()}, + path.join(configGet("cache", "locations"), "shutdown_time"), ) - logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid))) + + logWrite(locale("shutdown", "console").format(str(pid))) + + scheduler.shutdown() killProc(pid) From fba52bcfc4455a5d8473677c3188ea3785d47809 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 20 Mar 2023 13:55:55 +0100 Subject: [PATCH 78/98] Updated docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cbf728c..d8f3606 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ To make this bot run at first you need to have a Python interpreter, Photos API, Make sure MongoDB and Photos API are running and use `python poster.py` to start it. Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux. - Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/reboot` command. + Additionally there are `loop.sh` and `loop.bat` available if you want your bot to start again after being stopped or after using `/shutdown` command. If you need any further instructions on how to configure your bot or you had any difficulties doing so - please use [wiki in this repository](https://git.end-play.xyz/profitroll/TelegramPoster/wiki) to get more detailed instructions. From 1684c5177c21ef84a13130a43c16c41b4792bedf Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 20 Mar 2023 13:56:18 +0100 Subject: [PATCH 79/98] Replaced /reboot with /shutdown --- locale/en.json | 5 ++++- locale/uk.json | 5 ++++- modules/utils.py | 2 +- requirements.txt | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/locale/en.json b/locale/en.json index 6be4d9c..e563ea5 100644 --- a/locale/en.json +++ b/locale/en.json @@ -9,13 +9,16 @@ "export": "Get .zip archive with all photos", "remove": "Delete photo by its ID", "purge": "Completely purge bot's queue", - "reboot": "Restart the bot" + "shutdown": "Restart the bot" }, "message": { "start": "Hi and welcome!\n\nYou can submit your pictures and videos here. We'll review and add them, if we like them. Make sure you send your stuff one at a time and have chosen media that corresponds to our rules.\n\nYou can also write something to us in the description field. We'll send it with the submission itself, if needed.\n\nAlso, make sure you follow the /rules of submission, otherwise your submission will be declined. In case of spam/abuse you may even be blocked.\n\nHave fun and happy submitting!", "rules": "Photos submission rules:\n1. No porn, only erotics and aesthetics\n2. Nipples are semi-allowed, should be either veiled or barely visible\n3. Genitalia strictly prohibited, but labia prints on clothes or nice pubes/panties/butts - are fine\n4. Submitting russians is forbidden", "shutdown": "Shutting down bot with pid `{0}`", "startup": "Starting with pid `{0}`", + "startup_downtime_minutes": "Starting with pid `{0}` (was down for {1} m.)", + "startup_downtime_hours": "Starting with pid `{0}` (was down for {1} h.)", + "startup_downtime_days": "Starting with pid `{0}` (was down for {1} d.)", "sub_yes": "✅ Submission approved and accepted", "sub_yes_auto": "✅ Submission automatically accepted", "sub_no": "❌ Submission reviewed and declined", diff --git a/locale/uk.json b/locale/uk.json index 2e88c54..9496311 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -9,13 +9,16 @@ "export": "Отримати .zip архів з усіма фотографіями", "remove": "Видалити фото за його ID", "purge": "Повністю видалити всю чергу бота", - "reboot": "Перезапустити бота" + "shutdown": "Перезапустити бота" }, "message": { "start": "Привіт і ласкаво просимо!\n\nТут можна пропонувати свої фотографії та відео. Ми переглянемо та додамо їх, якщо вони нам сподобаються. Переконайтеся, що ви надсилаєте свої матеріали по одному та вибираєте медіа, які відповідають нашим правилам.\n\nВи також можете написати нам щось у полі опису. За потреби ми надішлемо це разом із самим фото.\n\nКрім того, переконайтеся, що ви дотримуєтеся /rules (правил) подання, інакше вашу пропозицію буде відхилено. У разі спаму/зловживань вас можуть навіть заблокувати.\n\nГарного дня та щасливого надсилання!", "rules": "Правила пропонування фото:\n1. Ніякого порно, тільки еротика та естетика\n2. Соски можна, але або завуальовані, або зовсім ледь помітні\n3. Геніталії суворо ні, а ось відбитки статевих губ на одязі або гарні лобочки/трусики/попки - без проблем\n4. Пропонувати русню заборонено", "shutdown": "Вимкнення бота з підом `{0}`", "startup": "Запуск бота з підом `{0}`", + "startup_downtime_minutes": "Запуск бота з підом `{0}` (лежав {1} хв.)", + "startup_downtime_hours": "Запуск бота з підом `{0}` (лежав {1} год.)", + "startup_downtime_days": "Запуск бота з підом `{0}` (лежав {1} дн.)", "sub_yes": "✅ Подання схвалено та прийнято", "sub_yes_auto": "✅ Подання автоматично прийнято", "sub_no": "❌ Подання розглянуто та відхилено", diff --git a/modules/utils.py b/modules/utils.py index 1a68b8d..0256377 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -88,7 +88,7 @@ default_config = { ], }, "commands": ["start", "rules"], - "commands_admin": ["import", "export", "reboot"], + "commands_admin": ["import", "export", "shutdown"], } diff --git a/requirements.txt b/requirements.txt index 87314de..a50f2cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +python_dateutil==2.8.2 apscheduler~=3.10.1 pytimeparse~=1.1.8 convopyro==0.5 From 56adb16a3e23a4da67d4f852b6f247061d50bea1 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 20 Mar 2023 13:59:22 +0100 Subject: [PATCH 80/98] Replaced strings --- locale/en.json | 2 +- locale/uk.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locale/en.json b/locale/en.json index e563ea5..d673070 100644 --- a/locale/en.json +++ b/locale/en.json @@ -9,7 +9,7 @@ "export": "Get .zip archive with all photos", "remove": "Delete photo by its ID", "purge": "Completely purge bot's queue", - "shutdown": "Restart the bot" + "shutdown": "Turn off the bot" }, "message": { "start": "Hi and welcome!\n\nYou can submit your pictures and videos here. We'll review and add them, if we like them. Make sure you send your stuff one at a time and have chosen media that corresponds to our rules.\n\nYou can also write something to us in the description field. We'll send it with the submission itself, if needed.\n\nAlso, make sure you follow the /rules of submission, otherwise your submission will be declined. In case of spam/abuse you may even be blocked.\n\nHave fun and happy submitting!", diff --git a/locale/uk.json b/locale/uk.json index 9496311..638d495 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -9,7 +9,7 @@ "export": "Отримати .zip архів з усіма фотографіями", "remove": "Видалити фото за його ID", "purge": "Повністю видалити всю чергу бота", - "shutdown": "Перезапустити бота" + "shutdown": "Вимкнути бота" }, "message": { "start": "Привіт і ласкаво просимо!\n\nТут можна пропонувати свої фотографії та відео. Ми переглянемо та додамо їх, якщо вони нам сподобаються. Переконайтеся, що ви надсилаєте свої матеріали по одному та вибираєте медіа, які відповідають нашим правилам.\n\nВи також можете написати нам щось у полі опису. За потреби ми надішлемо це разом із самим фото.\n\nКрім того, переконайтеся, що ви дотримуєтеся /rules (правил) подання, інакше вашу пропозицію буде відхилено. У разі спаму/зловживань вас можуть навіть заблокувати.\n\nГарного дня та щасливого надсилання!", From 399fc5050d479b8a756b674ed66f7fa3036bf8ea Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 20 Mar 2023 14:50:57 +0100 Subject: [PATCH 81/98] Updated user messages --- locale/en.json | 17 ++++++++-- locale/uk.json | 17 ++++++++-- plugins/commands/photos.py | 65 ++++++++++++++++++++++++++++++-------- 3 files changed, 79 insertions(+), 20 deletions(-) diff --git a/locale/en.json b/locale/en.json index d673070..bb0762b 100644 --- a/locale/en.json +++ b/locale/en.json @@ -40,9 +40,21 @@ "post_low": "Low amount of content: `There are only {0} files left in the queue.`", "api_creds_invalid": "Could not authorize API access. Please check whether provided in config file are valid and update them if they're not.", "sub_wip": "Post submission is now WIP. It will be available again in a few days. Thank you for your patience.", - "sub_duplicates_found": "__TO_BE_ADDED__", "sub_error": "⚠️ Could not upload this image due to bot error. Admins are advised.", - "sub_error_admin": "User {0} could not submit photo without additional confirmation due to:\n```\n{1}\n```" + "sub_error_admin": "User {0} could not submit photo without additional confirmation due to:\n```\n{1}\n```", + "import_request": "Alright, please send me a zip archive with your media to be imported. Use /cancel if you want to abort this operation.", + "import_ignored": "No response, aborting import.", + "import_abort": "Import aborted.", + "import_invalid_media": "File to import must be a zip archive. Aborting.", + "import_invalid_mime": "Provided file is not supported. Please send `application/zip`. Aborting.", + "import_too_big": "You archive is `{0} GiB` big, but system has only `{1} GiB` free. Unpacking may take even more space. Aborting.", + "import_downloading": "Downloading archive...", + "import_unpacking": "Unpacking archive...", + "import_unpack_error": "Could not unpack the archive\n\nException: {0}\n\nTraceback:\n```python\n{1}\n```", + "import_uploading": "Uploading archive contents...", + "import_upload_error_duplicate": "Could not upload `{0}` because there're duplicates on server.", + "import_upload_error_other": "Could not upload `{0}`. Probably disallowed filetype.", + "import_finished": "Import finished." }, "button": { "sub_yes": "✅ Accept", @@ -62,7 +74,6 @@ "sub_msg_unavail": "Submission message no longer exist", "sub_media_unavail": "Could not download submission", "sub_done": "You've already decided what to do with submission", - "sub_upload_failed": "__TO_BE_ADDED__", "sub_duplicates_found": "There're duplicates in bot's database", "nothing": "🏁 This action is already finished" }, diff --git a/locale/uk.json b/locale/uk.json index 638d495..f41ba8e 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -40,9 +40,21 @@ "post_low": "Мала кількість контенту: `Залишилось всього {0} файлів в черзі.`", "api_creds_invalid": "Не вдалося авторизувати запит до API. Будь ласка, перевірте чи дані авторизації в конфігураційному файлі вірні та оновіть їх, якщо це не так.", "sub_wip": "Подання постів зараз знаходиться у розробці. Він буде знову доступний через кілька днів. Дякуємо за ваше терпіння.", - "sub_duplicates_found": "__TO_BE_ADDED__", "sub_error": "⚠️ Не вдалось завантажити фото через помилку бота. Адміністрацію повідомлено.", - "sub_error_admin": "Користувач {0} не зміг надіслати фото без додаткової перевірки через помилку:\n```\n{1}\n```" + "sub_error_admin": "Користувач {0} не зміг надіслати фото без додаткової перевірки через помилку:\n```\n{1}\n```", + "import_request": "Гаразд, будь ласка, надішліть zip-архів з медіа для імпортування. Використовуйте /cancel, якщо ви хочете перервати цю операцію.", + "import_ignored": "Немає відповіді, перериваємо імпорт.", + "import_abort": "Імпорт перервано.", + "import_invalid_media": "Файл для імпорту має бути zip-архівом. Перериваємо.", + "import_invalid_mime": "Наданий файл не підтримується. Будь ласка, надішліть `application/zip`. Перервано.", + "import_too_big": "Ваш архів має розмір `{0} GiB`, але система має лише `{1} GiB` вільних. Розпакування може зайняти значно більше місця. Перервано.", + "import_downloading": "Завантажуємо архів...", + "import_unpacking": "Розпаковуємо архів...", + "import_unpack_error": "Не вдалося розпакувати архів\n\nПомилка: {0}\n\nTraceback:\n```python\n{1}\n```", + "import_uploading": "Завантажуємо вміст архіву...", + "import_upload_error_duplicate": "Не вдалося завантажити `{0}`, оскільки на сервері є дублікати.", + "import_upload_error_other": "Не вдалося завантажити `{0}`. Ймовірно, заборонений тип файлу.", + "import_finished": "Імпорт завершено." }, "button": { "sub_yes": "✅ Прийняти", @@ -62,7 +74,6 @@ "sub_msg_unavail": "Повідомлення більше не існує", "sub_media_unavail": "Не вдалося завантажити подання", "sub_done": "Ви вже обрали що зробити з цим поданням", - "sub_upload_failed": "__TO_BE_ADDED__", "sub_duplicates_found": "Знайдено дублікати в базі даних бота", "nothing": "🏁 Цю дію вже було завершено" }, diff --git a/plugins/commands/photos.py b/plugins/commands/photos.py index 9c38d2a..0737093 100644 --- a/plugins/commands/photos.py +++ b/plugins/commands/photos.py @@ -14,7 +14,7 @@ from classes.poster_client import PosterClient from modules.api_client import upload_pic from modules.app import app, users_with_context from modules.logger import logWrite -from modules.utils import configGet, extract_and_save +from modules.utils import configGet, extract_and_save, locale @app.on_message(~filters.scheduled & filters.command(["import"], prefixes=["", "/"])) @@ -26,30 +26,47 @@ async def cmd_import(app: PosterClient, msg: Message): else: return await msg.reply_text( - f"Alright, please send me a zip archive with your media to be imported. Use /cancel if you want to abort this operation." + locale("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: - await msg.reply_text("No response, aborting import.", quote=True) + await msg.reply_text( + locale("import_ignored", "message", locale=msg.from_user.language_code), + quote=True, + ) return if answer.text == "/cancel": - await answer.reply_text("Okay, aborting.") + await answer.reply_text( + locale("import_abort", "message", locale=msg.from_user.language_code) + ) return if answer.document is None: await answer.reply_text( - "File to import must be a zip archive. Aborting.", quote=True + locale( + "import_invalid_media", + "message", + locale=msg.from_user.language_code, + ), + quote=True, ) return if answer.document.mime_type != "application/zip": await answer.reply_text( - "Provided file is not supported. Please send `application/zip`. Aborting.", + locale( + "import_invalid_mime", "message", locale=msg.from_user.language_code + ), quote=True, ) return if disk_usage(getcwd())[2] < (answer.document.file_size) * 3: await msg.reply_text( - f"You archive is `{answer.document.file_size//(2**30)} GiB` big, but system has only `{disk_usage(getcwd())[2]//(2**30)} GiB` free. Unpacking may take even more space. Aborting." + locale( + "import_too_big", "message", locale=msg.from_user.language_code + ).format( + answer.document.file_size // (2**30), + disk_usage(getcwd())[2] // (2**30), + ) ) return tmp_dir = str(uuid4()) @@ -58,9 +75,14 @@ async def cmd_import(app: PosterClient, msg: Message): ) makedirs(path.join(configGet("tmp", "locations"), tmp_dir), exist_ok=True) tmp_path = path.join(configGet("tmp", "locations"), answer.document.file_id) - downloading = await answer.reply_text("Okay, downloading...", quote=True) + downloading = await answer.reply_text( + locale("import_downloading", "message", locale=msg.from_user.language_code), + quote=True, + ) await app.download_media(answer, file_name=tmp_path) - await downloading.edit("Downloaded, unpacking...") + await downloading.edit( + locale("import_unpacking", "message", locale=msg.from_user.language_code) + ) try: with ZipFile(tmp_path, "r") as handle: tasks = [ @@ -75,11 +97,15 @@ async def cmd_import(app: PosterClient, msg: Message): f"Could not import '{answer.document.file_name}' due to {exp}: {format_exc}" ) await answer.reply_text( - f"Could not unpack the archive\n\nException: {exp}\n\nTraceback:\n```python\n{format_exc}\n```" + locale( + "import_unpack_error", "message", locale=msg.from_user.language_code + ).format(exp, format_exc()) ) return logWrite(f"Downloaded '{answer.document.file_name}' - awaiting upload") - await downloading.edit("Unpacked, uploading...") + await downloading.edit( + locale("import_uploading", "message", locale=msg.from_user.language_code) + ) remove(tmp_path) for filename in iglob( @@ -96,12 +122,20 @@ async def cmd_import(app: PosterClient, msg: Message): ) if len(uploaded[1]) > 0: await msg.reply_text( - f"Could not upload `{path.basename(filename)}` because there're duplicates on server.", + locale( + "import_upload_error_duplicate", + "message", + locale=msg.from_user.language_code, + ).format(path.basename(filename)), disable_notification=True, ) else: await msg.reply_text( - f"Could not upload `{path.basename(filename)}`. Probably disallowed filetype", + locale( + "import_upload_error_other", + "message", + locale=msg.from_user.language_code, + ).format(path.basename(filename)), disable_notification=True, ) else: @@ -116,7 +150,10 @@ async def cmd_import(app: PosterClient, msg: Message): debug=True, ) rmtree(path.join(configGet("tmp", "locations"), tmp_dir), ignore_errors=True) - await answer.reply_text("Done.", quote=True) + await answer.reply_text( + locale("import_finished", "message", locale=msg.from_user.language_code), + quote=True, + ) return From f82c7b309f9b7721befc21b87421fe999caf10a8 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 21 Mar 2023 14:34:25 +0100 Subject: [PATCH 82/98] Added media removal --- config_example.json | 1 + locale/en.json | 9 ++++++-- locale/uk.json | 9 ++++++-- modules/api_client.py | 20 +++++++++++++++++- plugins/commands/photos.py | 43 ++++++++++++++++++++++++++++++++++++-- 5 files changed, 75 insertions(+), 7 deletions(-) diff --git a/config_example.json b/config_example.json index 70d9612..bd335e4 100644 --- a/config_example.json +++ b/config_example.json @@ -116,6 +116,7 @@ "commands_admin": [ "import", "export", + "remove", "shutdown" ] } \ No newline at end of file diff --git a/locale/en.json b/locale/en.json index bb0762b..8bc568f 100644 --- a/locale/en.json +++ b/locale/en.json @@ -42,7 +42,7 @@ "sub_wip": "Post submission is now WIP. It will be available again in a few days. Thank you for your patience.", "sub_error": "⚠️ Could not upload this image due to bot error. Admins are advised.", "sub_error_admin": "User {0} could not submit photo without additional confirmation due to:\n```\n{1}\n```", - "import_request": "Alright, please send me a zip archive with your media to be imported. Use /cancel if you want to abort this operation.", + "import_request": "Please send me a zip archive with your media to be imported. Use /cancel if you want to abort this operation.", "import_ignored": "No response, aborting import.", "import_abort": "Import aborted.", "import_invalid_media": "File to import must be a zip archive. Aborting.", @@ -54,7 +54,12 @@ "import_uploading": "Uploading archive contents...", "import_upload_error_duplicate": "Could not upload `{0}` because there're duplicates on server.", "import_upload_error_other": "Could not upload `{0}`. Probably disallowed filetype.", - "import_finished": "Import finished." + "import_finished": "Import finished.", + "remove_request": "Please send me an ID to delete. You might have it from upload dialog. Use /cancel if you want to abort this operation.", + "remove_ignored": "No response, aborting removal.", + "remove_abort": "Removal aborted.", + "remove_success": "Removed media with ID `{0}`.", + "remove_failure": "Could not remove media with ID `{0}`. Check if provided ID is correct and if it is - you can also check bot's log for details." }, "button": { "sub_yes": "✅ Accept", diff --git a/locale/uk.json b/locale/uk.json index f41ba8e..31e36a9 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -42,7 +42,7 @@ "sub_wip": "Подання постів зараз знаходиться у розробці. Він буде знову доступний через кілька днів. Дякуємо за ваше терпіння.", "sub_error": "⚠️ Не вдалось завантажити фото через помилку бота. Адміністрацію повідомлено.", "sub_error_admin": "Користувач {0} не зміг надіслати фото без додаткової перевірки через помилку:\n```\n{1}\n```", - "import_request": "Гаразд, будь ласка, надішліть zip-архів з медіа для імпортування. Використовуйте /cancel, якщо ви хочете перервати цю операцію.", + "import_request": "Будь ласка, надішліть zip-архів з медіа для імпортування. Використовуйте /cancel, якщо ви хочете перервати цю операцію.", "import_ignored": "Немає відповіді, перериваємо імпорт.", "import_abort": "Імпорт перервано.", "import_invalid_media": "Файл для імпорту має бути zip-архівом. Перериваємо.", @@ -54,7 +54,12 @@ "import_uploading": "Завантажуємо вміст архіву...", "import_upload_error_duplicate": "Не вдалося завантажити `{0}`, оскільки на сервері є дублікати.", "import_upload_error_other": "Не вдалося завантажити `{0}`. Ймовірно, заборонений тип файлу.", - "import_finished": "Імпорт завершено." + "import_finished": "Імпорт завершено.", + "remove_request": "Будь ласка, надішліть мені ID для видалення. Ви могли отримати його з діалогу завантаження. Використовуйте /cancel, якщо ви хочете перервати цю операцію.", + "remove_ignored": "Немає відповіді, перериваємо видалення.", + "remove_abort": "Видалення перервано.", + "remove_success": "Видалено медіа з ID `{0}`.", + "remove_failure": "Не вдалося видалити медіа з ID `{0}`. Перевірте, чи вказано правильний ID, і якщо він правильний, ви також можете переглянути логи бота для отримання більш детальної інформації." }, "button": { "sub_yes": "✅ Прийняти", diff --git a/modules/api_client.py b/modules/api_client.py index 9cb6dc0..63ff0fd 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -208,10 +208,28 @@ async def find_pic( async def move_pic(id: str, token: Union[str, None] = None) -> bool: token = await authorize() if token is None else token try: - await http_session.patch( + response = await http_session.patch( f'{configGet("address", "posting", "api")}/photos/{id}?caption=sent', headers={"Authorization": f"Bearer {token}"}, ) + if response.status != 200: + logWrite(f"Media moving failed with HTTP {response.status}", debug=True) + return False + return True + except: + return False + + +async def remove_pic(id: str, token: Union[str, None] = None) -> bool: + token = await authorize() if token is None else token + try: + response = await http_session.delete( + f'{configGet("address", "posting", "api")}/photos/{id}', + headers={"Authorization": f"Bearer {token}"}, + ) + if response.status != 204: + logWrite(f"Media removal failed with HTTP {response.status}", debug=True) + return False return True except: return False diff --git a/plugins/commands/photos.py b/plugins/commands/photos.py index 0737093..e56a798 100644 --- a/plugins/commands/photos.py +++ b/plugins/commands/photos.py @@ -11,7 +11,7 @@ from pyrogram import filters from pyrogram.types import Message from classes.poster_client import PosterClient -from modules.api_client import upload_pic +from modules.api_client import remove_pic, upload_pic from modules.app import app, users_with_context from modules.logger import logWrite from modules.utils import configGet, extract_and_save, locale @@ -166,7 +166,46 @@ async def cmd_export(app: PosterClient, msg: Message): @app.on_message(~filters.scheduled & filters.command(["remove"], prefixes=["", "/"])) async def cmd_remove(app: PosterClient, msg: Message): if msg.from_user.id in app.admins: - pass + 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( + locale("remove_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: + await msg.reply_text( + locale("remove_ignored", "message", locale=msg.from_user.language_code), + quote=True, + ) + return + if answer.text == "/cancel": + await answer.reply_text( + locale("remove_abort", "message", locale=msg.from_user.language_code) + ) + return + response = await remove_pic(answer.text) + if response: + logWrite( + f"Removed '{answer.text}' by request of user {answer.from_user.id}" + ) + await answer.reply_text( + locale( + "remove_success", "message", locale=msg.from_user.language_code + ).format(answer.text) + ) + else: + logWrite( + f"Could not remove '{answer.text}' by request of user {answer.from_user.id}" + ) + await answer.reply_text( + locale( + "remove_failure", "message", locale=msg.from_user.language_code + ).format(answer.text) + ) @app.on_message(~filters.scheduled & filters.command(["purge"], prefixes=["", "/"])) From 4422a13ba9a413a19533922e1510a1e08892b30c Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Wed, 22 Mar 2023 10:33:21 +0100 Subject: [PATCH 83/98] Improved async context --- poster.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/poster.py b/poster.py index ea43709..c2d6860 100644 --- a/poster.py +++ b/poster.py @@ -116,10 +116,11 @@ if configGet("post", "mode"): # uvloop.install() # asyncio.run(main()) -if __name__ == "__main__": + +async def main(): logWrite(locale("startup", "console").format(str(pid))) - app.start() + await app.start() if configGet("startup", "reports"): try: @@ -133,7 +134,7 @@ if __name__ == "__main__": ), ) if downtime.days >= 1: - app.send_message( + await app.send_message( configGet("owner"), locale( "startup_downtime_days", @@ -141,7 +142,7 @@ if __name__ == "__main__": ).format(pid, downtime.days), ) elif downtime.hours >= 1: - app.send_message( + await app.send_message( configGet("owner"), locale( "startup_downtime_hours", @@ -149,7 +150,7 @@ if __name__ == "__main__": ).format(pid, downtime.hours), ) else: - app.send_message( + await app.send_message( configGet("owner"), locale( "startup_downtime_minutes", @@ -158,7 +159,7 @@ if __name__ == "__main__": ).format(pid, downtime.minutes), ) else: - app.send_message( + await app.send_message( configGet("owner"), locale("startup", "message").format(pid), ) @@ -175,10 +176,10 @@ if __name__ == "__main__": # if len(get(f'{configGet("address", "posting", "api")}/albums?q={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}).json()["results"]) == 0: # post(f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}) - idle() + await idle() try: - app.send_message( + await app.send_message( configGet("owner"), locale("shutdown", "message").format(pid), ) @@ -197,4 +198,7 @@ if __name__ == "__main__": scheduler.shutdown() - killProc(pid) + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) From 45789ad013c2f9c95342ade75256835767ec6031 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Wed, 22 Mar 2023 11:02:13 +0100 Subject: [PATCH 84/98] Updates wiki init --- docs/updating.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/updating.md diff --git a/docs/updating.md b/docs/updating.md new file mode 100644 index 0000000..0693c6d --- /dev/null +++ b/docs/updating.md @@ -0,0 +1 @@ +# Updating your bot From 5ad52aa3f8dab3444d039ab9a06e2e7417f98d76 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Wed, 22 Mar 2023 11:02:55 +0100 Subject: [PATCH 85/98] Changed aiohttp module location --- modules/api_client.py | 8 ++------ modules/http_client.py | 6 ++++++ 2 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 modules/http_client.py diff --git a/modules/api_client.py b/modules/api_client.py index 63ff0fd..d7cf50b 100644 --- a/modules/api_client.py +++ b/modules/api_client.py @@ -8,8 +8,7 @@ from traceback import print_exc from typing import Tuple, Union import aiofiles -from aiohttp import ClientSession, FormData -from ujson import dumps +from aiohttp import FormData from classes.exceptions import ( AlbumCreationDuplicateError, @@ -21,10 +20,7 @@ from classes.exceptions import ( ) from modules.logger import logWrite from modules.utils import configGet, locale - -http_session = ClientSession( - json_serialize=dumps, -) +from modules.http_client import http_session async def authorize() -> str: diff --git a/modules/http_client.py b/modules/http_client.py new file mode 100644 index 0000000..295160f --- /dev/null +++ b/modules/http_client.py @@ -0,0 +1,6 @@ +from aiohttp import ClientSession +from ujson import dumps + +http_session = ClientSession( + json_serialize=dumps, +) From cc6523f60494ce740e6bacb5bfc771eceeee3548 Mon Sep 17 00:00:00 2001 From: Profitroll <47523801+profitrollgame@users.noreply.github.com> Date: Wed, 22 Mar 2023 11:03:03 +0100 Subject: [PATCH 86/98] Added updates checker --- config_example.json | 1 + locale/en.json | 3 ++- locale/uk.json | 3 ++- poster.py | 46 ++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/config_example.json b/config_example.json index bd335e4..473f439 100644 --- a/config_example.json +++ b/config_example.json @@ -23,6 +23,7 @@ "reports": { "sent": false, "error": true, + "update": true, "startup": true, "shutdown": true }, diff --git a/locale/en.json b/locale/en.json index 8bc568f..3a23761 100644 --- a/locale/en.json +++ b/locale/en.json @@ -59,7 +59,8 @@ "remove_ignored": "No response, aborting removal.", "remove_abort": "Removal aborted.", "remove_success": "Removed media with ID `{0}`.", - "remove_failure": "Could not remove media with ID `{0}`. Check if provided ID is correct and if it is - you can also check bot's log for details." + "remove_failure": "Could not remove media with ID `{0}`. Check if provided ID is correct and if it is - you can also check bot's log for details.", + "update_available": "**New version found**\nThere's a newer version of a bot found. You can update your bot to [{0}]({1}) using command line of your host.\n\n**Release notes**\n{2}\n\nRead more about updating you bot on the [wiki page](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Updating-Instance).\n\nPlease not that you can also disable this notification by editing `reports.update` key of the config." }, "button": { "sub_yes": "✅ Accept", diff --git a/locale/uk.json b/locale/uk.json index 31e36a9..0696eb3 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -59,7 +59,8 @@ "remove_ignored": "Немає відповіді, перериваємо видалення.", "remove_abort": "Видалення перервано.", "remove_success": "Видалено медіа з ID `{0}`.", - "remove_failure": "Не вдалося видалити медіа з ID `{0}`. Перевірте, чи вказано правильний ID, і якщо він правильний, ви також можете переглянути логи бота для отримання більш детальної інформації." + "remove_failure": "Не вдалося видалити медіа з ID `{0}`. Перевірте, чи вказано правильний ID, і якщо він правильний, ви також можете переглянути логи бота для отримання більш детальної інформації.", + "update_available": "**Знайдено нову версію**\nЗнайдено нову версію бота. Ви можете оновити бота до [{0}]({1}) за допомогою командного рядка вашого хосту.\n\n**Примітки до релізу**\n{2}\n\nДетальніше про оновлення бота можна знайти на [вікі-сторінці](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Updating-Instance).\n\nЗверніть увагу, що ви також можете вимкнути це сповіщення, відредагувавши ключ `reports.update` у конфігурації." }, "button": { "sub_yes": "✅ Прийняти", diff --git a/poster.py b/poster.py index c2d6860..e71d991 100644 --- a/poster.py +++ b/poster.py @@ -1,18 +1,20 @@ -from os import getpid, path from datetime import datetime +from os import getpid, path from sys import exit from time import time +from traceback import format_exc from modules.cli import * +from modules.http_client import http_session from modules.logger import logWrite from modules.scheduler import scheduler -from modules.utils import configGet, jsonLoad, jsonSave, killProc, locale +from modules.utils import configGet, jsonLoad, jsonSave, locale # Import =================================================================================================================================== try: - from pyrogram.sync import idle - from pyrogram.errors import bad_request_400 from dateutil.relativedelta import relativedelta + from pyrogram.errors import bad_request_400 + from pyrogram.sync import idle from modules.app import app except ModuleNotFoundError: @@ -22,6 +24,7 @@ except ModuleNotFoundError: pid = getpid() +version = 0.1 # Work in progress # def check_forwards(app): @@ -59,9 +62,10 @@ pid = getpid() # check_forwards(app) +from plugins.callbacks.shutdown import * + # Imports ================================================================================================================================== from plugins.commands.general import * -from plugins.callbacks.shutdown import * if configGet("submit", "mode"): from plugins.callbacks.nothing import * @@ -168,6 +172,38 @@ async def main(): f"Could not send startup message to bot owner. Perhaps user has not started the bot yet." ) + if configGet("update", "reports"): + try: + check_update = await http_session.get( + "https://git.end-play.xyz/api/v1/repos/profitroll/TelegramPoster/releases?page=1&limit=1" + ) + response = await check_update.json() + if len(response) == 0: + raise ValueError("No bot releases on git found.") + if float(response[0]["tag_name"].replace("v.", "")) > version: + logWrite( + f'New version {response[0]["tag_name"].replace("v.", "")} found (current {version})' + ) + await app.send_message( + configGet("owner"), + locale( + "update_available", + "message", + ).format( + response[0]["tag_name"], + response[0]["html_url"], + response[0]["body"], + ), + ) + else: + logWrite(f"No updates found, bot is up to date.") + except bad_request_400.PeerIdInvalid: + logWrite( + f"Could not send startup message to bot owner. Perhaps user has not started the bot yet." + ) + except Exception as exp: + logWrite(f"Update check failed due to {exp}: {format_exc()}") + if configGet("post", "mode"): scheduler.start() From af2bcd088f838005a0442c936dbf6840c20537c8 Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 22 Mar 2023 15:52:34 +0100 Subject: [PATCH 87/98] Album check returned --- poster.py | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/poster.py b/poster.py index e71d991..0ab5c20 100644 --- a/poster.py +++ b/poster.py @@ -3,6 +3,7 @@ from os import getpid, path from sys import exit from time import time from traceback import format_exc +from modules.api_client import authorize from modules.cli import * from modules.http_client import http_session @@ -207,10 +208,38 @@ async def main(): if configGet("post", "mode"): scheduler.start() - # if configGet("api_based", "mode"): - # token = await authorize() - # if len(get(f'{configGet("address", "posting", "api")}/albums?q={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}).json()["results"]) == 0: - # post(f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}) + try: + + token = await authorize() + + if ( + len( + ( + await ( + await http_session.get( + f'{configGet("address", "posting", "api")}/albums?q={configGet("queue", "posting", "api", "albums")}', + headers={"Authorization": f"Bearer {token}"}, + ) + ).json() + )["results"] + ) + == 0 + ): + logWrite("Media album does not exist on API server. Trying to create it...") + try: + await http_session.post( + f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', + headers={"Authorization": f"Bearer {token}"}, + ) + logWrite( + "Created media album on API server."}" + ) + except Exception as exp: + logWrite( + f"Could not create media album on API server due to {exp}: {format_exc()}" + ) + except Exception as exp: + logWrite(f"Could not check if API album exists due to {exp}: {format_exc()}") await idle() From ae654d686ec66751fabbf16d2bc852c60555c02c Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 23 Mar 2023 15:12:53 +0100 Subject: [PATCH 88/98] TgCrypto is no longer optional dependency --- requirements-optional.txt | 1 - requirements.txt | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-optional.txt b/requirements-optional.txt index c5a65b8..e69de29 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -1 +0,0 @@ -tgcrypto==1.2.5 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a50f2cf..5d7f51c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ pytimeparse~=1.1.8 convopyro==0.5 pyrogram~=2.0.102 aiofiles~=23.1.0 +tgcrypto==1.2.5 aiohttp~=3.8.4 psutil~=5.9.4 pymongo~=4.3.3 From 4dd754055ed0294a6472288d2abaa63011d7b342 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 23 Mar 2023 15:13:38 +0100 Subject: [PATCH 89/98] Removed optional dependencies as a concept --- README.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d8f3606..2a4b80d 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,7 @@ To make this bot run at first you need to have a Python interpreter, Photos API, `python -m pip install -r requirements.txt` Without installing those - bot cannot work at all. -5. Install optional dependencies [Optional]: - - `python -m pip install -r requirements-optional.txt` - These are not required but can make the bot run a bit faster. - -6. Configure "bot" and "owner" with your favorite text editor: +5. Configure "bot" and "owner" with your favorite text editor: 1. Copy file `config_example.json` to `config.json` 2. Open `config.json` using your favorite text editor. For example `nano config.json`, but you can edit with vim, nano, on Windows it's Notepad or Notepad++. Whatever @@ -63,7 +58,7 @@ To make this bot run at first you need to have a Python interpreter, Photos API, If you don't know where to find bot_token and your id - here you can find some hints: [get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [get your id](https://www.alphr.com/telegram-find-user-id), [get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id). -7. Configure database and API: +6. Configure database and API: 1. Configure database: 1. Change database host and port in keys `"database.host"` and `"database.port"`. For default local installation those will be `127.0.0.1` and `27017` respectively @@ -74,15 +69,15 @@ To make this bot run at first you need to have a Python interpreter, Photos API, 1. Change `"posting.api.address"` to the one your API servers uses 2. Run your bot using `python poster.py --create-user --create-album` to configure its new user and album. You can also use manual user and album creation described [in the wiki](https://git.end-play.xyz/profitroll/TelegramPoster/wiki/Configuring-API). You can also change username, password and album in`"posting.api"` to the user and album you have if you already have Photos API album and user set up. In that case you don't need to create a new one. -8. Add bot to the channel: +7. Add bot to the channel: To use your bot of course you need to have a channel or group otherwise makes no sense to have such a bot. [Here](https://stackoverflow.com/a/33497769) you can find a quick guide how to add your bot to a channel. After that simply set `"posting.channel"` to your channel's ID. -9. Configure posting time: +8. Configure posting time: To make your bot post random content you need to configure `"posting.time"` with a list of "DD:MM" formatted strings or use `"posting.interval"` formatted as "XdXhXmXs". To use interval instead of selected time set `"posting.use_interval"` to `true`. -10. Good to go, run it! +9. Good to go, run it! Make sure MongoDB and Photos API are running and use `python poster.py` to start it. Or you can also use `.\start.bat` on Windows and `bash ./start.sh` on Linux. From 9d340b32c109c91c55e7ede8f9194e21a2da8a85 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 20 Apr 2023 13:32:52 +0200 Subject: [PATCH 90/98] Added Renovate config --- .renovaterc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .renovaterc diff --git a/.renovaterc b/.renovaterc new file mode 100644 index 0000000..3de8304 --- /dev/null +++ b/.renovaterc @@ -0,0 +1,18 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ], + "branchName": "dev", + "packageRules": [ + { + "matchUpdateTypes": [ + "minor", + "patch", + "pin", + "digest" + ], + "automerge": true + } + ] +} \ No newline at end of file From 2b520760d3fa81383012b897279a3f336263fc5b Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 20 Apr 2023 13:35:52 +0200 Subject: [PATCH 91/98] Fixed branch name in Renovate config --- .renovaterc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.renovaterc b/.renovaterc index 3de8304..c416352 100644 --- a/.renovaterc +++ b/.renovaterc @@ -3,7 +3,9 @@ "extends": [ "config:base" ], - "branchName": "dev", + "baseBranches": [ + "dev" + ], "packageRules": [ { "matchUpdateTypes": [ From ad653146e1ecee858c32d30418a277a03fe4721b Mon Sep 17 00:00:00 2001 From: Renovate Date: Thu, 20 Apr 2023 16:04:39 +0300 Subject: [PATCH 92/98] Update dependency pillow to ~=9.5.0 (#15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Update | Change | |---|---|---| | [pillow](https://python-pillow.org) ([source](https://github.com/python-pillow/Pillow), [changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)) | minor | `~=9.4.0` -> `~=9.5.0` | --- ### Release Notes
python-pillow/Pillow ### [`v9.5.0`](https://github.com/python-pillow/Pillow/blob/HEAD/CHANGES.rst#​950-2023-04-01) [Compare Source](https://github.com/python-pillow/Pillow/compare/9.4.0...9.5.0) - Added ImageSourceData to TAGS_V2 [#​7053](https://github.com/python-pillow/Pillow/issues/7053) \[radarhere] - Clear PPM half token after use [#​7052](https://github.com/python-pillow/Pillow/issues/7052) \[radarhere] - Removed absolute path to ldconfig [#​7044](https://github.com/python-pillow/Pillow/issues/7044) \[radarhere] - Support custom comments and PLT markers when saving JPEG2000 images [#​6903](https://github.com/python-pillow/Pillow/issues/6903) \[joshware, radarhere, hugovk] - Load before getting size in **array_interface** [#​7034](https://github.com/python-pillow/Pillow/issues/7034) \[radarhere] - Support creating BGR;15, BGR;16 and BGR;24 images, but drop support for BGR;32 [#​7010](https://github.com/python-pillow/Pillow/issues/7010) \[radarhere] - Consider transparency when applying APNG blend mask [#​7018](https://github.com/python-pillow/Pillow/issues/7018) \[radarhere] - Round duration when saving animated WebP images [#​6996](https://github.com/python-pillow/Pillow/issues/6996) \[radarhere] - Added reading of JPEG2000 comments [#​6909](https://github.com/python-pillow/Pillow/issues/6909) \[radarhere] - Decrement reference count [#​7003](https://github.com/python-pillow/Pillow/issues/7003) \[radarhere, nulano] - Allow libtiff_support_custom_tags to be missing [#​7020](https://github.com/python-pillow/Pillow/issues/7020) \[radarhere] - Improved I;16N support [#​6834](https://github.com/python-pillow/Pillow/issues/6834) \[radarhere] - Added QOI reading [#​6852](https://github.com/python-pillow/Pillow/issues/6852) \[radarhere, hugovk] - Added saving RGBA images as PDFs [#​6925](https://github.com/python-pillow/Pillow/issues/6925) \[radarhere] - Do not raise an error if os.environ does not contain PATH [#​6935](https://github.com/python-pillow/Pillow/issues/6935) \[radarhere, hugovk] - Close OleFileIO instance when closing or exiting FPX or MIC [#​7005](https://github.com/python-pillow/Pillow/issues/7005) \[radarhere] - Added **int** to IFDRational for Python >= 3.11 [#​6998](https://github.com/python-pillow/Pillow/issues/6998) \[radarhere] - Added memoryview support to Dib.frombytes() [#​6988](https://github.com/python-pillow/Pillow/issues/6988) \[radarhere, nulano] - Close file pointer copy in the libtiff encoder if still open [#​6986](https://github.com/python-pillow/Pillow/issues/6986) \[fcarron, radarhere] - Raise an error if ImageDraw co-ordinates are incorrectly ordered [#​6978](https://github.com/python-pillow/Pillow/issues/6978) \[radarhere] - Added "corners" argument to ImageDraw rounded_rectangle() [#​6954](https://github.com/python-pillow/Pillow/issues/6954) \[radarhere] - Added memoryview support to frombytes() [#​6974](https://github.com/python-pillow/Pillow/issues/6974) \[radarhere] - Allow comments in FITS images [#​6973](https://github.com/python-pillow/Pillow/issues/6973) \[radarhere] - Support saving PDF with different X and Y resolutions [#​6961](https://github.com/python-pillow/Pillow/issues/6961) \[jvanderneutstulen, radarhere, hugovk] - Fixed writing int as UNDEFINED tag [#​6950](https://github.com/python-pillow/Pillow/issues/6950) \[radarhere] - Raise an error if EXIF data is too long when saving JPEG [#​6939](https://github.com/python-pillow/Pillow/issues/6939) \[radarhere] - Handle more than one directory returned by pkg-config [#​6896](https://github.com/python-pillow/Pillow/issues/6896) \[sebastic, radarhere] - Do not retry past formats when loading all formats for the first time [#​6902](https://github.com/python-pillow/Pillow/issues/6902) \[radarhere] - Do not retry specified formats if they failed when opening [#​6893](https://github.com/python-pillow/Pillow/issues/6893) \[radarhere] - Do not unintentionally load TIFF format at first [#​6892](https://github.com/python-pillow/Pillow/issues/6892) \[radarhere] - Stop reading when EPS line becomes too long [#​6897](https://github.com/python-pillow/Pillow/issues/6897) \[radarhere] - Allow writing IFDRational to BYTE tag [#​6890](https://github.com/python-pillow/Pillow/issues/6890) \[radarhere] - Raise ValueError for BoxBlur filter with negative radius [#​6874](https://github.com/python-pillow/Pillow/issues/6874) \[hugovk, radarhere] - Support arbitrary number of loaded modules on Windows [#​6761](https://github.com/python-pillow/Pillow/issues/6761) \[javidcf, radarhere, nulano]
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/15 Co-authored-by: Renovate Co-committed-by: Renovate --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5d7f51c..8eb92a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,5 +8,5 @@ tgcrypto==1.2.5 aiohttp~=3.8.4 psutil~=5.9.4 pymongo~=4.3.3 -pillow~=9.4.0 +pillow~=9.5.0 ujson~=5.7.0 \ No newline at end of file From 7c8e07bbc935abb7fe3a70892e274c5b1df3a9b7 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 24 Apr 2023 12:13:14 +0200 Subject: [PATCH 93/98] Made some dependencies strict --- requirements.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8eb92a7..90d6e12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,12 @@ python_dateutil==2.8.2 -apscheduler~=3.10.1 -pytimeparse~=1.1.8 +apscheduler==3.10.1 +pytimeparse==1.1.8 convopyro==0.5 -pyrogram~=2.0.102 +pyrogram==2.0.102 aiofiles~=23.1.0 tgcrypto==1.2.5 aiohttp~=3.8.4 -psutil~=5.9.4 -pymongo~=4.3.3 +psutil==5.9.4 +pymongo==4.3.3 pillow~=9.5.0 -ujson~=5.7.0 \ No newline at end of file +ujson==5.7.0 \ No newline at end of file From 06813389702db647ae9e4408c435dac6326faed8 Mon Sep 17 00:00:00 2001 From: Renovate Date: Mon, 24 Apr 2023 13:36:12 +0300 Subject: [PATCH 94/98] Update dependency psutil to v5.9.5 (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Update | Change | |---|---|---| | [psutil](https://github.com/giampaolo/psutil) | patch | `==5.9.4` -> `==5.9.5` | --- ### Release Notes
giampaolo/psutil ### [`v5.9.5`](https://github.com/giampaolo/psutil/blob/HEAD/HISTORY.rst#​595) [Compare Source](https://github.com/giampaolo/psutil/compare/release-5.9.4...release-5.9.5) \===== 2023-04-17 **Enhancements** - 2196\_: in case of exception, display a cleaner error traceback by hiding the `KeyError` bit deriving from a missed cache hit. - 2217\_: print the full traceback when a `DeprecationWarning` or `UserWarning` is raised. - 2230\_, \[OpenBSD]: `psutil.net_connections`\_ implementation was rewritten from scratch: - We're now able to retrieve the path of AF_UNIX sockets (before it was an empty string) - The function is faster since it no longer iterates over all processes. - No longer produces duplicate connection entries. - 2238\_: there are cases where `Process.cwd()`\_ cannot be determined (e.g. directory no longer exists), in which case we returned either `None` or an empty string. This was consolidated and we now return `""` on all platforms. - 2239\_, \[UNIX]: if process is a zombie, and we can only determine part of the its truncated `Process.name()`\_ (15 chars), don't fail with `ZombieProcess`\_ when we try to guess the full name from the `Process.cmdline()`\_. Just return the truncated name. - 2240\_, \[NetBSD], \[OpenBSD]: add CI testing on every commit for NetBSD and OpenBSD platforms (python 3 only). **Bug fixes** - 1043\_, \[OpenBSD] `psutil.net_connections`\_ returns duplicate entries. - 1915\_, \[Linux]: on certain kernels, `"MemAvailable"` field from `/proc/meminfo` returns `0` (possibly a kernel bug), in which case we calculate an approximation for `available` memory which matches "free" CLI utility. - 2164\_, \[Linux]: compilation fails on kernels < 2.6.27 (e.g. CentOS 5). - 2186\_, \[FreeBSD]: compilation fails with Clang 15. (patch by Po-Chuan Hsieh) - 2191\_, \[Linux]: `disk_partitions()`*: do not unnecessarily read /proc/filesystems and raise `AccessDenied`* unless user specified `all=False` argument. - 2216\_, \[Windows]: fix tests when running in a virtual environment (patch by Matthieu Darbois) - 2225\_, \[POSIX]: `users()`\_ loses precision for `started` attribute (off by 1 minute). - 2229\_, \[OpenBSD]: unable to properly recognize zombie processes. `NoSuchProcess`\_ may be raised instead of `ZombieProcess`\_. - 2231\_, \[NetBSD]: *available* `virtual_memory()`\_ is higher than *total*. - 2234\_, \[NetBSD]: `virtual_memory()`\_ metrics are wrong: *available* and *used* are too high. We now match values shown by *htop* CLI utility. - 2236\_, \[NetBSD]: `Process.num_threads()`\_ and `Process.threads()`\_ return threads that are already terminated. - 2237\_, \[OpenBSD], \[NetBSD]: `Process.cwd()`\_ may raise `FileNotFoundError` if cwd no longer exists. Return an empty string instead.
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/17 Co-authored-by: Renovate Co-committed-by: Renovate --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 90d6e12..14eb7ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ pyrogram==2.0.102 aiofiles~=23.1.0 tgcrypto==1.2.5 aiohttp~=3.8.4 -psutil==5.9.4 +psutil==5.9.5 pymongo==4.3.3 pillow~=9.5.0 ujson==5.7.0 \ No newline at end of file From 826e031a395eb916a527989c5c04cdffcc11dbbf Mon Sep 17 00:00:00 2001 From: Renovate Date: Mon, 24 Apr 2023 13:36:39 +0300 Subject: [PATCH 95/98] Update dependency pyrogram to v2.0.104 (#18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Update | Change | |---|---|---| | [pyrogram](https://github.com/pyrogram) ([source](https://github.com/pyrogram/pyrogram)) | patch | `==2.0.102` -> `==2.0.104` | --- ### Release Notes
pyrogram/pyrogram ### [`v2.0.104`](https://github.com/pyrogram/pyrogram/compare/v2.0.103...v2.0.104) [Compare Source](https://github.com/pyrogram/pyrogram/compare/v2.0.103...v2.0.104) ### [`v2.0.103`](https://github.com/pyrogram/pyrogram/compare/v2.0.102...v2.0.103) [Compare Source](https://github.com/pyrogram/pyrogram/compare/v2.0.102...v2.0.103)
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Reviewed-on: https://git.end-play.xyz/profitroll/TelegramPoster/pulls/18 Co-authored-by: Renovate Co-committed-by: Renovate --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 14eb7ab..bba88b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ python_dateutil==2.8.2 apscheduler==3.10.1 pytimeparse==1.1.8 convopyro==0.5 -pyrogram==2.0.102 +pyrogram==2.0.104 aiofiles~=23.1.0 tgcrypto==1.2.5 aiohttp~=3.8.4 From aa82a8f3827b69b894a74f69b87ecd14fe6a9934 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 24 Apr 2023 12:40:06 +0200 Subject: [PATCH 96/98] Fixed SyntaxError --- poster.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/poster.py b/poster.py index 0ab5c20..f58b6eb 100644 --- a/poster.py +++ b/poster.py @@ -209,9 +209,8 @@ async def main(): scheduler.start() try: - token = await authorize() - + if ( len( ( @@ -231,9 +230,7 @@ async def main(): f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', headers={"Authorization": f"Bearer {token}"}, ) - logWrite( - "Created media album on API server."}" - ) + logWrite("Created media album on API server.") except Exception as exp: logWrite( f"Could not create media album on API server due to {exp}: {format_exc()}" From fe60b3f8a5e4e9a7f4a5f1fce7fb30a8c9f07976 Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 24 Apr 2023 12:44:10 +0200 Subject: [PATCH 97/98] Changed album config path --- poster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poster.py b/poster.py index f58b6eb..9299740 100644 --- a/poster.py +++ b/poster.py @@ -216,7 +216,7 @@ async def main(): ( await ( await http_session.get( - f'{configGet("address", "posting", "api")}/albums?q={configGet("queue", "posting", "api", "albums")}', + f'{configGet("address", "posting", "api")}/albums?q={configGet("album", "posting", "api")}', headers={"Authorization": f"Bearer {token}"}, ) ).json() @@ -227,7 +227,7 @@ async def main(): logWrite("Media album does not exist on API server. Trying to create it...") try: await http_session.post( - f'{configGet("address", "posting", "api")}/albums?name={configGet("queue", "posting", "api", "albums")}&title={configGet("queue", "posting", "api", "albums")}', + f'{configGet("address", "posting", "api")}/albums?name={configGet("album", "posting", "api")}&title={configGet("album", "posting", "api")}', headers={"Authorization": f"Bearer {token}"}, ) logWrite("Created media album on API server.") From adef3b3afc5e15c4458a9ecf52cfba2b17c3c7fe Mon Sep 17 00:00:00 2001 From: profitroll Date: Mon, 24 Apr 2023 12:53:22 +0200 Subject: [PATCH 98/98] Changed versioning prefix in code --- poster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poster.py b/poster.py index 9299740..b570fba 100644 --- a/poster.py +++ b/poster.py @@ -181,9 +181,9 @@ async def main(): response = await check_update.json() if len(response) == 0: raise ValueError("No bot releases on git found.") - if float(response[0]["tag_name"].replace("v.", "")) > version: + if float(response[0]["tag_name"].replace("v", "")) > version: logWrite( - f'New version {response[0]["tag_name"].replace("v.", "")} found (current {version})' + f'New version {response[0]["tag_name"].replace("v", "")} found (current {version})' ) await app.send_message( configGet("owner"),