fix: rename commands.py to slash_bridge.py to stop import shadowing
The file userslash/commands.py shadowed redbot.core.commands when Python
registered the submodule on the parent package, causing:
AttributeError: module 'userslash.commands' has no attribute 'Cog'
Renaming the file to slash_bridge.py avoids the collision entirely.
Also adds a user-install whitelist feature:
- [p]userslash whitelist enable/disable
- [p]userslash whitelist add/remove @user
- [p]userslash whitelist list
- [p]userslash whitelist clear
When enabled, only whitelisted users may use the bot via user-install
(DMs, group DMs, non-member guilds). Guild-installed usage is unaffected.
This commit is contained in:
parent
5c7046ff7c
commit
d20f988a93
2 changed files with 160 additions and 6 deletions
|
|
@ -8,9 +8,10 @@ automatically patched with ``integration_types: [0, 1]`` and
|
|||
``contexts: [0, 1, 2]`` so the bot works as a user-installable app.
|
||||
|
||||
Owner commands:
|
||||
[p]userslash sync — Sync the command tree + patch user-install flags
|
||||
[p]userslash patch — Patch flags on already-synced commands
|
||||
[p]userslash status — Show per-command user-install status
|
||||
[p]userslash sync — Sync the command tree + patch user-install flags
|
||||
[p]userslash patch — Patch flags on already-synced commands
|
||||
[p]userslash status — Show per-command user-install status
|
||||
[p]userslash whitelist — Manage the user-install whitelist
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
@ -18,11 +19,11 @@ import logging
|
|||
from typing import Optional
|
||||
|
||||
import discord
|
||||
from redbot.core import app_commands, commands
|
||||
from redbot.core import Config, app_commands, commands
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.errors import CogLoadError
|
||||
|
||||
from .commands import user_slash_command
|
||||
from .slash_bridge import user_slash_command
|
||||
from .context import InterContext
|
||||
from .patcher import apply_user_install_native, patch_all_commands
|
||||
from .utils import valid_app_name
|
||||
|
|
@ -40,12 +41,19 @@ class UserSlash(commands.Cog):
|
|||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
self._slash_registered = False
|
||||
self.config = Config.get_conf(self, identifier=0x55534C41534800, force_registration=True)
|
||||
self.config.register_global(
|
||||
whitelist_enabled=False,
|
||||
whitelisted_users=[],
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Lifecycle
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def cog_load(self) -> None:
|
||||
# Expose config to the slash bridge module for whitelist checks
|
||||
user_slash_command.extras["_userslash_config"] = self.config
|
||||
self.bot.before_invoke(self._before_hook)
|
||||
self.bot.add_dev_env_value(
|
||||
"interaction", lambda ctx: getattr(ctx, "interaction", None)
|
||||
|
|
@ -235,6 +243,108 @@ class UserSlash(commands.Cog):
|
|||
if page:
|
||||
await ctx.send("\n".join(page))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Whitelist commands
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@_userslash.group(name="whitelist", aliases=["wl"])
|
||||
async def _whitelist(self, ctx: commands.Context) -> None:
|
||||
"""Manage the user-install whitelist.
|
||||
|
||||
When enabled, only whitelisted users can use the bot via user-install
|
||||
(DMs, group DMs, and non-member guilds). Guild-installed usage is
|
||||
unaffected.
|
||||
"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help(ctx.command)
|
||||
|
||||
@_whitelist.command(name="enable")
|
||||
async def _wl_enable(self, ctx: commands.Context) -> None:
|
||||
"""Enable the user-install whitelist."""
|
||||
await self.config.whitelist_enabled.set(True)
|
||||
await ctx.send("✅ User-install whitelist is now *enabled*. Only whitelisted users can use the bot outside of guilds.")
|
||||
|
||||
@_whitelist.command(name="disable")
|
||||
async def _wl_disable(self, ctx: commands.Context) -> None:
|
||||
"""Disable the user-install whitelist (anyone can use user-install)."""
|
||||
await self.config.whitelist_enabled.set(False)
|
||||
await ctx.send("✅ User-install whitelist is now *disabled*. Anyone can use the bot via user-install.")
|
||||
|
||||
@_whitelist.command(name="add")
|
||||
async def _wl_add(self, ctx: commands.Context, *users: discord.User) -> None:
|
||||
"""Add one or more users to the whitelist.
|
||||
|
||||
Example: `[p]userslash whitelist add @User1 @User2`
|
||||
"""
|
||||
if not users:
|
||||
await ctx.send("❌ Provide at least one user to add.")
|
||||
return
|
||||
async with self.config.whitelisted_users() as wl:
|
||||
added = []
|
||||
for user in users:
|
||||
if user.id not in wl:
|
||||
wl.append(user.id)
|
||||
added.append(str(user))
|
||||
if added:
|
||||
await ctx.send(f"✅ Added to whitelist: {', '.join(added)}")
|
||||
else:
|
||||
await ctx.send("ℹ️ All specified users are already whitelisted.")
|
||||
|
||||
@_whitelist.command(name="remove", aliases=["rm"])
|
||||
async def _wl_remove(self, ctx: commands.Context, *users: discord.User) -> None:
|
||||
"""Remove one or more users from the whitelist.
|
||||
|
||||
Example: `[p]userslash whitelist remove @User1`
|
||||
"""
|
||||
if not users:
|
||||
await ctx.send("❌ Provide at least one user to remove.")
|
||||
return
|
||||
async with self.config.whitelisted_users() as wl:
|
||||
removed = []
|
||||
for user in users:
|
||||
if user.id in wl:
|
||||
wl.remove(user.id)
|
||||
removed.append(str(user))
|
||||
if removed:
|
||||
await ctx.send(f"✅ Removed from whitelist: {', '.join(removed)}")
|
||||
else:
|
||||
await ctx.send("ℹ️ None of the specified users were on the whitelist.")
|
||||
|
||||
@_whitelist.command(name="list", aliases=["ls"])
|
||||
async def _wl_list(self, ctx: commands.Context) -> None:
|
||||
"""Show the current whitelist and its status."""
|
||||
enabled = await self.config.whitelist_enabled()
|
||||
wl = await self.config.whitelisted_users()
|
||||
status = "🟢 Enabled" if enabled else "🔴 Disabled"
|
||||
|
||||
if not wl:
|
||||
await ctx.send(f"**Whitelist status:** {status}\n\nThe whitelist is empty.")
|
||||
return
|
||||
|
||||
lines = []
|
||||
for uid in wl:
|
||||
user = self.bot.get_user(uid)
|
||||
lines.append(f"• {user} (`{uid}`)" if user else f"• Unknown user (`{uid}`)")
|
||||
|
||||
header = f"**Whitelist status:** {status}\n**{len(wl)}** user(s):\n"
|
||||
page = [header]
|
||||
length = len(header)
|
||||
for line in lines:
|
||||
if length + len(line) + 1 > 1900:
|
||||
await ctx.send("\n".join(page))
|
||||
page = []
|
||||
length = 0
|
||||
page.append(line)
|
||||
length += len(line) + 1
|
||||
if page:
|
||||
await ctx.send("\n".join(page))
|
||||
|
||||
@_whitelist.command(name="clear")
|
||||
async def _wl_clear(self, ctx: commands.Context) -> None:
|
||||
"""Remove all users from the whitelist."""
|
||||
await self.config.whitelisted_users.set([])
|
||||
await ctx.send("✅ Whitelist has been cleared.")
|
||||
|
||||
|
||||
async def setup(bot: Red) -> None:
|
||||
await bot.add_cog(UserSlash(bot))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ The proxy slash command and its autocomplete / error handler.
|
|||
This is the heart of UserSlash: a single ``/botname`` command that bridges
|
||||
*every* Red text command through Discord's slash-command interface, with
|
||||
full support for DM / group-DM / non-member-guild contexts.
|
||||
|
||||
Renamed from ``commands.py`` to ``slash_bridge.py`` to avoid shadowing
|
||||
``redbot.core.commands`` inside the package.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
@ -24,7 +27,34 @@ from redbot.core.i18n import set_contextual_locale
|
|||
from .context import InterContext
|
||||
from .utils import walk_aliases
|
||||
|
||||
LOG = logging.getLogger("red.evecogs.userslash.commands")
|
||||
LOG = logging.getLogger("red.evecogs.userslash.slash_bridge")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Whitelist helper
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def _check_whitelist(interaction: discord.Interaction) -> bool:
|
||||
"""Return True if the user is allowed, False if blocked by the whitelist.
|
||||
|
||||
The whitelist only applies to *user-install* contexts (no guild, or a guild
|
||||
the bot isn't a member of). Guild-installed usage is always permitted.
|
||||
"""
|
||||
config = getattr(
|
||||
interaction.command, "extras", {}
|
||||
).get("_userslash_config")
|
||||
if config is None:
|
||||
return True # config not wired yet — allow
|
||||
|
||||
# If the bot is a full member of this guild, skip the whitelist
|
||||
if interaction.guild and interaction.guild.me:
|
||||
return True
|
||||
|
||||
if not await config.whitelist_enabled():
|
||||
return True
|
||||
|
||||
whitelist: list = await config.whitelisted_users()
|
||||
return interaction.user.id in whitelist
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
|
@ -53,6 +83,16 @@ async def user_slash_command(
|
|||
assert isinstance(interaction.client, Red)
|
||||
set_contextual_locale(str(interaction.guild_locale or interaction.locale))
|
||||
|
||||
# --- Whitelist gate ---
|
||||
if not await _check_whitelist(interaction):
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.send_message(
|
||||
"❌ You are not authorised to use this bot via user-install. "
|
||||
"Ask a bot owner to add you with `[p]userslash whitelist add`.",
|
||||
ephemeral=True,
|
||||
)
|
||||
return
|
||||
|
||||
actual = interaction.client.get_command(command)
|
||||
ctx = await InterContext.from_interaction(interaction, recreate_message=True)
|
||||
error = None
|
||||
|
|
@ -131,6 +171,10 @@ async def _autocomplete(
|
|||
) -> List[app_commands.Choice[str]]:
|
||||
assert isinstance(interaction.client, Red)
|
||||
|
||||
# --- Whitelist gate ---
|
||||
if not await _check_whitelist(interaction):
|
||||
return []
|
||||
|
||||
try:
|
||||
if not await interaction.client.allowed_by_whitelist_blacklist(
|
||||
interaction.user
|
||||
Loading…
Add table
Reference in a new issue