Add more config options
All checks were successful
Build and Publish Jimmy.2 / build_and_publish (push) Successful in 9s

And also document those config options better
This commit is contained in:
Nexus 2024-04-29 01:29:48 +01:00
parent 020a698ba6
commit 68edb16337
Signed by: nex
GPG key ID: 0FA334385D0B689F
8 changed files with 113 additions and 18 deletions

View file

@ -1,6 +1,10 @@
# Example config file for Jimmy v2.
# This file is populated mostly with the default values set in src/conf.py.
[jimmy] [jimmy]
token = "token" # the bot token token = "token" # the bot token
debug_guilds = [994710566612500550] # server IDs to create slash commands in. Set to null for all guilds. debug_guilds = [994710566612500550] # server IDs to create slash commands in. Omit for all guilds.
disabled_commands = ["i do not exist"] # A list of full command names to disable (e.g. "<group name> <subcommand", "command_name").
modules = ["cogs.*"] # a list of modules to load. Glob-based patterns can be used. Defaults to all cogs if ommitted
[logging] [logging]
level = "DEBUG" # can be one of DEBUG, INFO, WARNING, ERROR, CRITICAL. Defaults to INFO level = "DEBUG" # can be one of DEBUG, INFO, WARNING, ERROR, CRITICAL. Defaults to INFO
@ -15,6 +19,8 @@ suppress = [
] ]
# All the loggers specified here will have their log level set to WARNING. # All the loggers specified here will have their log level set to WARNING.
# Ollama server setups. Omit all `ollama.*` to disable all AI-related features.
[ollama.internal] [ollama.internal]
# name is "internal" # name is "internal"
owner = 421698654189912064 # who owns the server owner = 421698654189912064 # who owns the server
@ -29,5 +35,39 @@ base_url = "http://ollama:11434/api" # this is the default if you're running vi
owner = 421698654189912064 owner = 421698654189912064
allowed_models = ["*"] allowed_models = ["*"]
base_url = "http://example.com/api" base_url = "http://example.com/api"
icon_url = "http://example.com/favicon.png" icon_url = "http://example.com/favicon.png" # An icon to use in the footer of embeds
[screenshot]
# Configuration for the /screenshot command.
proxy = "http://example.test:8888" # the HTTP/S/OCKS proxy to use
[quote_a]
# configuration for the /quota command.
# channel = 123456
# ^ The channel ID. If omitted, will default to a channel called "quotes".
[quote_a.names]
foo = "bar"
# A key = "Value" mapping of names to true names. For example:
# foo = "bar"
# foobar = "bar"
# ipv6 = "security"
[redis]
# Redis configuration. If you are using the docker-compose setup, then you do not need to configure this.
host = "redis" # the host[name or IP] of the redis server
port = 6379 # self-explanatory
no_ping = false # disables the startup health-check. Not recommended to set to true.
[responder]
# Configures the auto-responder.
overpowered_by = [421698654189912064] # if these users are present in the server's member cache, auto-responses will be disabled in that server.
overrule_offline_superiors = true # if all the overpower users are missing or offline, ignore overpower rules
downloads_pdfs = true # If False, PDF links will not be downloaded and uploaded to discord.
[responder.transcoding]
# Configured specifically automated transcoding.
enabled = true # whether any transcoding at all should be done. If false, no links or attachments are ever probed.
hevc = true # Enables probing video links and converting any HEVC videos to H264+opus.
on_demand = true # Enables transcoding any video to h264+opus when 📼 (VHS) is reacted.

View file

