diff --git a/Dockerfile b/Dockerfile index 270e844..22c0378 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11-slim +FROM python:3.12 LABEL org.opencontainers.image.source https://git.i-am.nexus/nex/college-bot-v2 LABEL org.opencontainers.image.url https://git.i-am.nexus/nex/college-bot-v2 @@ -11,50 +11,50 @@ WORKDIR /app RUN DEBIAN_FRONTEND=noninteractive apt-get update # Install chrome dependencies -RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ -webext-ublock-origin-chromium \ -gconf-service \ -libasound2 \ -libatk1.0-0 \ -libc6 \ -libcairo2 \ -libcups2 \ -libdbus-1-3 \ -libexpat1 \ -libfontconfig1 \ -libgcc1 \ -libgconf-2-4 \ -libgdk-pixbuf2.0-0 \ -libglib2.0-0 \ -libgtk-3-0 \ -libnspr4 \ -libpango-1.0-0 \ -libpangocairo-1.0-0 \ -libstdc++6 \ -libx11-6 \ -libx11-xcb1 \ -libxcb1 \ -libxcomposite1 \ -libxcursor1 \ -libxdamage1 \ -libxext6 \ -libxfixes3 \ -libxi6 \ -libxrandr2 \ -libxrender1 \ -libxss1 \ -libxtst6 \ -ca-certificates \ -fonts-liberation \ -libappindicator1 \ -libnss3 \ -lsb-release \ -xdg-utils - -RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - chromium-common \ - chromium-driver \ - chromium-sandbox +#RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ +#webext-ublock-origin-chromium \ +#gconf-service \ +#libasound2 \ +#libatk1.0-0 \ +#libc6 \ +#libcairo2 \ +#libcups2 \ +#libdbus-1-3 \ +#libexpat1 \ +#libfontconfig1 \ +#libgcc1 \ +#libgconf-2-4 \ +#libgdk-pixbuf2.0-0 \ +#libglib2.0-0 \ +#libgtk-3-0 \ +#libnspr4 \ +#libpango-1.0-0 \ +#libpangocairo-1.0-0 \ +#libstdc++6 \ +#libx11-6 \ +#libx11-xcb1 \ +#libxcb1 \ +#libxcomposite1 \ +#libxcursor1 \ +#libxdamage1 \ +#libxext6 \ +#libxfixes3 \ +#libxi6 \ +#libxrandr2 \ +#libxrender1 \ +#libxss1 \ +#libxtst6 \ +#ca-certificates \ +#fonts-liberation \ +#libappindicator1 \ +#libnss3 \ +#lsb-release \ +#xdg-utils +# +#RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ +# chromium-common \ +# chromium-driver \ +# chromium-sandbox # Install general utilities RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ @@ -67,11 +67,6 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ curl \ libmagic-dev -# Install image dependencies -RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - ffmpeg \ - imagemagick - RUN python3 -m venv /app/venv RUN /app/venv/bin/pip install --upgrade --no-input pip wheel setuptools diff --git a/docker-compose.yml b/docker-compose.yml index 4a6619c..c8adbb4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,11 @@ services: - ./jimmy.log:/app/jimmy.log - /dev/dri:/dev/dri - jimmy-data:/app/data + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + - /usr/bin/nmap:/usr/bin/nmap:ro + - /usr/bin/ffmpeg:/usr/bin/ffmpeg:ro + - /usr/bin/ffprobe:/usr/bin/ffprobe:ro extra_hosts: - host.docker.internal:host-gateway depends_on: diff --git a/src/cogs/auto_responder.py b/src/cogs/auto_responder.py index 44dd2b4..03f8329 100644 --- a/src/cogs/auto_responder.py +++ b/src/cogs/auto_responder.py @@ -1,5 +1,11 @@ +# In order to use commands in this system, the following things must be installed (ideally in /usr/local/bin): +# - `ffmpeg`, usually `ffmpeg`, sometimes `ffmpeg-full`. +# - /dev/dri should be passed through to the container for hardware acceleration. +# - `ffprobe`, usually `ffmpeg`, sometimes `ffmpeg-full`. +# In docker, you can just bind these as volumes, and read-only too. import asyncio import io +import shutil import typing from collections.abc import Iterable import logging @@ -105,6 +111,12 @@ class AutoResponder(commands.Cog): :param uri: The URI to transcode :return: A transcoded file """ + if not shutil.which("ffmpeg"): + self.log.error("ffmpeg not installed") + return + if not shutil.which("ffprobe"): + self.log.error("ffprobe not installed") + return last_reaction: str | None = None def update_reaction(new: str | None = None) -> None: @@ -118,9 +130,7 @@ class AutoResponder(commands.Cog): last_reaction = new self.log.info("Waiting for transcode lock to release") async with self.transcode_lock: - cog: FFMeta = self.bot.get_cog("FFMeta") - if not cog: - raise RuntimeError("FFMeta cog not loaded") + cog = FFMeta(self.bot) if not isinstance(uri, str): uri = str(uri) @@ -240,6 +250,12 @@ class AutoResponder(commands.Cog): *domains: str, additional: Iterable[str] = None ) -> None: + if not shutil.which("ffmpeg"): + self.log.error("ffmpeg not installed") + return + if not shutil.which("ffprobe"): + self.log.error("ffprobe not installed") + return if self.allow_hevc_to_h264 is False: self.log.debug("hevc_to_h264 is disabled, not engaging.") return diff --git a/src/cogs/election.py b/src/cogs/election.py index b490787..7dac3be 100644 --- a/src/cogs/election.py +++ b/src/cogs/election.py @@ -2,7 +2,6 @@ This module is only meant to be loaded during election times. """ import asyncio -import datetime import logging import re diff --git a/src/cogs/ffmeta.py b/src/cogs/ffmeta.py index ef69879..06bda53 100644 --- a/src/cogs/ffmeta.py +++ b/src/cogs/ffmeta.py @@ -1,8 +1,14 @@ +# In order to use commands in this system, the following things must be installed (ideally in /usr/local/bin): +# - `ffmpeg`, usually `ffmpeg`, sometimes `ffmpeg-full`. +# - /dev/dri should be passed through to the container for hardware acceleration. +# - `ffprobe`, usually `ffmpeg`, sometimes `ffmpeg-full`. +# In docker, you can just bind these as volumes, and read-only too. import asyncio import io import json import logging import pathlib +import shutil import sys import tempfile import typing @@ -56,6 +62,8 @@ class FFMeta(commands.Cog): @commands.slash_command() async def ffprobe(self, ctx: discord.ApplicationContext, url: str = None, attachment: discord.Attachment = None): """Runs ffprobe on a given URL or attachment""" + if not shutil.which("ffprobe"): + return await ctx.respond("ffprobe is not installed on this system.") if url is None: if attachment is None: return await ctx.respond("No URL or attachment provided") @@ -142,6 +150,10 @@ class FFMeta(commands.Cog): ] = False, ): """Converts a given URL or attachment to an Opus file""" + if not shutil.which("ffmpeg"): + return await ctx.respond("ffmpeg is not installed on this system.") + if not shutil.which("ffprobe"): + return await ctx.respond("ffprobe is not installed on this system.") if bitrate == 0: bitrate = 0.5 if mono: @@ -244,6 +256,8 @@ class FFMeta(commands.Cog): rbh = Path("assets/right-behind-you.ogg").resolve() if not rbh.exists(): return await ctx.respond("The file `right-behind-you.ogg` is missing.") + if not shutil.which("ffmpeg"): + return await ctx.respond("ffmpeg is not installed on this system.") if not image.content_type.startswith("image"): return await ctx.respond("That's not an image!") with tempfile.NamedTemporaryFile(suffix=Path(image.filename).suffix) as temp: diff --git a/src/cogs/net.py b/src/cogs/net.py index cb1ef66..95c1c22 100644 --- a/src/cogs/net.py +++ b/src/cogs/net.py @@ -1,3 +1,9 @@ +# In order to use commands in this system, the following things must be installed (ideally in /usr/local/bin): +# - `ping`, usually iputils-ping +# - `whois`, usually whois +# - `traceroute`, usually traceroute +# - `nmap`, usually nmap +# In docker, you can just bind these as volumes, and read-only too. import asyncio import io import json @@ -49,6 +55,8 @@ class NetworkCog(commands.Cog): @commands.slash_command() async def ping(self, ctx: discord.ApplicationContext, target: str = None): """Get the bot's latency, or the network latency to a target.""" + if not shutil.which("ping"): + return await ctx.respond("Ping is not installed on this system.") if target is None: return await ctx.respond(f"Pong! {round(self.bot.latency * 1000)}ms") else: @@ -75,6 +83,8 @@ class NetworkCog(commands.Cog): @commands.slash_command() async def whois(self, ctx: discord.ApplicationContext, target: str): """Get information about a user.""" + if not shutil.which("whois"): + return await ctx.respond("Whois is not installed on this system.") async def run_command(with_disclaimer: bool = False): args = [] if with_disclaimer else ["-H"] @@ -221,6 +231,8 @@ class NetworkCog(commands.Cog): await ctx.defer() if re.search(r"\s+", url): return await ctx.respond("URL cannot contain spaces.") + if not shutil.which("traceroute"): + return await ctx.respond("Traceroute is not installed on this system.") args = ["sudo", "-E", "-n", "traceroute"] flags = { diff --git a/src/cogs/ytdl.py b/src/cogs/ytdl.py index c3023c5..833b1f2 100644 --- a/src/cogs/ytdl.py +++ b/src/cogs/ytdl.py @@ -3,6 +3,7 @@ import functools import hashlib import logging import math +import shutil import time import datetime @@ -190,6 +191,8 @@ class YTDLCog(commands.Cog): :param file: The file to convert :return: The converted file """ + if not shutil.which("ffmpeg"): + raise RuntimeError("ffmpeg is not installed.") new_file = file.with_suffix(".m4a") args = [ "-vn", @@ -319,8 +322,9 @@ class YTDLCog(commands.Cog): else: chosen_format = user_format + ffmpeg_installed = bool(shutil.which("ffmpeg")) options.setdefault("postprocessors", []) - if audio_only: + if audio_only and ffmpeg_installed: # Overwrite format here to be best audio under 25 megabytes. chosen_format = "ba[filesize<20M]" # Also force sorting by the best audio bitrate first. @@ -332,7 +336,7 @@ class YTDLCog(commands.Cog): options["format"] = chosen_format options["paths"] = paths - if subtitles: + if subtitles and ffmpeg_installed: subtitles, burn = subtitles.split("+", 1) if "+" in subtitles else (subtitles, "0") burn = burn[0].lower() in ("y", "1", "t") if subtitles.lower() == "auto":