add black code formatter gitaction, update all cogs to follow format

This commit is contained in:
brandons209 2020-01-27 15:04:50 -05:00
parent 7e4b0c18b8
commit f5891ffb90
15 changed files with 632 additions and 527 deletions

13
.github/workflows/black-checker.yml vendored Normal file
View file

@ -0,0 +1,13 @@
name: black-checker
on: [pull_request, push]
jobs:
black:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Black Code Formatter
uses: lgeiger/black-action@v1.0.1
with:
args: "--line-length 120 --target-version py38 --check ."

View file

@ -1,5 +1,6 @@
from .activitylog import ActivityLogger from .activitylog import ActivityLogger
async def setup(bot): async def setup(bot):
n = ActivityLogger(bot) n = ActivityLogger(bot)
await n.initialize() await n.initialize()

View file

@ -12,13 +12,9 @@ import os
import asyncio import asyncio
import glob import glob
__version__ = '3.0.0' __version__ = "3.0.0"
TIMESTAMP_FORMAT = '%Y-%m-%d %X' # YYYY-MM-DD HH:MM:SS TIMESTAMP_FORMAT = "%Y-%m-%d %X" # YYYY-MM-DD HH:MM:SS
#PATH_LIST = ['data', 'activitylogger']
#JSON = os.path.join(*PATH_LIST, "settings.json")
#NAMES_JSON = os.path.join(*PATH_LIST, "past_names.json")
EDIT_TIMEDELTA = timedelta(seconds=3)
# 0 is Message object # 0 is Message object
AUTHOR_TEMPLATE = "@{0.author.name}#{0.author.discriminator}(id:{0.author.id})" AUTHOR_TEMPLATE = "@{0.author.name}#{0.author.discriminator}(id:{0.author.id})"
@ -42,6 +38,7 @@ DELETE_AUDIT_TEMPLATE = "@{0.name}#{0.discriminator}(id:{0.id}) deleted message
MAX_LINES = 50000 MAX_LINES = 50000
def get_all_names(guild_files, user): def get_all_names(guild_files, user):
names = [str(user)] names = [str(user)]
for log in guild_files: for log in guild_files:
@ -68,19 +65,21 @@ def get_all_names(guild_files, user):
names.append(username) names.append(username)
return set(names) return set(names)
def format_list(*items, join='and', delim=', '):
def format_list(*items, join="and", delim=", "):
if len(items) > 1: if len(items) > 1:
return (' %s ' % join).join((delim.join(items[:-1]), items[-1])) return (" %s " % join).join((delim.join(items[:-1]), items[-1]))
elif items: elif items:
return items[0] return items[0]
else: else:
return '' return ""
class LogHandle: class LogHandle:
"""basic wrapper for logfile handles, used to keep track of stale handles""" """basic wrapper for logfile handles, used to keep track of stale handles"""
def __init__(self, path, time=None, mode='a', buf=1):
self.handle = open(path, mode, buf, errors='backslashreplace') def __init__(self, path, time=None, mode="a", buf=1):
self.handle = open(path, mode, buf, errors="backslashreplace")
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
if time: if time:
@ -111,34 +110,13 @@ class ActivityLogger(commands.Cog):
self.bot = bot self.bot = bot
self.config = Config.get_conf(self, identifier=9584736583, force_registration=True) self.config = Config.get_conf(self, identifier=9584736583, force_registration=True)
default_global = { default_global = {
"attrs": { "attrs": {"attachments": False, "default": False, "direct": False, "everything": False, "rotation": "m"}
"attachments" : False,
"default" : False,
"direct" : False,
"everything" : False,
"rotation" : "m"
}
}
self.default_guild = {
"all_s": False,
"voice": False,
"events": False,
"prefixes": []
}
self.default_channel = {
"enabled": False
}
default_user = {
"past_names": []
} }
self.default_guild = {"all_s": False, "voice": False, "events": False, "prefixes": []}
self.default_channel = {"enabled": False}
default_user = {"past_names": []}
default_member = { default_member = {
"stats": { "stats": {"total_msg": 0, "bot_cmd": 0, "avg_len": 0.0, "vc_time_sec": 0.0, "last_vc_time": None}
"total_msg": 0,
"bot_cmd": 0,
"avg_len": 0.0,
"vc_time_sec": 0.0,
"last_vc_time": None
}
} }
self.config.register_global(**default_global) self.config.register_global(**default_global)
self.config.register_guild(**self.default_guild) self.config.register_guild(**self.default_guild)
@ -203,9 +181,7 @@ class ActivityLogger(commands.Cog):
since_joined = "?" since_joined = "?"
user_joined = "Unknown" user_joined = "Unknown"
user_created = user.created_at.strftime("%b %d, %Y %H:%M UTC") user_created = user.created_at.strftime("%b %d, %Y %H:%M UTC")
member_number = (sorted(guild.members, member_number = sorted(guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user) + 1
key=lambda m: m.joined_at or ctx.message.created_at)
.index(user) + 1)
created_on = "{}\n({} days ago)".format(user_created, since_created) created_on = "{}\n({} days ago)".format(user_created, since_created)
joined_on = "{}\n({} days ago)".format(user_joined, since_joined) joined_on = "{}\n({} days ago)".format(user_joined, since_joined)
@ -243,8 +219,7 @@ class ActivityLogger(commands.Cog):
names = pagify(names, page_length=1000) names = pagify(names, page_length=1000)
for name in names: for name in names:
data.add_field(name="Also known as:", value=name, inline=False) data.add_field(name="Also known as:", value=name, inline=False)
data.set_footer(text="Member #{} | User ID:{}" data.set_footer(text="Member #{} | User ID:{}" "".format(member_number, user.id))
"".format(member_number, user.id))
name = str(user) name = str(user)
name = " ~ ".join((name, user.nick)) if user.nick else name name = " ~ ".join((name, user.nick)) if user.nick else name
@ -274,7 +249,7 @@ class ActivityLogger(commands.Cog):
stats = await self.config.member(user).stats() stats = await self.config.member(user).stats()
async with self.config.user(user).past_names() as past_names: async with self.config.user(user).past_names() as past_names:
if not past_names: if not past_names:
guild_files = sorted(glob.glob(os.path.join(PATH, 'usernames', "*.log"))) guild_files = sorted(glob.glob(os.path.join(PATH, "usernames", "*.log")))
names = get_all_names(guild_files, user) names = get_all_names(guild_files, user)
else: else:
names = past_names names = past_names
@ -306,7 +281,9 @@ class ActivityLogger(commands.Cog):
msg += "Average message length: `{:.2f}` words\n".format(avg_len / (num_messages - num_bot_commands)) msg += "Average message length: `{:.2f}` words\n".format(avg_len / (num_messages - num_bot_commands))
except ZeroDivisionError: except ZeroDivisionError:
msg += "Average message length: `{:.2f}` words\n".format(0) msg += "Average message length: `{:.2f}` words\n".format(0)
msg += "Time spent in voice chat: `{:.0f}` {}.\n".format(minutes if minutes <= 120 else hours, "minutes" if minutes <= 120 else "hours") msg += "Time spent in voice chat: `{:.0f}` {}.\n".format(
minutes if minutes <= 120 else hours, "minutes" if minutes <= 120 else "hours"
)
msg += f"Bans: `{bans}`, Kicks: `{kicks}`, Mutes: `{mutes}`" msg += f"Bans: `{bans}`, Kicks: `{kicks}`, Mutes: `{mutes}`"
if len(names) > 1: if len(names) > 1:
return msg, format_list(*names) return msg, format_list(*names)
@ -351,12 +328,12 @@ class ActivityLogger(commands.Cog):
messages = [message for message in messages if str(user.id) in message] messages = [message for message in messages if str(user.id) in message]
message_chunks = [ message_chunks = [
messages[i * MAX_LINES:(i + 1) * MAX_LINES] for i in range((len(messages) + MAX_LINES - 1) // MAX_LINES) messages[i * MAX_LINES : (i + 1) * MAX_LINES] for i in range((len(messages) + MAX_LINES - 1) // MAX_LINES)
] ]
for msgs in message_chunks: for msgs in message_chunks:
temp_file = os.path.join(log_path, datetime.utcnow().strftime("%Y%m%d%X").replace(":", "") + ".txt") temp_file = os.path.join(log_path, datetime.utcnow().strftime("%Y%m%d%X").replace(":", "") + ".txt")
with open(temp_file, encoding='utf-8', mode="w") as f: with open(temp_file, encoding="utf-8", mode="w") as f:
f.writelines(msgs) f.writelines(msgs)
await ctx.channel.send(file=discord.File(temp_file)) await ctx.channel.send(file=discord.File(temp_file))
@ -436,12 +413,12 @@ class ActivityLogger(commands.Cog):
""" """
try: try:
dates = date.split(";") dates = date.split(";")
dates = [dates[0].strip(), dates[1].strip()] # only use 2 dates dates = [dates[0].strip(), dates[1].strip()] # only use 2 dates
start, end = [parse_time(date).replace(tzinfo=None) for date in dates] start, end = [parse_time(date).replace(tzinfo=None) for date in dates]
# order doesnt matter, so check which date is older than the other # order doesnt matter, so check which date is older than the other
# end time should be the newest date since logs are processed in reverse # end time should be the newest date since logs are processed in reverse
if start < end: # start is before end date if start < end: # start is before end date
start, end = end, start # swap order start, end = end, start # swap order
except: except:
await ctx.send("Invalid dates! Try again.") await ctx.send("Invalid dates! Try again.")
return return
@ -530,12 +507,12 @@ class ActivityLogger(commands.Cog):
""" """
try: try:
dates = date.split(";") dates = date.split(";")
dates = [dates[0].strip(), dates[1].strip()] # only use 2 dates dates = [dates[0].strip(), dates[1].strip()] # only use 2 dates
start, end = [parse_time(date).replace(tzinfo=None) for date in dates] start, end = [parse_time(date).replace(tzinfo=None) for date in dates]
# order doesnt matter, so check which date is older than the other # order doesnt matter, so check which date is older than the other
# end time should be the newest date since logs are processed in reverse # end time should be the newest date since logs are processed in reverse
if start < end: # start is before end date if start < end: # start is before end date
start, end = end, start # swap order start, end = end, start # swap order
except: except:
await ctx.send("Invalid dates! Try again.") await ctx.send("Invalid dates! Try again.")
return return
@ -624,12 +601,12 @@ class ActivityLogger(commands.Cog):
""" """
try: try:
dates = date.split(";") dates = date.split(";")
dates = [dates[0].strip(), dates[1].strip()] # only use 2 dates dates = [dates[0].strip(), dates[1].strip()] # only use 2 dates
start, end = [parse_time(date).replace(tzinfo=None) for date in dates] start, end = [parse_time(date).replace(tzinfo=None) for date in dates]
# order doesnt matter, so check which date is older than the other # order doesnt matter, so check which date is older than the other
# end time should be the newest date since logs are processed in reverse # end time should be the newest date since logs are processed in reverse
if start < end: # start is before end date if start < end: # start is before end date
start, end = end, start # swap order start, end = end, start # swap order
except: except:
await ctx.send("Invalid dates! Try again.") await ctx.send("Invalid dates! Try again.")
return return
@ -719,12 +696,12 @@ class ActivityLogger(commands.Cog):
""" """
try: try:
dates = date.split(";") dates = date.split(";")
dates = [dates[0].strip(), dates[1].strip()] # only use 2 dates dates = [dates[0].strip(), dates[1].strip()] # only use 2 dates
start, end = [parse_time(date).replace(tzinfo=None) for date in dates] start, end = [parse_time(date).replace(tzinfo=None) for date in dates]
# order doesnt matter, so check which date is older than the other # order doesnt matter, so check which date is older than the other
# end time should be the newest date since logs are processed in reverse # end time should be the newest date since logs are processed in reverse
if start < end: # start is before end date if start < end: # start is before end date
start, end = end, start # swap order start, end = end, start # swap order
except: except:
await ctx.send("Invalid dates! Try again.") await ctx.send("Invalid dates! Try again.")
return return
@ -746,7 +723,7 @@ class ActivityLogger(commands.Cog):
""" """
pass pass
@logset.command(name='everything', aliases=['global']) @logset.command(name="everything", aliases=["global"])
async def set_everything(self, ctx, on_off: bool = None): async def set_everything(self, ctx, on_off: bool = None):
""" """
Global override for all logging Global override for all logging
@ -762,7 +739,7 @@ class ActivityLogger(commands.Cog):
else: else:
await ctx.send("Global logging override is disabled.") await ctx.send("Global logging override is disabled.")
@logset.command(name='default') @logset.command(name="default")
async def set_default(self, ctx, on_off: bool = None): async def set_default(self, ctx, on_off: bool = None):
""" """
Sets whether logging is on or off where unset Sets whether logging is on or off where unset
@ -780,7 +757,7 @@ class ActivityLogger(commands.Cog):
else: else:
await ctx.send("Logging is disabled by default.") await ctx.send("Logging is disabled by default.")
@logset.command(name='dm') @logset.command(name="dm")
async def set_direct(self, ctx, on_off: bool = None): async def set_direct(self, ctx, on_off: bool = None):
""" """
Log direct messages? Log direct messages?
@ -797,7 +774,7 @@ class ActivityLogger(commands.Cog):
else: else:
await ctx.send("Logging of direct messages is disabled.") await ctx.send("Logging of direct messages is disabled.")
@logset.command(name='attachments') @logset.command(name="attachments")
async def set_attachments(self, ctx, on_off: bool = None): async def set_attachments(self, ctx, on_off: bool = None):
""" """
Download message attachments? Download message attachments?
@ -813,7 +790,7 @@ class ActivityLogger(commands.Cog):
else: else:
await ctx.send("Downloading of attachments is disabled.") await ctx.send("Downloading of attachments is disabled.")
@logset.command(name='channel') @logset.command(name="channel")
@commands.guild_only() @commands.guild_only()
async def set_channel(self, ctx, on_off: bool, channel: discord.TextChannel = None): async def set_channel(self, ctx, on_off: bool, channel: discord.TextChannel = None):
""" """
@ -830,11 +807,11 @@ class ActivityLogger(commands.Cog):
await self.config.channel(channel).enabled.set(on_off) await self.config.channel(channel).enabled.set(on_off)
if on_off: if on_off:
await ctx.send('Logging enabled for %s' % channel.mention) await ctx.send("Logging enabled for %s" % channel.mention)
else: else:
await ctx.send('Logging disabled for %s' % channel.mention) await ctx.send("Logging disabled for %s" % channel.mention)
@logset.command(name='server') @logset.command(name="server")
@commands.guild_only() @commands.guild_only()
async def set_guild(self, ctx, on_off: bool): async def set_guild(self, ctx, on_off: bool):
""" """
@ -846,11 +823,11 @@ class ActivityLogger(commands.Cog):
await self.config.guild(guild).all_s.set(on_off) await self.config.guild(guild).all_s.set(on_off)
if on_off: if on_off:
await ctx.send('Logging enabled for %s' % guild) await ctx.send("Logging enabled for %s" % guild)
else: else:
await ctx.send('Logging disabled for %s' % guild) await ctx.send("Logging disabled for %s" % guild)
@logset.command(name='voice') @logset.command(name="voice")
@commands.guild_only() @commands.guild_only()
async def set_voice(self, ctx, on_off: bool): async def set_voice(self, ctx, on_off: bool):
""" """
@ -862,11 +839,11 @@ class ActivityLogger(commands.Cog):
await self.config.guild(guild).voice.set(on_off) await self.config.guild(guild).voice.set(on_off)
if on_off: if on_off:
await ctx.send('Voice event logging enabled for %s' % guild) await ctx.send("Voice event logging enabled for %s" % guild)
else: else:
await ctx.send('Voice event logging disabled for %s' % guild) await ctx.send("Voice event logging disabled for %s" % guild)
@logset.command(name='events') @logset.command(name="events")
@commands.guild_only() @commands.guild_only()
async def set_events(self, ctx, on_off: bool): async def set_events(self, ctx, on_off: bool):
""" """
@ -878,9 +855,9 @@ class ActivityLogger(commands.Cog):
await self.config.guild(guild).events.set(on_off) await self.config.guild(guild).events.set(on_off)
if on_off: if on_off:
await ctx.send('Logging enabled for guild events in %s' % guild) await ctx.send("Logging enabled for guild events in %s" % guild)
else: else:
await ctx.send('Logging disabled for guild events in %s' % guild) await ctx.send("Logging disabled for guild events in %s" % guild)
@logset.command(name="prefixes") @logset.command(name="prefixes")
@commands.guild_only() @commands.guild_only()
@ -895,16 +872,16 @@ class ActivityLogger(commands.Cog):
await self.config.guild(ctx.guild).prefixes.set([ctx.clean_prefix]) await self.config.guild(ctx.guild).prefixes.set([ctx.clean_prefix])
self.cache[ctx.guild.id]["prefixes"] = [ctx.clean_prefix] self.cache[ctx.guild.id]["prefixes"] = [ctx.clean_prefix]
return return
await ctx.send("Current Prefixes: " + format_list(*curr, delim=', ')) await ctx.send("Current Prefixes: " + format_list(*curr, delim=", "))
return return
prefixes = [p for p in prefixes if p != ' '] prefixes = [p for p in prefixes if p != " "]
await self.config.guild(ctx.guild).prefixes.set(prefixes) await self.config.guild(ctx.guild).prefixes.set(prefixes)
self.cache[ctx.guild.id]["prefixes"] = prefixes self.cache[ctx.guild.id]["prefixes"] = prefixes
prefixes = [f"`{p}`" for p in prefixes] prefixes = [f"`{p}`" for p in prefixes]
await ctx.send("Prefixes set to: " + format_list(*prefixes, delim=', ')) await ctx.send("Prefixes set to: " + format_list(*prefixes, delim=", "))
@logset.command(name='rotation') @logset.command(name="rotation")
async def set_rotation(self, ctx, freq: str = None): async def set_rotation(self, ctx, freq: str = None):
""" """
Show, disable, or set the log rotation period Show, disable, or set the log rotation period
@ -921,12 +898,12 @@ class ActivityLogger(commands.Cog):
- y: one log file per year (starts 00:00Z Jan 1) - y: one log file per year (starts 00:00Z Jan 1)
""" """
if freq: if freq:
freq = freq.lower().strip('"\'` ') freq = freq.lower().strip("\"'` ")
if freq in ('d', 'w', 'm', 'y', 'none', 'disable'): if freq in ("d", "w", "m", "y", "none", "disable"):
adj = 'now' adj = "now"
if freq in ('none', 'disable'): if freq in ("none", "disable"):
freq = None freq = None
async with self.config.attrs() as attrs: async with self.config.attrs() as attrs:
@ -937,53 +914,48 @@ class ActivityLogger(commands.Cog):
await self.bot.send_cmd_help(ctx) await self.bot.send_cmd_help(ctx)
return return
else: else:
adj = 'currently' adj = "currently"
freq = self.cache["rotation"] freq = self.cache["rotation"]
if not freq: if not freq:
await ctx.send("Log rotation is %s disabled." % adj) await ctx.send("Log rotation is %s disabled." % adj)
else: else:
desc = { desc = {"d": "daily", "w": "weekly", "m": "monthly", "y": "yearly"}[freq]
'd' : 'daily',
'w' : 'weekly',
'm' : 'monthly',
'y' : 'yearly'
}[freq]
await ctx.send('Log rotation period is %s %s.' % (adj, desc)) await ctx.send("Log rotation period is %s %s." % (adj, desc))
@staticmethod @staticmethod
def format_rotation_string(timestamp, rotation_code, filename=None): def format_rotation_string(timestamp, rotation_code, filename=None):
kwargs = dict(hour=0, minute=0, second=0, microsecond=0) kwargs = dict(hour=0, minute=0, second=0, microsecond=0)
if not rotation_code: if not rotation_code:
return filename or '' return filename or ""
if rotation_code == 'y': if rotation_code == "y":
kwargs.update(day=1, month=1) kwargs.update(day=1, month=1)
start = timestamp.replace(**kwargs) start = timestamp.replace(**kwargs)
elif rotation_code == 'm': elif rotation_code == "m":
kwargs.update(day=1) kwargs.update(day=1)
start = timestamp.replace(**kwargs) start = timestamp.replace(**kwargs)
elif rotation_code == 'w': elif rotation_code == "w":
start = timestamp - timedelta(days=timestamp.weekday()) start = timestamp - timedelta(days=timestamp.weekday())
spec = start.strftime('%Y%m%d') spec = start.strftime("%Y%m%d")
if rotation_code == 'w': if rotation_code == "w":
spec += '--P7D' spec += "--P7D"
else: else:
spec += '--P1%c' % rotation_code.upper() spec += "--P1%c" % rotation_code.upper()
if filename: if filename:
return '%s_%s' % (spec, filename) return "%s_%s" % (spec, filename)
else: else:
return spec return spec
@staticmethod @staticmethod
def get_voice_flags(voice_state): def get_voice_flags(voice_state):
flags = [] flags = []
for f in ('deaf', 'mute', 'self_deaf', 'self_mute', 'self_stream', 'self_video'): for f in ("deaf", "mute", "self_deaf", "self_mute", "self_stream", "self_video"):
if getattr(voice_state, f, None): if getattr(voice_state, f, None):
flags.append(f) flags.append(f)
@ -992,11 +964,13 @@ class ActivityLogger(commands.Cog):
@staticmethod @staticmethod
def format_overwrite(target, channel, before, after, user=None): def format_overwrite(target, channel, before, after, user=None):
if user: if user:
target_str = 'Channel overwrites by @{1.name}#{1.discriminator}(id:{1.id}): {0.name} ({0.id}): '.format(channel, user) target_str = "Channel overwrites by @{1.name}#{1.discriminator}(id:{1.id}): {0.name} ({0.id}): ".format(
channel, user
)
else: else:
target_str = 'Channel overwrites: {0.name} ({0.id}): '.format(channel) target_str = "Channel overwrites: {0.name} ({0.id}): ".format(channel)
target_str += 'role' if isinstance(target, discord.Role) else 'member' target_str += "role" if isinstance(target, discord.Role) else "member"
target_str += ' {0.name} ({0.id})'.format(target) target_str += " {0.name} ({0.id})".format(target)
if before: if before:
bpair = [x.value for x in before.pair()] bpair = [x.value for x in before.pair()]
@ -1005,14 +979,14 @@ class ActivityLogger(commands.Cog):
apair = [x.value for x in after.pair()] apair = [x.value for x in after.pair()]
if before and after: if before and after:
fmt = ' updated to values %i, %i (was %i, %i)' fmt = " updated to values %i, %i (was %i, %i)"
return target_str + fmt % tuple(apair + bpair) return target_str + fmt % tuple(apair + bpair)
elif after: elif after:
return target_str + ' added with values %i, %i' % tuple(apair) return target_str + " added with values %i, %i" % tuple(apair)
elif before: elif before:
return target_str + ' removed (was %i, %i)' % tuple(bpair) return target_str + " removed (was %i, %i)" % tuple(bpair)
def gethandle(self, path, mode='a'): def gethandle(self, path, mode="a"):
"""Manages logfile handles, culling stale ones and creating folders""" """Manages logfile handles, culling stale ones and creating folders"""
if path in self.handles: if path in self.handles:
if os.path.exists(path): if os.path.exists(path):
@ -1047,34 +1021,34 @@ class ActivityLogger(commands.Cog):
return handle return handle
def should_log(self, location): def should_log(self, location):
if self.cache.get('everything', False): if self.cache.get("everything", False):
return True return True
default = self.cache.get('default', False) default = self.cache.get("default", False)
if type(location) is discord.Guild: if type(location) is discord.Guild:
loc = self.cache[location.id] loc = self.cache[location.id]
return loc.get('all_s', False) or loc.get('events', default) return loc.get("all_s", False) or loc.get("events", default)
elif type(location) is discord.TextChannel: elif type(location) is discord.TextChannel:
loc = self.cache[location.guild.id] loc = self.cache[location.guild.id]
opts = [loc.get('all_s', False), self.cache[location.id].get("enabled", default)] opts = [loc.get("all_s", False), self.cache[location.id].get("enabled", default)]
return any(opts) return any(opts)
elif type(location) is discord.VoiceChannel: elif type(location) is discord.VoiceChannel:
loc = self.cache[location.guild.id] loc = self.cache[location.guild.id]
opts = [loc.get('all_s', False), loc.get('voice', False)] opts = [loc.get("all_s", False), loc.get("voice", False)]
return any(opts) return any(opts)
elif isinstance(location, discord.abc.PrivateChannel): elif isinstance(location, discord.abc.PrivateChannel):
return self.cache.get('direct', default) return self.cache.get("direct", default)
else: # can't log other types else: # can't log other types
return False return False
def should_download(self, msg): def should_download(self, msg):
return self.should_log(msg.channel) and self.cache.get('attachments', False) return self.should_log(msg.channel) and self.cache.get("attachments", False)
def process_attachment(self, message, a): def process_attachment(self, message, a):
aid = a.id aid = a.id
@ -1086,23 +1060,23 @@ class ActivityLogger(commands.Cog):
if type(channel) is discord.TextChannel: if type(channel) is discord.TextChannel:
guildid = channel.guild.id guildid = channel.guild.id
elif isinstance(channel, discord.abc.PrivateChannel): elif isinstance(channel, discord.abc.PrivateChannel):
guildid = 'direct' guildid = "direct"
path = os.path.join(path, str(guildid), str(channel.id) + '_attachments') path = os.path.join(path, str(guildid), str(channel.id) + "_attachments")
filename = str(aid) + '_' + aname filename = str(aid) + "_" + aname
if len(filename) > 255: if len(filename) > 255:
target_len = 255 - len(aid) - 4 target_len = 255 - len(aid) - 4
part_a = target_len // 2 part_a = target_len // 2
part_b = target_len - part_a part_b = target_len - part_a
filename = aid + '_' + aname[:part_a] + '...' + aname[-part_b:] filename = aid + "_" + aname[:part_a] + "..." + aname[-part_b:]
truncated = True truncated = True
else: else:
truncated = False truncated = False
return aid, url, path, filename, truncated return aid, url, path, filename, truncated
async def log(self, location, text, timestamp=None, force=False, subfolder=None, mode='a'): async def log(self, location, text, timestamp=None, force=False, subfolder=None, mode="a"):
if not timestamp: if not timestamp:
timestamp = datetime.utcnow() timestamp = datetime.utcnow()
@ -1111,24 +1085,24 @@ class ActivityLogger(commands.Cog):
path = [] path = []
entry = [timestamp.strftime(TIMESTAMP_FORMAT)] entry = [timestamp.strftime(TIMESTAMP_FORMAT)]
rotation = self.cache['rotation'] rotation = self.cache["rotation"]
if type(location) is discord.Guild: if type(location) is discord.Guild:
path += [str(location.id), 'guild.log'] path += [str(location.id), "guild.log"]
elif type(location) is discord.TextChannel or type(location) is discord.VoiceChannel: elif type(location) is discord.TextChannel or type(location) is discord.VoiceChannel:
guildid = str(location.guild.id) guildid = str(location.guild.id)
entry.append('#' + location.name) entry.append("#" + location.name)
path += [guildid, str(location.id) + '.log'] path += [guildid, str(location.id) + ".log"]
elif isinstance(location, discord.abc.PrivateChannel): elif isinstance(location, discord.abc.PrivateChannel):
path += ['direct', str(location.id) + '.log'] path += ["direct", str(location.id) + ".log"]
elif type(location) is discord.User or type(location) is discord.Member: elif type(location) is discord.User or type(location) is discord.Member:
path += ['usernames', 'usernames.log'] path += ["usernames", "usernames.log"]
else: else:
return return
if subfolder: if subfolder:
path.insert(-1, str(subfolder)) path.insert(-1, str(subfolder))
text = text.replace('\n', '\\n') text = text.replace("\n", "\\n")
entry.append(text) entry.append(text)
if rotation: if rotation:
@ -1136,7 +1110,7 @@ class ActivityLogger(commands.Cog):
fname = os.path.join(PATH, *path) fname = os.path.join(PATH, *path)
handle = self.gethandle(fname, mode=mode) handle = self.gethandle(fname, mode=mode)
await handle.write(' '.join(entry) + '\n') await handle.write(" ".join(entry) + "\n")
async def message_handler(self, message, *args, force_attachments=None, **kwargs): async def message_handler(self, message, *args, force_attachments=None, **kwargs):
dl_attachment = self.should_download(message) dl_attachment = self.should_download(message)
@ -1149,15 +1123,17 @@ class ActivityLogger(commands.Cog):
for a in message.attachments: for a in message.attachments:
attachments += [self.process_attachment(message, a)] attachments += [self.process_attachment(message, a)]
entry = DOWNLOAD_TEMPLATE.format(message, [a[3] + ' (filename truncated)' if a[4] else a[3] for a in attachments]) entry = DOWNLOAD_TEMPLATE.format(
message, [a[3] + " (filename truncated)" if a[4] else a[3] for a in attachments]
)
elif message.attachments: elif message.attachments:
urls = ','.join(a.url for a in message.attachments) urls = ",".join(a.url for a in message.attachments)
entry = ATTACHMENT_TEMPLATE.format(message, urls) entry = ATTACHMENT_TEMPLATE.format(message, urls)
else: else:
entry = MESSAGE_TEMPLATE.format(message) entry = MESSAGE_TEMPLATE.format(message)
if message.author.id != self.bot.user.id: # don't calculate bot stats if message.author.id != self.bot.user.id: # don't calculate bot stats
async with self.config.member(message.author).stats() as stats: async with self.config.member(message.author).stats() as stats:
stats["total_msg"] += 1 stats["total_msg"] += 1
if len(message.content) > 0 and message.content[0] in self.cache[message.guild.id]["prefixes"]: if len(message.content) > 0 and message.content[0] in self.cache[message.guild.id]["prefixes"]:
@ -1199,10 +1175,12 @@ class ActivityLogger(commands.Cog):
async for entry in message.guild.audit_logs(limit=1): async for entry in message.guild.audit_logs(limit=1):
# target is user who had message deleted # target is user who had message deleted
if entry.action is discord.AuditLogAction.message_delete: if entry.action is discord.AuditLogAction.message_delete:
if entry.target.id == message.author.id and \ if (
entry.extra.channel.id == message.channel.id and \ entry.target.id == message.author.id
entry.created_at.timestamp() > time.time() - 3000 and \ and entry.extra.channel.id == message.channel.id
entry.extra.count >= 1: and entry.created_at.timestamp() > time.time() - 3000
and entry.extra.count >= 1
):
entry_s = DELETE_AUDIT_TEMPLATE.format(entry.user, message, message.author, timestamp) entry_s = DELETE_AUDIT_TEMPLATE.format(entry.user, message, message.author, timestamp)
break break
except: except:
@ -1215,12 +1193,12 @@ class ActivityLogger(commands.Cog):
@commands.Cog.listener() @commands.Cog.listener()
async def on_guild_join(self, guild): async def on_guild_join(self, guild):
entry = 'this bot joined the guild' entry = "this bot joined the guild"
await self.log(guild, entry) await self.log(guild, entry)
@commands.Cog.listener() @commands.Cog.listener()
async def on_guild_remove(self, guild): async def on_guild_remove(self, guild):
entry = 'this bot left the guild' entry = "this bot left the guild"
await self.log(guild, entry) await self.log(guild, entry)
@commands.Cog.listener() @commands.Cog.listener()
@ -1230,39 +1208,49 @@ class ActivityLogger(commands.Cog):
try: try:
async for entry in after.audit_logs(limit=1): async for entry in after.audit_logs(limit=1):
if entry.action is discord.AuditLogAction.guild_update: if entry.action is discord.AuditLogAction.guild_update:
user = entry.user user = entry.user
except: except:
pass pass
if before.owner != after.owner: if before.owner != after.owner:
if user: if user:
entries.append('guild owner changed by @{2.name}#{2.discriminator}(id:{2.id}), from {0.owner} (id {0.owner.id}) to {1.owner} (id {1.owner.id})') entries.append(
"guild owner changed by @{2.name}#{2.discriminator}(id:{2.id}), from {0.owner} (id {0.owner.id}) to {1.owner} (id {1.owner.id})"
)
else: else:
entries.append('guild owner changed from {0.owner} (id {0.owner.id}) to {1.owner} (id {1.owner.id})') entries.append("guild owner changed from {0.owner} (id {0.owner.id}) to {1.owner} (id {1.owner.id})")
if before.region != after.region: if before.region != after.region:
if user: if user:
entries.append('guild region changed by @{2.name}#{2.discriminator}(id:{2.id}), from {0.region} to {1.region}') entries.append(
"guild region changed by @{2.name}#{2.discriminator}(id:{2.id}), from {0.region} to {1.region}"
)
else: else:
entries.append('guild region changed from {0.region} to {1.region}') entries.append("guild region changed from {0.region} to {1.region}")
if before.name != after.name: if before.name != after.name:
if user: if user:
entries.append('guild name changed by @{2.name}#{2.discriminator}(id:{2.id}), from "{0.name}" to "{1.name}"') entries.append(
'guild name changed by @{2.name}#{2.discriminator}(id:{2.id}), from "{0.name}" to "{1.name}"'
)
else: else:
entries.append('guild name changed from "{0.name}" to "{1.name}"') entries.append('guild name changed from "{0.name}" to "{1.name}"')
if before.icon_url != after.icon_url: if before.icon_url != after.icon_url:
if user: if user:
entries.append('guild icon changed by @{2.name}#{2.discriminator}(id:{2.id}), from {0.icon_url} to {1.icon_url}') entries.append(
"guild icon changed by @{2.name}#{2.discriminator}(id:{2.id}), from {0.icon_url} to {1.icon_url}"
)
else: else:
entries.append('guild icon changed from {0.icon_url} to {1.icon_url}') entries.append("guild icon changed from {0.icon_url} to {1.icon_url}")
if before.splash != after.splash: if before.splash != after.splash:
if user: if user:
entries.append('guild splash changed by @{2.name}#{2.discriminator}(id:{2.id}), from {0.splash} to {1.splash}') entries.append(
"guild splash changed by @{2.name}#{2.discriminator}(id:{2.id}), from {0.splash} to {1.splash}"
)
else: else:
entries.append('guild splash changed from {0.splash} to {1.splash}') entries.append("guild splash changed from {0.splash} to {1.splash}")
for e in entries: for e in entries:
if user: if user:
@ -1326,45 +1314,61 @@ class ActivityLogger(commands.Cog):
if before.color != after.color: if before.color != after.color:
if user: if user:
entries.append('Role color by @{2.name}#{2.discriminator}(id:{2.id}): "{0}" (id {0.id}) changed from {0.color} to {1.color}') entries.append(
'Role color by @{2.name}#{2.discriminator}(id:{2.id}): "{0}" (id {0.id}) changed from {0.color} to {1.color}'
)
else: else:
entries.append('Role color: "{0}" (id {0.id}) changed from {0.color} to {1.color}') entries.append('Role color: "{0}" (id {0.id}) changed from {0.color} to {1.color}')
if before.mentionable != after.mentionable: if before.mentionable != after.mentionable:
if after.mentionable: if after.mentionable:
if user: if user:
entries.append('Role mentionable by @{2.name}#{2.discriminator}(id:{2.id}): "{1.name}" (id {1.id}) is now mentionable') entries.append(
'Role mentionable by @{2.name}#{2.discriminator}(id:{2.id}): "{1.name}" (id {1.id}) is now mentionable'
)
else: else:
entries.append('Role mentionable: "{1.name}" (id {1.id}) is now mentionable') entries.append('Role mentionable: "{1.name}" (id {1.id}) is now mentionable')
else: else:
if user: if user:
entries.append('Role mentionable by @{2.name}#{2.discriminator}(id:{2.id}): "{1.name}" (id {1.id}) is no longer mentionable') entries.append(
'Role mentionable by @{2.name}#{2.discriminator}(id:{2.id}): "{1.name}" (id {1.id}) is no longer mentionable'
)
else: else:
entries.append('Role mentionable: "{1.name}" (id {1.id}) is no longer mentionable') entries.append('Role mentionable: "{1.name}" (id {1.id}) is no longer mentionable')
if before.hoist != after.hoist: if before.hoist != after.hoist:
if after.hoist: if after.hoist:
if user: if user:
entries.append('Role hoist by @{2.name}#{2.discriminator}(id:{2.id}): "{1.name}" (id {1.id}) is now shown seperately') entries.append(
'Role hoist by @{2.name}#{2.discriminator}(id:{2.id}): "{1.name}" (id {1.id}) is now shown seperately'
)
else: else:
entries.append('Role hoist: "{1.name}" (id {1.id}) is now shown seperately') entries.append('Role hoist: "{1.name}" (id {1.id}) is now shown seperately')
else: else:
if user: if user:
entries.append('Role hoist by @{2.name}#{2.discriminator}(id:{2.id}): "{1.name}" (id {1.id}) is no longer shown seperately') entries.append(
'Role hoist by @{2.name}#{2.discriminator}(id:{2.id}): "{1.name}" (id {1.id}) is no longer shown seperately'
)
else: else:
entries.append('Role hoist: "{1.name}" (id {1.id}) is no longer shown seperately') entries.append('Role hoist: "{1.name}" (id {1.id}) is no longer shown seperately')
if before.permissions != after.permissions: if before.permissions != after.permissions:
if user: if user:
entries.append('Role permissions by @{2.name}#{2.discriminator}(id:{2.id}): "{1.name}" (id {1.id}) changed from {0.permissions.value} ' entries.append(
'to {1.permissions.value}') 'Role permissions by @{2.name}#{2.discriminator}(id:{2.id}): "{1.name}" (id {1.id}) changed from {0.permissions.value} '
"to {1.permissions.value}"
)
else: else:
entries.append('Role permissions: "{1.name}" (id {1.id}) changed from {0.permissions.value} ' entries.append(
'to {1.permissions.value}') 'Role permissions: "{1.name}" (id {1.id}) changed from {0.permissions.value} '
"to {1.permissions.value}"
)
if before.position != after.position: if before.position != after.position:
if user: if user:
entries.append('Role position by @{2.name}#{2.discriminator}(id:{2.id}): "{0}" changed from {0.position} to {1.position}') entries.append(
'Role position by @{2.name}#{2.discriminator}(id:{2.id}): "{0}" changed from {0.position} to {1.position}'
)
else: else:
entries.append('Role position: "{0}" changed from {0.position} to {1.position}') entries.append('Role position: "{0}" changed from {0.position} to {1.position}')
@ -1376,7 +1380,7 @@ class ActivityLogger(commands.Cog):
@commands.Cog.listener() @commands.Cog.listener()
async def on_member_join(self, member): async def on_member_join(self, member):
entry = 'Member join: @{0} (id {0.id})'.format(member) entry = "Member join: @{0} (id {0.id})".format(member)
async with self.config.user(member).past_names() as past_names: async with self.config.user(member).past_names() as past_names:
if str(member) not in past_names: if str(member) not in past_names:
@ -1396,9 +1400,9 @@ class ActivityLogger(commands.Cog):
pass pass
if user: if user:
entry = 'Member kicked by @{1.name}#{1.discriminator}(id:{1.id}): @{0} (id {0.id})'.format(member, user) entry = "Member kicked by @{1.name}#{1.discriminator}(id:{1.id}): @{0} (id {0.id})".format(member, user)
else: else:
entry = 'Member leave: @{0} (id {0.id})'.format(member) entry = "Member leave: @{0} (id {0.id})".format(member)
await self.config.member(member).clear() await self.config.member(member).clear()
await self.log(member.guild, entry) await self.log(member.guild, entry)
@ -1415,9 +1419,9 @@ class ActivityLogger(commands.Cog):
pass pass
if user: if user:
entry = 'Member banned by @{1.name}#{1.discriminator}(id:{1.id}): @{0} (id {0.id})'.format(member, user) entry = "Member banned by @{1.name}#{1.discriminator}(id:{1.id}): @{0} (id {0.id})".format(member, user)
else: else:
entry = 'Member ban: @{0} (id {0.id})'.format(member) entry = "Member ban: @{0} (id {0.id})".format(member)
await self.log(member.guild, entry) await self.log(member.guild, entry)
@ -1433,9 +1437,9 @@ class ActivityLogger(commands.Cog):
pass pass
if user: if user:
entry = 'Member unbanned by @{1.name}#{1.discriminator}(id:{1.id}): @{0} (id {0.id})'.format(member, user) entry = "Member unbanned by @{1.name}#{1.discriminator}(id:{1.id}): @{0} (id {0.id})".format(member, user)
else: else:
entry = 'Member unban: @{0} (id {0.id})'.format(member) entry = "Member unban: @{0} (id {0.id})".format(member)
await self.log(guild, entry) await self.log(guild, entry)
@ -1445,8 +1449,10 @@ class ActivityLogger(commands.Cog):
user = None user = None
try: try:
async for entry in after.guild.audit_logs(limit=1): async for entry in after.guild.audit_logs(limit=1):
if entry.action is discord.AuditLogAction.member_update \ if (
or entry.action is discord.AuditLogAction.member_role_update: entry.action is discord.AuditLogAction.member_update
or entry.action is discord.AuditLogAction.member_role_update
):
if entry.target.id == after.id: if entry.target.id == after.id:
user = entry.user user = entry.user
except: except:
@ -1454,7 +1460,9 @@ class ActivityLogger(commands.Cog):
if before.nick != after.nick: if before.nick != after.nick:
if user: if user:
entries.append('Member nickname changed by @{2.name}#{2.discriminator}(id:{2.id}): "@{0}" (id {0.id}) nickname change from "{0.nick}" to "{1.nick}"') entries.append(
'Member nickname changed by @{2.name}#{2.discriminator}(id:{2.id}): "@{0}" (id {0.id}) nickname change from "{0.nick}" to "{1.nick}"'
)
else: else:
entries.append('Member nickname: "@{0}" (id {0.id}) changed nickname from "{0.nick}" to "{1.nick}"') entries.append('Member nickname: "@{0}" (id {0.id}) changed nickname from "{0.nick}" to "{1.nick}"')
@ -1466,18 +1474,27 @@ class ActivityLogger(commands.Cog):
for r in added: for r in added:
if user: if user:
entries.append('Member role added by @{1.name}#{1.discriminator}(id:{1.id}): "{0}" (id {0.id}) role ' entries.append(
'was added to "@{{0}}" (id {{0.id}})'.format(r, user)) 'Member role added by @{1.name}#{1.discriminator}(id:{1.id}): "{0}" (id {0.id}) role '
'was added to "@{{0}}" (id {{0.id}})'.format(r, user)
)
else: else:
entries.append('Member role add: "{0}" (id {0.id}) role ' entries.append(
'was added to "@{{0}}" (id {{0.id}})'.format(r)) 'Member role add: "{0}" (id {0.id}) role ' 'was added to "@{{0}}" (id {{0.id}})'.format(r)
)
for r in removed: for r in removed:
if user: if user:
entries.append('Member role removed by @{1.name}#{1.discriminator}(id:{1.id}): "{0}" (id {0.id}) role was removed from "@{{0}}" (id {{0.id}})'.format(r, user)) entries.append(
'Member role removed by @{1.name}#{1.discriminator}(id:{1.id}): "{0}" (id {0.id}) role was removed from "@{{0}}" (id {{0.id}})'.format(
r, user
)
)
else: else:
entries.append('Member role remove: "{0}" (id {0.id}) role ' entries.append(
'was removed from "@{{0}}" (id {{0.id}})'.format(r)) 'Member role remove: "{0}" (id {0.id}) role '
'was removed from "@{{0}}" (id {{0.id}})'.format(r)
)
for e in entries: for e in entries:
await self.log(before.guild, e.format(before, after, user)) await self.log(before.guild, e.format(before, after, user))
@ -1513,7 +1530,9 @@ class ActivityLogger(commands.Cog):
pass pass
if user: if user:
entry = 'Channel created by @{1.name}#{1.discriminator}(id:{1.id}): "{0.name}" (id {0.id})'.format(channel, user) entry = 'Channel created by @{1.name}#{1.discriminator}(id:{1.id}): "{0.name}" (id {0.id})'.format(
channel, user
)
else: else:
entry = 'Channel created: "{0.name}" (id {0.id})'.format(channel) entry = 'Channel created: "{0.name}" (id {0.id})'.format(channel)
@ -1531,7 +1550,9 @@ class ActivityLogger(commands.Cog):
pass pass
if user: if user:
entry = 'Channel deleted by @{1.name}#{1.discriminator}(id:{1.id}): "{0.name}" (id {0.id})'.format(channel, user) entry = 'Channel deleted by @{1.name}#{1.discriminator}(id:{1.id}): "{0.name}" (id {0.id})'.format(
channel, user
)
else: else:
entry = 'Channel deleted: "{0.name}" (id {0.id})'.format(channel) entry = 'Channel deleted: "{0.name}" (id {0.id})'.format(channel)
@ -1552,19 +1573,25 @@ class ActivityLogger(commands.Cog):
if before.name != after.name: if before.name != after.name:
if user: if user:
entries.append('Channel rename by @{2.name}#{2.discriminator}(id:{2.id}): "{0.name}" (id {0.id}) renamed to "{1.name}"') entries.append(
'Channel rename by @{2.name}#{2.discriminator}(id:{2.id}): "{0.name}" (id {0.id}) renamed to "{1.name}"'
)
else: else:
entries.append('Channel rename: "{0.name}" (id {0.id}) renamed to "{1.name}"') entries.append('Channel rename: "{0.name}" (id {0.id}) renamed to "{1.name}"')
if before.topic != after.topic: if before.topic != after.topic:
if user: if user:
entries.append('Channel topic by @{2.name}#{2.discriminator}(id:{2.id}): "{0.name}" (id {0.id}) topic was set to "{1.topic}"') entries.append(
'Channel topic by @{2.name}#{2.discriminator}(id:{2.id}): "{0.name}" (id {0.id}) topic was set to "{1.topic}"'
)
else: else:
entries.append('Channel topic: "{0.name}" (id {0.id}) topic was set to "{1.topic}"') entries.append('Channel topic: "{0.name}" (id {0.id}) topic was set to "{1.topic}"')
if before.position != after.position: if before.position != after.position:
if user: if user:
entries.append('Channel position by @{2.name}#{2.discriminator}(id:{2.id}): "{0.name}" (id {0.id}) moved from {0.position} to {1.position}') entries.append(
'Channel position by @{2.name}#{2.discriminator}(id:{2.id}): "{0.name}" (id {0.id}) moved from {0.position} to {1.position}'
)
else: else:
entries.append('Channel position: "{0.name}" (id {0.id}) moved from {0.position} to {1.position}') entries.append('Channel position: "{0.name}" (id {0.id}) moved from {0.position} to {1.position}')
@ -1603,11 +1630,11 @@ class ActivityLogger(commands.Cog):
msg = "Voice channel leave: {0} (id {0.id})" msg = "Voice channel leave: {0} (id {0.id})"
async with self.config.member(member).stats() as stats: async with self.config.member(member).stats() as stats:
stats["vc_time_sec"] += (time.time() - stats["last_vc_time"]) stats["vc_time_sec"] += time.time() - stats["last_vc_time"]
stats["last_vc_time"] = None stats["last_vc_time"] = None
if after.channel: if after.channel:
msg += ' moving to {1.channel}' msg += " moving to {1.channel}"
await self.log(before.channel, msg.format(member, after)) await self.log(before.channel, msg.format(member, after))
@ -1618,35 +1645,35 @@ class ActivityLogger(commands.Cog):
stats["last_vc_time"] = time.time() stats["last_vc_time"] = time.time()
if before.channel: if before.channel:
msg += ', moved from {1.channel}' msg += ", moved from {1.channel}"
flags = self.get_voice_flags(after) flags = self.get_voice_flags(after)
if flags: if flags:
msg += ', flags: %s' % ','.join(flags) msg += ", flags: %s" % ",".join(flags)
await self.log(after.channel, msg.format(member, before)) await self.log(after.channel, msg.format(member, before))
if before.deaf != after.deaf: if before.deaf != after.deaf:
verb = 'deafen' if after.deaf else 'undeafen' verb = "deafen" if after.deaf else "undeafen"
await self.log(before.channel, 'guild {0}: {1} (id {1.id})'.format(verb, member)) await self.log(before.channel, "guild {0}: {1} (id {1.id})".format(verb, member))
if before.mute != after.mute: if before.mute != after.mute:
verb = 'mute' if after.mute else 'unmute' verb = "mute" if after.mute else "unmute"
await self.log(before.channel, 'guild {0}: {1} (id {1.id})'.format(verb, member)) await self.log(before.channel, "guild {0}: {1} (id {1.id})".format(verb, member))
if before.self_deaf != after.self_deaf: if before.self_deaf != after.self_deaf:
verb = 'deafen' if after.self_deaf else 'undeafen' verb = "deafen" if after.self_deaf else "undeafen"
await self.log(before.channel, 'guild self-{0}: {1} (id {1.id})'.format(verb, member)) await self.log(before.channel, "guild self-{0}: {1} (id {1.id})".format(verb, member))
if before.self_mute != after.self_mute: if before.self_mute != after.self_mute:
verb = 'mute' if after.self_mute else 'unmute' verb = "mute" if after.self_mute else "unmute"
await self.log(before.channel, 'guild self-{0}: {1} (id {1.id})'.format(verb, member)) await self.log(before.channel, "guild self-{0}: {1} (id {1.id})".format(verb, member))
if before.self_stream != after.self_stream: if before.self_stream != after.self_stream:
verb = 'stop-stream' if not after.self_stream else 'start-stream' verb = "stop-stream" if not after.self_stream else "start-stream"
await self.log(before.channel, 'guild self-{0}: {1} (id {1.id})'.format(verb, member)) await self.log(before.channel, "guild self-{0}: {1} (id {1.id})".format(verb, member))
if before.self_video != after.self_video: if before.self_video != after.self_video:
verb = 'start-video' if after.self_video else 'stop-video' verb = "start-video" if after.self_video else "stop-video"
await self.log(before.channel, 'guild self-{0}: {1} (id {1.id})'.format(verb, member)) await self.log(before.channel, "guild self-{0}: {1} (id {1.id})".format(verb, member))

View file

@ -43,7 +43,6 @@ def parse_time(datetimestring: str):
return ret return ret
def parse_time_naive(datetimestring: str): def parse_time_naive(datetimestring: str):
return parser.parse(datetimestring) return parser.parse(datetimestring)

View file

@ -59,9 +59,7 @@ class Admin(commands.Cog):
self.conf.register_global(serverlocked=False) self.conf.register_global(serverlocked=False)
self.conf.register_guild( self.conf.register_guild(
announce_ignore=False, announce_ignore=False, announce_channel=None, selfroles=[], # Integer ID # List of integer ID's
announce_channel=None, # Integer ID
selfroles=[], # List of integer ID's
) )
self.__current_announcer = None self.__current_announcer = None
@ -114,16 +112,12 @@ class Admin(commands.Cog):
await member.add_roles(role) await member.add_roles(role)
except discord.Forbidden: except discord.Forbidden:
if not self.pass_hierarchy_check(ctx, role): if not self.pass_hierarchy_check(ctx, role):
await self.complain( await self.complain(ctx, T_(HIERARCHY_ISSUE), role=role, member=member, verb=_("add"))
ctx, T_(HIERARCHY_ISSUE), role=role, member=member, verb=_("add")
)
else: else:
await self.complain(ctx, T_(GENERIC_FORBIDDEN)) await self.complain(ctx, T_(GENERIC_FORBIDDEN))
else: else:
await ctx.send( await ctx.send(
_("I successfully added {role.name} to {member.display_name}").format( _("I successfully added {role.name} to {member.display_name}").format(role=role, member=member)
role=role, member=member
)
) )
async def _removerole(self, ctx: commands.Context, member: discord.Member, role: discord.Role): async def _removerole(self, ctx: commands.Context, member: discord.Member, role: discord.Role):
@ -134,24 +128,18 @@ class Admin(commands.Cog):
await member.remove_roles(role) await member.remove_roles(role)
except discord.Forbidden: except discord.Forbidden:
if not self.pass_hierarchy_check(ctx, role): if not self.pass_hierarchy_check(ctx, role):
await self.complain( await self.complain(ctx, T_(HIERARCHY_ISSUE), role=role, member=member, verb=_("remove"))
ctx, T_(HIERARCHY_ISSUE), role=role, member=member, verb=_("remove")
)
else: else:
await self.complain(ctx, T_(GENERIC_FORBIDDEN)) await self.complain(ctx, T_(GENERIC_FORBIDDEN))
else: else:
await ctx.send( await ctx.send(
_("I successfully removed {role.name} from {member.display_name}").format( _("I successfully removed {role.name} from {member.display_name}").format(role=role, member=member)
role=role, member=member
)
) )
@commands.command() @commands.command()
@commands.guild_only() @commands.guild_only()
@checks.admin_or_permissions(manage_roles=True) @checks.admin_or_permissions(manage_roles=True)
async def addrole( async def addrole(self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None):
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
):
"""Add a role to a user. """Add a role to a user.
If user is left blank it defaults to the author of the command. If user is left blank it defaults to the author of the command.
@ -162,16 +150,12 @@ class Admin(commands.Cog):
# noinspection PyTypeChecker # noinspection PyTypeChecker
await self._addrole(ctx, user, rolename) await self._addrole(ctx, user, rolename)
else: else:
await self.complain( await self.complain(ctx, T_(USER_HIERARCHY_ISSUE), member=user, role=rolename, verb=_("add"))
ctx, T_(USER_HIERARCHY_ISSUE), member=user, role=rolename, verb=_("add")
)
@commands.command() @commands.command()
@commands.guild_only() @commands.guild_only()
@checks.admin_or_permissions(manage_roles=True) @checks.admin_or_permissions(manage_roles=True)
async def removerole( async def removerole(self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None):
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
):
"""Remove a role from a user. """Remove a role from a user.
If user is left blank it defaults to the author of the command. If user is left blank it defaults to the author of the command.
@ -182,9 +166,7 @@ class Admin(commands.Cog):
# noinspection PyTypeChecker # noinspection PyTypeChecker
await self._removerole(ctx, user, rolename) await self._removerole(ctx, user, rolename)
else: else:
await self.complain( await self.complain(ctx, T_(USER_HIERARCHY_ISSUE), member=user, role=rolename, verb=_("remove"))
ctx, T_(USER_HIERARCHY_ISSUE), member=user, role=rolename, verb=_("remove")
)
@commands.group() @commands.group()
@commands.guild_only() @commands.guild_only()
@ -194,9 +176,7 @@ class Admin(commands.Cog):
pass pass
@editrole.command(name="colour", aliases=["color"]) @editrole.command(name="colour", aliases=["color"])
async def editrole_colour( async def editrole_colour(self, ctx: commands.Context, role: discord.Role, value: discord.Colour):
self, ctx: commands.Context, role: discord.Role, value: discord.Colour
):
"""Edit a role's colour. """Edit a role's colour.
Use double quotes if the role contains spaces. Use double quotes if the role contains spaces.
@ -234,9 +214,7 @@ class Admin(commands.Cog):
""" """
author = ctx.message.author author = ctx.message.author
old_name = role.name old_name = role.name
reason = "{}({}) changed the name of role '{}' to '{}'".format( reason = "{}({}) changed the name of role '{}' to '{}'".format(author.name, author.id, old_name, name)
author.name, author.id, old_name, name
)
if not self.pass_user_hierarchy_check(ctx, role): if not self.pass_user_hierarchy_check(ctx, role):
await self.complain(ctx, T_(ROLE_USER_HIERARCHY_ISSUE), role=role) await self.complain(ctx, T_(ROLE_USER_HIERARCHY_ISSUE), role=role)
@ -285,9 +263,7 @@ class Admin(commands.Cog):
channel = ctx.channel channel = ctx.channel
await self.conf.guild(ctx.guild).announce_channel.set(channel.id) await self.conf.guild(ctx.guild).announce_channel.set(channel.id)
await ctx.send( await ctx.send(_("The announcement channel has been set to {channel.mention}").format(channel=channel))
_("The announcement channel has been set to {channel.mention}").format(channel=channel)
)
@announce.command(name="ignore") @announce.command(name="ignore")
@commands.guild_only() @commands.guild_only()
@ -298,15 +274,9 @@ class Admin(commands.Cog):
await self.conf.guild(ctx.guild).announce_ignore.set(not ignored) await self.conf.guild(ctx.guild).announce_ignore.set(not ignored)
if ignored: # Keeping original logic.... if ignored: # Keeping original logic....
await ctx.send( await ctx.send(_("The server {guild.name} will receive announcements.").format(guild=ctx.guild))
_("The server {guild.name} will receive announcements.").format(guild=ctx.guild)
)
else: else:
await ctx.send( await ctx.send(_("The server {guild.name} will not receive announcements.").format(guild=ctx.guild))
_("The server {guild.name} will not receive announcements.").format(
guild=ctx.guild
)
)
async def _valid_selfroles(self, guild: discord.Guild) -> Tuple[discord.Role]: async def _valid_selfroles(self, guild: discord.Guild) -> Tuple[discord.Role]:
""" """

View file

@ -66,9 +66,7 @@ class Announcer:
try: try:
await channel.send(self.message) await channel.send(self.message)
except discord.Forbidden: except discord.Forbidden:
await bot_owner.send( await bot_owner.send(_("I could not announce to server: {server.id}").format(server=g))
_("I could not announce to server: {server.id}").format(server=g)
)
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
self.active = False self.active = False

View file

@ -1,5 +1,6 @@
from .events import Events from .events import Events
def setup(bot): def setup(bot):
n = Events(bot) n = Events(bot)
bot.add_cog(n) bot.add_cog(n)

View file

@ -11,11 +11,30 @@ import pytz
from tzlocal import get_localzone from tzlocal import get_localzone
basic_colors = [discord.Colour.blue(), discord.Colour.teal(), discord.Colour.dark_teal(), discord.Colour.green(), discord.Colour.dark_green(), discord.Colour.dark_blue(), discord.Colour.purple(), discord.Colour.dark_purple(), discord.Colour.magenta(), discord.Colour.gold(), discord.Colour.orange(), discord.Colour.red(), discord.Colour.dark_red(), discord.Colour.blurple(), discord.Colour.greyple()] basic_colors = [
discord.Colour.blue(),
discord.Colour.teal(),
discord.Colour.dark_teal(),
discord.Colour.green(),
discord.Colour.dark_green(),
discord.Colour.dark_blue(),
discord.Colour.purple(),
discord.Colour.dark_purple(),
discord.Colour.magenta(),
discord.Colour.gold(),
discord.Colour.orange(),
discord.Colour.red(),
discord.Colour.dark_red(),
discord.Colour.blurple(),
discord.Colour.greyple(),
]
class Events(commands.Cog): class Events(commands.Cog):
""" """
Set events that track time since set events Set events that track time since set events
""" """
def __init__(self, bot): def __init__(self, bot):
super().__init__() super().__init__()
self.config = Config.get_conf(self, identifier=6748392754) self.config = Config.get_conf(self, identifier=6748392754)
@ -55,18 +74,33 @@ class Events(commands.Cog):
elapsed_time = datetime.datetime.utcnow() - start_time elapsed_time = datetime.datetime.utcnow() - start_time
embed = discord.Embed(title=event_name, colour=random.choice(basic_colors)) embed = discord.Embed(title=event_name, colour=random.choice(basic_colors))
embed.add_field(name="Event time", value=start_time.replace(tzinfo=pytz.utc).astimezone(self.timezone).strftime("%b %d, %Y, %H:%M")) embed.add_field(
name="Event time",
value=start_time.replace(tzinfo=pytz.utc).astimezone(self.timezone).strftime("%b %d, %Y, %H:%M"),
)
day_msg = "{} day{},".format(elapsed_time.days, "s" if elapsed_time.days > 1 else "") day_msg = "{} day{},".format(elapsed_time.days, "s" if elapsed_time.days > 1 else "")
hour_msg = " {} hour{}".format(int(elapsed_time.seconds / 60 / 60), "s" if int(elapsed_time.seconds / 60 / 60) > 1 else "") hour_msg = " {} hour{}".format(
int(elapsed_time.seconds / 60 / 60), "s" if int(elapsed_time.seconds / 60 / 60) > 1 else ""
)
if elapsed_time.days > 0 or int(elapsed_time.seconds / 60 / 60) > 0: if elapsed_time.days > 0 or int(elapsed_time.seconds / 60 / 60) > 0:
minute_msg = ", and {} minute{}".format(int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60), "s" if int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60) > 1 else "") minute_msg = ", and {} minute{}".format(
int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60),
"s" if int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60) > 1 else "",
)
else: else:
minute_msg = "{} minute{}".format(int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60), "s" if int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60) > 1 else "") minute_msg = "{} minute{}".format(
msg = "{}{}{}".format(day_msg if elapsed_time.days > 0 else "", hour_msg if int(elapsed_time.seconds / 60 / 60) > 0 else "", minute_msg) int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60),
"s" if int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60) > 1 else "",
)
msg = "{}{}{}".format(
day_msg if elapsed_time.days > 0 else "",
hour_msg if int(elapsed_time.seconds / 60 / 60) > 0 else "",
minute_msg,
)
embed.add_field(name="Elapsed time", value=msg) embed.add_field(name="Elapsed time", value=msg)
message = await channel.send(embed=embed) message = await channel.send(embed=embed)
async with self.config.guild(guild).events() as events: async with self.config.guild(guild).events() as events:
new_event = {"start_time" : int(start_time.replace(tzinfo=pytz.utc).timestamp()), "name" : event_name} new_event = {"start_time": int(start_time.replace(tzinfo=pytz.utc).timestamp()), "name": event_name}
events[message.id] = new_event events[message.id] = new_event
await ctx.send("Event added!") await ctx.send("Event added!")
@ -98,13 +132,20 @@ class Events(commands.Cog):
msg += "```" msg += "```"
await ctx.send(msg) await ctx.send(msg)
await ctx.send("Please choose which event you want to delete. (type number in chat)") await ctx.send("Please choose which event you want to delete. (type number in chat)")
def m_check(m): def m_check(m):
try: try:
return m.author.id == ctx.author.id and m.channel.id == ctx.channel.id and int(m.content) <= counter and int(m.content) >= 0 return (
m.author.id == ctx.author.id
and m.channel.id == ctx.channel.id
and int(m.content) <= counter
and int(m.content) >= 0
)
except: except:
return False return False
try: try:
response = await self.bot.wait_for('message', timeout=30, check=m_check) response = await self.bot.wait_for("message", timeout=30, check=m_check)
except: except:
await ctx.send("Timed out, event deletion cancelled.") await ctx.send("Timed out, event deletion cancelled.")
return return
@ -179,14 +220,35 @@ class Events(commands.Cog):
elapsed_time = datetime.datetime.utcnow() - start_time elapsed_time = datetime.datetime.utcnow() - start_time
embed = message.embeds[0] embed = message.embeds[0]
embed.clear_fields() embed.clear_fields()
embed.add_field(name="Event time", value=start_time.replace(tzinfo=pytz.utc).astimezone(self.timezone).strftime("%b %d, %Y, %H:%M")) embed.add_field(
name="Event time",
value=start_time.replace(tzinfo=pytz.utc)
.astimezone(self.timezone)
.strftime("%b %d, %Y, %H:%M"),
)
day_msg = "{} day{},".format(elapsed_time.days, "s" if elapsed_time.days > 1 else "") day_msg = "{} day{},".format(elapsed_time.days, "s" if elapsed_time.days > 1 else "")
hour_msg = " {} hour{}".format(int(elapsed_time.seconds / 60 / 60), "s" if int(elapsed_time.seconds / 60 / 60) > 1 else "") hour_msg = " {} hour{}".format(
int(elapsed_time.seconds / 60 / 60), "s" if int(elapsed_time.seconds / 60 / 60) > 1 else ""
)
if elapsed_time.days > 0 or int(elapsed_time.seconds / 60 / 60) > 0: if elapsed_time.days > 0 or int(elapsed_time.seconds / 60 / 60) > 0:
minute_msg = ", and {} minute{}".format(int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60), "s" if int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60) > 1 else "") minute_msg = ", and {} minute{}".format(
int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60),
"s"
if int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60) > 1
else "",
)
else: else:
minute_msg = "{} minute{}".format(int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60), "s" if int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60) > 1 else "") minute_msg = "{} minute{}".format(
msg = "{}{}{}".format(day_msg if elapsed_time.days > 0 else "", hour_msg if int(elapsed_time.seconds / 60 / 60) > 0 else "", minute_msg) int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60),
"s"
if int(elapsed_time.seconds / 60 - int(elapsed_time.seconds / 60 / 60) * 60) > 1
else "",
)
msg = "{}{}{}".format(
day_msg if elapsed_time.days > 0 else "",
hour_msg if int(elapsed_time.seconds / 60 / 60) > 0 else "",
minute_msg,
)
embed.add_field(name="Elapsed time", value=msg) embed.add_field(name="Elapsed time", value=msg)
await message.edit(embed=embed) await message.edit(embed=embed)
await asyncio.sleep(30) await asyncio.sleep(30)

