From 6d8154b7daf1661d1d87a96840bf9402ae610750 Mon Sep 17 00:00:00 2001 From: Caleb Smith Date: Mon, 10 Jun 2024 11:29:02 -0700 Subject: [PATCH] yeehaw, now things actually work with v21 --- Dockerfile | 1 + main.py | 134 ++++++++++++++++++----------------------------- requirements.txt | 2 +- 3 files changed, 54 insertions(+), 83 deletions(-) diff --git a/Dockerfile b/Dockerfile index 106b7c7..fb62b2c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ COPY . /app WORKDIR /app RUN pip install --no-cache-dir -r requirements.txt +RUN apk add --no-cache ffmpeg ENV PYTHON_BIN python3 ENV PYTHONUNBUFFERED 1 diff --git a/main.py b/main.py index 78a0018..8bfac67 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,11 @@ """ vidifierbot -Copyright (c) 2021-2022 classabbyamp +Copyright (c) 2021-2022 classabbyamp, 2024 cschmittiey Released under the BSD-3-Clause license """ - -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import logging from pathlib import Path from typing import Optional @@ -20,22 +19,20 @@ import yt_dlp import telegram as tg -from telegram.ext import Updater, CommandHandler, MessageHandler, filters, CallbackContext +from telegram import Update +from telegram.ext import Application, CommandHandler, MessageHandler, filters, CallbackContext, ContextTypes from data import keys - # Enable logging logging.basicConfig(format='[%(asctime)s] [%(levelname)s] %(name)s - %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) - HELP_TEXT = "" with Path("./data/help.md").open() as hf: HELP_TEXT = hf.read() - YDL_OPTS = { "outtmpl": f"{keys.tempdir}/%(id)s.%(ext)s", "logger": logger, @@ -46,7 +43,6 @@ "postprocessors": [], } - YDL_OPTS_GIF = deepcopy(YDL_OPTS) YDL_OPTS_GIF["outtmpl"] = f"{keys.tempdir}/%(id)s_gif.%(ext)s" YDL_OPTS_GIF["postprocessors"].append({ @@ -54,50 +50,43 @@ "exec_cmd": "ffmpeg -y -v 16 -i {} -c copy -an {}.mp4 && mv {}.mp4 {}", }) - class InternalError(Exception): def __init__(self, msg: str): self.msg = msg - -def help_command(update: tg.Update, _: CallbackContext): +async def help_command(update: Update, context: CallbackContext): if update.message: - update.message.reply_text(HELP_TEXT, disable_web_page_preview=True, parse_mode=tg.ParseMode.MARKDOWN_V2) - + await update.message.reply_text(HELP_TEXT, disable_web_page_preview=True, parse_mode=tg.constants.ParseMode.MARKDOWN_V2) -def shutdown_command(update: tg.Update, context: CallbackContext): +async def shutdown_command(update: Update, context: CallbackContext): if update.effective_user: if update.effective_user.id == keys.owner_id: logger.info(f"Shutdown command received from {update.effective_user.id}") - context.bot.send_message(chat_id=keys.owner_id, text="Shutting down", parse_mode=tg.ParseMode.MARKDOWN_V2) + await context.bot.send_message(chat_id=keys.owner_id, text="Shutting down", parse_mode=tg.constants.ParseMode.MARKDOWN_V2) os.kill(os.getpid(), SIGUSR1) return logger.info(f"Shutdown command received from {update.effective_user.id}, ignoring") return logger.info("Shutdown command received from unknown user, ignoring") - -def restart_command(update: tg.Update, context: CallbackContext): +async def restart_command(update: Update, context: CallbackContext): if update.effective_user: if update.effective_user.id == keys.owner_id: logger.info(f"Restart command received from {update.effective_user.id}") - context.bot.send_message(chat_id=keys.owner_id, text="Restarting", parse_mode=tg.ParseMode.MARKDOWN_V2) + await context.bot.send_message(chat_id=keys.owner_id, text="Restarting", parse_mode=tg.constants.ParseMode.MARKDOWN_V2) os.kill(os.getpid(), SIGUSR2) return logger.info(f"Restart command received from {update.effective_user.id}, ignoring") return logger.info("Restart command received from unknown user, ignoring") +async def vidify_command(update: Update, context: CallbackContext): + await run_cmd(update, gif=False) -def vidify_command(update: tg.Update, _: CallbackContext): - run_cmd(update, gif=False) - - -def gifify_command(update: tg.Update, _: CallbackContext): - run_cmd(update, gif=True) +async def gifify_command(update: Update, context: CallbackContext): + await run_cmd(update, gif=True) - -def run_cmd(update: tg.Update, gif: bool): +async def run_cmd(update: Update, gif: bool): urls = [] if update.message: if update.message.text: @@ -107,13 +96,12 @@ def run_cmd(update: tg.Update, gif: bool): urls += list(update.message.reply_to_message.parse_entities(types=["url"]).values()) if urls: - get_and_send_videos(update.message, urls, gif) + await get_and_send_videos(update.message, urls, gif) else: - update.message.reply_text(("Unable to find any URLs to convert in your message or the replied-to message. " - "If this is a private group, I probably can't see the replied-to message.")) - + await update.message.reply_text(("Unable to find any URLs to convert in your message or the replied-to message. " + "If this is a private group, I probably can't see the replied-to message.")) -def get_and_send_videos(msg: tg.Message, urls: list[str], gif: bool = False): +async def get_and_send_videos(msg: tg.Message, urls: list[str], gif: bool = False): logger.info(f"[{msg.message_id}] {', '.join(urls)}") opts: dict = YDL_OPTS if not gif else YDL_OPTS_GIF try: @@ -121,7 +109,7 @@ def get_and_send_videos(msg: tg.Message, urls: list[str], gif: bool = False): opts["postprocessors"].append(trim) except InternalError as e: logger.error(f"[{msg.message_id}] {e.msg}") - msg.reply_text(f"{e.msg}\nid: {msg.message_id}", quote=True, disable_web_page_preview=True) + await msg.reply_text(f"{e.msg}\nid: {msg.message_id}", quote=True, disable_web_page_preview=True) return with yt_dlp.YoutubeDL(opts) as ydl: for url in urls: @@ -135,16 +123,15 @@ def get_and_send_videos(msg: tg.Message, urls: list[str], gif: bool = False): fn += ".trim.mp4" except Exception as e: logger.error(f"[{msg.message_id}] {type(e)}: {e}") - msg.reply_text(f"Unable to find video at {url}\nid: {msg.message_id}", - quote=True, disable_web_page_preview=True) + await msg.reply_text(f"Unable to find video at {url}\nid: {msg.message_id}", + quote=True, disable_web_page_preview=True) else: - send_videos(msg, url, fn, v_id, gif) + await send_videos(msg, url, fn, v_id, gif) f = Path(fn) if f.is_file(): f.unlink() - -def send_videos(msg: tg.Message, url: str, fn: str, v_id: str, gif: bool = False): +async def send_videos(msg: tg.Message, url: str, fn: str, v_id: str, gif: bool = False): if fn: fp = Path(fn) # check vid is <50MB @@ -152,22 +139,21 @@ def send_videos(msg: tg.Message, url: str, fn: str, v_id: str, gif: bool = False with fp.open("rb") as video: try: if gif: - msg.reply_animation(animation=video, caption=url, quote=True) + await msg.reply_animation(animation=video, caption=url, quote=True) else: - msg.reply_video(video=video, caption=url, quote=True) + await msg.reply_video(video=video, caption=url, quote=True, write_timeout=60) except tg.error.TelegramError as e: logger.error(f"[{v_id}] {e}") - msg.reply_text(f"Unable to upload video from {url}\nid: {v_id}", - quote=True, disable_web_page_preview=True) + await msg.reply_text(f"Unable to upload video from {url}\nid: {v_id}", + quote=True, disable_web_page_preview=True) else: logger.error(f"[{v_id}] file does not exist or too large for upload") - msg.reply_text(f"Unable to find video at {url}, or video is too large to upload\nid: {v_id}", - quote=True, disable_web_page_preview=True) + await msg.reply_text(f"Unable to find video at {url}, or video is too large to upload\nid: {v_id}", + quote=True, disable_web_page_preview=True) else: logger.error(f"[{v_id}] file does not exist") - msg.reply_text(f"Unable to find video at {url}\nid: {v_id}", - quote=True, disable_web_page_preview=True) - + await msg.reply_text(f"Unable to find video at {url}\nid: {v_id}", + quote=True, disable_web_page_preview=True) def parse_timestamp(msg: tg.Message) -> Optional[dict[str, str]]: start = None @@ -194,7 +180,6 @@ def parse_timestamp(msg: tg.Message) -> Optional[dict[str, str]]: raise InternalError("Must provide end= or dur= with start=") return None - def filter_text_entities(msg: tg.Message) -> str: entities = msg.parse_entities(types=["url", "email", "mention", "hashtag", "bot_command", "text_link", "text_mention", "phone_number", "cashtag"]) @@ -205,7 +190,6 @@ def filter_text_entities(msg: tg.Message) -> str: return text return "" - def get_timestamp(pfxs: list[str], text: str) -> Optional[str]: TS = (r"(?:(?P[0-9]+(?:\.[0-9]+)?(?:s|ms|us))|" r"(?:(?:(?P[0-9]+):)?(?P[0-9]{1,2}):)?(?P[0-9]{1,2}(?:\.[0-9]+)?))") @@ -222,29 +206,28 @@ def get_timestamp(pfxs: list[str], text: str) -> Optional[str]: ts_val = f"{int(h):0>2}:{int(m):0>2}:{float(s):02.3f}" return ts_val - -def cleanup_files(_: CallbackContext): +async def cleanup_files(context: ContextTypes.DEFAULT_TYPE): # delete mp4 files in keys.tempdir older than 5 min deleted = 0 - del_age = timedelta(minutes=1) + del_age = timedelta(minutes=5) tmp = Path(keys.tempdir) files = tmp.glob("*.mp4") for f in files: - age = abs(datetime.utcnow() - datetime.utcfromtimestamp(f.stat().st_ctime)) + file_creation_time = datetime.fromtimestamp(f.stat().st_ctime, timezone.utc) + age = abs(datetime.now(timezone.utc) - file_creation_time) if f.is_file() and age > del_age: f.unlink() deleted += 1 if deleted: logger.info(f"Deleted {deleted} cached mp4 files from {keys.tempdir}") - -def error_handler(update: object, context: CallbackContext): +async def error_handler(update: object, context: CallbackContext): logger.error("Exception while handling an update:", exc_info=context.error) tb_list = traceback.format_exception(None, context.error, context.error.__traceback__) # type: ignore tb_string = ''.join(tb_list) - update_str = update.to_dict() if isinstance(update, tg.Update) else str(update) + update_str = await update.to_dict() if isinstance(update, Update) else str(update) message = ( f'An exception was raised while handling an update\n' f'
update = {html.escape(json.dumps(update_str, indent=2, ensure_ascii=False))}'
@@ -254,34 +237,21 @@ def error_handler(update: object, context: CallbackContext):
         f'
{html.escape(tb_string)}
' ) - context.bot.send_message(chat_id=keys.owner_id, text=message, parse_mode=tg.ParseMode.HTML) + await context.bot.send_message(chat_id=keys.owner_id, text=message, parse_mode=tg.constants.ParseMode.HTML) +if __name__ == "__main__": + application = Application.builder().token(keys.tg_token).build() -def signal_handler(signum: int, _): - match signum: - case 10: - logger.info("Shutting down...") - raise SystemExit(0) - case 12: - logger.info("Restarting...") - raise SystemExit(42) + application.add_handler(CommandHandler("start", help_command)) + application.add_handler(CommandHandler("shutdown", shutdown_command)) + application.add_handler(CommandHandler("restart", restart_command)) + application.add_handler(CommandHandler("help", help_command)) + application.add_handler(CommandHandler("vidify", vidify_command)) + application.add_handler(CommandHandler("gifify", gifify_command)) + application.add_handler(MessageHandler(filters.ChatType.PRIVATE & filters.Entity("url"), vidify_command)) + application.add_error_handler(error_handler) + job_queue = application.job_queue + job_queue.run_repeating(cleanup_files, interval=timedelta(minutes=5), first=10) -if __name__ == "__main__": - updater = Updater(keys.tg_token, use_context=True, user_sig_handler=signal_handler) - - dp = updater.dispatcher - dp.add_handler(CommandHandler("start", help_command, run_async=True)) - dp.add_handler(CommandHandler("shutdown", shutdown_command, run_async=False)) - dp.add_handler(CommandHandler("restart", restart_command, run_async=False)) - dp.add_handler(CommandHandler("help", help_command, run_async=True)) - dp.add_handler(CommandHandler("vidify", vidify_command, run_async=True)) - dp.add_handler(CommandHandler("gifify", gifify_command, run_async=True)) - dp.add_handler(MessageHandler(filters.chat_type.private & filters.entity("url"), vidify_command, run_async=True)) - dp.add_error_handler(error_handler) - - jq = updater.job_queue - jq.run_repeating(cleanup_files, interval=timedelta(minutes=5), first=10) - - updater.start_polling() - updater.idle(stop_signals=(SIGINT, SIGTERM, SIGABRT, SIGUSR1, SIGUSR2)) + application.run_polling(allowed_updates=Update.ALL_TYPES) diff --git a/requirements.txt b/requirements.txt index f844bda..f58c676 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -python-telegram-bot==21.3 +python-telegram-bot[job-queue]==21.3 yt_dlp