feat: full feature suite — welcome, modmail, security, staff, avis, rules, links, shop, custom
All checks were successful
ci / ci (push) Successful in 1m1s

features/shared.py: per-guild JSON config store
features/welcome.py: on_member_join message + invite tracking + /invites
features/modmail.py: DM-based tickets with staff relay
features/security.py: antiraid (join rate detection), ban protection, join/leave log
features/staff.py: staff ranks, promote/demote, /staff_list
features/avis.py: /avis modal -> embed in configured channel
features/rules.py: /rules_setup with accept button + role assignment
features/links.py: /links panel, /links_setup, /links_remove
features/shop.py: /shop_announce new product embeds
features/custom.py: /botstatus, /custom_send, /embed
This commit is contained in:
Mathis 2026-10-02 19:45:46 +02:00
parent 33b9303948
commit 5d9baee102
12 changed files with 911 additions and 2 deletions

58
features/shared.py Normal file
View file

@ -0,0 +1,58 @@
from __future__ import annotations
import json
import threading
from pathlib import Path
from config import DATA_DIR
FEATURES_DIR = DATA_DIR / "features"
_LOCK = threading.Lock()
_cache: dict = {}
def _ensure():
FEATURES_DIR.mkdir(parents=True, exist_ok=True)
def _path(guild_id: int) -> Path:
return FEATURES_DIR / f"{guild_id}.json"
def get(guild_id: int, key: str = "") -> dict:
with _LOCK:
_ensure()
if guild_id not in _cache:
f = _path(guild_id)
if f.exists():
with open(f, "r", encoding="utf-8") as fh:
_cache[guild_id] = json.load(fh)
else:
_cache[guild_id] = {}
data = _cache[guild_id]
return data.get(key, {}) if key else data
def set(guild_id: int, key: str, value) -> None:
with _LOCK:
_ensure()
if guild_id not in _cache:
f = _path(guild_id)
if f.exists():
with open(f, "r", encoding="utf-8") as fh:
_cache[guild_id] = json.load(fh)
else:
_cache[guild_id] = {}
_cache[guild_id][key] = value
_save(guild_id)
def _save(guild_id: int) -> None:
_ensure()
with open(_path(guild_id), "w", encoding="utf-8") as fh:
json.dump(_cache[guild_id], fh, indent=2, ensure_ascii=False)
def section(guild_id: int, name: str) -> dict:
data = get(guild_id)
return data.get(name, {})