Added guild and user settings module.
This commit is contained in:
331
bot/utils/seekers.py
Normal file
331
bot/utils/seekers.py
Normal file
@@ -0,0 +1,331 @@
|
||||
import discord
|
||||
|
||||
from cmdClient import Context
|
||||
from cmdClient.lib import InvalidContext, UserCancelled, ResponseTimedOut, SafeCancellation
|
||||
from . import interactive
|
||||
|
||||
|
||||
@Context.util
|
||||
async def find_role(ctx, userstr, create=False, interactive=False, collection=None, allow_notfound=True):
|
||||
"""
|
||||
Find a guild role given a partial matching string,
|
||||
allowing custom role collections and several behavioural switches.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
userstr: str
|
||||
String obtained from a user, expected to partially match a role in the collection.
|
||||
The string will be tested against both the id and the name of the role.
|
||||
create: bool
|
||||
Whether to offer to create the role if it does not exist.
|
||||
The bot will only offer to create the role if it has the `manage_channels` permission.
|
||||
interactive: bool
|
||||
Whether to offer the user a list of roles to choose from,
|
||||
or pick the first matching role.
|
||||
collection: List[Union[discord.Role, discord.Object]]
|
||||
Collection of roles to search amongst.
|
||||
If none, uses the guild role list.
|
||||
allow_notfound: bool
|
||||
Whether to return `None` when there are no matches, instead of raising `SafeCancellation`.
|
||||
Overriden by `create`, if it is set.
|
||||
|
||||
Returns
|
||||
-------
|
||||
discord.Role:
|
||||
If a valid role is found.
|
||||
None:
|
||||
If no valid role has been found.
|
||||
|
||||
Raises
|
||||
------
|
||||
cmdClient.lib.UserCancelled:
|
||||
If the user cancels interactive role selection.
|
||||
cmdClient.lib.ResponseTimedOut:
|
||||
If the user fails to respond to interactive role selection within `60` seconds`
|
||||
cmdClient.lib.SafeCancellation:
|
||||
If `allow_notfound` is `False`, and the search returned no matches.
|
||||
"""
|
||||
# Handle invalid situations and input
|
||||
if not ctx.guild:
|
||||
raise InvalidContext("Attempt to use find_role outside of a guild.")
|
||||
|
||||
if userstr == "":
|
||||
raise ValueError("User string passed to find_role was empty.")
|
||||
|
||||
# Create the collection to search from args or guild roles
|
||||
collection = collection if collection is not None else ctx.guild.roles
|
||||
|
||||
# If the unser input was a number or possible role mention, get it out
|
||||
userstr = userstr.strip()
|
||||
roleid = userstr.strip('<#@&!> ')
|
||||
roleid = int(roleid) if roleid.isdigit() else None
|
||||
searchstr = userstr.lower()
|
||||
|
||||
# Find the role
|
||||
role = None
|
||||
|
||||
# Check method to determine whether a role matches
|
||||
def check(role):
|
||||
return (role.id == roleid) or (searchstr in role.name.lower())
|
||||
|
||||
# Get list of matching roles
|
||||
roles = list(filter(check, collection))
|
||||
|
||||
if len(roles) == 0:
|
||||
# Nope
|
||||
role = None
|
||||
elif len(roles) == 1:
|
||||
# Select our lucky winner
|
||||
role = roles[0]
|
||||
else:
|
||||
# We have multiple matching roles!
|
||||
if interactive:
|
||||
# Interactive prompt with the list of roles, handle `Object`s
|
||||
role_names = [
|
||||
role.name if isinstance(role, discord.Role) else str(role.id) for role in roles
|
||||
]
|
||||
|
||||
try:
|
||||
selected = await ctx.selector(
|
||||
"`{}` roles found matching `{}`!".format(len(roles), userstr),
|
||||
role_names,
|
||||
timeout=60
|
||||
)
|
||||
except UserCancelled:
|
||||
raise UserCancelled("User cancelled role selection.") from None
|
||||
except ResponseTimedOut:
|
||||
raise ResponseTimedOut("Role selection timed out.") from None
|
||||
|
||||
role = roles[selected]
|
||||
else:
|
||||
# Just select the first one
|
||||
role = roles[0]
|
||||
|
||||
# Handle non-existence of the role
|
||||
if role is None:
|
||||
msgstr = "Couldn't find a role matching `{}`!".format(userstr)
|
||||
if create:
|
||||
# Inform the user
|
||||
msg = await ctx.error_reply(msgstr)
|
||||
if ctx.guild.me.guild_permissions.manage_roles:
|
||||
# Offer to create it
|
||||
resp = await ctx.ask("Would you like to create this role?", timeout=30)
|
||||
if resp:
|
||||
# They accepted, create the role
|
||||
# Before creation, check if the role name is too long
|
||||
if len(userstr) > 100:
|
||||
await ctx.error_reply("Could not create a role with a name over 100 characters long!")
|
||||
else:
|
||||
role = await ctx.guild.create_role(
|
||||
name=userstr,
|
||||
reason="Interactive role creation for {} (uid:{})".format(ctx.author, ctx.author.id)
|
||||
)
|
||||
await msg.delete()
|
||||
await ctx.reply("You have created the role `{}`!".format(userstr))
|
||||
|
||||
# If we still don't have a role, cancel unless allow_notfound is set
|
||||
if role is None and not allow_notfound:
|
||||
raise SafeCancellation
|
||||
elif not allow_notfound:
|
||||
raise SafeCancellation(msgstr)
|
||||
else:
|
||||
await ctx.error_reply(msgstr)
|
||||
|
||||
return role
|
||||
|
||||
|
||||
@Context.util
|
||||
async def find_channel(ctx, userstr, interactive=False, collection=None, chan_type=None):
|
||||
"""
|
||||
Find a guild channel given a partial matching string,
|
||||
allowing custom channel collections and several behavioural switches.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
userstr: str
|
||||
String obtained from a user, expected to partially match a channel in the collection.
|
||||
The string will be tested against both the id and the name of the channel.
|
||||
interactive: bool
|
||||
Whether to offer the user a list of channels to choose from,
|
||||
or pick the first matching channel.
|
||||
collection: List(discord.Channel)
|
||||
Collection of channels to search amongst.
|
||||
If none, uses the full guild channel list.
|
||||
chan_type: discord.ChannelType
|
||||
Type of channel to restrict the collection to.
|
||||
|
||||
Returns
|
||||
-------
|
||||
discord.Channel:
|
||||
If a valid channel is found.
|
||||
None:
|
||||
If no valid channel has been found.
|
||||
|
||||
Raises
|
||||
------
|
||||
cmdClient.lib.UserCancelled:
|
||||
If the user cancels interactive channel selection.
|
||||
cmdClient.lib.ResponseTimedOut:
|
||||
If the user fails to respond to interactive channel selection within `60` seconds`
|
||||
"""
|
||||
# Handle invalid situations and input
|
||||
if not ctx.guild:
|
||||
raise InvalidContext("Attempt to use find_channel outside of a guild.")
|
||||
|
||||
if userstr == "":
|
||||
raise ValueError("User string passed to find_channel was empty.")
|
||||
|
||||
# Create the collection to search from args or guild channels
|
||||
collection = collection if collection else ctx.guild.channels
|
||||
if chan_type is not None:
|
||||
collection = [chan for chan in collection if chan.type == chan_type]
|
||||
|
||||
# If the user input was a number or possible channel mention, extract it
|
||||
chanid = userstr.strip('<#@&!>')
|
||||
chanid = int(chanid) if chanid.isdigit() else None
|
||||
searchstr = userstr.lower()
|
||||
|
||||
# Find the channel
|
||||
chan = None
|
||||
|
||||
# Check method to determine whether a channel matches
|
||||
def check(chan):
|
||||
return (chan.id == chanid) or (searchstr in chan.name.lower())
|
||||
|
||||
# Get list of matching roles
|
||||
channels = list(filter(check, collection))
|
||||
|
||||
if len(channels) == 0:
|
||||
# Nope
|
||||
chan = None
|
||||
elif len(channels) == 1:
|
||||
# Select our lucky winner
|
||||
chan = channels[0]
|
||||
else:
|
||||
# We have multiple matching channels!
|
||||
if interactive:
|
||||
# Interactive prompt with the list of channels
|
||||
chan_names = [chan.name for chan in channels]
|
||||
|
||||
try:
|
||||
selected = await ctx.selector(
|
||||
"`{}` channels found matching `{}`!".format(len(channels), userstr),
|
||||
chan_names,
|
||||
timeout=60
|
||||
)
|
||||
except UserCancelled:
|
||||
raise UserCancelled("User cancelled channel selection.") from None
|
||||
except ResponseTimedOut:
|
||||
raise ResponseTimedOut("Channel selection timed out.") from None
|
||||
|
||||
chan = channels[selected]
|
||||
else:
|
||||
# Just select the first one
|
||||
chan = channels[0]
|
||||
|
||||
if chan is None:
|
||||
await ctx.error_reply("Couldn't find a channel matching `{}`!".format(userstr))
|
||||
|
||||
return chan
|
||||
|
||||
@Context.util
|
||||
async def find_member(ctx, userstr, interactive=False, collection=None, silent=False):
|
||||
"""
|
||||
Find a guild member given a partial matching string,
|
||||
allowing custom member collections.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
userstr: str
|
||||
String obtained from a user, expected to partially match a member in the collection.
|
||||
The string will be tested against both the userid, full user name and user nickname.
|
||||
interactive: bool
|
||||
Whether to offer the user a list of members to choose from,
|
||||
or pick the first matching channel.
|
||||
collection: List(discord.Member)
|
||||
Collection of members to search amongst.
|
||||
If none, uses the full guild member list.
|
||||
silent: bool
|
||||
Whether to reply with an error when there are no matches.
|
||||
|
||||
Returns
|
||||
-------
|
||||
discord.Member:
|
||||
If a valid member is found.
|
||||
None:
|
||||
If no valid member has been found.
|
||||
|
||||
Raises
|
||||
------
|
||||
cmdClient.lib.UserCancelled:
|
||||
If the user cancels interactive member selection.
|
||||
cmdClient.lib.ResponseTimedOut:
|
||||
If the user fails to respond to interactive member selection within `60` seconds`
|
||||
"""
|
||||
# Handle invalid situations and input
|
||||
if not ctx.guild:
|
||||
raise InvalidContext("Attempt to use find_member outside of a guild.")
|
||||
|
||||
if userstr == "":
|
||||
raise ValueError("User string passed to find_member was empty.")
|
||||
|
||||
# Create the collection to search from args or guild members
|
||||
collection = collection if collection else ctx.guild.members
|
||||
|
||||
# If the user input was a number or possible member mention, extract it
|
||||
userid = userstr.strip('<#@&!>')
|
||||
userid = int(userid) if userid.isdigit() else None
|
||||
searchstr = userstr.lower()
|
||||
|
||||
# Find the member
|
||||
member = None
|
||||
|
||||
# Check method to determine whether a member matches
|
||||
def check(member):
|
||||
return (
|
||||
member.id == userid
|
||||
or searchstr in member.display_name.lower()
|
||||
or searchstr in str(member).lower()
|
||||
)
|
||||
|
||||
# Get list of matching roles
|
||||
members = list(filter(check, collection))
|
||||
|
||||
if len(members) == 0:
|
||||
# Nope
|
||||
member = None
|
||||
elif len(members) == 1:
|
||||
# Select our lucky winner
|
||||
member = members[0]
|
||||
else:
|
||||
# We have multiple matching members!
|
||||
if interactive:
|
||||
# Interactive prompt with the list of members
|
||||
member_names = [
|
||||
"{} {}".format(
|
||||
member.nick if member.nick else (member if members.count(member) > 1
|
||||
else member.name),
|
||||
("<{}>".format(member)) if member.nick else ""
|
||||
) for member in members
|
||||
]
|
||||
|
||||
try:
|
||||
selected = await ctx.selector(
|
||||
"`{}` members found matching `{}`!".format(len(members), userstr),
|
||||
member_names,
|
||||
timeout=60
|
||||
)
|
||||
except UserCancelled:
|
||||
raise UserCancelled("User cancelled member selection.") from None
|
||||
except ResponseTimedOut:
|
||||
raise ResponseTimedOut("Member selection timed out.") from None
|
||||
|
||||
member = members[selected]
|
||||
else:
|
||||
# Just select the first one
|
||||
member = members[0]
|
||||
|
||||
if member is None and not silent:
|
||||
await ctx.error_reply("Couldn't find a member matching `{}`!".format(userstr))
|
||||
|
||||
return member
|
||||
Reference in New Issue
Block a user