Merge remote-tracking branch 'origin/master'
Some checks failed
Build and Publish / build_and_publish (push) Has been cancelled
Some checks failed
Build and Publish / build_and_publish (push) Has been cancelled
This commit is contained in:
commit
ddc101b486
8 changed files with 212 additions and 42 deletions
59
Dockerfile
59
Dockerfile
|
@ -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"]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
144
src/cogs/net.py
144
src/cogs/net.py
|
@ -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))
|
||||
|
|
|
@ -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":
|
||||
|
|
Loading…
Reference in a new issue