@ -14,6 +14,7 @@ import httpx
from discord.ext import commands from discord.ext import commands
from .ffmeta import FFMeta from .ffmeta import FFMeta
from conf import CONFIG
class AutoResponder(commands.Cog): class AutoResponder(commands.Cog):
@ -21,6 +22,42 @@ class AutoResponder(commands.Cog):
self.bot = bot self.bot = bot
self.log = logging.getLogger("jimmy.cogs.auto_responder") self.log = logging.getLogger("jimmy.cogs.auto_responder")
self.transcode_lock = asyncio.Lock() self.transcode_lock = asyncio.Lock()
self.config = CONFIG["responder"]
if self.config.get("overpowered_by") and bot.intents.members is False:
raise ValueError(
"overpowered_by is set, however members intent is disabled, making it useless."
)
if self.config.get("overrule_offline_superiors", True) is True and bot.intents.presences is False:
raise ValueError(
"overrule_offline_superiors is enabled, however presences intent is not!"
)
self.config.setdefault("transcoding", {"enabled": True, "hevc": True, "on_demand": True})
@property
def transcoding_enabled(self) -> bool:
return self.config["transcoding"].get("enabled", True)
@property
def allow_hevc_to_h264(self) -> bool:
return self.transcoding_enabled and self.config["transcoding"].get("hevc", True)
@property
def allow_on_demand_transcoding(self) -> bool:
return self.transcoding_enabled and self.config["transcoding"].get("on_demand", True)
def overpowered(self, guild: discord.Guild | None) -> bool:
if not guild:
self.log.debug("Not overpowered, in DM")
return False # in a DM
enable_offline = self.config.get("overrule_offline_superiors", False)
for _id in self.config.get("overpowered_by", []):
member = guild.get_member(_id)
if member:
if member.status != discord.Status.offline or enable_offline is False:
self.log.debug("Overpowered by %s (%s)", member, member.status)
return True
self.log.debug("No overpowered.")
return False
@staticmethod @staticmethod
@typing.overload @typing.overload
@ -71,7 +108,6 @@ class AutoResponder(commands.Cog):
if new: if new:
_ = asyncio.create_task(update.add_reaction(new)) _ = asyncio.create_task(update.add_reaction(new))
last_reaction = new last_reaction = new
self.log.info("Waiting for transcode lock to release") self.log.info("Waiting for transcode lock to release")
async with self.transcode_lock: async with self.transcode_lock:
cog: FFMeta = self.bot.get_cog("FFMeta") cog: FFMeta = self.bot.get_cog("FFMeta")
@ -195,6 +231,9 @@ class AutoResponder(commands.Cog):
*domains: str, *domains: str,
additional: Iterable[str] = None additional: Iterable[str] = None
) -> None: ) -> None:
if self.allow_hevc_to_h264 is False:
self.log.debug("hevc_to_h264 is disabled, not engaging.")
return
additional = additional or [] additional = additional or []
links = self.extract_links(message.content, *domains) + list(additional) links = self.extract_links(message.content, *domains) + list(additional)
if links: if links:
@ -259,6 +298,9 @@ class AutoResponder(commands.Cog):
async def copy_ncfe_docs(self, message: discord.Message, links: list[ParseResult]) -> None: async def copy_ncfe_docs(self, message: discord.Message, links: list[ParseResult]) -> None:
files = [] files = []
if self.config.get("download_pdfs", True) is False:
self.log.debug("Download PDFs is disabled in config, disengaging.")
return
self.log.info("Preparing to download: %s", ", ".join(map(ParseResult.geturl, links))) self.log.info("Preparing to download: %s", ", ".join(map(ParseResult.geturl, links)))
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
for link in set(links): for link in set(links):
@ -291,10 +333,10 @@ class AutoResponder(commands.Cog):
@commands.Cog.listener("on_message") @commands.Cog.listener("on_message")
async def auto_responder(self, message: discord.Message): async def auto_responder(self, message: discord.Message):
if message.author == self.bot.user: if message.author == self.bot.user or self.overpowered(message.guild):
return return
# Check for HEVC truth social links and convert into h264 # Check for HEVC links and convert into h264
if message.channel.name == "spam" and message.author != self.bot.user: if message.channel.name == "spam" and message.author != self.bot.user:
await self.transcode_hevc_to_h264(message) await self.transcode_hevc_to_h264(message)
@ -304,7 +346,7 @@ class AutoResponder(commands.Cog):
@commands.Cog.listener("on_reaction_add") @commands.Cog.listener("on_reaction_add")
async def on_reaction_add(self, reaction: discord.Reaction, user: discord.User): async def on_reaction_add(self, reaction: discord.Reaction, user: discord.User):
if user == self.bot.user: if user == self.bot.user or self.overpowered(reaction.message.guild):
return return
if reaction.message.author != self.bot.user: if reaction.message.author != self.bot.user:
@ -313,6 +355,7 @@ class AutoResponder(commands.Cog):
extra = [] extra = []
if reaction.message.attachments: if reaction.message.attachments:
extra = [attachment.url for attachment in reaction.message.attachments] extra = [attachment.url for attachment in reaction.message.attachments]
if self.allow_on_demand_transcoding:
await self.transcode_hevc_to_h264(reaction.message, additional=extra) await self.transcode_hevc_to_h264(reaction.message, additional=extra)
@commands.Cog.listener("on_raw_reaction_add") @commands.Cog.listener("on_raw_reaction_add")

View file

@ -1,6 +1,5 @@
import asyncio import asyncio
import base64 import base64
import contextlib
import io import io
import json import json
import logging import logging
@ -12,7 +11,6 @@ from fnmatch import fnmatch
import aiohttp import aiohttp
import discord import discord
import httpx
import redis import redis
from discord import Interaction from discord import Interaction
from discord.ext import commands from discord.ext import commands

View file

@ -3,7 +3,7 @@ import io
import logging import logging
import re import re
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Annotated, Callable, Iterable from typing import Annotated, Callable
import discord import discord
import matplotlib.pyplot as plt import matplotlib.pyplot as plt

View file

@ -215,7 +215,7 @@ class ScreenshotCog(commands.Cog):
f"Cleanup time: {seconds(start_cleanup, end_cleanup)}s\n" f"Cleanup time: {seconds(start_cleanup, end_cleanup)}s\n"
f"Total time: {seconds(start_init, end_cleanup)}s\n" f"Total time: {seconds(start_init, end_cleanup)}s\n"
f"Screenshot size: {screenshot_size_mb}MB\n" f"Screenshot size: {screenshot_size_mb}MB\n"
f"Resolution: {resolution}" f"Resolution: {resolution}\n"
f"Used proxy: {use_proxy}", f"Used proxy: {use_proxy}",
colour=discord.Colour.dark_theme(), colour=discord.Colour.dark_theme(),
timestamp=discord.utils.utcnow(), timestamp=discord.utils.utcnow(),

