EveCogs/wormhole/commands/social.py
Viktor AI 4bc143b26f fix: group all commands under 'wh' parent — no more flat wh-xxx commands
Commands now show as 'wh create', 'wh staff add', 'wh set relaymode'
instead of 'wh-create', 'wh-staff add', 'wh-set relaymode'.

- Added commands/_base.py with root 'wh' group and all 20 subgroup stubs
- All 13 command mixins inherit from WormholeBase
- Decorators changed: @commands.hybrid_command(name='wh-X') → @WormholeBase.wh.command(name='X')
- Subgroup commands: @wh_X.command() → @WormholeBase.wh_X.command()
- Removed duplicate 'wh' group from core.py (now in _base.py)
- Fixed alias: 'wh-color' → 'color' for colour command
2026-05-14 19:24:37 +00:00

170 lines
8.8 KiB
Python

"""Social commands — karma, starboard, highlights, bookmarks, profiles."""
from __future__ import annotations
from datetime import datetime, timezone
from typing import Optional
import discord
from redbot.core import commands
from ..models.permissions import Role, has_role, requires_role, get_role, role_name
from ..utils import ok_embed, err_embed, info_embed, star_embed, warn_embed, truncate, COLOUR_INFO, COLOUR_STAR
from ._base import WormholeBase
class SocialCommands(WormholeBase):
"""Mixin — social features."""
# ── Profiles ───────────────────────────────────────────────────────────
@WormholeBase.wh.command(name="profile")
async def wh_profile(self, ctx: commands.Context, name: str, user: discord.User = None) -> None:
"""View a user's wormhole profile in a network."""
nd = await self._net(name)
if not nd:
return await ctx.send(embed=err_embed(f"Network `{name}` not found."))
target = user or ctx.author
p = nd.get("user_profiles", {}).get(str(target.id))
if not p:
return await ctx.send(embed=info_embed(f"{target.display_name} hasn't been seen in `{name}`."))
role = get_role(nd, target.id)
karma = nd.get("karma_scores", {}).get(str(target.id), 0)
em = discord.Embed(
title=f"👤 {target.display_name}",
colour=COLOUR_INFO,
)
em.set_thumbnail(url=target.display_avatar.url)
em.add_field(name="Role", value=role_name(role), inline=True)
em.add_field(name="Messages", value=f"{p.get('message_count', 0):,}", inline=True)
em.add_field(name="Karma", value=str(karma), inline=True)
em.add_field(name="First Seen", value=p.get("first_seen", "Unknown")[:10], inline=True)
em.add_field(name="Servers", value=str(len(p.get("servers", []))), inline=True)
em.set_footer(text=f"Network: {name}")
await ctx.send(embed=em)
# ── Karma ──────────────────────────────────────────────────────────────
@WormholeBase.wh_karma.command(name="enable")
@requires_role(Role.ADMIN)
async def wh_karma_enable(self, ctx: commands.Context, name: str, enabled: bool = True) -> None:
"""Enable or disable the karma system."""
async with self.config.networks() as ns:
ns[name]["karma_enabled"] = enabled
await ctx.send(embed=ok_embed(f"Karma {'enabled' if enabled else 'disabled'} for `{name}`."))
@WormholeBase.wh_karma.command(name="emoji")
@requires_role(Role.ADMIN)
async def wh_karma_emoji(self, ctx: commands.Context, name: str, emoji: str = "👍") -> None:
"""Set the karma reaction emoji."""
async with self.config.networks() as ns:
ns[name]["karma_emoji"] = emoji
await ctx.send(embed=ok_embed(f"Karma emoji set to {emoji} for `{name}`."))
@WormholeBase.wh_karma.command(name="top")
async def wh_karma_top(self, ctx: commands.Context, name: str, count: int = 10) -> None:
"""Show karma leaderboard."""
nd = await self._net(name)
if not nd:
return await ctx.send(embed=err_embed(f"Network `{name}` not found."))
scores = nd.get("karma_scores", {})
if not scores:
return await ctx.send(embed=info_embed("No karma data yet."))
sorted_scores = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:count]
lines = []
medals = ["🥇", "🥈", "🥉"]
for i, (uid_str, score) in enumerate(sorted_scores):
user = self.bot.get_user(int(uid_str))
medal = medals[i] if i < 3 else f"**{i+1}.**"
lines.append(f"{medal} {user or uid_str} — **{score}** karma")
em = discord.Embed(title=f"🏆 Karma Leaderboard — {name}", description="\n".join(lines), colour=COLOUR_INFO)
await ctx.send(embed=em)
# ── Starboard ──────────────────────────────────────────────────────────
@WormholeBase.wh_star.command(name="enable")
@requires_role(Role.ADMIN)
async def wh_star_enable(self, ctx: commands.Context, name: str, enabled: bool = True) -> None:
"""Enable or disable starboard."""
async with self.config.networks() as ns:
ns[name]["starboard_enabled"] = enabled
await ctx.send(embed=ok_embed(f"Starboard {'enabled' if enabled else 'disabled'} for `{name}`."))
@WormholeBase.wh_star.command(name="channel")
@requires_role(Role.ADMIN)
async def wh_star_channel(self, ctx: commands.Context, name: str, channel: discord.TextChannel = None) -> None:
"""Set the starboard channel."""
async with self.config.networks() as ns:
ns[name]["starboard_channel"] = channel.id if channel else None
await ctx.send(embed=ok_embed(f"Starboard channel {'set' if channel else 'cleared'} for `{name}`."))
@WormholeBase.wh_star.command(name="threshold")
@requires_role(Role.ADMIN)
async def wh_star_threshold(self, ctx: commands.Context, name: str, threshold: int = 3) -> None:
"""Set minimum stars to reach the starboard."""
async with self.config.networks() as ns:
ns[name]["starboard_threshold"] = max(1, threshold)
await ctx.send(embed=ok_embed(f"Starboard threshold set to {threshold} for `{name}`."))
# ── Highlights ─────────────────────────────────────────────────────────
@WormholeBase.wh_hl.command(name="add")
async def wh_hl_add(self, ctx: commands.Context, name: str, *, keyword: str) -> None:
"""Add a keyword to highlight."""
async with self.config.networks() as ns:
hl = ns[name].setdefault("highlights", {})
uid_str = str(ctx.author.id)
user_hl = hl.setdefault(uid_str, [])
if keyword.lower() in [k.lower() for k in user_hl]:
return await ctx.send(embed=err_embed("Keyword already highlighted."))
if len(user_hl) >= 25:
return await ctx.send(embed=err_embed("Max 25 highlights per user."))
user_hl.append(keyword)
await ctx.send(embed=ok_embed(f"Highlight `{keyword}` added for `{name}`."))
@WormholeBase.wh_hl.command(name="remove", aliases=["rm"])
async def wh_hl_remove(self, ctx: commands.Context, name: str, *, keyword: str) -> None:
"""Remove a highlight keyword."""
async with self.config.networks() as ns:
hl = ns[name].get("highlights", {})
user_hl = hl.get(str(ctx.author.id), [])
ns[name]["highlights"][str(ctx.author.id)] = [k for k in user_hl if k.lower() != keyword.lower()]
await ctx.send(embed=ok_embed(f"Highlight `{keyword}` removed."))
@WormholeBase.wh_hl.command(name="list")
async def wh_hl_list(self, ctx: commands.Context, name: str) -> None:
"""List your highlights."""
nd = await self._net(name)
if not nd:
return await ctx.send(embed=err_embed(f"Network `{name}` not found."))
user_hl = nd.get("highlights", {}).get(str(ctx.author.id), [])
if not user_hl:
return await ctx.send(embed=info_embed("No highlights set."))
await ctx.send(embed=info_embed("\n".join(f"• `{k}`" for k in user_hl), title="🔔 Your Highlights"))
# ── Bookmarks ──────────────────────────────────────────────────────────
@WormholeBase.wh_bm.command(name="list")
async def wh_bm_list(self, ctx: commands.Context) -> None:
"""List your bookmarks."""
bm = await self.config.bookmarks()
user_bm = bm.get(str(ctx.author.id), [])
if not user_bm:
return await ctx.send(embed=info_embed("No bookmarks yet. Use the context menu to bookmark!"))
lines = []
for b in reversed(user_bm[-15:]):
lines.append(
f"• [{b.get('timestamp', '?')[:10]}] **{b.get('author', '?')}** in {b.get('server', '?')}\n"
f" {truncate(b.get('content', ''), 80)} — [Jump]({b.get('jump_url', '#')})"
)
em = discord.Embed(title="📌 Bookmarks", description="\n".join(lines), colour=COLOUR_INFO)
await ctx.send(embed=em)
@WormholeBase.wh_bm.command(name="clear")
async def wh_bm_clear(self, ctx: commands.Context) -> None:
"""Clear all your bookmarks."""
async with self.config.bookmarks() as bm:
bm.pop(str(ctx.author.id), None)
await ctx.send(embed=ok_embed("All bookmarks cleared."))