View file

@ -1,4 +1,5 @@
from .pony import Pony from .pony import Pony
def setup(bot): def setup(bot):
bot.add_cog(Pony()) bot.add_cog(Pony())

View file

@ -7,18 +7,14 @@ import os
import traceback import traceback
import json import json
class Pony(commands.Cog): class Pony(commands.Cog):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.config = Config.get_conf(self, identifier=7384662719) self.config = Config.get_conf(self, identifier=7384662719)
default_global = { default_global = {"maxfilters": 50}
"maxfilters":50 self.default_guild = {"filters": ["-meme", "safe", "-spoiler:*", "-vulgar"], "verbose": False}
}
self.default_guild = {
"filters": ["-meme", "safe", "-spoiler:*", "-vulgar"],
"verbose": False
}
self.config.register_guild(**self.default_guild) self.config.register_guild(**self.default_guild)
self.config.register_global(**default_global) self.config.register_global(**default_global)
@ -40,7 +36,7 @@ class Pony(commands.Cog):
""" """
Gives a random picture of our mascot! Gives a random picture of our mascot!
""" """
await fetch_image(self, ctx, randomize=True, mascot=True, tags=['safe,', 'coe']) await fetch_image(self, ctx, randomize=True, mascot=True, tags=["safe,", "coe"])
@commands.group() @commands.group()
@commands.guild_only() @commands.guild_only()
@ -53,7 +49,7 @@ class Pony(commands.Cog):
pass pass
@ponyfilter.command(name="add") @ponyfilter.command(name="add")
async def _add_ponyfilter(self, ctx, filter_tag : str): async def _add_ponyfilter(self, ctx, filter_tag: str):
"""Adds a tag to the server's pony filter list """Adds a tag to the server's pony filter list
Example: !ponyfilter add safe""" Example: !ponyfilter add safe"""
@ -72,7 +68,7 @@ class Pony(commands.Cog):
await ctx.send("This server has exceeded the maximum filters ({}/{}).".format(len(filters), max_filters)) await ctx.send("This server has exceeded the maximum filters ({}/{}).".format(len(filters), max_filters))
@ponyfilter.command(name="del") @ponyfilter.command(name="del")
async def _del_ponyfilter(self, ctx, filter_tag : str=""): async def _del_ponyfilter(self, ctx, filter_tag: str = ""):
"""Deletes a tag from the server's pony filter list """Deletes a tag from the server's pony filter list
Without arguments, reverts to the default pony filter list Without arguments, reverts to the default pony filter list
@ -100,10 +96,10 @@ class Pony(commands.Cog):
guild = ctx.guild guild = ctx.guild
filters = await self.config.guild(guild).filters() filters = await self.config.guild(guild).filters()
if filters: if filters:
filter_list = '\n'.join(sorted(filters)) filter_list = "\n".join(sorted(filters))
target_guild = "{}'s".format(guild.name) target_guild = "{}'s".format(guild.name)
else: else:
filter_list = '\n'.join(sorted(filters["default"])) filter_list = "\n".join(sorted(filters["default"]))
target_guild = "Default" target_guild = "Default"
await ctx.send("{} pony filter list contains:```\n{}```".format(target_guild, filter_list)) await ctx.send("{} pony filter list contains:```\n{}```".format(target_guild, filter_list))
@ -114,7 +110,7 @@ class Pony(commands.Cog):
pass pass
@ponyset.command(name="verbose") @ponyset.command(name="verbose")
async def _verbose_ponyset(self, ctx, toggle : str="toggle"): async def _verbose_ponyset(self, ctx, toggle: str = "toggle"):
"""Toggles verbose mode""" """Toggles verbose mode"""
guild = ctx.guild guild = ctx.guild
verbose = await self.config.guild(guild).verbose() verbose = await self.config.guild(guild).verbose()
@ -140,7 +136,7 @@ class Pony(commands.Cog):
@ponyset.command(name="maxfilters") @ponyset.command(name="maxfilters")
@checks.is_owner() @checks.is_owner()
async def _maxfilters_ponyset(self, ctx, new_max_filters : int): async def _maxfilters_ponyset(self, ctx, new_max_filters: int):
"""Sets the global tag limit for the filter list. """Sets the global tag limit for the filter list.
Leave blank to get current max filters. Leave blank to get current max filters.
@ -178,7 +174,7 @@ class Pony(commands.Cog):
if guild is None: if guild is None:
continue continue
await self.config.guild(guild).verbose.set(json_guild_verbose['verbose']) await self.config.guild(guild).verbose.set(json_guild_verbose["verbose"])
msg += "**{}**\n".format(guild) msg += "**{}**\n".format(guild)
if len(msg) + 100 > 2000: if len(msg) + 100 > 2000:
await ctx.send(msg) await ctx.send(msg)
@ -194,7 +190,7 @@ class Pony(commands.Cog):
for json_guild_id, json_guild_filters in import_filters.items(): for json_guild_id, json_guild_filters in import_filters.items():
if json_guild_id != "default": if json_guild_id != "default":
guild = bot.get_guild(int(json_guild_id)) # returns None if guild is not found guild = bot.get_guild(int(json_guild_id)) # returns None if guild is not found
if guild is None: if guild is None:
continue continue
@ -204,7 +200,7 @@ class Pony(commands.Cog):
await ctx.send(msg) await ctx.send(msg)
msg = "" msg = ""
else: else:
continue continue
if msg != "": if msg != "":
await ctx.send(msg) await ctx.send(msg)
@ -213,25 +209,25 @@ class Pony(commands.Cog):
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
await ctx.send("Invalid or malformed json files.") await ctx.send("Invalid or malformed json files.")
async def fetch_image(self, ctx, randomize : bool=False, tags : list=[], mascot=False): async def fetch_image(self, ctx, randomize: bool = False, tags: list = [], mascot=False):
guild = ctx.guild guild = ctx.guild
filters = await self.config.guild(guild).filters() filters = await self.config.guild(guild).filters()
verbose = await self.config.guild(guild).verbose() verbose = await self.config.guild(guild).verbose()
#Initialize variables # Initialize variables
artist = "unknown artist" artist = "unknown artist"
artists = "" artists = ""
artistList = [] artistList = []
embedLink = "" embedLink = ""
embedTitle = "" embedTitle = ""
imageId = "" imageId = ""
message = "" message = ""
output = None output = None
rating = "" rating = ""
ratingColor = "FFFFFF" ratingColor = "FFFFFF"
ratingWord = "unknown" ratingWord = "unknown"
search = "https://derpibooru.org/search.json?q=" search = "https://derpibooru.org/search.json?q="
tagSearch = "" tagSearch = ""
# Assign tags to URL # Assign tags to URL
if tags: if tags:
@ -250,12 +246,12 @@ class Pony(commands.Cog):
# Randomize results and apply Derpibooru's "Everything" filter # Randomize results and apply Derpibooru's "Everything" filter
if randomize: if randomize:
if not tags and filters: if not tags and filters:
if filters == []: if filters == []:
search = "https://derpibooru.org/images/random.json?filter_id=56027" search = "https://derpibooru.org/images/random.json?filter_id=56027"
else: else:
search += "&random_image=y&filter_id=56027" search += "&random_image=y&filter_id=56027"
else: else:
search += "&random_image=y&filter_id=56027" search += "&random_image=y&filter_id=56027"
# Inform users about image retrieving # Inform users about image retrieving
message = await ctx.send("Fetching pony image...") message = await ctx.send("Fetching pony image...")
@ -263,7 +259,7 @@ class Pony(commands.Cog):
# Fetch the image or display an error # Fetch the image or display an error
try: try:
async with aiohttp.ClientSession(loop=ctx.bot.loop) as session: async with aiohttp.ClientSession(loop=ctx.bot.loop) as session:
async with session.get(search, headers={'User-Agent': "Booru-Cogs (https://git.io/booru)"}) as r: async with session.get(search, headers={"User-Agent": "Booru-Cogs (https://git.io/booru)"}) as r:
website = await r.json() website = await r.json()
if randomize: if randomize:
if "id" in website: if "id" in website:

View file

@ -1,5 +1,6 @@
from .punish import Punish from .punish import Punish
async def setup(bot): async def setup(bot):
punish = Punish(bot) punish = Punish(bot)
await punish.initialize() await punish.initialize()

View file

@ -2,7 +2,8 @@ class Memoizer:
""" """
General purpose cache for function results. Appends positional args, overlays kwargs. Both must be hashable. General purpose cache for function results. Appends positional args, overlays kwargs. Both must be hashable.
""" """
__slots__ = ['_cache', '_func', '_args', '_kwargs']
__slots__ = ["_cache", "_func", "_args", "_kwargs"]
def __init__(self, func, *args, **kwargs): def __init__(self, func, *args, **kwargs):
self._cache = {} self._cache = {}

View file

@ -16,21 +16,22 @@ import logging
import time import time
import textwrap import textwrap
log = logging.getLogger('red.punish') log = logging.getLogger("red.punish")
__version__ = '3.0.0' __version__ = "3.0.0"
PURGE_MESSAGES = 1 # for cpunish PURGE_MESSAGES = 1 # for cpunish
DEFAULT_ROLE_NAME = 'Punished' DEFAULT_ROLE_NAME = "Punished"
DEFAULT_TEXT_OVERWRITE = discord.PermissionOverwrite(send_messages=False, send_tts_messages=False, add_reactions=False) DEFAULT_TEXT_OVERWRITE = discord.PermissionOverwrite(send_messages=False, send_tts_messages=False, add_reactions=False)
DEFAULT_VOICE_OVERWRITE = discord.PermissionOverwrite(speak=False, connect=False) DEFAULT_VOICE_OVERWRITE = discord.PermissionOverwrite(speak=False, connect=False)
DEFAULT_TIMEOUT_OVERWRITE = discord.PermissionOverwrite(send_messages=True, read_messages=True) DEFAULT_TIMEOUT_OVERWRITE = discord.PermissionOverwrite(send_messages=True, read_messages=True)
QUEUE_TIME_CUTOFF = 30 QUEUE_TIME_CUTOFF = 30
DEFAULT_TIMEOUT = '5m' DEFAULT_TIMEOUT = "5m"
DEFAULT_CASE_MIN_LENGTH = '5m' # only create modlog cases when length is longer than this DEFAULT_CASE_MIN_LENGTH = "5m" # only create modlog cases when length is longer than this
class Punish(commands.Cog): class Punish(commands.Cog):
""" """
@ -38,6 +39,7 @@ class Punish(commands.Cog):
do other things that can be denied using discord permissions. Includes do other things that can be denied using discord permissions. Includes
auto-setup and more. auto-setup and more.
""" """
def __init__(self, bot): def __init__(self, bot):
super().__init__() super().__init__()
@ -53,7 +55,7 @@ class Punish(commands.Cog):
"VOICE_OVERWRITE": overwrite_to_dict(DEFAULT_VOICE_OVERWRITE), "VOICE_OVERWRITE": overwrite_to_dict(DEFAULT_VOICE_OVERWRITE),
"ROLE_ID": None, "ROLE_ID": None,
"NITRO_ID": None, "NITRO_ID": None,
"CHANNEL_ID": None "CHANNEL_ID": None,
} }
self.config.register_guild(**default_guild) self.config.register_guild(**default_guild)
@ -100,7 +102,7 @@ class Punish(commands.Cog):
elif user: elif user:
await self._punish_cmd_common(ctx, user, duration, reason) await self._punish_cmd_common(ctx, user, duration, reason)
@punish.command(name='cstart') @punish.command(name="cstart")
@commands.guild_only() @commands.guild_only()
@checks.mod() @checks.mod()
async def punish_cstart(self, ctx, user: discord.Member, duration: str = None, *, reason: str = None): async def punish_cstart(self, ctx, user: discord.Member, duration: str = None, *, reason: str = None):
@ -121,7 +123,7 @@ class Punish(commands.Cog):
except discord.errors.Forbidden: except discord.errors.Forbidden:
await ctx.send("Punishment set, but I need permissions to manage messages to clean up.") await ctx.send("Punishment set, but I need permissions to manage messages to clean up.")
@punish.command(name='list') @punish.command(name="list")
@commands.guild_only() @commands.guild_only()
@checks.mod() @checks.mod()
async def punish_list(self, ctx): async def punish_list(self, ctx):
@ -135,7 +137,7 @@ class Punish(commands.Cog):
guild = ctx.guild guild = ctx.guild
guild_id = guild.id guild_id = guild.id
now = time.time() now = time.time()
headers = ['Member', 'Remaining', 'Moderator', 'Reason'] headers = ["Member", "Remaining", "Moderator", "Reason"]
punished = await self.config.guild(guild).PUNISHED() punished = await self.config.guild(guild).PUNISHED()
embeds = [] embeds = []
@ -143,13 +145,13 @@ class Punish(commands.Cog):
for i, data in enumerate(punished.items()): for i, data in enumerate(punished.items()):
member_id, data = data member_id, data = data
member_name = getmname(member_id, guild) member_name = getmname(member_id, guild)
moderator = getmname(data['by'], guild) moderator = getmname(data["by"], guild)
reason = data['reason'] reason = data["reason"]
until = data['until'] until = data["until"]
sort = until or float("inf") sort = until or float("inf")
remaining = generate_timespec(until - now, short=True) if until else 'forever' remaining = generate_timespec(until - now, short=True) if until else "forever"
row = [member_name, remaining, moderator, reason or 'No reason set.'] row = [member_name, remaining, moderator, reason or "No reason set."]
embed = discord.Embed(title="Punish List", colour=discord.Colour.from_rgb(255, 0, 0)) embed = discord.Embed(title="Punish List", colour=discord.Colour.from_rgb(255, 0, 0))
for header, row_val in zip(headers, row): for header, row_val in zip(headers, row):
@ -164,7 +166,7 @@ class Punish(commands.Cog):
await menu(ctx, embeds, DEFAULT_CONTROLS) await menu(ctx, embeds, DEFAULT_CONTROLS)
@punish.command(name='clean') @punish.command(name="clean")
@commands.guild_only() @commands.guild_only()
@checks.mod() @checks.mod()
async def punish_clean(self, ctx, clean_pending: bool = False): async def punish_clean(self, ctx, clean_pending: bool = False):
@ -189,14 +191,14 @@ class Punish(commands.Cog):
if not mid.isdigit() or guild.get_member(mid): if not mid.isdigit() or guild.get_member(mid):
continue continue
elif clean_pending or ((mdata['until'] or 0) < now): elif clean_pending or ((mdata["until"] or 0) < now):
del(data[mid]) del data[mid]
count += 1 count += 1
await self.config.guild(guild).PUNISHED.set(data) await self.config.guild(guild).PUNISHED.set(data)
await ctx.send('Cleaned %i absent members from the list.' % count) await ctx.send("Cleaned %i absent members from the list." % count)
@punish.command(name='clean-bans') @punish.command(name="clean-bans")
@commands.guild_only() @commands.guild_only()
@checks.mod() @checks.mod()
async def punish_clean_bans(self, ctx): async def punish_clean_bans(self, ctx):
@ -220,13 +222,13 @@ class Punish(commands.Cog):
continue continue
elif mid in ban_ids: elif mid in ban_ids:
del(data[mid]) del data[mid]
count += 1 count += 1
await self.config.guild(guild).PUNISHED.set(data) await self.config.guild(guild).PUNISHED.set(data)
await ctx.send('Cleaned %i banned users from the list.' % count) await ctx.send("Cleaned %i banned users from the list." % count)
@punish.command(name='warn') @punish.command(name="warn")
@commands.guild_only() @commands.guild_only()
@checks.mod_or_permissions(manage_messages=True) @checks.mod_or_permissions(manage_messages=True)
async def punish_warn(self, ctx, user: discord.Member, *, reason: str = None): async def punish_warn(self, ctx, user: discord.Member, *, reason: str = None):
@ -234,16 +236,15 @@ class Punish(commands.Cog):
Warns a user with boilerplate about the rules Warns a user with boilerplate about the rules
""" """
msg = ['Hey %s, ' % user.mention] msg = ["Hey %s, " % user.mention]
msg.append("you're doing something that might get you muted if you keep " msg.append("you're doing something that might get you muted if you keep " "doing it.")
"doing it.")
if reason: if reason:
msg.append(" Specifically, %s." % reason) msg.append(" Specifically, %s." % reason)
msg.append("Be sure to review the guild rules.") msg.append("Be sure to review the guild rules.")
await ctx.send(' '.join(msg)) await ctx.send(" ".join(msg))
@punish.command(name='end', aliases=['remove']) @punish.command(name="end", aliases=["remove"])
@commands.guild_only() @commands.guild_only()
@checks.mod() @checks.mod()
async def punish_end(self, ctx, user: discord.Member, *, reason: str = None): async def punish_end(self, ctx, user: discord.Member, *, reason: str = None):
@ -260,60 +261,64 @@ class Punish(commands.Cog):
now = time.time() now = time.time()
punished = await self.config.guild(guild).PUNISHED() punished = await self.config.guild(guild).PUNISHED()
data = punished.get(str(user.id), {}) data = punished.get(str(user.id), {})
removed_roles_parsed = resolve_role_list(guild, data.get('removed_roles', [])) removed_roles_parsed = resolve_role_list(guild, data.get("removed_roles", []))
if role and role in user.roles: if role and role in user.roles:
msg = 'Punishment manually ended early by %s.' % ctx.author msg = "Punishment manually ended early by %s." % ctx.author
original_start = data.get('start') original_start = data.get("start")
original_end = data.get('until') original_end = data.get("until")
remaining = original_end and (original_end - now) remaining = original_end and (original_end - now)
if remaining: if remaining:
msg += ' %s was left' % generate_timespec(round(remaining)) msg += " %s was left" % generate_timespec(round(remaining))
if original_start: if original_start:
msg += ' of the original %s.' % generate_timespec(round(original_end - original_start)) msg += " of the original %s." % generate_timespec(round(original_end - original_start))
else: else:
msg += '.' msg += "."
if reason: if reason:
msg += '\n\nReason for ending early: ' + reason msg += "\n\nReason for ending early: " + reason
if data.get('reason'): if data.get("reason"):
msg += '\n\nOriginal reason was: ' + data['reason'] msg += "\n\nOriginal reason was: " + data["reason"]
updated_reason = str(msg) # copy string updated_reason = str(msg) # copy string
if removed_roles_parsed: if removed_roles_parsed:
names_list = format_list(*(r.name for r in removed_roles_parsed)) names_list = format_list(*(r.name for r in removed_roles_parsed))
msg += "\nRestored role(s): {}".format(names_list) msg += "\nRestored role(s): {}".format(names_list)
if not await self._unpunish(user, reason=updated_reason, update=True, moderator=moderator): if not await self._unpunish(user, reason=updated_reason, update=True, moderator=moderator):
msg += '\n\n(failed to send punishment end notification DM)' msg += "\n\n(failed to send punishment end notification DM)"
await ctx.send(msg) await ctx.send(msg)
elif data: # This shouldn't happen, but just in case elif data: # This shouldn't happen, but just in case
now = time.time() now = time.time()
until = data.get('until') until = data.get("until")
remaining = until and generate_timespec(round(until - now)) or 'forever' remaining = until and generate_timespec(round(until - now)) or "forever"
data_fmt = '\n'.join([ data_fmt = "\n".join(
"**Reason:** %s" % (data.get('reason') or 'no reason set'), [
"**Time remaining:** %s" % remaining, "**Reason:** %s" % (data.get("reason") or "no reason set"),
"**Moderator**: %s" % (user.guild.get_member(data.get('by')) or 'Missing ID#%s' % data.get('by')) "**Time remaining:** %s" % remaining,
]) "**Moderator**: %s" % (user.guild.get_member(data.get("by")) or "Missing ID#%s" % data.get("by")),
del(punished[str(user.id)]) ]
)
del punished[str(user.id)]
await self.config.guild(guild).PUNISHED.set(punished) await self.config.guild(guild).PUNISHED.set(punished)
await ctx.send("That user doesn't have the %s role, but they still have a data entry. I removed it, " await ctx.send(
"but in case it's needed, this is what was there:\n\n%s" % (role.name, data_fmt)) "That user doesn't have the %s role, but they still have a data entry. I removed it, "
"but in case it's needed, this is what was there:\n\n%s" % (role.name, data_fmt)
)
elif role: elif role:
await ctx.send("That user doesn't have the %s role." % role.name) await ctx.send("That user doesn't have the %s role." % role.name)
else: else:
await ctx.send("The punish role couldn't be found in this guild.") await ctx.send("The punish role couldn't be found in this guild.")
@punish.command(name='reason') @punish.command(name="reason")
@commands.guild_only() @commands.guild_only()
@checks.mod() @checks.mod()
async def punish_reason(self, ctx, user: discord.Member, *, reason: str = None): async def punish_reason(self, ctx, user: discord.Member, *, reason: str = None):
@ -325,19 +330,21 @@ class Punish(commands.Cog):
data = punished.get(str(user.id), None) data = punished.get(str(user.id), None)
if not data: if not data:
await ctx.send("That user doesn't have an active punishment entry. To update modlog " await ctx.send(
"cases manually, use the `%sreason` command." % ctx.prefix) "That user doesn't have an active punishment entry. To update modlog "
"cases manually, use the `%sreason` command." % ctx.prefix
)
return return
punished[str(user.id)]['reason'] = reason punished[str(user.id)]["reason"] = reason
await self.config.guild(guild).PUNISHED.set(punished) await self.config.guild(guild).PUNISHED.set(punished)
if reason: if reason:
msg = 'Reason updated.' msg = "Reason updated."
else: else:
msg = 'Reason cleared.' msg = "Reason cleared."
caseno = data.get('caseno') caseno = data.get("caseno")
try: try:
case = await modlog.get_case(caseno, guild, self.bot) case = await modlog.get_case(caseno, guild, self.bot)
except: except:
@ -348,12 +355,12 @@ class Punish(commands.Cog):
moderator = ctx.author moderator = ctx.author
try: try:
edits = {'reason': reason} edits = {"reason": reason}
if moderator.id != data.get('by'): if moderator.id != data.get("by"):
edits['amended_by'] = moderator edits["amended_by"] = moderator
edits['modified_at'] = ctx.message.created_at.timestamp() edits["modified_at"] = ctx.message.created_at.timestamp()
await case.edit(edits) await case.edit(edits)
except: except:
@ -504,23 +511,23 @@ class Punish(commands.Cog):
try: try:
# Combine sets to get the baseline (roles they'd have normally) # Combine sets to get the baseline (roles they'd have normally)
member_roles |= set(role_memo.filter(member_data['removed_roles'], skip_nulls=True)) member_roles |= set(role_memo.filter(member_data["removed_roles"], skip_nulls=True))
except KeyError: except KeyError:
pass pass
# update new removed roles with intersection of guild removal list and baseline # update new removed roles with intersection of guild removal list and baseline
new_removed = guild_remove_roles & member_roles new_removed = guild_remove_roles & member_roles
punished[str(member.id)]['removed_roles'] = [r.id for r in new_removed] punished[str(member.id)]["removed_roles"] = [r.id for r in new_removed]
member_roles -= guild_remove_roles member_roles -= guild_remove_roles
# can't restore, so skip (remove from set) # can't restore, so skip (remove from set)
for role in (member_roles - original_roles): for role in member_roles - original_roles:
if role >= highest_role: if role >= highest_role:
member_roles.discard(role) member_roles.discard(role)
# can't remove, so skip (re-add to set) # can't remove, so skip (re-add to set)
for role in (original_roles - member_roles): for role in original_roles - member_roles:
if role >= highest_role: if role >= highest_role:
member_roles.add(role) member_roles.add(role)
@ -541,7 +548,7 @@ class Punish(commands.Cog):
await ctx.send(msg) await ctx.send(msg)
@punishset.command(name='setup') @punishset.command(name="setup")
async def punishset_setup(self, ctx): async def punishset_setup(self, ctx):
""" """
(Re)configures the punish role and channel overrides (Re)configures the punish role and channel overrides
@ -568,28 +575,29 @@ class Punish(commands.Cog):
perms = discord.Permissions.none() perms = discord.Permissions.none()
role = await guild.create_role(name=default_name, permissions=perms, reason="punish cog.") role = await guild.create_role(name=default_name, permissions=perms, reason="punish cog.")
else: else:
msgobj = await ctx.send('%s role exists... ' % role.name) msgobj = await ctx.send("%s role exists... " % role.name)
if role.position != (guild.me.top_role.position - 1): if role.position != (guild.me.top_role.position - 1):
if role < guild.me.top_role: if role < guild.me.top_role:
await msgobj.edit(content=msgobj.content + 'moving role to higher position... ') await msgobj.edit(content=msgobj.content + "moving role to higher position... ")
await role.edit(position=guild.me.top_role.position - 1) await role.edit(position=guild.me.top_role.position - 1)
else: else:
await msgobj.edit(content=msgobj.content + 'role is too high to manage.' await msgobj.edit(
' Please move it to below my highest role.') content=msgobj.content + "role is too high to manage." " Please move it to below my highest role."
)
return return
await msgobj.edit(content=msgobj.content + '(re)configuring channels... ') await msgobj.edit(content=msgobj.content + "(re)configuring channels... ")
for channel in guild.channels: for channel in guild.channels:
await self.setup_channel(channel, role) await self.setup_channel(channel, role)
await msgobj.edit(content=msgobj.content + 'done.') await msgobj.edit(content=msgobj.content + "done.")
if role and role.id != role_id: if role and role.id != role_id:
await self.config.guild(guild).ROLE_ID.set(role.id) await self.config.guild(guild).ROLE_ID.set(role.id)
@punishset.command(name='channel') @punishset.command(name="channel")
async def punishset_channel(self, ctx, channel: discord.TextChannel = None): async def punishset_channel(self, ctx, channel: discord.TextChannel = None):
""" """
Sets or shows the punishment "timeout" channel. Sets or shows the punishment "timeout" channel.
@ -613,13 +621,16 @@ class Punish(commands.Cog):
await ctx.send("The timeout channel is currently %s." % current.mention) await ctx.send("The timeout channel is currently %s." % current.mention)
else: else:
if current == channel: if current == channel:
await ctx.send("The timeout channel is already %s. If you need to repair its permissions, use `%spunishset setup`." % (current.mention, ctx.prefix)) await ctx.send(
"The timeout channel is already %s. If you need to repair its permissions, use `%spunishset setup`."
% (current.mention, ctx.prefix)
)
return return
await self.config.guild(guild).CHANNEL_ID.set(channel.id) await self.config.guild(guild).CHANNEL_ID.set(channel.id)
role = await self.get_role(guild, create=True) role = await self.get_role(guild, create=True)
update_msg = '{} to the %s role' % role update_msg = "{} to the %s role" % role
grants = [] grants = []
denies = [] denies = []
perms = permissions_for_roles(channel, role) perms = permissions_for_roles(channel, role)
@ -631,7 +642,7 @@ class Punish(commands.Cog):
if getattr(perms, perm) != value: if getattr(perms, perm) != value:
setattr(overwrite, perm, value) setattr(overwrite, perm, value)
name = perm.replace('_', ' ').title().replace("Tts", "TTS") name = perm.replace("_", " ").title().replace("Tts", "TTS")
if value: if value:
grants.append(name) grants.append(name)
@ -640,8 +651,8 @@ class Punish(commands.Cog):
# Any changes made? Apply them. # Any changes made? Apply them.
if grants or denies: if grants or denies:
grants = grants and ('grant ' + format_list(*grants)) grants = grants and ("grant " + format_list(*grants))
denies = denies and ('deny ' + format_list(*denies)) denies = denies and ("deny " + format_list(*denies))
to_join = [x for x in (grants, denies) if x] to_join = [x for x in (grants, denies) if x]
update_msg = update_msg.format(format_list(*to_join)) update_msg = update_msg.format(format_list(*to_join))
@ -655,14 +666,14 @@ class Punish(commands.Cog):
await self.setup_channel(current, role) await self.setup_channel(current, role)
if channel.permissions_for(guild.me).manage_roles: if channel.permissions_for(guild.me).manage_roles:
await ctx.send(info('Updating permissions in %s to %s...' % (channel.mention, update_msg))) await ctx.send(info("Updating permissions in %s to %s..." % (channel.mention, update_msg)))
await channel.set_permissions(role, overwrite=overwrite) await channel.set_permissions(role, overwrite=overwrite)
else: else:
await ctx.send(error("I don't have permissions to %s." % update_msg)) await ctx.send(error("I don't have permissions to %s." % update_msg))
await ctx.send("Timeout channel set to %s." % channel.mention) await ctx.send("Timeout channel set to %s." % channel.mention)
@punishset.command(name='clear-channel') @punishset.command(name="clear-channel")
async def punishset_clear_channel(self, ctx): async def punishset_clear_channel(self, ctx):
""" """
Clears the timeout channel and resets its permissions Clears the timeout channel and resets its permissions
@ -678,7 +689,7 @@ class Punish(commands.Cog):
if current.permissions_for(guild.me).manage_roles: if current.permissions_for(guild.me).manage_roles:
role = await self.get_role(guild, quiet=True) role = await self.get_role(guild, quiet=True)
await self.setup_channel(current, role) await self.setup_channel(current, role)
msg = ' and its permissions reset' msg = " and its permissions reset"
else: else:
msg = ", but I don't have permissions to reset its permissions." msg = ", but I don't have permissions to reset its permissions."
@ -686,7 +697,7 @@ class Punish(commands.Cog):
else: else:
await ctx.send("No timeout channel has been set yet.") await ctx.send("No timeout channel has been set yet.")
@punishset.command(name='case-min') @punishset.command(name="case-min")
async def punishset_case_min(self, ctx, *, timespec: str = None): async def punishset_case_min(self, ctx, *, timespec: str = None):
""" """
Set/disable or display the minimum punishment case duration Set/disable or display the minimum punishment case duration
@ -699,11 +710,11 @@ class Punish(commands.Cog):
if not timespec: if not timespec:
if current: if current:
await ctx.send('Punishments longer than %s will create cases.' % generate_timespec(current)) await ctx.send("Punishments longer than %s will create cases." % generate_timespec(current))
else: else:
await ctx.send("Punishment case creation is disabled.") await ctx.send("Punishment case creation is disabled.")
else: else:
if timespec.strip('\'"').lower() == 'disable': if timespec.strip("'\"").lower() == "disable":
value = None value = None
else: else:
try: try:
@ -714,9 +725,9 @@ class Punish(commands.Cog):
await self.config.guild(guild).CASE_MIN_LENGTH.set(value) await self.config.guild(guild).CASE_MIN_LENGTH.set(value)
await ctx.send('Punishments longer than %s will create cases.' % generate_timespec(value)) await ctx.send("Punishments longer than %s will create cases." % generate_timespec(value))
@punishset.command(name='overrides') @punishset.command(name="overrides")
async def punishset_overrides(self, ctx, *, channel_id: int = None): async def punishset_overrides(self, ctx, *, channel_id: int = None):
""" """
Copy or display the punish role overrides Copy or display the punish role overrides
@ -751,52 +762,58 @@ class Punish(commands.Cog):
confirm_msg = "Are you sure you want to copy overrides from this channel?" confirm_msg = "Are you sure you want to copy overrides from this channel?"
if channel.type is discord.ChannelType.text: if channel.type is discord.ChannelType.text:
key = 'text' key = "text"
elif channel.type is discord.ChannelType.voice: elif channel.type is discord.ChannelType.voice:
key = 'voice' key = "voice"
else: else:
await ctx.send(error("Unknown channel type!")) await ctx.send(error("Unknown channel type!"))
return return
if confirm_msg: if confirm_msg:
await ctx.send(warning(confirm_msg + '(reply `yes` within 30s to confirm)')) await ctx.send(warning(confirm_msg + "(reply `yes` within 30s to confirm)"))
def check(m): def check(m):
return m.author == ctx.author and m.channel == ctx.channel return m.author == ctx.author and m.channel == ctx.channel
try: try:
reply = await self.bot.wait_for('message', check=check, timeout=30.0) reply = await self.bot.wait_for("message", check=check, timeout=30.0)
if reply.content.strip(' `"\'').lower() != 'yes': if reply.content.strip(" `\"'").lower() != "yes":
await ctx.send('Commmand cancelled.') await ctx.send("Commmand cancelled.")
return return
except asyncio.TimeoutError: except asyncio.TimeoutError:
await ctx.send('Timed out waiting for a response.') await ctx.send("Timed out waiting for a response.")
return return
if key == 'text': if key == "text":
await self.config.guild(guild).TEXT_OVERWRITE.set(overwrite_to_dict(overwrite)) await self.config.guild(guild).TEXT_OVERWRITE.set(overwrite_to_dict(overwrite))
else: else:
await self.config.guild(guild).VOICE_OVERWRITE.set(overwrite_to_dict(overwrite)) await self.config.guild(guild).VOICE_OVERWRITE.set(overwrite_to_dict(overwrite))
await ctx.send("{} channel overrides set to:\n".format(key.title()) + await ctx.send(
format_permissions(overwrite) + "{} channel overrides set to:\n".format(key.title())
"\n\nRun `%spunishset setup` to apply them to all channels." % ctx.prefix) + format_permissions(overwrite)
+ "\n\nRun `%spunishset setup` to apply them to all channels." % ctx.prefix
)
else: else:
msg = [] msg = []
for key in ('text', 'voice'): for key in ("text", "voice"):
if key == 'text': if key == "text":
data = await self.config.guild(guild).TEXT_OVERWRITE() data = await self.config.guild(guild).TEXT_OVERWRITE()
else: else:
data = await self.config.guild(guild).VOICE_OVERWRITE() data = await self.config.guild(guild).VOICE_OVERWRITE()
title = '%s permission overrides:' % key.title() title = "%s permission overrides:" % key.title()
if data == overwrite_to_dict(DEFAULT_TEXT_OVERWRITE) or data == overwrite_to_dict(DEFAULT_VOICE_OVERWRITE): if data == overwrite_to_dict(DEFAULT_TEXT_OVERWRITE) or data == overwrite_to_dict(
title = title[:-1] + ' (defaults):' DEFAULT_VOICE_OVERWRITE
):
title = title[:-1] + " (defaults):"
msg.append(bold(title) + '\n' + format_permissions(overwrite_from_dict(data))) msg.append(bold(title) + "\n" + format_permissions(overwrite_from_dict(data)))
await ctx.send('\n\n'.join(msg)) await ctx.send("\n\n".join(msg))
@punishset.command(name='reset-overrides') @punishset.command(name="reset-overrides")
async def punishset_reset_overrides(self, ctx, channel_type: str = 'both'): async def punishset_reset_overrides(self, ctx, channel_type: str = "both"):
""" """
Resets the punish role overrides for text, voice or both (default) Resets the punish role overrides for text, voice or both (default)
@ -804,21 +821,21 @@ class Punish(commands.Cog):
for newly created channels. for newly created channels.
""" """
channel_type = channel_type.strip('`"\' ').lower() channel_type = channel_type.strip("`\"' ").lower()
msg = [] msg = []
for key in ('text', 'voice'): for key in ("text", "voice"):
if channel_type not in ['both', key]: if channel_type not in ["both", key]:
continue continue
title = '%s permission overrides reset to:' % key.title() title = "%s permission overrides reset to:" % key.title()
if key == 'text': if key == "text":
await self.config.guild(guild).TEXT_OVERWRITE.set(overwrite_to_dict(DEFAULT_TEXT_OVERWRITE)) await self.config.guild(guild).TEXT_OVERWRITE.set(overwrite_to_dict(DEFAULT_TEXT_OVERWRITE))
msg.append(bold(title) + '\n' + format_permissions(overwrite_to_dict(DEFAULT_TEXT_OVERWRITE))) msg.append(bold(title) + "\n" + format_permissions(overwrite_to_dict(DEFAULT_TEXT_OVERWRITE)))
else: else:
await self.config.guild(guild).VOICE_OVERWRITE.set(overwrite_to_dict(DEFAULT_VOICE_OVERWRITE)) await self.config.guild(guild).VOICE_OVERWRITE.set(overwrite_to_dict(DEFAULT_VOICE_OVERWRITE))
msg.append(bold(title) + '\n' + format_permissions(overwrite_to_dict(DEFAULT_VOICE_OVERWRITE))) msg.append(bold(title) + "\n" + format_permissions(overwrite_to_dict(DEFAULT_VOICE_OVERWRITE)))
if not msg: if not msg:
await ctx.send("Invalid channel type. Use `text`, `voice`, or `both` (the default, if not specified)") await ctx.send("Invalid channel type. Use `text`, `voice`, or `both` (the default, if not specified)")
@ -826,7 +843,7 @@ class Punish(commands.Cog):
msg.append("Run `%spunishset setup` to apply them to all channels." % ctx.prefix) msg.append("Run `%spunishset setup` to apply them to all channels." % ctx.prefix)
await ctx.send('\n\n'.join(msg)) await ctx.send("\n\n".join(msg))
async def get_role(self, guild, quiet=False, create=False): async def get_role(self, guild, quiet=False, create=False):
role_id = await self.config.guild(guild).ROLE_ID() role_id = await self.config.guild(guild).ROLE_ID()
@ -848,19 +865,19 @@ class Punish(commands.Cog):
if not quiet: if not quiet:
msgobj = await ctx.send(msg) msgobj = await ctx.send(msg)
log.debug('Creating punish role in %s' % guild.name) log.debug("Creating punish role in %s" % guild.name)
perms = discord.Permissions.none() perms = discord.Permissions.none()
role = await guild.create_role(name=DEFAULT_ROLE_NAME, permissions=perms, reason="punish cog.") role = await guild.create_role(name=DEFAULT_ROLE_NAME, permissions=perms, reason="punish cog.")
await role.edit(position=guild.me.top_role.position - 1) await role.edit(position=guild.me.top_role.position - 1)
if not quiet: if not quiet:
await msgobj.edit(content=msgobj.content + '\nconfiguring channels... ') await msgobj.edit(content=msgobj.content + "\nconfiguring channels... ")
for channel in guild.channels: for channel in guild.channels:
await self.setup_channel(channel, role) await self.setup_channel(channel, role)
if not quiet: if not quiet:
await msgobj.edit(content=msgobj.content + '\ndone.') await msgobj.edit(content=msgobj.content + "\ndone.")
if role and role.id != role_id: if role and role.id != role_id:
await self.config.guild(guild).ROLE_ID.set(role.id) await self.config.guild(guild).ROLE_ID.set(role.id)
@ -906,23 +923,23 @@ class Punish(commands.Cog):
for member_id, data in punished.items(): for member_id, data in punished.items():
until = data['until'] until = data["until"]
member = guild.get_member(member_id) member = guild.get_member(member_id)
if until and (until - time.time()) < 0: if until and (until - time.time()) < 0:
if member: if member:
reason = 'Punishment removal overdue, maybe the bot was offline. ' reason = "Punishment removal overdue, maybe the bot was offline. "
if data['reason']: if data["reason"]:
reason += data['reason'] reason += data["reason"]
await self._unpunish(member, reason=reason) await self._unpunish(member, reason=reason)
else: # member disappeared else: # member disappeared
del(punished[str(member_id)]) del punished[str(member_id)]
elif member: elif member:
# re-check roles # re-check roles
user_roles = set(member.roles) user_roles = set(member.roles)
removed_roles = set(role_memo.filter(data.get('removed_roles', ()), skip_nulls=True)) removed_roles = set(role_memo.filter(data.get("removed_roles", ()), skip_nulls=True))
removed_roles = user_roles & {r for r in removed_roles if r < me.top_role} removed_roles = user_roles & {r for r in removed_roles if r < me.top_role}
user_roles -= removed_roles user_roles -= removed_roles
@ -954,7 +971,7 @@ class Punish(commands.Cog):
except Exception: except Exception:
pass pass
log.debug('queue manager dying') log.debug("queue manager dying")
while not self.queue.empty(): while not self.queue.empty():
self.queue.get_nowait() self.queue.get_nowait()
@ -985,7 +1002,7 @@ class Punish(commands.Cog):
return removed is not None return removed is not None
async def put_queue_event(self, run_at : float, *args): async def put_queue_event(self, run_at: float, *args):
diff = run_at - time.time() diff = run_at - time.time()
if args in self.enqueued: if args in self.enqueued:
@ -1038,7 +1055,7 @@ class Punish(commands.Cog):
remove_role_set = set(resolve_role_list(guild, remove_role_set)) remove_role_set = set(resolve_role_list(guild, remove_role_set))
punished = await self.config.guild(guild).PUNISHED() punished = await self.config.guild(guild).PUNISHED()
current = punished.get(str(member.id), {}) current = punished.get(str(member.id), {})
reason = reason or current.get('reason') # don't clear if not given reason = reason or current.get("reason") # don't clear if not given
hierarchy_allowed = ctx.author.top_role > member.top_role hierarchy_allowed = ctx.author.top_role > member.top_role
case_min_length = await self.config.guild(guild).CASE_MIN_LENGTH() case_min_length = await self.config.guild(guild).CASE_MIN_LENGTH()
nitro_role = await self.config.guild(guild).NITRO_ID() nitro_role = await self.config.guild(guild).NITRO_ID()
@ -1051,7 +1068,7 @@ class Punish(commands.Cog):
await ctx.send("You can't punish the bot.") await ctx.send("You can't punish the bot.")
return return
if duration and duration.lower() in ['forever', 'inf', 'infinite']: if duration and duration.lower() in ["forever", "inf", "infinite"]:
duration = None duration = None
else: else:
if not duration: if not duration:
@ -1071,7 +1088,7 @@ class Punish(commands.Cog):
if role is None: if role is None:
return return
elif role >= guild.me.top_role: elif role >= guild.me.top_role:
await ctx.send('The %s role is too high for me to manage.' % role) await ctx.send("The %s role is too high for me to manage." % role)
return return
# Call time() after getting the role due to potential creation delay # Call time() after getting the role due to potential creation delay
@ -1085,22 +1102,26 @@ class Punish(commands.Cog):
try: try:
if current: if current:
case_number = current.get('caseno') case_number = current.get("caseno")
try: try:
case = await modlog.get_case(case_number, guild, self.bot) case = await modlog.get_case(case_number, guild, self.bot)
except: # shouldn't happen except: # shouldn't happen
await ctx.send(warning("Error, modlog case not found, but user is punished with case.\nTry unpunishing and punishing again.")) await ctx.send(
warning(
"Error, modlog case not found, but user is punished with case.\nTry unpunishing and punishing again."
)
)
return return
moderator = ctx.author moderator = ctx.author
try: try:
edits = {'reason': reason} edits = {"reason": reason}
if moderator.id != current.get('by'): if moderator.id != current.get("by"):
edits['amended_by'] = moderator edits["amended_by"] = moderator
edits['modified_at'] = ctx.message.created_at.timestamp() edits["modified_at"] = ctx.message.created_at.timestamp()
await case.edit(edits) await case.edit(edits)
except Exception as e: except Exception as e:
@ -1110,7 +1131,16 @@ class Punish(commands.Cog):
updating_case = True updating_case = True
else: else:
case = await modlog.create_case(self.bot, guild, now_date, "Timed Mute", member, moderator=ctx.author, reason=reason, until=mod_until) case = await modlog.create_case(
self.bot,
guild,
now_date,
"Timed Mute",
member,
moderator=ctx.author,
reason=reason,
until=mod_until,
)
case_number = case.case_number case_number = case.case_number
except Exception as e: except Exception as e:
@ -1118,18 +1148,18 @@ class Punish(commands.Cog):
else: else:
case_number = None case_number = None
subject = 'the %s role' % role.name subject = "the %s role" % role.name
if str(member.id) in punished: if str(member.id) in punished:
if role in member.roles: if role in member.roles:
msg = '{0} already had the {1.name} role; resetting their timer.' msg = "{0} already had the {1.name} role; resetting their timer."
else: else:
msg = '{0} is missing the {1.name} role for some reason. I added it and reset their timer.' msg = "{0} is missing the {1.name} role for some reason. I added it and reset their timer."
elif role in member.roles: elif role in member.roles:
msg = '{0} already had the {1.name} role, but had no timer; setting it now.' msg = "{0} already had the {1.name} role, but had no timer; setting it now."
else: else:
msg = 'Applied the {1.name} role to {0}.' msg = "Applied the {1.name} role to {0}."
subject = 'it' subject = "it"
msg = msg.format(member, role) msg = msg.format(member, role)
@ -1137,24 +1167,24 @@ class Punish(commands.Cog):
timespec = generate_timespec(duration) timespec = generate_timespec(duration)
if using_default: if using_default:
timespec += ' (the default)' timespec += " (the default)"
msg += ' I will remove %s in %s.' % (subject, timespec) msg += " I will remove %s in %s." % (subject, timespec)
if case_error: if case_error:
if isinstance(case_error, CaseMessageNotFound): if isinstance(case_error, CaseMessageNotFound):
case_error = 'the case message could not be found' case_error = "the case message could not be found"
elif isinstance(case_error, NoModLogAccess): elif isinstance(case_error, NoModLogAccess):
case_error = 'I do not have access to the modlog channel' case_error = "I do not have access to the modlog channel"
else: else:
case_error = None case_error = None
if case_error: if case_error:
verb = 'updating' if updating_case else 'creating' verb = "updating" if updating_case else "creating"
msg += '\n\n' + warning('There was an error %s the modlog case: %s.' % (verb, case_error)) msg += "\n\n" + warning("There was an error %s the modlog case: %s." % (verb, case_error))
elif case_number: elif case_number:
verb = 'updated' if updating_case else 'created' verb = "updated" if updating_case else "created"
msg += ' I also %s case #%i in the modlog.' % (verb, case_number) msg += " I also %s case #%i in the modlog." % (verb, case_number)
voice_overwrite = await self.config.guild(guild).VOICE_OVERWRITE() voice_overwrite = await self.config.guild(guild).VOICE_OVERWRITE()
@ -1175,12 +1205,12 @@ class Punish(commands.Cog):
# build lists of roles that *should* be removed and ones that *can* be # build lists of roles that *should* be removed and ones that *can* be
removed_roles = user_roles & remove_role_set removed_roles = user_roles & remove_role_set
too_high_to_remove = {r for r in removed_roles if r >= guild.me.top_role} too_high_to_remove = {r for r in removed_roles if r >= guild.me.top_role}
user_roles -= (removed_roles - too_high_to_remove) user_roles -= removed_roles - too_high_to_remove
user_roles.add(role) # add punish role to the set user_roles.add(role) # add punish role to the set
await member.edit(roles=user_roles, reason=f"punish {member}") await member.edit(roles=user_roles, reason=f"punish {member}")
else: else:
removed_roles = set(resolve_role_list(guild, current.get('removed_roles', []))) removed_roles = set(resolve_role_list(guild, current.get("removed_roles", [])))
too_high_to_remove = {r for r in removed_roles if r >= guild.me.top_role} too_high_to_remove = {r for r in removed_roles if r >= guild.me.top_role}
if removed_roles: if removed_roles:
@ -1190,8 +1220,10 @@ class Punish(commands.Cog):
if too_high_to_remove: if too_high_to_remove:
fmt_list = format_list(*(r.name for r in removed_roles)) fmt_list = format_list(*(r.name for r in removed_roles))
msg += "\n" + warning("These roles were too high to remove (fix hierarchy, then run " msg += "\n" + warning(
"`{}punishset sync-roles`): {}".format(ctx.prefix, fmt_list)) "These roles were too high to remove (fix hierarchy, then run "
"`{}punishset sync-roles`): {}".format(ctx.prefix, fmt_list)
)
if member.voice: if member.voice:
muted = member.voice.mute muted = member.voice.mute
else: else:
@ -1199,13 +1231,13 @@ class Punish(commands.Cog):
async with self.config.guild(guild).PUNISHED() as punished: async with self.config.guild(guild).PUNISHED() as punished:
punished[str(member.id)] = { punished[str(member.id)] = {
'start' : current.get('start') or now, # don't override start time if updating "start": current.get("start") or now, # don't override start time if updating
'until' : until, "until": until,
'by' : current.get('by') or ctx.author.id, # don't override original moderator "by": current.get("by") or ctx.author.id, # don't override original moderator
'reason' : reason, "reason": reason,
'unmute' : overwrite_denies_speak and not muted, "unmute": overwrite_denies_speak and not muted,
'caseno' : case_number, "caseno": case_number,
'removed_roles' : [r.id for r in removed_roles] "removed_roles": [r.id for r in removed_roles],
} }
if member.voice and overwrite_denies_speak: if member.voice and overwrite_denies_speak:
@ -1256,8 +1288,8 @@ class Punish(commands.Cog):
if role: if role:
data = await self.config.guild(guild).PUNISHED() data = await self.config.guild(guild).PUNISHED()
member_data = data.get(str(member.id), {}) member_data = data.get(str(member.id), {})
caseno = member_data.get('caseno') caseno = member_data.get("caseno")
removed_roles = set(resolve_role_list(guild, member_data.get('removed_roles', []))) removed_roles = set(resolve_role_list(guild, member_data.get("removed_roles", [])))
# Has to be done first to prevent triggering listeners # Has to be done first to prevent triggering listeners
await self._unpunish_data(member) await self._unpunish_data(member)
@ -1280,20 +1312,20 @@ class Punish(commands.Cog):
await member.edit(roles=user_roles, reason="punish end") await member.edit(roles=user_roles, reason="punish end")
if update and caseno: if update and caseno:
until = member_data.get('until') or False until = member_data.get("until") or False
# fallback gracefully # fallback gracefully
moderator = moderator or guild.get_member(member_data.get('by')) or guild.me moderator = moderator or guild.get_member(member_data.get("by")) or guild.me
if until: if until:
until = datetime.utcfromtimestamp(until).timestamp() until = datetime.utcfromtimestamp(until).timestamp()
edits = {'reason': reason} edits = {"reason": reason}
if moderator.id != data.get('by'): if moderator.id != data.get("by"):
edits['amended_by'] = moderator edits["amended_by"] = moderator
edits['modified_at'] = time.time() edits["modified_at"] = time.time()
edits['until'] = until edits["until"] = until
try: try:
case = await modlog.get_case(caseno, guild, self.bot) case = await modlog.get_case(caseno, guild, self.bot)
@ -1301,7 +1333,7 @@ class Punish(commands.Cog):
except Exception: except Exception:
pass pass
if member_data.get('unmute', False): if member_data.get("unmute", False):
if member.voice: if member.voice:
if member.voice.channel: if member.voice.channel:
await member.edit(mute=False) await member.edit(mute=False)
@ -1313,7 +1345,7 @@ class Punish(commands.Cog):
if quiet: if quiet:
return True return True
msg = 'Your punishment in %s has ended.' % member.guild.name msg = "Your punishment in %s has ended." % member.guild.name
if reason: if reason:
msg += "\nReason: %s" % reason msg += "\nReason: %s" % reason
@ -1323,8 +1355,9 @@ class Punish(commands.Cog):
if too_high_to_restore: if too_high_to_restore:
fmt_list = format_list(*(r.name for r in too_high_to_restore)) fmt_list = format_list(*(r.name for r in too_high_to_restore))
msg += "\n" + warning("These roles were too high for me to restore: {}. " msg += "\n" + warning(
"Ask a mod for help.".format(fmt_list)) "These roles were too high for me to restore: {}. " "Ask a mod for help.".format(fmt_list)
)
try: try:
await member.send(msg) await member.send(msg)
@ -1338,7 +1371,7 @@ class Punish(commands.Cog):
async with self.config.guild(guild).PUNISHED() as punished: async with self.config.guild(guild).PUNISHED() as punished:
if str(member.id) in punished: if str(member.id) in punished:
del(punished[str(member.id)]) del punished[str(member.id)]
# Listeners # Listeners
@commands.Cog.listener() @commands.Cog.listener()
@ -1365,14 +1398,14 @@ class Punish(commands.Cog):
new_roles = {role.id: role for role in after.roles} new_roles = {role.id: role for role in after.roles}
if role in before.roles and role.id not in new_roles: if role in before.roles and role.id not in new_roles:
msg = 'Punishment manually ended early by a moderator/admin.' msg = "Punishment manually ended early by a moderator/admin."
if member_data['reason']: if member_data["reason"]:
msg += '\nReason was: ' + member_data['reason'] msg += "\nReason was: " + member_data["reason"]
await self._unpunish(after, reason=msg, update=True) await self._unpunish(after, reason=msg, update=True)
else: else:
to_remove = {new_roles.get(role_id) for role_id in member_data.get('removed_roles', [])} to_remove = {new_roles.get(role_id) for role_id in member_data.get("removed_roles", [])}
to_remove = [r for r in to_remove if r and r < after.guild.me.top_role] to_remove = [r for r in to_remove if r and r < after.guild.me.top_role]
if to_remove: if to_remove:
@ -1393,7 +1426,7 @@ class Punish(commands.Cog):
member = self.bot.get_guild(guild.id).get_member(member.id) member = self.bot.get_guild(guild.id).get_member(member.id)
role = await self.get_role(member.guild, quiet=True) role = await self.get_role(member.guild, quiet=True)
until = data['until'] until = data["until"]
duration = until - time.time() duration = until - time.time()
if role and duration > 0: if role and duration > 0:
@ -1433,7 +1466,7 @@ class Punish(commands.Cog):
msg = "Punishment ended early due to ban." msg = "Punishment ended early due to ban."
if member_data.get('reason'): if member_data.get("reason"):
msg += '\n\nOriginal reason was: ' + member_data['reason'] msg += "\n\nOriginal reason was: " + member_data["reason"]
await self._unpunish(member, reason=msg, apply_roles=False, update=True, quiet=True) await self._unpunish(member, reason=msg, apply_roles=False, update=True, quiet=True)

View file

@ -2,13 +2,14 @@ import re
import discord import discord
UNIT_TABLE = ( UNIT_TABLE = (
(('weeks', 'wks', 'w'), 60 * 60 * 24 * 7), (("weeks", "wks", "w"), 60 * 60 * 24 * 7),
(('days', 'dys', 'd'), 60 * 60 * 24), (("days", "dys", "d"), 60 * 60 * 24),
(('hours', 'hrs', 'h'), 60 * 60), (("hours", "hrs", "h"), 60 * 60),
(('minutes', 'mins', 'm'), 60), (("minutes", "mins", "m"), 60),
(('seconds', 'secs', 's'), 1), (("seconds", "secs", "s"), 1),
) )
class BadTimeExpr(Exception): class BadTimeExpr(Exception):
pass pass
@ -23,24 +24,23 @@ def _find_unit(unit):
def parse_time(time): def parse_time(time):
time = time.lower() time = time.lower()
if not time.isdigit(): if not time.isdigit():
time = re.split(r'\s*([\d.]+\s*[^\d\s,;]*)(?:[,;\s]|and)*', time) time = re.split(r"\s*([\d.]+\s*[^\d\s,;]*)(?:[,;\s]|and)*", time)
time = sum(map(_timespec_sec, filter(None, time))) time = sum(map(_timespec_sec, filter(None, time)))
return int(time) return int(time)
def _timespec_sec(expr): def _timespec_sec(expr):
atoms = re.split(r'([\d.]+)\s*([^\d\s]*)', expr) atoms = re.split(r"([\d.]+)\s*([^\d\s]*)", expr)
atoms = list(filter(None, atoms)) atoms = list(filter(None, atoms))
if len(atoms) > 2: # This shouldn't ever happen if len(atoms) > 2: # This shouldn't ever happen
raise BadTimeExpr("invalid expression: '%s'" % expr) raise BadTimeExpr("invalid expression: '%s'" % expr)
elif len(atoms) == 2: elif len(atoms) == 2:
names, length = _find_unit(atoms[1]) names, length = _find_unit(atoms[1])
if atoms[0].count('.') > 1 or \ if atoms[0].count(".") > 1 or not atoms[0].replace(".", "").isdigit():
not atoms[0].replace('.', '').isdigit():
raise BadTimeExpr("Not a number: '%s'" % atoms[0]) raise BadTimeExpr("Not a number: '%s'" % atoms[0])
else: else:
names, length = _find_unit('seconds') names, length = _find_unit("seconds")
try: try:
return float(atoms[0]) * length return float(atoms[0]) * length
@ -59,48 +59,46 @@ def generate_timespec(sec: int, short=False, micro=False) -> str:
if n: if n:
if micro: if micro:
s = '%d%s' % (n, names[2]) s = "%d%s" % (n, names[2])
elif short: elif short:
s = '%d%s' % (n, names[1]) s = "%d%s" % (n, names[1])
else: else:
s = '%d %s' % (n, names[0]) s = "%d %s" % (n, names[0])
if n <= 1 and not (micro and names[2] == 's'): if n <= 1 and not (micro and names[2] == "s"):
s = s.rstrip('s') s = s.rstrip("s")
timespec.append(s) timespec.append(s)
if len(timespec) > 1: if len(timespec) > 1:
if micro: if micro:
spec = ''.join(timespec) spec = "".join(timespec)
segments = timespec[:-1], timespec[-1:] segments = timespec[:-1], timespec[-1:]
spec = ' and '.join(', '.join(x) for x in segments) spec = " and ".join(", ".join(x) for x in segments)
elif timespec: elif timespec:
spec = timespec[0] spec = timespec[0]
else: else:
return '0' return "0"
if neg: if neg:
spec += ' ago' spec += " ago"
return spec return spec
def format_list(*items, join='and', delim=', '): def format_list(*items, join="and", delim=", "):
if len(items) > 1: if len(items) > 1:
return (' %s ' % join).join((delim.join(items[:-1]), items[-1])) return (" %s " % join).join((delim.join(items[:-1]), items[-1]))
elif items: elif items:
return items[0] return items[0]
else: else:
return '' return ""
def overwrite_to_dict(overwrite): def overwrite_to_dict(overwrite):
allow, deny = overwrite.pair() allow, deny = overwrite.pair()
return { return {"allow": allow.value, "deny": deny.value}
'allow' : allow.value,
'deny' : deny.value
}
def format_permissions(permissions, include_null=False): def format_permissions(permissions, include_null=False):
@ -116,10 +114,10 @@ def format_permissions(permissions, include_null=False):
else: else:
continue continue
entries.append(symbol + ' ' + perm.replace('_', ' ').title().replace("Tts", "TTS")) entries.append(symbol + " " + perm.replace("_", " ").title().replace("Tts", "TTS"))
if entries: if entries:
return '\n'.join(entries) return "\n".join(entries)
else: else:
return "No permission entries." return "No permission entries."
@ -130,7 +128,7 @@ def getmname(mid, guild):
if member: if member:
return str(member) return str(member)
else: else:
return '(absent user #%s)' % mid return "(absent user #%s)" % mid
def role_from_string(guild, rolename, roles=None): def role_from_string(guild, rolename, roles=None):
@ -186,7 +184,7 @@ def permissions_for_roles(channel, *roles):
if overwrite.id == default.id: if overwrite.id == default.id:
base.handle_overwrite(allow=overwrite.allow, deny=overwrite.deny) base.handle_overwrite(allow=overwrite.allow, deny=overwrite.deny)
if overwrite.type == 'role' and overwrite.id in role_ids: if overwrite.type == "role" and overwrite.id in role_ids:
denies |= overwrite.deny denies |= overwrite.deny
allows |= overwrite.allow allows |= overwrite.allow
@ -218,6 +216,6 @@ def permissions_for_roles(channel, *roles):
def overwrite_from_dict(data): def overwrite_from_dict(data):
allow = discord.Permissions(data.get('allow', 0)) allow = discord.Permissions(data.get("allow", 0))
deny = discord.Permissions(data.get('deny', 0)) deny = discord.Permissions(data.get("deny", 0))
return discord.PermissionOverwrite.from_pair(allow, deny) return discord.PermissionOverwrite.from_pair(allow, deny)

View file

@ -14,6 +14,7 @@ import asyncio
import os import os
import json import json
class RolePlay(commands.Cog): class RolePlay(commands.Cog):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -31,7 +32,8 @@ class RolePlay(commands.Cog):
"lotsa spaghetti", "lotsa spaghetti",
"a brick", "a brick",
"a slice of cheese", "a slice of cheese",
"my foot"], "my foot",
],
"high_iq_msgs": [ "high_iq_msgs": [
"wow!", "wow!",
"that's pretty big.", "that's pretty big.",
@ -40,7 +42,8 @@ class RolePlay(commands.Cog):
"someone here is actually smart.", "someone here is actually smart.",
"thats a dab.", "thats a dab.",
"<:aureliawink:549481308519399425>", "<:aureliawink:549481308519399425>",
"you must of watched Rick and Morty."], "you must of watched Rick and Morty.",
],
"low_iq_msgs": [ "low_iq_msgs": [
":rofl:", ":rofl:",
"oof.", "oof.",
@ -49,7 +52,8 @@ class RolePlay(commands.Cog):
"awww you're special aren't you.", "awww you're special aren't you.",
":crying_cat_face:", ":crying_cat_face:",
"god I'm sorry (not).", "god I'm sorry (not).",
"I didn't know people could have IQ that low."] "I didn't know people could have IQ that low.",
],
} }
self.config.register_guild(**self.default_guild) self.config.register_guild(**self.default_guild)
@ -104,12 +108,11 @@ class RolePlay(commands.Cog):
elif user.id == botid: elif user.id == botid:
user = ctx.message.author user = ctx.message.author
botname = ctx.bot.user.name botname = ctx.bot.user.name
await ctx.send("`-" + botname + " slaps " + user.display_name + await ctx.send(
" multiple times with " + "`-" + botname + " slaps " + user.display_name + " multiple times with " + (choice(slap_items) + "-`")
(choice(slap_items) + "-`")) )
else: else:
await ctx.send("`-slaps " + user.display_name + " with " + await ctx.send("`-slaps " + user.display_name + " with " + (choice(slap_items) + "-`"))
(choice(slap_items) + "-`"))
@slap.command(name="add") @slap.command(name="add")
@checks.admin() @checks.admin()
@ -126,7 +129,7 @@ class RolePlay(commands.Cog):
@slap.command(name="remove") @slap.command(name="remove")
@checks.admin() @checks.admin()
async def _remove_slap(self, ctx, slap_item: str=""): async def _remove_slap(self, ctx, slap_item: str = ""):
"""Removes item to use for slaps!""" """Removes item to use for slaps!"""
guild = ctx.guild guild = ctx.guild
slap_items = await self.config.guild(guild).slap_items() slap_items = await self.config.guild(guild).slap_items()
@ -146,7 +149,7 @@ class RolePlay(commands.Cog):
msg = "" msg = ""
for item in slap_items: for item in slap_items:
msg += "+ {}\n".format(item) msg += "+ {}\n".format(item)
pages = pagify(msg) # pages is an iterator of pages pages = pagify(msg) # pages is an iterator of pages
for page in pages: for page in pages:
await ctx.send(box(page, lang="diff")) await ctx.send(box(page, lang="diff"))
@ -173,9 +176,8 @@ class RolePlay(commands.Cog):
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
await ctx.send("Invalid or malformed json file.") await ctx.send("Invalid or malformed json file.")
@commands.group(invoke_without_command=True) @commands.group(invoke_without_command=True)
async def iq(self, ctx, *users : discord.Member): async def iq(self, ctx, *users: discord.Member):
""" """
Gets IQ of a user. Use multiple users to compare IQs Gets IQ of a user. Use multiple users to compare IQs
""" """
@ -199,7 +201,9 @@ class RolePlay(commands.Cog):
iqs = sorted(iqs.items(), key=lambda x: x[1]) iqs = sorted(iqs.items(), key=lambda x: x[1])
for user, iq in iqs: for user, iq in iqs:
msg += "{}'s iq is {}, {}\n".format(user.display_name, iq, choice(high_iq_msgs) if int(iq) > 130 else choice(low_iq_messages)) msg += "{}'s iq is {}, {}\n".format(
user.display_name, iq, choice(high_iq_msgs) if int(iq) > 130 else choice(low_iq_messages)
)
await ctx.send(msg) await ctx.send(msg)
@ -215,11 +219,11 @@ class RolePlay(commands.Cog):
for high_phrase in high_iq_msgs: for high_phrase in high_iq_msgs:
msg1 += "+ {}\n".format(high_phrase) msg1 += "+ {}\n".format(high_phrase)
high_pages = pagify(msg1) # pages is an iterator of pages high_pages = pagify(msg1) # pages is an iterator of pages
for low_phrase in low_iq_msgs: for low_phrase in low_iq_msgs:
msg2 += "+ {}\n".format(low_phrase) msg2 += "+ {}\n".format(low_phrase)
low_pages = pagify(msg2) # pages is an iterator of pages low_pages = pagify(msg2) # pages is an iterator of pages
for high_page in high_pages: for high_page in high_pages:
await ctx.send(box(high_page, lang="diff")) await ctx.send(box(high_page, lang="diff"))
@ -255,7 +259,7 @@ class RolePlay(commands.Cog):
@iq.command(name="removehigh") @iq.command(name="removehigh")
@checks.admin() @checks.admin()
async def _removehigh_iq(self, ctx, high_phrase: str=""): async def _removehigh_iq(self, ctx, high_phrase: str = ""):
"""Removes phrases for high IQ's!""" """Removes phrases for high IQ's!"""
guild = ctx.guild guild = ctx.guild
high_iq_msgs = await self.config.guild(guild).high_iq_msgs() high_iq_msgs = await self.config.guild(guild).high_iq_msgs()
@ -274,7 +278,7 @@ class RolePlay(commands.Cog):
@iq.command(name="removelow") @iq.command(name="removelow")
@checks.admin() @checks.admin()
async def _removelow_iq(self, ctx, low_phrase: str=""): async def _removelow_iq(self, ctx, low_phrase: str = ""):
"""Removes phrases for low IQ's!""" """Removes phrases for low IQ's!"""
guild = ctx.guild guild = ctx.guild
low_iq_msgs = await self.config.guild(guild).low_iq_msgs() low_iq_msgs = await self.config.guild(guild).low_iq_msgs()
@ -306,7 +310,7 @@ class RolePlay(commands.Cog):
largest_factor = 1 largest_factor = 1
else: else:
largest_factor = [x for x in range(1, horses) if horses % x == 0][-1] largest_factor = [x for x in range(1, horses) if horses % x == 0][-1]
#largest_factor = [x for x in largest_factor if x <= 15][-1] # largest_factor = [x for x in largest_factor if x <= 15][-1]
if largest_factor == 1: if largest_factor == 1:
largest_factor = horses largest_factor = horses
rows = 1 rows = 1
@ -367,7 +371,7 @@ class RolePlay(commands.Cog):
if user.id == ctx.bot.user.id: if user.id == ctx.bot.user.id:
await ctx.send(":newspaper2: :newspaper2: :newspaper2: " + italics(ctx.message.author.display_name)) await ctx.send(":newspaper2: :newspaper2: :newspaper2: " + italics(ctx.message.author.display_name))
else: else:
await ctx.send(":newspaper2: " + italics(user.display_name) ) await ctx.send(":newspaper2: " + italics(user.display_name))
@commands.command() @commands.command()
async def flip(self, ctx, *, user: discord.Member = None): async def flip(self, ctx, *, user: discord.Member = None):