View file

@ -23,9 +23,9 @@ class YTDLCog(commands.Cog):
self.log = logging.getLogger("jimmy.cogs.ytdl") self.log = logging.getLogger("jimmy.cogs.ytdl")
self.common_formats = { self.common_formats = {
"144p": "17", # mp4 (h264+aac) v "144p": "17", # mp4 (h264+aac) v
"240p": "133+139", "240p": "bv[width==240]+ba[ext=webm]/bv[width==240]+ba[ext=m4a]/bv[width==240]+ba",
"360p": "18", "360p": "18",
"480p": "135+139", "480p": "bv[width==480]+ba[ext=webm]/bv[width==480]+ba[ext=m4a]/bv[width==480]+ba",
"720p": "22", "720p": "22",
"1080p": "bv[width==1080]+ba[ext=webm]/bv[width==1080]+ba[ext=m4a]/bv[width==1080]+ba", "1080p": "bv[width==1080]+ba[ext=webm]/bv[width==1080]+ba[ext=m4a]/bv[width==1080]+ba",
"1440p": "bv[width==1440]+ba[ext=webm]/bv[width==1440]+ba[ext=m4a]/bv[width==1440]+ba", "1440p": "bv[width==1440]+ba[ext=webm]/bv[width==1440]+ba[ext=m4a]/bv[width==1440]+ba",
@ -102,9 +102,13 @@ class YTDLCog(commands.Cog):
:return: The created hash key :return: The created hash key
""" """
snip = snip or "*" snip = snip or "*"
await self._init_db()
async with aiosqlite.connect("./data/ytdl.db") as db:
_hash = hashlib.md5(f"{webpage_url}:{format_id}:{snip}".encode()).hexdigest() _hash = hashlib.md5(f"{webpage_url}:{format_id}:{snip}".encode()).hexdigest()
try:
await self._init_db()
except Exception as e:
logging.error("Failed to initialise ytdl database: %s", e, exc_info=True)
return
async with aiosqlite.connect("./data/ytdl.db") as db:
self.log.debug( self.log.debug(
"Saving %r (%r:%r:%r) with message %d>%d, index %d", "Saving %r (%r:%r:%r) with message %d>%d, index %d",
_hash, _hash,
@ -137,7 +141,11 @@ class YTDLCog(commands.Cog):
:param snip: The start and end time to snip the video. e.g. 00:00:00-00:10:00 :param snip: The start and end time to snip the video. e.g. 00:00:00-00:10:00
:return: the URL, if found and valid. :return: the URL, if found and valid.
""" """
try:
await self._init_db() await self._init_db()
except Exception as e:
logging.error("Failed to initialise ytdl database: %s", e, exc_info=True)
return
async with aiosqlite.connect("./data/ytdl.db") as db: async with aiosqlite.connect("./data/ytdl.db") as db:
_hash = hashlib.md5(f"{webpage_url}:{format_id}:{snip}".encode()).hexdigest() _hash = hashlib.md5(f"{webpage_url}:{format_id}:{snip}".encode()).hexdigest()
self.log.debug( self.log.debug(

View file

@ -28,10 +28,8 @@ try:
CONFIG.setdefault("logging", {}) CONFIG.setdefault("logging", {})
CONFIG.setdefault("jimmy", {}) CONFIG.setdefault("jimmy", {})
CONFIG.setdefault("ollama", {}) CONFIG.setdefault("ollama", {})
CONFIG.setdefault("rss", {"meta": {"channel": None}})
CONFIG.setdefault("screenshot", {}) CONFIG.setdefault("screenshot", {})
CONFIG.setdefault("quote_a", {"channel": None}) CONFIG.setdefault("quote_a", {"channel": None})
CONFIG.setdefault("server", {"host": "0.0.0.0", "port": 8080, "channel": 1032974266527907901})
CONFIG.setdefault("redis", {"host": "redis", "port": 6379, "decode_responses": True}) CONFIG.setdefault("redis", {"host": "redis", "port": 6379, "decode_responses": True})
except FileNotFoundError: except FileNotFoundError:
cwd = Path.cwd() cwd = Path.cwd()

View file

@ -179,6 +179,14 @@ async def delete_message(ctx: discord.ApplicationContext, message: discord.Messa
await ctx.delete(delay=15) await ctx.delete(delay=15)
@bot.check_once
async def check_is_enabled(ctx: commands.Context | discord.ApplicationContext) -> bool:
disabled = CONFIG["jimmy"].get("disabled_commands", [])
if ctx.command.qualified_name in disabled:
raise commands.DisabledCommand("%s is disabled via this instance's configuration file.")
return True
if not CONFIG["jimmy"].get("token"): if not CONFIG["jimmy"].get("token"):
log.critical("No token specified in config.toml. Exiting. (hint: set jimmy.token in config.toml)") log.critical("No token specified in config.toml. Exiting. (hint: set jimmy.token in config.toml)")
sys.exit(1) sys.exit(1)