cogs refactoring, removing useless bloat, main.py refactor

This commit is contained in:
2026-04-03 21:24:24 +05:00
commit 6c9a482ee7
14 changed files with 537 additions and 0 deletions

0
cogs/__init__.py Normal file
View File

15
cogs/help/config.json Normal file
View File

@@ -0,0 +1,15 @@
{
"description": "хмм даже не знаюююююю",
"usages": ["help"],
"hidden": false,
"messages": {
"upper_desc": "команды:",
"footer": "что то поломалось - пинг меня\nесли имеются предложения по улучшению, в лс или пинг",
"pfx_message": "префикс:"
},
"settings": {
"command_pfx": "!",
"cogs_dir": "..",
"column_width": 20
}
}

107
cogs/help/help.py Normal file
View File

@@ -0,0 +1,107 @@
import logging
from discord.ext import commands
import os
import json
logger = logging.getLogger(__name__)
CONFIG_FILE = 'config.json'
REQUIRED_MESSAGES = ['upper_desc', 'footer', 'pfx_message']
class HelpCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.help_config = self._load_and_validate_config()
self.cogs_dir = os.path.join(
os.path.dirname(__file__),
self.help_config["settings"]["cogs_dir"]
)
def _load_and_validate_config(self) -> dict:
path = os.path.join(os.path.dirname(__file__), CONFIG_FILE)
if not os.path.exists(path):
raise FileNotFoundError(f"Help cog config not found at {path}")
with open(path, 'r', encoding='utf-8') as f:
config = json.load(f)
messages = config.get("messages", {})
missing_msgs = [key for key in REQUIRED_MESSAGES if key not in messages]
if missing_msgs:
raise KeyError(f"Help config 'messages' is missing required keys: {missing_msgs}")
settings = config.get("settings", {})
settings.setdefault("cogs_dir", "..")
settings.setdefault("column_width", 20)
settings.setdefault("command_pfx", "!")
config["messages"] = messages
config["settings"] = settings
logger.info("Help cog config loaded successfully")
return config
def _build_help_text(self, prefix: str) -> list[str]:
settings = self.help_config["settings"]
column_width = settings["column_width"]
lines = []
if not os.path.exists(self.cogs_dir):
logger.error(f"Cogs directory not found at: {self.cogs_dir}")
return lines
for item in sorted(os.listdir(self.cogs_dir)):
cog_dir = os.path.join(self.cogs_dir, item)
if not os.path.isdir(cog_dir):
continue
config_path = os.path.join(cog_dir, CONFIG_FILE)
if not os.path.exists(config_path):
continue
try:
with open(config_path, 'r', encoding='utf-8') as f:
cog_config = json.load(f)
except (json.JSONDecodeError, OSError) as e:
logger.warning(f"Failed to load config for cog '{item}': {e}")
continue
description = cog_config.get('description', '')
usages = cog_config.get('usages', [])
hidden = cog_config.get('hidden', False)
if not usages or hidden:
continue
for usage in usages:
usage_text = usage if usage.startswith(prefix) else f"{prefix}{usage}"
lines.append(f"{usage_text:<{column_width}} : {description}")
return lines
@commands.command(name="help")
async def help(self, ctx: commands.Context):
cfg_msgs = self.help_config["messages"]
cfg_sets = self.help_config["settings"]
prefix = ctx.prefix or cfg_sets['command_pfx']
lines = self._build_help_text(prefix)
if not lines:
await ctx.send("No commands available.")
return
help_text = (
f"{cfg_msgs['upper_desc']}\n"
f"```\n"
f"{chr(10).join(lines)}\n"
f"```\n"
f"{cfg_msgs['pfx_message']} `{cfg_sets['command_pfx']}`\n"
f"{cfg_msgs['footer']}"
)
await ctx.send(help_text)
async def setup(bot: commands.Bot):
await bot.add_cog(HelpCog(bot))

23
cogs/uptime/config.json Normal file
View File

@@ -0,0 +1,23 @@
{
"description": "аптайм бота",
"usages": ["!uptime"],
"first_seen": null,
"total_downtime": 0.0,
"last_start": null,
"labels": {
"days": ["день", "дня", "дней"],
"hours": ["час", "часа", "часов"],
"minutes": ["минуту", "минуты", "минут"],
"seconds": ["секунду", "секунды","секунд"]
},
"embed": {
"session_field": "текущая сессия",
"availability_field":"тотал аптайм"
},
"settings": {
"max_units": 4,
"show_percent": true
}
}

150
cogs/uptime/uptime.py Normal file
View File

