From 91a1d7e3494bf798c68aa979c699dd06081d0728 Mon Sep 17 00:00:00 2001 From: profitroll Date: Tue, 30 Aug 2022 13:53:17 +0200 Subject: [PATCH] Logging now has debug and bot has various modes --- config.json | 1 + data/index.json | 1 + locale/en.json | 1 + locale/uk.json | 1 + modules/logging.py | 30 ++- poster.py | 458 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 482 insertions(+), 10 deletions(-) create mode 100644 poster.py diff --git a/config.json b/config.json index b4621f7..e2253f5 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,6 @@ { "module": null, + "mode": "both", "locale": "en", "locale_fallback": "en", "admin": 0, diff --git a/data/index.json b/data/index.json index 2af118e..25f21fd 100644 --- a/data/index.json +++ b/data/index.json @@ -1,4 +1,5 @@ { + "last_id": 0, "sent": [], "captions": {} } \ No newline at end of file diff --git a/locale/en.json b/locale/en.json index a39f2f4..c6075e2 100644 --- a/locale/en.json +++ b/locale/en.json @@ -4,6 +4,7 @@ "rules": "Photos submission rules" }, "commands_admin": { + "forwards": "Check post forwards", "reboot": "Restart the bot" }, "message": { diff --git a/locale/uk.json b/locale/uk.json index 11ba523..e110a84 100644 --- a/locale/uk.json +++ b/locale/uk.json @@ -4,6 +4,7 @@ "rules": "Правила пропонування фото" }, "commands_admin": { + "forwards": "Переглянути репости", "reboot": "Перезапустити бота" }, "message": { diff --git a/modules/logging.py b/modules/logging.py index f723db6..1bb5795 100644 --- a/modules/logging.py +++ b/modules/logging.py @@ -12,37 +12,47 @@ with open(os.getcwd()+os.path.sep+"config.json", "r", encoding='utf8') as file: file.close() # Check latest log size -def checkSize(): +def checkSize(debug=False): global log_folder + if debug: + log_file = "debug.log" + else: + log_file = "latest.log" + try: os.makedirs(log_folder, exist_ok=True) - log = os.stat(os.path.join(log_folder, "latest.log")) + log = os.stat(os.path.join(log_folder, log_file)) if (log.st_size / 1024) > log_size: - with open(os.path.join(log_folder, "latest.log"), 'rb') as f_in: + with open(os.path.join(log_folder, log_file), 'rb') as f_in: with gzip.open(os.path.join(log_folder, f'{datetime.now().strftime("%d.%m.%Y_%H:%M:%S")}.log.gz'), 'wb') as f_out: shutil.copyfileobj(f_in, f_out) print(f'Copied {os.path.join(log_folder, datetime.now().strftime("%d.%m.%Y_%H:%M:%S"))}.log.gz') - open(os.path.join(log_folder, "latest.log"), 'w').close() + open(os.path.join(log_folder, log_file), 'w').close() except FileNotFoundError: - print(f'Log file {os.path.join(log_folder, "latest.log")} does not exist') + print(f'Log file {os.path.join(log_folder, log_file)} does not exist') pass # Append string to log -def logAppend(message): +def logAppend(message, debug=False): global log_folder message_formatted = f'[{datetime.now().strftime("%d.%m.%Y")}] [{datetime.now().strftime("%H:%M:%S")}] {message}' - checkSize() + checkSize(debug=debug) - log = open(os.path.join(log_folder, "latest.log"), 'a') + if debug: + log_file = "debug.log" + else: + log_file = "latest.log" + + log = open(os.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): +def logWrite(message, debug=False): # save to log file and rotation is to be done - logAppend(f'{message}') + logAppend(f'{message}', debug=debug) print(f"{message}", flush=True) \ No newline at end of file diff --git a/poster.py b/poster.py new file mode 100644 index 0000000..4de306d --- /dev/null +++ b/poster.py @@ -0,0 +1,458 @@ +import os +import random +import shutil +import sys +from threading import Thread +import time +import traceback +from pathlib import Path + +from modules.logging import logWrite +from modules.utils import configGet, jsonLoad, jsonSave, killProc, locale + +# Args ===================================================================================================================================== +if "--move-sent" in sys.argv: + for entry in jsonLoad(configGet("index", "locations"))["sent"]: + try: + shutil.move(configGet("queue", "locations")+os.sep+entry, configGet("sent", "locations")+os.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 "--cleanup" in sys.argv: + if "--confirm" in sys.argv: + index = jsonLoad(configGet("index", "locations")) + for entry in index["sent"]: + try: + try: + os.remove(configGet("queue", "locations")+os.sep+entry) + except FileNotFoundError: + pass + try: + os.remove(configGet("sent", "locations")+os.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 "--cleanup-index" in sys.argv: + if "--confirm" in sys.argv: + 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 "--norun" in sys.argv: + logWrite(locale("passed_norun", "console", locale=configGet("locale"))) + sys.exit() +#=========================================================================================================================================== + + +# Import =================================================================================================================================== +try: + import schedule # type: ignore + from pyrogram import Client, filters, idle # type: ignore + from pyrogram.types import ChatPermissions, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton, BotCommand, BotCommandScopeChat # type: ignore + from pyrogram.raw.types import UpdateChannelMessageForwards, InputChannel, InputPeerChannel + from pyrogram.raw.functions.stats import GetMessagePublicForwards +except ModuleNotFoundError: + print(locale("deps_missing", "console", locale=configGet("locale")), flush=True) + sys.exit() +#=========================================================================================================================================== + + +pid = os.getpid() +app = Client("duptsiaposter", bot_token=configGet("bot_token", "bot"), api_id=configGet("api_id", "bot"), api_hash=configGet("api_hash", "bot")) + + +# Work in progress +# def check_forwards(app): + +# try: + +# index = jsonLoad(configGet("index", "locations")) +# channel = app.get_chat(configGet("channel", "posting")) + +# peer = app.resolve_peer(configGet("channel", "posting")) +# print(peer, flush=True) + +# posts_list = [i for i in range(index["last_id"]-100,index["last_id"])] +# last_posts = app.get_messages(configGet("channel", "posting"), message_ids=posts_list) + +# for post in last_posts: +# post_forwards = GetMessagePublicForwards(channel=peer, msg_id=post.id, offset_peer=peer, offset_rate=0, offset_id=0, limit=100) +# print(post_forwards, flush=True) +# for forward in post_forwards: +# print(forward, flush=True) + +# except Exception as exp: + +# logWrite("Could not get last posts forwards due to {0} with traceback {1}".format(str(exp), traceback.format_exc()), debug=True) + +# if configGet("error", "reports"): +# app.send_message(configGet("admin"), traceback.format_exc()) # type: ignore + +# pass + + +def send_content(): + + # Send post to channel + try: + + index = jsonLoad(configGet("index", "locations")) + list_queue = os.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 = random.choice(list_queue) + candidate = configGet("queue", "locations")+os.sep+candidate_file + else: + logWrite(locale("post_empty", "console", locale=configGet("locale"))) + if configGet("error", "reports"): + app.send_message(configGet("admin"), locale("post_empty", "message", locale=configGet("locale"))) # type: ignore + 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": # type: ignore + + if configGet("enabled", "caption"): + if configGet("link", "caption") != None: + sent = app.send_photo(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore + else: + sent = app.send_photo(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore + else: + sent = app.send_photo(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore + + elif ext_type == "video": # type: ignore + + if configGet("enabled", "caption"): + if configGet("link", "caption") != None: + sent = app.send_video(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore + else: + sent = app.send_video(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore + else: + sent = app.send_video(configGet("channel", "posting"), candidate, caption=caption, disable_notification=configGet("silent", "posting")) # type: ignore + + else: + return + + index["sent"].append(candidate_file) + index["last_id"] = sent.id + + jsonSave(index, configGet("index", "locations")) + + if configGet("move_sent", "posting"): + shutil.move(candidate, configGet("sent", "locations")+os.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")))) # type: ignore + + if configGet("sent", "reports"): + 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)] # type: ignore + ])) # type: ignore + + except Exception as exp: + logWrite(locale("post_exception", "console", locale=configGet("locale")).format(str(exp), traceback.format_exc())) + if configGet("error", "reports"): + app.send_message(configGet("admin"), locale("post_exception", "message", locale=configGet("locale")).format(exp, traceback.format_exc())) # type: ignore + pass + + + # Work in progress + # Check last posts forwards + # check_forwards(app) + +if configGet("mode") in ["both", "submit"]: + @app.on_message(~ filters.scheduled & filters.command(["start"], prefixes="/")) + def cmd_start(app, msg): + if msg.from_user.id not in jsonLoad(configGet("blocked", "locations")): + msg.reply_text(locale("start", "message", locale=msg.from_user.language_code)) + +if configGet("mode") in ["both", "submit"]: + @app.on_message(~ filters.scheduled & filters.command(["rules", "help"], prefixes="/")) + def cmd_rules(app, msg): + if msg.from_user.id not in jsonLoad(configGet("blocked", "locations")): + msg.reply_text(locale("rules", "message", locale=msg.from_user.language_code)) + +# Work in progress +# @app.on_message(~ filters.scheduled & filters.command(["forwards"], prefixes="/")) +# def cmd_forwards(app, msg): +# check_forwards(app) + +@app.on_message(~ filters.scheduled & filters.command(["kill", "die", "reboot"], prefixes=["", "/"])) +def cmd_kill(app, msg): + + if msg.from_user.id == configGet("admin"): + logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid))) + msg.reply_text(locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) + killProc(pid) + + +# Submission ===================================================================================================================================== +def subLimit(user): + submit = jsonLoad(configGet("submit", "locations")) + submit[str(user.id)] = time.time() + jsonSave(submit, configGet("submit", "locations")) + +def subLimited(user): + if user.id == configGet("admin"): + return False + else: + submit = jsonLoad(configGet("submit", "locations")) + if str(user.id) in submit: + if (time.time() - submit[str(user.id)]) < configGet("timeout", "submission"): + return True + else: + return False + else: + return False + +def subBlock(user): + blocked = jsonLoad(configGet("blocked", "locations")) + if user not in blocked: + blocked.append(user) + jsonSave(blocked, configGet("blocked", "locations")) + +def subUnblock(user): + blocked = jsonLoad(configGet("blocked", "locations")) + if user in blocked: + blocked.remove(user) + jsonSave(blocked, configGet("blocked", "locations")) + +if configGet("mode") in ["both", "submit"]: + @app.on_message(~ filters.scheduled & filters.photo | filters.video | filters.animation | filters.document) + def get_submission(_, msg): + try: + if msg.from_user.id not in jsonLoad(configGet("blocked", "locations")): + user_locale = msg.from_user.language_code + if not subLimited(msg.from_user): + + if msg.document != None: + if msg.document.mime_type not in configGet("mime_types", "submission"): + msg.reply_text(locale("mime_not_allowed", "message", locale=user_locale), quote=True) + return + if msg.document.file_size > configGet("file_size", "submission"): + 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 != None: + if msg.video.file_size > configGet("file_size", "submission"): + msg.reply_text(locale("document_too_large", "message", locale=user_locale).format(str(configGet("file_size", "submission")/1024/1024)), quote=True) + return + + buttons = [ + [ + InlineKeyboardButton(text=locale("sub_yes", "button", locale=configGet("locale")), callback_data=f"sub_yes_{msg.from_user.id}_{msg.id}") + ] + ] + + if msg.caption != None: + caption = str(msg.caption) + buttons[0].append( + InlineKeyboardButton(text=locale("sub_yes_caption", "button", locale=configGet("locale")), callback_data=f"sub_yes_{msg.from_user.id}_{msg.id}_caption") + ) + buttons[0].append( + InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{msg.from_user.id}_{msg.id}") + ) + else: + caption = "" + buttons[0].append( + InlineKeyboardButton(text=locale("sub_no", "button", locale=configGet("locale")), callback_data=f"sub_no_{msg.from_user.id}_{msg.id}") + ) + + caption += locale("sub_by", "message", locale=locale(configGet("locale"))) + + if msg.from_user.first_name != None: + caption += f" {msg.from_user.first_name}" + if msg.from_user.last_name != None: + caption += f" {msg.from_user.last_name}" + if msg.from_user.username != None: + caption += f" (@{msg.from_user.username})" + if msg.from_user.phone_number != None: + caption += f" ({msg.from_user.phone_number})" + + msg.copy(configGet("admin"), caption=caption, reply_markup=InlineKeyboardMarkup(buttons)) + + 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}") + ] + ] + + msg.reply_text(locale("sub_sent", "message", locale=user_locale), quote=True) + subLimit(msg.from_user) + + else: + msg.reply_text(locale("sub_cooldown", "message", locale=user_locale).format(str(configGet("timeout", "submission")))) + except AttributeError: + logWrite(f"from_user in function get_submission does not seem to contain id") + +@app.on_callback_query(filters.regex("sub_yes_[\s\S]*_[\s\S]*")) # type: ignore +def callback_query_yes(app, clb): # type: ignore + fullclb = clb.data.split("_") + user_locale = clb.from_user.language_code + try: + submission = app.get_messages(int(fullclb[2]), int(fullclb[3])) + except: + clb.answer(text=locale("sub_msg_unavail", "message", locale=user_locale), show_alert=True) + return + try: + media = app.download_media(submission, file_name=configGet("queue", "locations")+os.sep) + if clb.data.endswith("_caption"): + index = jsonLoad(configGet("index", "locations")) + index["captions"][Path(media).name] = submission.caption + jsonSave(index, configGet("index", "locations")) + except: + clb.answer(text=locale("sub_media_unavail", "message", locale=user_locale), show_alert=True) + return + submission.reply_text(locale("sub_yes", "message", locale=submission.from_user.language_code), quote=True) + clb.answer(text=locale("sub_yes", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) + +@app.on_callback_query(filters.regex("sub_no_[\s\S]*_[\s\S]*")) # type: ignore +def callback_query_no(app, clb): # type: ignore + fullclb = clb.data.split("_") + user_locale = clb.from_user.language_code + try: + submission = app.get_messages(int(fullclb[2]), int(fullclb[3])) + except: + clb.answer(text=locale("sub_msg_unavail", "message", locale=user_locale), show_alert=True) + return + submission.reply_text(locale("sub_no", "message", locale=submission.from_user.language_code), quote=True) + clb.answer(text=locale("sub_no", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) + +@app.on_callback_query(filters.regex("sub_block_[\s\S]*")) # type: ignore +def callback_query_block(app, clb): # type: ignore + fullclb = clb.data.split("_") + user_locale = clb.from_user.language_code + app.send_message(int(fullclb[2]), locale("sub_msg_unavail", "message", locale=configGet("locale"))) + subBlock(int(fullclb[2])) + clb.answer(text=locale("sub_block", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) + +@app.on_callback_query(filters.regex("sub_unblock_[\s\S]*")) # type: ignore +def callback_query_unblock(app, clb): # type: ignore + fullclb = clb.data.split("_") + user_locale = clb.from_user.language_code + app.send_message(int(fullclb[2]), locale("sub_msg_unavail", "message", locale=configGet("locale"))) + subUnblock(int(fullclb[2])) + clb.answer(text=locale("sub_unblock", "callback", locale=user_locale).format(fullclb[2]), show_alert=True) +#=========================================================================================================================================== + +@app.on_raw_update() +def fwd_got(app, update, users, chats): + if isinstance(update, UpdateChannelMessageForwards): + logWrite(f'Forward count increased to {update["forwards"]} on post {update["id"]} in channel {update["channel_id"]}') + logWrite(str(users), debug=True) + logWrite(str(chats), debug=True) + # else: + # logWrite(f"Got raw update of type {type(update)} with contents {update}", debug=True) + +for entry in configGet("time", "posting"): + schedule.every().day.at(entry).do(send_content) + + +def background_task(): + try: + while True: + try: + schedule.run_pending() + time.sleep(1) + except: + pass + except Exception as exp: + logWrite(locale("exception_occured", "console", locale=configGet("locale")).format(exp)) + except KeyboardInterrupt: + logWrite(locale("keyboard_interrupt", "console", locale=configGet("locale"))) + if configGet("shutdown", "reports"): + app.send_message(configGet("admin"), locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) # type: ignore + killProc(pid) + +if __name__ == "__main__": + + logWrite(locale("startup", "console", locale=configGet("locale")).format(str(pid))) + + app.start() # type: ignore + if configGet("startup", "reports"): + app.send_message(configGet("admin"), locale("startup", "message", locale=configGet("locale")).format(str(pid))) # type: ignore + + if configGet("mode") in ["both", "post"]: + t = Thread(target=background_task) + t.start() + + if configGet("mode") in ["both", "submit"]: + # Registering user commands + for entry in os.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", "")))) + app.set_bot_commands(commands_list, language_code=entry.replace(".json", "")) # type: ignore + + # 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")))) + app.set_bot_commands(commands_list) # type: ignore + + # Registering admin commands + commands_admin_list = [] + if configGet("mode") in ["both", "submit"]: + for command in configGet("commands"): + 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")))) + app.set_bot_commands(commands_admin_list, scope=BotCommandScopeChat(chat_id=configGet("admin"))) # type: ignore + + idle() + + app.send_message(configGet("admin"), locale("shutdown", "message", locale=configGet("locale")).format(str(pid))) # type: ignore + logWrite(locale("shutdown", "console", locale=configGet("locale")).format(str(pid))) + + killProc(pid) \ No newline at end of file