Merge remote-tracking branch 'origin/master'
Some checks failed
Build and Publish / build_and_publish (push) Has been cancelled

This commit is contained in:
Nexus 2024-07-04 01:53:16 +01:00
commit ddc101b486
Signed by: nex
GPG key ID: 0FA334385D0B689F
8 changed files with 212 additions and 42 deletions

View file

@ -1,17 +1,15 @@
FROM python:3.11-slim
FROM python:3.12-slim
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
LABEL org.opencontainers.image.licenses AGPL-3.0
LABEL org.opencontainers.image.title "College Bot v2"
LABEL org.opencontainers.image.description "Version 2 of jimmy."
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"
LABEL org.opencontainers.image.licenses="AGPL-3.0"
LABEL org.opencontainers.image.title="College Bot v2"
LABEL org.opencontainers.image.description="Version 2 of jimmy."
WORKDIR /app
RUN DEBIAN_FRONTEND=noninteractive apt-get update
# Install chrome dependencies
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y --no-install-recommends \
webext-ublock-origin-chromium \
gconf-service \
libasound2 \
@ -49,35 +47,24 @@ 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 \
traceroute \
iputils-ping \
dnsutils \
net-tools \
git \
whois \
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
xdg-utils \
chromium-common \
chromium-driver \
chromium-sandbox \
traceroute \
iputils-ping \
dnsutils \
net-tools \
git \
whois \
curl \
libmagic-dev \
nmap \
ffmpeg
COPY requirements.txt /tmp/requirements.txt
RUN /app/venv/bin/pip install -Ur /tmp/requirements.txt --no-input
RUN pip install -Ur /tmp/requirements.txt --no-input
COPY ./src/ /app/
CMD ["/app/venv/bin/python", "main.py"]
CMD ["python", "main.py"]

View file

@ -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:

View file

@ -20,3 +20,4 @@ python-magic~=0.4
aiofiles~=23.2
fuzzywuzzy[speedup]~=0.18
tortoise-orm[asyncpg]~=0.21
superpaste @ git+https://github.com/nexy7574/superpaste.git@e31eca6

View file

@ -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

View file

@ -2,7 +2,6 @@
This module is only meant to be loaded during election times.
"""
import asyncio
import datetime
import logging
import random
import re

View file

@ -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:

View file

@ -1,10 +1,20 @@
# 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
import os
import re
import shlex
import shutil
import tempfile
import time
import typing
import warnings
from pathlib import Path
import discord
@ -14,6 +24,8 @@ from dns import asyncresolver
from rich.console import Console
from rich.tree import Tree
from conf import CONFIG
from superpaste import HstSHBackend
from superpaste.backends import GenericFile
class GetFilteredTextView(discord.ui.View):
@ -43,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:
@ -69,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"]
@ -215,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 = {
@ -346,6 +364,132 @@ class NetworkCog(commands.Cog):
await ctx.respond(embed=embed)
@commands.slash_command()
@commands.max_concurrency(1, commands.BucketType.user)
async def nmap(
self,
ctx: discord.ApplicationContext,
target: str,
technique: typing.Annotated[
str,
discord.Option(
str,
choices=[
discord.OptionChoice(name="TCP SYN", value="S"),
discord.OptionChoice(name="TCP Connect", value="T"),
discord.OptionChoice(name="TCP ACK", value="A"),
discord.OptionChoice(name="TCP Window", value="W"),
discord.OptionChoice(name="TCP Maimon", value="M"),
discord.OptionChoice(name="UDP", value="U"),
discord.OptionChoice(name="TCP Null", value="N"),
discord.OptionChoice(name="TCP FIN", value="F"),
discord.OptionChoice(name="TCP XMAS", value="X"),
],
default="T"
)
] = "T",
treat_all_hosts_online: bool = False,
service_scan: bool = False,
fast_mode: bool = False,
enable_os_detection: bool = False,
timing: typing.Annotated[
int,
discord.Option(
int,
description="Timing template to use 0 is slowest, 5 is fastest.",
choices=[0, 1, 2, 3, 4, 5],
default=3
)
] = 3,
ports: str = None
):
"""Runs nmap on a target. You cannot specify multiple targets."""
await ctx.defer()
if len(shlex.split(target)) > 1:
return await ctx.respond("You cannot specify multiple targets.")
if not shutil.which("nmap"):
warnings.warn(
"NMAP is not installed on this system, so the /nmap command is not enabled. "
"If you would like to enable it, install nmap, or mount the binary as a volume in docker."
)
return await ctx.respond("Nmap is not installed on this system.")
is_superuser = os.getuid() == 0
if technique != "T" and not is_superuser:
return await ctx.respond("Only `TCP Connect` can be used on this system.")
if enable_os_detection and not is_superuser:
return await ctx.respond("OS detection is not available on this system.")
with tempfile.TemporaryDirectory(prefix=f"nmap-{ctx.user.id}-{discord.utils.utcnow().timestamp():.0f}") as tmp:
tmp_dir = Path(tmp)
args = [
"nmap",
"-oA",
str(tmp_dir.resolve() / target),
"-T",
str(timing),
"-s" + technique,
"--reason",
"--noninteractive"
]
if treat_all_hosts_online:
args.append("-Pn")
if service_scan:
args.append("-sV")
if fast_mode:
args.append("-F")
if ports:
args.extend(("-p", ports))
if enable_os_detection:
args.append("-O")
args.append(target)
await ctx.respond(
embed=discord.Embed(
title="Running nmap...",
description="Command:\n"
"```{}```".format(shlex.join(args)),
)
)
process = await asyncio.create_subprocess_exec(
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
_, stderr = await process.communicate()
files = [discord.File(x, filename=x.name + ".txt") for x in tmp_dir.iterdir()]
if not files:
if len(stderr) <= 4089:
return await ctx.edit(
embed=discord.Embed(
title="Nmap failed.",
description="```\n" + stderr.decode() + "```",
color=discord.Color.red()
)
)
result = await HstSHBackend().async_create_paste(
GenericFile(stderr.decode())
)
return await ctx.edit(
embed=discord.Embed(
title="Nmap failed.",
description=f"Output was too long. [View full output]({result.url})",
color=discord.Color.red()
)
)
await ctx.edit(
embed=discord.Embed(
title="Nmap finished!",
description="Result files are attached.\n"
"* `gnmap` is 'greppable'\n"
"* `xml` is XML output\n"
"* `nmap` is normal output",
color=discord.Color.green()
),
files=files
)
def setup(bot):
bot.add_cog(NetworkCog(bot))

View file

@ -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":