Add more config options
All checks were successful
Build and Publish Jimmy.2 / build_and_publish (push) Successful in 9s
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:
parent
020a698ba6
commit
68edb16337
8 changed files with 113 additions and 18 deletions
|
@ -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.
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue