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 rolename = rolename.lower() role = discord.utils.find(lambda r: r.name.lower() == rolename, roles) 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 overwrite in channel.overwrites: # Handle default role first, if present if overwrite.id == default.id: base.handle_overwrite(allow=overwrite.allow, deny=overwrite.deny) if overwrite.type == 'role' and overwrite.id in role_ids: denies |= overwrite.deny allows |= overwrite.allow base.handle_overwrite(allow=allows, deny=denies) # default channels can always be read if channel.is_default: base.read_messages = True # 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)