640 lines
38 KiB
Python
640 lines
38 KiB
Python
|
|
"""NexusCore — Dashboard Integration: web-based config for all 8 modules."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import typing
|
||
|
|
|
||
|
|
import discord
|
||
|
|
from redbot.core import commands
|
||
|
|
|
||
|
|
if typing.TYPE_CHECKING:
|
||
|
|
pass
|
||
|
|
|
||
|
|
# Try importing the dashboard SDK (graceful if not installed)
|
||
|
|
try:
|
||
|
|
from dashboard.sdk import dashboard_page
|
||
|
|
except ImportError:
|
||
|
|
# Stub if dashboard cog isn't loaded
|
||
|
|
def dashboard_page(**kwargs):
|
||
|
|
def decorator(func):
|
||
|
|
return func
|
||
|
|
return decorator
|
||
|
|
|
||
|
|
from .utils import ts_now
|
||
|
|
|
||
|
|
|
||
|
|
class DashboardMixin:
|
||
|
|
"""Dashboard integration mixin — registers pages for all NexusCore modules."""
|
||
|
|
|
||
|
|
# ── Lifecycle listeners ────────────────────────────────────────────────
|
||
|
|
@commands.Cog.listener()
|
||
|
|
async def on_dashboard_cog_add(self, dashboard_cog):
|
||
|
|
dashboard_cog.rpc.add_third_party(self)
|
||
|
|
|
||
|
|
@commands.Cog.listener()
|
||
|
|
async def on_dashboard_cog_remove(self, dashboard_cog):
|
||
|
|
dashboard_cog.rpc.remove_third_party(self)
|
||
|
|
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
# OVERVIEW PAGE
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
@dashboard_page(name="overview", description="NexusCore Overview & Dashboard", icon="fas fa-home", order=0)
|
||
|
|
async def dashboard_overview(self, user, guild, **kwargs):
|
||
|
|
# Gather stats from all modules
|
||
|
|
ticket_data = await self.ticket_config.guild(guild).all()
|
||
|
|
app_data = await self.app_config.guild(guild).all()
|
||
|
|
suggest_data = await self.suggest_config.guild(guild).all()
|
||
|
|
rr_data = await self.rr_config.guild(guild).all()
|
||
|
|
give_data = await self.give_config.guild(guild).all()
|
||
|
|
log_data = await self.log_config.guild(guild).all()
|
||
|
|
mod_data = await self.mod_config.guild(guild).all()
|
||
|
|
eco_data = await self.eco_config.guild(guild).all()
|
||
|
|
|
||
|
|
open_tickets = len(ticket_data.get("open_tickets", {}))
|
||
|
|
pending_apps = sum(1 for s in app_data.get("submissions", {}).values() if s.get("status") == "pending")
|
||
|
|
total_suggestions = len(suggest_data.get("suggestions", {}))
|
||
|
|
active_giveaways = sum(1 for g in give_data.get("giveaways", {}).values() if not g.get("ended"))
|
||
|
|
total_cases = len(mod_data.get("cases", {}))
|
||
|
|
rr_panels = len(rr_data.get("panels", {}))
|
||
|
|
shop_items = len(eco_data.get("shop_items", {}))
|
||
|
|
|
||
|
|
return {
|
||
|
|
"status": 0,
|
||
|
|
"web_content": {
|
||
|
|
"source": "NexusCore overview",
|
||
|
|
"stats": {
|
||
|
|
"open_tickets": open_tickets,
|
||
|
|
"pending_applications": pending_apps,
|
||
|
|
"total_suggestions": total_suggestions,
|
||
|
|
"active_giveaways": active_giveaways,
|
||
|
|
"moderation_cases": total_cases,
|
||
|
|
"reaction_role_panels": rr_panels,
|
||
|
|
"shop_items": shop_items,
|
||
|
|
},
|
||
|
|
"modules": {
|
||
|
|
"tickets": {"enabled": ticket_data.get("enabled", False), "icon": "fas fa-ticket-alt"},
|
||
|
|
"applications": {"enabled": app_data.get("enabled", False), "icon": "fas fa-clipboard-list"},
|
||
|
|
"suggestions": {"enabled": suggest_data.get("enabled", False), "icon": "fas fa-lightbulb"},
|
||
|
|
"reaction_roles": {"enabled": rr_data.get("enabled", True), "icon": "fas fa-theater-masks"},
|
||
|
|
"giveaways": {"enabled": give_data.get("enabled", True), "icon": "fas fa-gift"},
|
||
|
|
"logging": {"enabled": log_data.get("enabled", False), "icon": "fas fa-clipboard"},
|
||
|
|
"moderation": {"enabled": mod_data.get("enabled", True), "icon": "fas fa-shield-alt"},
|
||
|
|
"economy": {"enabled": eco_data.get("enabled", True), "icon": "fas fa-coins"},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
# TICKETS DASHBOARD
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
@dashboard_page(name="tickets", description="Ticket System Settings", methods=("GET", "POST"), icon="fas fa-ticket-alt", order=1)
|
||
|
|
async def dashboard_tickets(self, user, guild, method="GET", data=None, **kwargs):
|
||
|
|
if method == "POST" and data:
|
||
|
|
mapping = {
|
||
|
|
"enabled": ("enabled", bool),
|
||
|
|
"log_channel": ("log_channel", lambda x: int(x) if x else None),
|
||
|
|
"category_id": ("category_id", lambda x: int(x) if x else None),
|
||
|
|
"transcript_channel": ("transcript_channel", lambda x: int(x) if x else None),
|
||
|
|
"max_per_user": ("max_per_user", int),
|
||
|
|
"claim_enabled": ("claim_enabled", bool),
|
||
|
|
"feedback_enabled": ("feedback_enabled", bool),
|
||
|
|
"dm_on_open": ("dm_on_open", bool),
|
||
|
|
"dm_on_close": ("dm_on_close", bool),
|
||
|
|
"allow_user_close": ("allow_user_close", bool),
|
||
|
|
"auto_pin_first": ("auto_pin_first", bool),
|
||
|
|
"thread_mode": ("thread_mode", bool),
|
||
|
|
"auto_close_hours": ("auto_close_hours", int),
|
||
|
|
"custom_open_msg": ("custom_open_msg", str),
|
||
|
|
"custom_close_msg": ("custom_close_msg", str),
|
||
|
|
}
|
||
|
|
for key, (config_key, converter) in mapping.items():
|
||
|
|
if key in data:
|
||
|
|
try:
|
||
|
|
val = converter(data[key])
|
||
|
|
await getattr(self.ticket_config.guild(guild), config_key).set(val)
|
||
|
|
except (ValueError, TypeError):
|
||
|
|
pass
|
||
|
|
return {"status": 0, "notifications": [{"type": "success", "message": "Ticket settings saved!"}]}
|
||
|
|
|
||
|
|
current = await self.ticket_config.guild(guild).all()
|
||
|
|
return {
|
||
|
|
"status": 0,
|
||
|
|
"web_content": {
|
||
|
|
"source": "EveDash auto-form",
|
||
|
|
"settings": [
|
||
|
|
{"id": "enabled", "type": "toggle", "label": "Enable Tickets", "value": current.get("enabled", False)},
|
||
|
|
{"id": "log_channel", "type": "channel_select", "label": "Log Channel", "value": current.get("log_channel")},
|
||
|
|
{"id": "category_id", "type": "channel_select", "label": "Ticket Category", "value": current.get("category_id")},
|
||
|
|
{"id": "transcript_channel", "type": "channel_select", "label": "Transcript Channel", "value": current.get("transcript_channel")},
|
||
|
|
{"id": "max_per_user", "type": "number", "label": "Max Tickets Per User", "value": current.get("max_per_user", 3), "min": 1, "max": 25},
|
||
|
|
{"id": "claim_enabled", "type": "toggle", "label": "Staff Claim System", "value": current.get("claim_enabled", True)},
|
||
|
|
{"id": "feedback_enabled", "type": "toggle", "label": "Feedback on Close", "value": current.get("feedback_enabled", True)},
|
||
|
|
{"id": "dm_on_open", "type": "toggle", "label": "DM on Open", "value": current.get("dm_on_open", True)},
|
||
|
|
{"id": "dm_on_close", "type": "toggle", "label": "DM on Close", "value": current.get("dm_on_close", True)},
|
||
|
|
{"id": "allow_user_close", "type": "toggle", "label": "Users Can Close", "value": current.get("allow_user_close", True)},
|
||
|
|
{"id": "auto_pin_first", "type": "toggle", "label": "Auto-Pin First Message", "value": current.get("auto_pin_first", True)},
|
||
|
|
{"id": "thread_mode", "type": "toggle", "label": "Thread Mode", "value": current.get("thread_mode", False)},
|
||
|
|
{"id": "auto_close_hours", "type": "number", "label": "Auto-Close After (hours, 0=off)", "value": current.get("auto_close_hours", 0), "min": 0, "max": 720},
|
||
|
|
{"id": "custom_open_msg", "type": "textarea", "label": "Custom Open Message", "description": "Vars: {user}, {category}, {ticket_id}", "value": current.get("custom_open_msg", "")},
|
||
|
|
{"id": "custom_close_msg", "type": "textarea", "label": "Custom Close Message", "value": current.get("custom_close_msg", "")},
|
||
|
|
],
|
||
|
|
"categories": current.get("categories", {}),
|
||
|
|
"open_tickets": len(current.get("open_tickets", {})),
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
# APPLICATIONS DASHBOARD
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
@dashboard_page(name="applications", description="Application System Settings", methods=("GET", "POST"), icon="fas fa-clipboard-list", order=2)
|
||
|
|
async def dashboard_applications(self, user, guild, method="GET", data=None, **kwargs):
|
||
|
|
if method == "POST" and data:
|
||
|
|
for key in ("enabled", "review_channel", "dm_on_accept", "dm_on_deny"):
|
||
|
|
if key in data:
|
||
|
|
val = data[key]
|
||
|
|
if key == "review_channel":
|
||
|
|
val = int(val) if val else None
|
||
|
|
elif key == "enabled":
|
||
|
|
val = bool(val)
|
||
|
|
await getattr(self.app_config.guild(guild), key).set(val)
|
||
|
|
return {"status": 0, "notifications": [{"type": "success", "message": "Application settings saved!"}]}
|
||
|
|
|
||
|
|
current = await self.app_config.guild(guild).all()
|
||
|
|
pending = sum(1 for s in current.get("submissions", {}).values() if s.get("status") == "pending")
|
||
|
|
accepted = sum(1 for s in current.get("submissions", {}).values() if s.get("status") == "accepted")
|
||
|
|
denied = sum(1 for s in current.get("submissions", {}).values() if s.get("status") == "denied")
|
||
|
|
|
||
|
|
return {
|
||
|
|
"status": 0,
|
||
|
|
"web_content": {
|
||
|
|
"source": "EveDash auto-form",
|
||
|
|
"settings": [
|
||
|
|
{"id": "enabled", "type": "toggle", "label": "Enable Applications", "value": current.get("enabled", False)},
|
||
|
|
{"id": "review_channel", "type": "channel_select", "label": "Review Channel", "value": current.get("review_channel")},
|
||
|
|
{"id": "dm_on_accept", "type": "toggle", "label": "DM on Accept", "value": current.get("dm_on_accept", True)},
|
||
|
|
{"id": "dm_on_deny", "type": "toggle", "label": "DM on Deny", "value": current.get("dm_on_deny", True)},
|
||
|
|
],
|
||
|
|
"types": current.get("types", {}),
|
||
|
|
"stats": {"pending": pending, "accepted": accepted, "denied": denied, "total": len(current.get("submissions", {}))},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
# SUGGESTIONS DASHBOARD
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
@dashboard_page(name="suggestions", description="Suggestion System Settings", methods=("GET", "POST"), icon="fas fa-lightbulb", order=3)
|
||
|
|
async def dashboard_suggestions(self, user, guild, method="GET", data=None, **kwargs):
|
||
|
|
if method == "POST" and data:
|
||
|
|
mapping = {
|
||
|
|
"enabled": bool, "channel": lambda x: int(x) if x else None,
|
||
|
|
"anonymous": bool, "auto_thread": bool,
|
||
|
|
"min_length": int, "max_length": int,
|
||
|
|
"auto_approve_threshold": int,
|
||
|
|
}
|
||
|
|
for key, converter in mapping.items():
|
||
|
|
if key in data:
|
||
|
|
try:
|
||
|
|
await getattr(self.suggest_config.guild(guild), key).set(converter(data[key]))
|
||
|
|
except (ValueError, TypeError):
|
||
|
|
pass
|
||
|
|
return {"status": 0, "notifications": [{"type": "success", "message": "Suggestion settings saved!"}]}
|
||
|
|
|
||
|
|
current = await self.suggest_config.guild(guild).all()
|
||
|
|
subs = current.get("suggestions", {})
|
||
|
|
status_counts = {}
|
||
|
|
for s in subs.values():
|
||
|
|
st = s.get("status", "pending")
|
||
|
|
status_counts[st] = status_counts.get(st, 0) + 1
|
||
|
|
|
||
|
|
return {
|
||
|
|
"status": 0,
|
||
|
|
"web_content": {
|
||
|
|
"source": "EveDash auto-form",
|
||
|
|
"settings": [
|
||
|
|
{"id": "enabled", "type": "toggle", "label": "Enable Suggestions", "value": current.get("enabled", False)},
|
||
|
|
{"id": "channel", "type": "channel_select", "label": "Suggestions Channel", "value": current.get("channel")},
|
||
|
|
{"id": "anonymous", "type": "toggle", "label": "Anonymous Mode", "value": current.get("anonymous", False)},
|
||
|
|
{"id": "auto_thread", "type": "toggle", "label": "Auto-Thread", "value": current.get("auto_thread", True)},
|
||
|
|
{"id": "min_length", "type": "number", "label": "Min Length", "value": current.get("min_length", 10), "min": 1, "max": 500},
|
||
|
|
{"id": "max_length", "type": "number", "label": "Max Length", "value": current.get("max_length", 2000), "min": 10, "max": 4000},
|
||
|
|
{"id": "auto_approve_threshold", "type": "number", "label": "Auto-Approve at X Upvotes (0=off)", "value": current.get("auto_approve_threshold", 0), "min": 0, "max": 1000},
|
||
|
|
],
|
||
|
|
"stats": {"total": len(subs), "by_status": status_counts},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
# REACTION ROLES DASHBOARD
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
@dashboard_page(name="reaction_roles", description="Reaction Role Panel Settings", methods=("GET", "POST"), icon="fas fa-theater-masks", order=4)
|
||
|
|
async def dashboard_reaction_roles(self, user, guild, method="GET", data=None, **kwargs):
|
||
|
|
if method == "POST" and data:
|
||
|
|
if data.get("action") == "toggle":
|
||
|
|
await self.rr_config.guild(guild).enabled.set(bool(data.get("enabled", True)))
|
||
|
|
return {"status": 0, "notifications": [{"type": "success", "message": "Reaction role settings saved!"}]}
|
||
|
|
|
||
|
|
current = await self.rr_config.guild(guild).all()
|
||
|
|
panels_info = {}
|
||
|
|
for pid, p in current.get("panels", {}).items():
|
||
|
|
ch = guild.get_channel(p["channel_id"])
|
||
|
|
panels_info[pid] = {
|
||
|
|
"title": p.get("title", "Untitled"),
|
||
|
|
"mode": p.get("mode", "button"),
|
||
|
|
"channel": {"id": str(p["channel_id"]), "name": ch.name if ch else "deleted"},
|
||
|
|
"roles_count": len(p.get("roles", [])),
|
||
|
|
"sticky": p.get("sticky", False),
|
||
|
|
"temp_minutes": p.get("temp_minutes", 0),
|
||
|
|
"max_roles": p.get("max_roles", 0),
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
"status": 0,
|
||
|
|
"web_content": {
|
||
|
|
"source": "EveDash auto-form",
|
||
|
|
"settings": [
|
||
|
|
{"id": "enabled", "type": "toggle", "label": "Enable Reaction Roles", "value": current.get("enabled", True)},
|
||
|
|
],
|
||
|
|
"panels": panels_info,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
# GIVEAWAYS DASHBOARD
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
@dashboard_page(name="giveaways", description="Giveaway System Settings", methods=("GET", "POST"), icon="fas fa-gift", order=5)
|
||
|
|
async def dashboard_giveaways(self, user, guild, method="GET", data=None, **kwargs):
|
||
|
|
if method == "POST" and data:
|
||
|
|
for key in ("enabled", "dm_winners", "dm_host", "default_colour"):
|
||
|
|
if key in data:
|
||
|
|
val = data[key]
|
||
|
|
if key in ("dm_winners", "dm_host", "enabled"):
|
||
|
|
val = bool(val)
|
||
|
|
await getattr(self.give_config.guild(guild), key).set(val)
|
||
|
|
return {"status": 0, "notifications": [{"type": "success", "message": "Giveaway settings saved!"}]}
|
||
|
|
|
||
|
|
current = await self.give_config.guild(guild).all()
|
||
|
|
gws = current.get("giveaways", {})
|
||
|
|
active = {k: v for k, v in gws.items() if not v.get("ended")}
|
||
|
|
ended = {k: v for k, v in gws.items() if v.get("ended")}
|
||
|
|
|
||
|
|
return {
|
||
|
|
"status": 0,
|
||
|
|
"web_content": {
|
||
|
|
"source": "EveDash auto-form",
|
||
|
|
"settings": [
|
||
|
|
{"id": "enabled", "type": "toggle", "label": "Enable Giveaways", "value": current.get("enabled", True)},
|
||
|
|
{"id": "dm_winners", "type": "toggle", "label": "DM Winners", "value": current.get("dm_winners", True)},
|
||
|
|
{"id": "dm_host", "type": "toggle", "label": "DM Host", "value": current.get("dm_host", True)},
|
||
|
|
],
|
||
|
|
"stats": {"active": len(active), "ended": len(ended), "total_entries": sum(len(g.get("entries", [])) for g in gws.values())},
|
||
|
|
"active_giveaways": [
|
||
|
|
{"id": k, "prize": v["prize"], "ends_at": v["ends_at"], "entries": len(v.get("entries", [])), "winners": v["winners_count"]}
|
||
|
|
for k, v in active.items()
|
||
|
|
],
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
# LOGGING DASHBOARD
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
@dashboard_page(name="logging", description="Server Logging Settings", methods=("GET", "POST"), icon="fas fa-clipboard", order=6)
|
||
|
|
async def dashboard_logging(self, user, guild, method="GET", data=None, **kwargs):
|
||
|
|
if method == "POST" and data:
|
||
|
|
if "enabled" in data:
|
||
|
|
await self.log_config.guild(guild).enabled.set(bool(data["enabled"]))
|
||
|
|
if "default_channel" in data:
|
||
|
|
await self.log_config.guild(guild).default_channel.set(int(data["default_channel"]) if data["default_channel"] else None)
|
||
|
|
if "ignore_bots" in data:
|
||
|
|
await self.log_config.guild(guild).ignore_bots.set(bool(data["ignore_bots"]))
|
||
|
|
if "channels" in data and isinstance(data["channels"], dict):
|
||
|
|
await self.log_config.guild(guild).channels.set(
|
||
|
|
{k: int(v) for k, v in data["channels"].items() if v}
|
||
|
|
)
|
||
|
|
return {"status": 0, "notifications": [{"type": "success", "message": "Logging settings saved!"}]}
|
||
|
|
|
||
|
|
current = await self.log_config.guild(guild).all()
|
||
|
|
from .serverlog import EVENT_TYPES
|
||
|
|
channel_routing = {}
|
||
|
|
for evt in EVENT_TYPES:
|
||
|
|
ch_id = current.get("channels", {}).get(evt)
|
||
|
|
ch = guild.get_channel(ch_id) if ch_id else None
|
||
|
|
channel_routing[evt] = {"channel_id": str(ch_id) if ch_id else None, "channel_name": ch.name if ch else None}
|
||
|
|
|
||
|
|
return {
|
||
|
|
"status": 0,
|
||
|
|
"web_content": {
|
||
|
|
"source": "EveDash auto-form",
|
||
|
|
"settings": [
|
||
|
|
{"id": "enabled", "type": "toggle", "label": "Enable Logging", "value": current.get("enabled", False)},
|
||
|
|
{"id": "default_channel", "type": "channel_select", "label": "Default Log Channel", "value": current.get("default_channel")},
|
||
|
|
{"id": "ignore_bots", "type": "toggle", "label": "Ignore Bots", "value": current.get("ignore_bots", True)},
|
||
|
|
],
|
||
|
|
"event_types": EVENT_TYPES,
|
||
|
|
"channel_routing": channel_routing,
|
||
|
|
"ignore": {
|
||
|
|
"channels": current.get("ignore_channels", []),
|
||
|
|
"roles": current.get("ignore_roles", []),
|
||
|
|
"users": current.get("ignore_users", []),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
# MODERATION DASHBOARD
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
@dashboard_page(name="moderation", description="Moderation Settings & Cases", methods=("GET", "POST"), icon="fas fa-shield-alt", order=7)
|
||
|
|
async def dashboard_moderation(self, user, guild, method="GET", data=None, **kwargs):
|
||
|
|
if method == "POST" and data:
|
||
|
|
simple_keys = {
|
||
|
|
"enabled": bool, "modlog_channel": lambda x: int(x) if x else None,
|
||
|
|
"dm_on_action": bool, "appeal_enabled": bool,
|
||
|
|
"appeal_channel": lambda x: int(x) if x else None,
|
||
|
|
"warn_decay_days": int,
|
||
|
|
}
|
||
|
|
for key, converter in simple_keys.items():
|
||
|
|
if key in data:
|
||
|
|
try:
|
||
|
|
await getattr(self.mod_config.guild(guild), key).set(converter(data[key]))
|
||
|
|
except (ValueError, TypeError):
|
||
|
|
pass
|
||
|
|
|
||
|
|
if "anti_raid" in data and isinstance(data["anti_raid"], dict):
|
||
|
|
await self.mod_config.guild(guild).anti_raid.set(data["anti_raid"])
|
||
|
|
if "anti_nuke" in data and isinstance(data["anti_nuke"], dict):
|
||
|
|
await self.mod_config.guild(guild).anti_nuke.set(data["anti_nuke"])
|
||
|
|
if "auto_mod" in data and isinstance(data["auto_mod"], dict):
|
||
|
|
await self.mod_config.guild(guild).auto_mod.set(data["auto_mod"])
|
||
|
|
if "escalation" in data and isinstance(data["escalation"], dict):
|
||
|
|
await self.mod_config.guild(guild).escalation.set(data["escalation"])
|
||
|
|
|
||
|
|
return {"status": 0, "notifications": [{"type": "success", "message": "Moderation settings saved!"}]}
|
||
|
|
|
||
|
|
current = await self.mod_config.guild(guild).all()
|
||
|
|
cases = current.get("cases", {})
|
||
|
|
type_counts = {}
|
||
|
|
for c in cases.values():
|
||
|
|
t = c.get("type", "unknown")
|
||
|
|
type_counts[t] = type_counts.get(t, 0) + 1
|
||
|
|
|
||
|
|
return {
|
||
|
|
"status": 0,
|
||
|
|
"web_content": {
|
||
|
|
"source": "EveDash auto-form",
|
||
|
|
"settings": [
|
||
|
|
{"id": "enabled", "type": "toggle", "label": "Enable Moderation", "value": current.get("enabled", True)},
|
||
|
|
{"id": "modlog_channel", "type": "channel_select", "label": "Modlog Channel", "value": current.get("modlog_channel")},
|
||
|
|
{"id": "dm_on_action", "type": "toggle", "label": "DM Users on Action", "value": current.get("dm_on_action", True)},
|
||
|
|
{"id": "appeal_enabled", "type": "toggle", "label": "Enable Appeals", "value": current.get("appeal_enabled", False)},
|
||
|
|
{"id": "appeal_channel", "type": "channel_select", "label": "Appeal Channel", "value": current.get("appeal_channel")},
|
||
|
|
{"id": "warn_decay_days", "type": "number", "label": "Warning Decay (days, 0=never)", "value": current.get("warn_decay_days", 0), "min": 0, "max": 365},
|
||
|
|
],
|
||
|
|
"stats": {"total_cases": len(cases), "by_type": type_counts},
|
||
|
|
"anti_raid": current.get("anti_raid", {}),
|
||
|
|
"anti_nuke": current.get("anti_nuke", {}),
|
||
|
|
"auto_mod": current.get("auto_mod", {}),
|
||
|
|
"escalation": current.get("escalation", {}),
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
# ECONOMY DASHBOARD
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
@dashboard_page(name="economy", description="Economy & Shop Settings", methods=("GET", "POST"), icon="fas fa-coins", order=8)
|
||
|
|
async def dashboard_economy(self, user, guild, method="GET", data=None, **kwargs):
|
||
|
|
if method == "POST" and data:
|
||
|
|
simple_keys = {
|
||
|
|
"enabled": bool, "currency_name": str, "currency_emoji": str,
|
||
|
|
"currency_symbol": str, "starting_balance": int, "max_balance": int,
|
||
|
|
"daily_amount": int, "daily_streak_bonus": int, "weekly_amount": int,
|
||
|
|
"work_min": int, "work_max": int, "work_cooldown": int,
|
||
|
|
"crime_min": int, "crime_max": int, "crime_cooldown": int,
|
||
|
|
"crime_fail_chance": int, "rob_enabled": bool, "rob_cooldown": int,
|
||
|
|
"rob_success_chance": int, "interest_rate": float, "tax_rate": float,
|
||
|
|
"log_channel": lambda x: int(x) if x else None,
|
||
|
|
}
|
||
|
|
for key, converter in simple_keys.items():
|
||
|
|
if key in data:
|
||
|
|
try:
|
||
|
|
await getattr(self.eco_config.guild(guild), key).set(converter(data[key]))
|
||
|
|
except (ValueError, TypeError, AttributeError):
|
||
|
|
pass
|
||
|
|
|
||
|
|
if "gambling" in data and isinstance(data["gambling"], dict):
|
||
|
|
await self.eco_config.guild(guild).gambling.set(data["gambling"])
|
||
|
|
if "pets" in data and isinstance(data["pets"], dict):
|
||
|
|
await self.eco_config.guild(guild).pets.set(data["pets"])
|
||
|
|
|
||
|
|
# Shop item management
|
||
|
|
if "add_item" in data:
|
||
|
|
item = data["add_item"]
|
||
|
|
from .utils import short_id
|
||
|
|
item_id = short_id(8)
|
||
|
|
async with self.eco_config.guild(guild).shop_items() as items:
|
||
|
|
items[item_id] = {
|
||
|
|
"name": item.get("name", "New Item"),
|
||
|
|
"description": item.get("description", ""),
|
||
|
|
"price": int(item.get("price", 100)),
|
||
|
|
"emoji": item.get("emoji", "📦"),
|
||
|
|
"role_id": int(item["role_id"]) if item.get("role_id") else None,
|
||
|
|
"stock": int(item.get("stock", -1)),
|
||
|
|
"max_per_user": int(item.get("max_per_user", 0)),
|
||
|
|
"type": item.get("type", "item"),
|
||
|
|
"usable": bool(item.get("usable", False)),
|
||
|
|
}
|
||
|
|
|
||
|
|
if "remove_item" in data:
|
||
|
|
async with self.eco_config.guild(guild).shop_items() as items:
|
||
|
|
items.pop(data["remove_item"], None)
|
||
|
|
|
||
|
|
return {"status": 0, "notifications": [{"type": "success", "message": "Economy settings saved!"}]}
|
||
|
|
|
||
|
|
current = await self.eco_config.guild(guild).all()
|
||
|
|
all_members = await self.eco_config.all_members(guild)
|
||
|
|
total_wealth = sum(d.get("wallet", 0) + d.get("bank", 0) for d in all_members.values())
|
||
|
|
total_users = len(all_members)
|
||
|
|
|
||
|
|
# Top 10 leaderboard
|
||
|
|
rankings = sorted(
|
||
|
|
[(uid, d.get("wallet", 0) + d.get("bank", 0)) for uid, d in all_members.items()],
|
||
|
|
key=lambda x: x[1], reverse=True,
|
||
|
|
)[:10]
|
||
|
|
top_users = []
|
||
|
|
for uid, total in rankings:
|
||
|
|
member = guild.get_member(uid)
|
||
|
|
top_users.append({
|
||
|
|
"id": str(uid),
|
||
|
|
"name": str(member) if member else f"Unknown#{uid}",
|
||
|
|
"total": total,
|
||
|
|
})
|
||
|
|
|
||
|
|
return {
|
||
|
|
"status": 0,
|
||
|
|
"web_content": {
|
||
|
|
"source": "EveDash auto-form",
|
||
|
|
"settings": [
|
||
|
|
{"id": "enabled", "type": "toggle", "label": "Enable Economy", "value": current.get("enabled", True)},
|
||
|
|
{"id": "currency_name", "type": "text", "label": "Currency Name", "value": current.get("currency_name", "coins")},
|
||
|
|
{"id": "currency_emoji", "type": "text", "label": "Currency Emoji", "value": current.get("currency_emoji", "🪙")},
|
||
|
|
{"id": "currency_symbol", "type": "text", "label": "Currency Symbol", "value": current.get("currency_symbol", "$")},
|
||
|
|
{"id": "starting_balance", "type": "number", "label": "Starting Balance", "value": current.get("starting_balance", 100), "min": 0},
|
||
|
|
{"id": "max_balance", "type": "number", "label": "Max Balance", "value": current.get("max_balance", 10000000), "min": 0},
|
||
|
|
{"id": "daily_amount", "type": "number", "label": "Daily Amount", "value": current.get("daily_amount", 200), "min": 0},
|
||
|
|
{"id": "daily_streak_bonus", "type": "number", "label": "Streak Bonus/Day", "value": current.get("daily_streak_bonus", 50), "min": 0},
|
||
|
|
{"id": "weekly_amount", "type": "number", "label": "Weekly Amount", "value": current.get("weekly_amount", 1500), "min": 0},
|
||
|
|
{"id": "work_min", "type": "number", "label": "Work Min", "value": current.get("work_min", 50)},
|
||
|
|
{"id": "work_max", "type": "number", "label": "Work Max", "value": current.get("work_max", 300)},
|
||
|
|
{"id": "work_cooldown", "type": "number", "label": "Work Cooldown (sec)", "value": current.get("work_cooldown", 3600)},
|
||
|
|
{"id": "crime_min", "type": "number", "label": "Crime Min", "value": current.get("crime_min", 100)},
|
||
|
|
{"id": "crime_max", "type": "number", "label": "Crime Max", "value": current.get("crime_max", 800)},
|
||
|
|
{"id": "crime_fail_chance", "type": "slider", "label": "Crime Fail %", "value": current.get("crime_fail_chance", 40), "min": 0, "max": 100, "step": 5},
|
||
|
|
{"id": "rob_enabled", "type": "toggle", "label": "Enable Robbing", "value": current.get("rob_enabled", True)},
|
||
|
|
{"id": "rob_success_chance", "type": "slider", "label": "Rob Success %", "value": current.get("rob_success_chance", 45), "min": 0, "max": 100, "step": 5},
|
||
|
|
{"id": "tax_rate", "type": "slider", "label": "Transfer Tax %", "value": current.get("tax_rate", 0), "min": 0, "max": 50, "step": 1},
|
||
|
|
{"id": "log_channel", "type": "channel_select", "label": "Economy Log Channel", "value": current.get("log_channel")},
|
||
|
|
],
|
||
|
|
"shop_items": current.get("shop_items", {}),
|
||
|
|
"gambling": current.get("gambling", {}),
|
||
|
|
"pets": current.get("pets", {}),
|
||
|
|
"stats": {
|
||
|
|
"total_wealth": total_wealth,
|
||
|
|
"total_users": total_users,
|
||
|
|
"avg_balance": total_wealth // max(total_users, 1),
|
||
|
|
},
|
||
|
|
"leaderboard": top_users,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
# EMBED BUILDER DASHBOARD
|
||
|
|
# ══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
@dashboard_page(name="embed_builder", description="Webhook Embed Builder", methods=("GET", "POST"), icon="fas fa-paint-brush", order=9)
|
||
|
|
async def dashboard_embed_builder(self, user, guild, method="GET", data=None, **kwargs):
|
||
|
|
if method == "POST" and data:
|
||
|
|
action = data.get("action")
|
||
|
|
|
||
|
|
if action == "send":
|
||
|
|
channel_id = int(data.get("channel_id", 0))
|
||
|
|
channel = guild.get_channel(channel_id)
|
||
|
|
if not channel:
|
||
|
|
return {"status": 1, "error": "Channel not found"}
|
||
|
|
|
||
|
|
embeds_data = data.get("embeds", [])
|
||
|
|
content = data.get("content", "")
|
||
|
|
webhook_name = data.get("webhook_name", "NexusCore")
|
||
|
|
webhook_avatar = data.get("webhook_avatar", "")
|
||
|
|
|
||
|
|
webhook = None
|
||
|
|
for wh in await channel.webhooks():
|
||
|
|
if wh.name == webhook_name:
|
||
|
|
webhook = wh
|
||
|
|
break
|
||
|
|
if not webhook:
|
||
|
|
webhook = await channel.create_webhook(name=webhook_name)
|
||
|
|
|
||
|
|
embeds = []
|
||
|
|
for ed in embeds_data[:10]:
|
||
|
|
embed = self._build_embed_from_data(ed, guild)
|
||
|
|
embeds.append(embed)
|
||
|
|
|
||
|
|
await webhook.send(
|
||
|
|
content=content or None,
|
||
|
|
embeds=embeds or None,
|
||
|
|
username=webhook_name,
|
||
|
|
avatar_url=webhook_avatar or None,
|
||
|
|
)
|
||
|
|
return {"status": 0, "notifications": [{"type": "success", "message": "Embed sent!"}]}
|
||
|
|
|
||
|
|
if action == "save_template":
|
||
|
|
template_name = data.get("template_name", "")
|
||
|
|
if not template_name:
|
||
|
|
return {"status": 1, "error": "Template name required"}
|
||
|
|
async with self.embed_config.guild(guild).templates() as templates:
|
||
|
|
templates[template_name] = {
|
||
|
|
"embeds": data.get("embeds", []),
|
||
|
|
"content": data.get("content", ""),
|
||
|
|
"webhook_name": data.get("webhook_name", "NexusCore"),
|
||
|
|
"saved_by": user.id,
|
||
|
|
"saved_at": ts_now(),
|
||
|
|
}
|
||
|
|
return {"status": 0, "notifications": [{"type": "success", "message": f"Template '{template_name}' saved!"}]}
|
||
|
|
|
||
|
|
return {"status": 1, "error": "Unknown action"}
|
||
|
|
|
||
|
|
templates = await self.embed_config.guild(guild).templates()
|
||
|
|
return {
|
||
|
|
"status": 0,
|
||
|
|
"web_content": {
|
||
|
|
"source": "NexusCore embed builder",
|
||
|
|
"templates": {k: {"embeds": v.get("embeds", []), "content": v.get("content", "")} for k, v in templates.items()},
|
||
|
|
"channels": [
|
||
|
|
{"id": str(c.id), "name": c.name}
|
||
|
|
for c in sorted(guild.text_channels, key=lambda x: x.position)
|
||
|
|
],
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
def _build_embed_from_data(self, data: dict, guild=None) -> discord.Embed:
|
||
|
|
"""Build a discord.Embed from a dict (dashboard/JSON input)."""
|
||
|
|
embed = discord.Embed()
|
||
|
|
if data.get("title"):
|
||
|
|
embed.title = self._replace_vars(data["title"], guild)
|
||
|
|
if data.get("description"):
|
||
|
|
embed.description = self._replace_vars(data["description"], guild)
|
||
|
|
if data.get("url"):
|
||
|
|
embed.url = data["url"]
|
||
|
|
if data.get("color") or data.get("colour"):
|
||
|
|
color_val = data.get("color") or data.get("colour")
|
||
|
|
if isinstance(color_val, str):
|
||
|
|
color_val = int(color_val.lstrip("#"), 16)
|
||
|
|
embed.colour = discord.Colour(color_val)
|
||
|
|
if data.get("timestamp"):
|
||
|
|
import datetime
|
||
|
|
embed.timestamp = datetime.datetime.now(datetime.timezone.utc)
|
||
|
|
if data.get("author"):
|
||
|
|
a = data["author"]
|
||
|
|
embed.set_author(name=a.get("name", ""), url=a.get("url", ""), icon_url=a.get("icon_url", ""))
|
||
|
|
if data.get("footer"):
|
||
|
|
f = data["footer"]
|
||
|
|
embed.set_footer(text=f.get("text", ""), icon_url=f.get("icon_url", ""))
|
||
|
|
if data.get("image"):
|
||
|
|
embed.set_image(url=data["image"])
|
||
|
|
if data.get("thumbnail"):
|
||
|
|
embed.set_thumbnail(url=data["thumbnail"])
|
||
|
|
for field in data.get("fields", [])[:25]:
|
||
|
|
embed.add_field(
|
||
|
|
name=field.get("name", "\u200b"),
|
||
|
|
value=field.get("value", "\u200b"),
|
||
|
|
inline=field.get("inline", True),
|
||
|
|
)
|
||
|
|
return embed
|
||
|
|
|
||
|
|
def _replace_vars(self, text: str, guild=None) -> str:
|
||
|
|
"""Replace placeholders in text."""
|
||
|
|
import datetime
|
||
|
|
now = datetime.datetime.now(datetime.timezone.utc)
|
||
|
|
replacements = {
|
||
|
|
"{server}": guild.name if guild else "Server",
|
||
|
|
"{server_name}": guild.name if guild else "Server",
|
||
|
|
"{member_count}": str(guild.member_count) if guild else "0",
|
||
|
|
"{channel_count}": str(len(guild.channels)) if guild else "0",
|
||
|
|
"{role_count}": str(len(guild.roles)) if guild else "0",
|
||
|
|
"{date}": now.strftime("%Y-%m-%d"),
|
||
|
|
"{time}": now.strftime("%H:%M UTC"),
|
||
|
|
"{datetime}": now.strftime("%Y-%m-%d %H:%M UTC"),
|
||
|
|
"{boost_count}": str(guild.premium_subscription_count or 0) if guild else "0",
|
||
|
|
"{boost_level}": str(guild.premium_tier) if guild else "0",
|
||
|
|
}
|
||
|
|
for k, v in replacements.items():
|
||
|
|
text = text.replace(k, v)
|
||
|
|
return text
|