Skip to content

Commit

Permalink
yeehaw, now things actually work with v21
Browse files Browse the repository at this point in the history
  • Loading branch information
cschmittiey committed Jun 10, 2024
1 parent 0201820 commit 6d8154b
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 83 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
134 changes: 52 additions & 82 deletions main.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
Expand All @@ -46,58 +43,50 @@
"postprocessors": [],
}


YDL_OPTS_GIF = deepcopy(YDL_OPTS)
YDL_OPTS_GIF["outtmpl"] = f"{keys.tempdir}/%(id)s_gif.%(ext)s"
YDL_OPTS_GIF["postprocessors"].append({
"key": "Exec",
"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:
Expand All @@ -107,21 +96,20 @@ 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:
if trim := parse_timestamp(msg):
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:
Expand All @@ -135,39 +123,37 @@ 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
if fp.is_file() and fp.stat().st_size < 49_500_000:
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
Expand All @@ -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"])
Expand All @@ -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<sfrac>[0-9]+(?:\.[0-9]+)?(?:s|ms|us))|"
r"(?:(?:(?P<h>[0-9]+):)?(?P<m>[0-9]{1,2}):)?(?P<s>[0-9]{1,2}(?:\.[0-9]+)?))")
Expand All @@ -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'<pre>update = {html.escape(json.dumps(update_str, indent=2, ensure_ascii=False))}'
Expand All @@ -254,34 +237,21 @@ def error_handler(update: object, context: CallbackContext):
f'<pre>{html.escape(tb_string)}</pre>'
)

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)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
python-telegram-bot==21.3
python-telegram-bot[job-queue]==21.3
yt_dlp

0 comments on commit 6d8154b

Please sign in to comment.