2020-02-09 11:09:50 +13:00
|
|
|
import re
|
|
|
|
import discord
|
|
|
|
|
|
|
|
UNIT_TABLE = (
|
|
|
|
(("weeks", "wks", "w"), 60 * 60 * 24 * 7),
|
|
|
|
(("days", "dys", "d"), 60 * 60 * 24),
|
|
|
|
(("hours", "hrs", "h"), 60 * 60),
|
|
|
|
(("minutes", "mins", "m"), 60),
|
|
|
|
(("seconds", "secs", "s"), 1),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class BadTimeExpr(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def _find_unit(unit):
|
|
|
|
for names, length in UNIT_TABLE:
|
|
|
|
if any(n.startswith(unit) for n in names):
|
|
|
|
return names, length
|
|
|
|
raise BadTimeExpr("Invalid unit: %s" % unit)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_time(time):
|
|
|
|
time = time.lower()
|
|
|
|
if not time.isdigit():
|
|
|
|
time = re.split(r"\s*([\d.]+\s*[^\d\s,;]*)(?:[,;\s]|and)*", time)
|
|
|
|
time = sum(map(_timespec_sec, filter(None, time)))
|
|
|
|
return int(time)
|
|
|
|
|
|
|
|
|
|
|
|
def _timespec_sec(expr):
|
|
|
|
atoms = re.split(r"([\d.]+)\s*([^\d\s]*)", expr)
|
|
|
|
atoms = list(filter(None, atoms))
|
|
|
|
|
|
|
|
if len(atoms) > 2: # This shouldn't ever happen
|
|
|
|
raise BadTimeExpr("invalid expression: '%s'" % expr)
|
|
|
|
elif len(atoms) == 2:
|
|
|
|
names, length = _find_unit(atoms[1])
|
|
|
|
if atoms[0].count(".") > 1 or not atoms[0].replace(".", "").isdigit():
|
|
|
|
raise BadTimeExpr("Not a number: '%s'" % atoms[0])
|
|
|
|
else:
|
|
|
|
names, length = _find_unit("seconds")
|
|
|
|
|
|
|
|
try:
|
|
|
|
return float(atoms[0]) * length
|
|
|
|
except ValueError:
|
|
|
|
raise BadTimeExpr("invalid value: '%s'" % atoms[0])
|
|
|
|
|
|
|
|
|
|
|
|
def generate_timespec(sec: int, short=False, micro=False) -> str:
|
|
|
|
timespec = []
|
|
|
|
sec = int(sec)
|
|
|
|
neg = sec < 0
|
|
|
|
sec = abs(sec)
|
|
|
|
|
|
|
|
for names, length in UNIT_TABLE:
|
|
|
|
n, sec = divmod(sec, length)
|
|
|
|
|
|
|
|
if n:
|
|
|
|
if micro:
|
|
|
|
s = "%d%s" % (n, names[2])
|
|
|
|
elif short:
|
|
|
|
s = "%d%s" % (n, names[1])
|
|
|
|
else:
|
|
|
|
s = "%d %s" % (n, names[0])
|
|
|
|
|
|
|
|
if n <= 1 and not (micro and names[2] == "s"):
|
|
|
|
s = s.rstrip("s")
|
|
|
|
|
|
|
|
timespec.append(s)
|
|
|
|
|
|
|
|
if len(timespec) > 1:
|
|
|
|
if micro:
|
|
|
|
spec = "".join(timespec)
|
|
|
|
|
|
|
|
segments = timespec[:-1], timespec[-1:]
|
|
|
|
spec = " and ".join(", ".join(x) for x in segments)
|
|
|
|
elif timespec:
|
|
|
|
spec = timespec[0]
|
|
|
|
else:
|
|
|
|
return "0"
|
|
|
|
|
|
|
|
if neg:
|
|
|
|
spec += " ago"
|
|
|
|
|
|
|
|
return spec
|
|
|
|
|
|
|
|
|
|
|
|
def format_list(*items, join="and", delim=", "):
|
|
|
|
if len(items) > 1:
|
|
|
|
return (" %s " % join).join((delim.join(items[:-1]), items[-1]))
|
|
|
|
elif items:
|
|
|
|
return items[0]
|
|
|
|
else:
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
def overwrite_to_dict(overwrite):
|
|
|
|
allow, deny = overwrite.pair()
|
|
|
|
return {"allow": allow.value, "deny": deny.value}
|
|
|
|
|
|
|
|
|
|
|
|
def format_permissions(permissions, include_null=False):
|
|
|
|
entries = []
|
|
|
|
|
|
|
|
for perm, value in sorted(permissions, key=lambda t: t[0]):
|
|
|
|
if value is True:
|
|
|
|
symbol = "\N{WHITE HEAVY CHECK MARK}"
|
|
|
|
elif value is False:
|
|
|
|
symbol = "\N{NO ENTRY SIGN}"
|
|
|
|
elif include_null:
|
|
|
|
symbol = "\N{RADIO BUTTON}"
|
|
|
|
else:
|
|
|
|
continue
|
|
|
|
|
|
|
|
entries.append(symbol + " " + perm.replace("_", " ").title().replace("Tts", "TTS"))
|
|
|
|
|
|
|
|
if entries:
|
|
|
|
return "\n".join(entries)
|
|
|
|
else:
|
|
|
|
return "No permission entries."
|
|
|
|
|
|
|
|
|
|
|
|
def getmname(mid, guild):
|
|
|
|
member = discord.utils.get(guild.members, id=int(mid))
|
|
|
|
|
|
|
|
if member:
|
|
|
|
return str(member)
|
|
|
|
else:
|
|
|
|
return "(absent user #%s)" % mid
|
|
|
|
|
|
|
|
|
|
|
|
def role_from_string(guild, rolename, roles=None):
|
|
|
|
if rolename is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if roles is None:
|
|
|
|
roles = guild.roles
|
|
|
|
else:
|
|
|
|
roles = [r for r in roles if isinstance(r, discord.Role)]
|
|
|
|
|
|
|
|
if type(rolename) == int:
|
|
|
|
role = discord.utils.get(roles, id=rolename)
|
|
|
|
|
|
|
|
if role:
|
|
|
|
return role
|
2020-10-29 21:36:43 +13:00
|
|
|
else:
|
|
|
|
rolename = rolename.lower()
|
|
|
|
role = discord.utils.find(lambda r: r.name.lower() == rolename, roles)
|
2020-02-09 11:09:50 +13:00
|
|
|
|
|
|
|
return role
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_role_list(guild: discord.guild, roles: list) -> list:
|
|
|
|
gen = (role_from_string(guild, name) for name in roles)
|
|
|
|
return list(filter(None, gen))
|
|
|
|
|
|
|
|
|
|
|
|
def permissions_for_roles(channel, *roles):
|
|
|
|
"""
|
|
|
|
Calculates the effective permissions for a role or combination of roles.
|
|
|
|
Naturally, if no roles are given, the default role's permissions are used
|
|
|
|
"""
|
|
|
|
default = channel.guild.default_role
|
|
|
|
base = discord.Permissions(default.permissions.value)
|
|
|
|
|
|
|
|
# Apply all role values
|
|
|
|
for role in roles:
|
|
|
|
base.value |= role.permissions.value
|
|
|
|
|
|
|
|
# guild-wide Administrator -> True for everything
|
|
|
|
# Bypass all channel-specific overrides
|
|
|
|
if base.administrator:
|
|
|
|
return discord.Permissions.all()
|
|
|
|
|
|
|
|
role_ids = set(map(lambda r: r.id, roles))
|
|
|
|
denies = 0
|
|
|
|
allows = 0
|
|
|
|
|
|
|
|
# Apply channel specific role permission overwrites
|
|
|
|
for target, overwrite in channel.overwrites.items():
|
|
|
|
# Handle default role first, if present
|
2020-02-09 13:42:43 +13:00
|
|
|
allow, deny = overwrite.pair()
|
2020-02-09 13:25:19 +13:00
|
|
|
if overwrite == default:
|
2020-02-09 13:42:43 +13:00
|
|
|
base.handle_overwrite(allow=allow, deny=deny)
|
2020-02-09 11:09:50 +13:00
|
|
|
|
|
|
|
if isinstance(target, discord.Role) and target.id in role_ids:
|
2020-02-09 13:42:43 +13:00
|
|
|
denies |= deny
|
|
|
|
allows |= allow
|
2020-02-09 11:09:50 +13:00
|
|
|
|
|
|
|
base.handle_overwrite(allow=allows, deny=denies)
|
|
|
|
|
|
|
|
# if you can't send a message in a channel then you can't have certain
|
|
|
|
# permissions as well
|
|
|
|
if not base.send_messages:
|
|
|
|
base.send_tts_messages = False
|
|
|
|
base.mention_everyone = False
|
|
|
|
base.embed_links = False
|
|
|
|
base.attach_files = False
|
|
|
|
|
|
|
|
# if you can't read a channel then you have no permissions there
|
|
|
|
if not base.read_messages:
|
|
|
|
denied = discord.Permissions.all_channel()
|
|
|
|
base.value &= ~denied.value
|
|
|
|
|
|
|
|
# text channels do not have voice related permissions
|
|
|
|
if channel.type is discord.ChannelType.text:
|
|
|
|
denied = discord.Permissions.voice()
|
|
|
|
base.value &= ~denied.value
|
|
|
|
|
|
|
|
return base
|
|
|
|
|
|
|
|
|
|
|
|
def overwrite_from_dict(data):
|
|
|
|
allow = discord.Permissions(data.get("allow", 0))
|
|
|
|
deny = discord.Permissions(data.get("deny", 0))
|
|
|
|
return discord.PermissionOverwrite.from_pair(allow, deny)
|