@@ -0,0 +1,150 @@
import discord
from discord.ext import commands
from datetime import datetime, timezone
import json
import os
CONFIG_FILE = "config.json"
STATE_FILE = "uptime_state.json"
REQUIRED_KEYS = ["labels", "embed", "settings"]
REQUIRED_LABEL_KEYS = ["days", "hours", "minutes", "seconds"]
REQUIRED_EMBED_KEYS = ["session_field", "availability_field"]
def pluralize(n: int, forms: tuple[str, str, str]) -> str:
if 11 <= n % 100 <= 14:
return f"{n} {forms[2]}"
r = n % 10
if r == 1: return f"{n} {forms[0]}"
if 2 <= r <= 4: return f"{n} {forms[1]}"
return f"{n} {forms[2]}"
def format_delta(total_seconds: int, labels: dict, max_units: int) -> str:
minutes, secs = divmod(total_seconds, 60)
hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24)
candidates = [
(days, labels["days"]),
(hours, labels["hours"]),
(minutes, labels["minutes"]),
(secs, labels["seconds"]),
]
parts = [pluralize(v, tuple(f)) for v, f in candidates if v][:max_units]
return " ".join(parts) if parts else pluralize(0, tuple(labels["seconds"]))
def _now_ts() -> float:
return datetime.now(timezone.utc).timestamp()
class UptimeSimple(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.session_start = _now_ts()
self.config = self._load_and_validate_config()
self.state = self._load_state()
if self.state["last_start"] is not None:
gap = self.session_start - self.state["last_start"]
if gap > 5:
self.state["total_downtime"] += gap
if self.state["first_seen"] is None:
self.state["first_seen"] = self.session_start
self.state["last_start"] = self.session_start
self._save_state()
def _parse_color(self, color_val) -> int:
if isinstance(color_val, str):
return int(color_val.lstrip("#").replace("0x", ""), 16)
return int(color_val)
def _load_and_validate_config(self) -> dict:
path = os.path.join(os.path.dirname(__file__), CONFIG_FILE)
if not os.path.exists(path):
raise FileNotFoundError(f"Uptime cog config not found at {path}")
with open(path, "r", encoding="utf-8") as f:
config = json.load(f)
missing = [k for k in REQUIRED_KEYS if k not in config]
if missing:
raise KeyError(f"Uptime config is missing required keys: {missing}")
missing_labels = [k for k in REQUIRED_LABEL_KEYS if k not in config["labels"]]
if missing_labels:
raise KeyError(f"Uptime config 'labels' is missing keys: {missing_labels}")
missing_embed = [k for k in REQUIRED_EMBED_KEYS if k not in config["embed"]]
if missing_embed:
raise KeyError(f"Uptime config 'embed' is missing keys: {missing_embed}")
settings = config.get("settings", {})
settings.setdefault("max_units", 4)
settings.setdefault("show_percent", True)
color_val = config["embed"].get("color", 0x2ecc71)
config["embed"]["parsed_color"] = discord.Color(self._parse_color(color_val))
return config
def _state_path(self) -> str:
return os.path.join(os.path.dirname(__file__), STATE_FILE)
def _load_state(self) -> dict:
path = self._state_path()
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
return {
"first_seen": None,
"total_downtime": 0.0,
"last_start": None,
}
def _save_state(self) -> None:
with open(self._state_path(), "w", encoding="utf-8") as f:
json.dump(self.state, f, indent=2)
def availability_percent(self) -> float:
now = _now_ts()
total_span = now - self.state["first_seen"]
if total_span <= 0:
return 100.0
return max(0.0, (total_span - self.state["total_downtime"]) / total_span * 100)
@staticmethod
def _pct_bar(pct: float, width: int = 10) -> str:
filled = round(pct / 100 * width)
return "" * filled + "" * (width - filled)
@commands.command(name="uptime")
async def uptime(self, ctx: commands.Context):
session_seconds = int(_now_ts() - self.session_start)
settings = self.config["settings"]
embed = discord.Embed(color=self.config["embed"]["parsed_color"])
embed.add_field(
name = self.config["embed"]["session_field"],
value = format_delta(session_seconds, self.config["labels"], settings["max_units"]),
inline = False,
)
if settings["show_percent"]:
pct = self.availability_percent()
embed.add_field(
name = self.config["embed"]["availability_field"],
value = f"{self._pct_bar(pct)} {pct:.2f}%",
inline = False,
)
await ctx.send(embed=embed)
async def cog_unload(self) -> None:
self.state["last_start"] = None
self._save_state()
async def setup(bot: commands.Bot):
await bot.add_cog(UptimeSimple(bot))

View File

@@ -0,0 +1,5 @@
{
"first_seen": 1775231576.45808,
"total_downtime": 1324.356654882431,
"last_start": 1775232900.814735
}