class TestConfig(unittest.TestCase): config_name = 'tests/test_config.json' config_name_alt = 'tests/test_save.json' def setUp(self): self.config = Config(TestConfig.config_name, save=False) def tearDown(self): Config.configs = {} if os.path.exists(TestConfig.config_name_alt): os.remove(TestConfig.config_name_alt) def test_empty(self): self.config = Config('does_not_exist', save=False) self.assertEqual(self.config, {}, "empty config not created") def test_keys(self): for key in "abcd": self.assertIn(key, self.config, "key missing in config") self.assertNotIn("e", self.config, "extra key present") def test_save(self): old_name = self.config.name self.config.name = TestConfig.config_name_alt self.config._save_force() self.config.name = old_name test_config = Config(TestConfig.config_name_alt, save=False) self.assertEqual(self.config, test_config, "saving does not work") def test_object_encode(self): #TODO pass def test_object_decode(self): #TODO pass def test_get(self): self.assertEqual(self.config.get("a"), 10, "get method failed") def test_brace_get(self): self.assertEqual(self.config["b"], [1, 2, 3], "get using brackets failed") def test_delete(self): del self.config["a"] self.assertNotIn("a", self.config, "key not deleted") def test_set(self): self.config["new"] = "testing insert" self.assertEqual(self.config.get("new"), "testing insert", "insert failed")
class MemeGenerator: pattern = re.compile(r'(\w+)\s+(.*)$') def __init__(self, bot): self.bot = bot self.conf = Config('configs/memes.json') doc = self.meme.__dict__['help'] doc += '\n ' doc += '\n '.join(sorted(self.conf.get('memes', {}).keys())) self.meme.__dict__['help'] = doc @commands.command(aliases=['memes']) async def meme(self, ctx, *, text : str): """ Adds text to images Valid names so far: """ async with ctx.typing(): match = MemeGenerator.pattern.match(text) name = match.group(1).lower() if match else text text = match.group(2) if match else '' text = ' '.join(dh.remove_comments(text.split())) cfg = self.conf.get('memes', {}).get(name, None) if not cfg: await ctx.send(error('Could not find image')) return if not text: await ctx.send(error('Are you trying to get an empty image?')) return temp = tempfile.NamedTemporaryFile(suffix=".png") if 'font' not in cfg: cfg['font'] = self.conf.get('font', '') if 'path' not in cfg: cfg['path'] = self.conf.get('path', '') write_image(text, temp.name, **cfg) await ctx.send(file=discord.File(temp.name)) temp.close()
class Games: def __init__(self, bot): self.bot = bot self.conf = Config('configs/games.json') @perms.is_owner() @commands.command(aliases=['faa']) async def fake_artist_add(self, ctx, *, themes): self.conf['fake_artist']['themes'].extend(themes.strip().split('\n')) self.conf.save() await ctx.send(formatter.ok()) @commands.command(aliases=['fa']) async def fake_artist(self, ctx, number : int): conf = self.conf.get('fake_artist', {}) themes = conf.get('themes', []) themes = random.sample(themes, len(themes)-len(themes)%number) output = [[] for i in range(number)] fakes = list(range(number))*(len(themes)//number) random.shuffle(fakes) say = 'here are the links:' # generate for theme,fake in zip(themes, fakes): for i in range(len(output)): output[i].append(theme if i != fake else 'YOU ARE THE FAKE') # generate master file with open(os.path.join(conf.get('path',''), 'master.html'), 'w') as f: f.write(conf.get('rules')) for i,theme in enumerate(themes): f.write(f'''<li><input class="spoilerbutton" type="button"'''+ \ f'''value="show" onclick="this.value=this.value=='show'''+ \ f'''\'?'{html.escape(theme)}':'show';"></li>''') f.write(conf.get('out')) # generate player files for i in range(len(output)): filename = os.path.join(conf.get('path',''), f'{i+1}.html') with open(filename, 'w') as f: f.write(conf.get('rules')) for theme in output[i]: f.write(f'<li>{html.escape(theme)}</li>') f.write(conf.get('out')) say += f'\nhttps://andy29485.tk/files/{i+1}.html' await ctx.send(formatter.ok(say))
async def on_message(message): logger.debug('global on message start') if message.author.bot: logger.debug(' ignoring message (reason: bot)') return # check if user is not the ignore list perms = Config('configs/perms.json') if str(message.author.id) in perms.get('ignore', []): logger.debug(' ignoring message (reason: user in ingore list)') return # check if command is a valid one if not re.search('^[\\.!\\?\\$]{2,}', message.content): await bot.process_commands(message) logger.debug('global on message end')
class Server: def __init__(self, bot): self.bot = bot self.conf = Config('configs/server.json') self.cut = {} heap = self.bot.get_cog('HeapCog') for rem in self.conf.pop('end_role', []): self.bot.loop.run_until_complete(heap.push(rem)) @perms.pm_or_perms(manage_messages=True) @commands.command(name='prune') async def _prune(self, ctx, num_to_delete: int, *message): """ deletes specified number of messages from channel if message is specified, message will be echoed by bot after prune USAGE: .prune <num> [user] [message...] NOTE: if first word after number is a user, only user's messages will be pruned """ # tmp channel/server pointer chan = ctx.message.channel serv = ctx.message.guild #if num_to_delete > 100: # api only allows up to 100 # await ctx.send('Sorry, only up to 100') # TODO - copy thing done in # return # self._paste if num_to_delete < 1: # delete nothing? await ctx.send('umm... no') # answer: no return # if the first word in the message matches a user, # remove that word from the message, store the user try: user = dh.get_user(serv or self.bot, message[0]) if user: message = message[1:] except: logger.debug('did not match a user') user = None check = lambda m: True if user: # if a user was matched, delete messages for that user only logger.debug(f'pruning for user {user.name}') check = lambda m: str(m.author.id) == str(user.id) message = ' '.join(message) #make the message a string logs = [] async for m in chan.history(limit=num_to_delete, reverse=True): if check(m): logs.append(m) deleted = len(logs) old = False while len(logs) > 0: # while there are messages to delete if len(logs) > 1: # if more than one left to delete and not old, if not old: # attempt batch delete [2-100] messages try: await chan.delete_messages(logs[:100]) except: # if problem when batch deleting old = True # then the messages must be old if old: # if old, traverse and delete individually for entry in logs[:100]: try: await entry.delete() except: logger.exception( '<{0.author.name}> {0.content}'.format(entry)) logs = logs[100:] else: # if only one message, delete individually await logs[0].delete() logs.remove(logs[0]) #report that prume was complete, how many were prunned, and the message await ctx.send( ok('Deleted {} message{} {}'.format( deleted, '' if deleted == 1 else 's', f'({message})' if message else ''))) @commands.group(name='role', aliases=['give', 'giveme', 'gimme']) async def _role(self, ctx): """ Manage publicly available roles """ # if no sub commands were called, guess at what the user wanted to do if ctx.invoked_subcommand is None: msg = ctx.message.content.split() # attempt to parse args if len(msg) < 2: await ctx.send('see help (`.help role`)') return role = msg[1] date = ' '.join(msg[2:]) # if the user cannot manage roles, then they must be requesting a role # or they are trying to do something that they are not allowed to if not perms.check_permissions(ctx.message, manage_roles=True): await self._request_wrap(ctx, role, date) # attempt to request role return #if the user does have permission to manage, they must be an admin/mod # ask them what they want to do - since they clearly did not know what # they were trying to do await ctx.send('Are you trying to [a]dd a new role ' + \ 'or are you [r]equesting this role for yourself?' ) try: # wait for them to reply def check(m): return m.author == ctx.message.author and \ m.channel == ctx.message.channel msg = await self.bot.wait_for('message', timeout=30, check=check) except: # if they do not reply, give them a helpful reply # without commenting on their IQ await ctx.send( error('Response timeout, maybe look at the help?')) return # if a reply was recived, check what they wanted to do and pass along msg = msg.content.lower() if msg.startswith('a') or 'add' in msg: # adding new role to list await self._add_wrap(ctx, role) reply = f"Please run `.role request {role}` to get the \"{role}\" role" await ctx.send(reply) elif msg.startswith( 'r') or 'request' in msg: # requesting existing role await self._request_wrap(ctx, role, date) else: # they can't read await ctx.send(error('I have no idea what you are attempting' + \ ' to do, maybe look at the help?') ) @_role.command(name='add', aliases=['create', 'a']) @perms.has_perms(manage_roles=True) async def _add(self, ctx, role: str): """ adds role to list of public roles """ await self._add_wrap(ctx, role) @_role.command(name='list', aliases=['ls', 'l']) async def _list(self, ctx): """ lists public roles avalible in the server """ # pull roles out of the config file serv = ctx.message.guild names = [] m_len = 0 available_roles = self.conf.get(str(serv.id), {}).get('pub_roles', []) # if no roles, say so if not available_roles: await ctx.send('no public roles in this server\n' + \ ' see `.help role create` and `.help role add`' ) return # For each id in list # find matching role in server # if role exists, add it to the role list # Note: this block also finds the strlen of the longest role name, # this will be used later for formatting for role_id in available_roles: role = discord.utils.find(lambda r: str(r.id) == role_id, serv.roles) if role: names.append(role.name) m_len = max(m_len, len(role.name)) # create a message with each role name and id on a seperate line # seperators(role - id) should align due to spacing - this is what the # lenght of the longest role name is used for msg = 'Roles:\n```' line = '{{:{}}} - {{}}\n'.format(m_len) for name, rid in zip(names, available_roles): msg += line.format(name, rid) # send message with role list await ctx.send(msg + '```') @_role.command(name='remove', aliases=['rm']) @perms.has_perms(manage_roles=True) async def _delete(self, ctx, role: str): """ removes role from list of public roles """ # attempt to find specified role and get list of roles in server serv = ctx.message.guild role = dh.get_role(serv, role) guild_id = str(serv.id) role_id = str(role.id) available_roles = self.conf.get(guild_id, {}).get('pub_roles', []) # if user failed to specify role, complain if not role: await ctx.send('Please specify a valid role') return if guild_id not in self.conf: self.conf[guild_id] = {'pub_roles': []} self.conf.save() elif 'pub_roles' not in self.conf[guild_id]: self.conf[guild_id]['pub_roles'] = [] self.conf.save() if role_id in available_roles: # if role is found, remove and report self.conf[guild_id]['pub_roles'].remove(guild_id) self.conf.save() await ctx.send(ok('role removed from public list')) else: # if role is not in server, just report await ctx.send(error('role is not in the list')) @_role.command(name='request', aliases=['r']) async def _request(self, ctx, role: str, date: str = ''): """ adds role to requester(if in list) """ await self._request_wrap(ctx, role, date) @_role.command(name='unrequest', aliases=['rmr', 'u']) async def _unrequest(self, ctx, role: str): """removes role from requester(if in list)""" # attempt to find role that user specied for removal auth = ctx.message.author serv = ctx.message.guild role = dh.get_role(serv, role) guild_id = str(serv.id) role_id = str(role.id) # if user failed to find specify role, complain if not role: await ctx.send('Please specify a valid role') return # get a list of roles that are listed as public and the user roles available_roles = self.conf.get(guild_id, {}).get('pub_roles', []) user_roles = discord.utils.find(lambda r: str(r.id) == role_id, auth.roles) # ONLY remove roles if they are in the public roles list # Unless there is no list, # in which case any of the user's roles can be removed if role_id in available_roles or user_roles: await auth.remove_roles(role) await ctx.send(ok('you no longer have that role')) else: await ctx.send(error('I\'m afraid that I can\'t remove that role')) # wrapper function for adding roles to public list async def _add_wrap(self, ctx, role): serv = ctx.message.guild # find the role, # if it is not found, create a new role role_str = role if type(role) != discord.Role: role = dh.get_role(serv, role_str) if not role: role = await serv.create_role(name=role_str, mentionable=True) await ctx.send(ok(f'New role created: {role_str}')) # if still no role, report and stop if not role: await ctx.send(error("could not find or create role role")) return guild_id = str(serv.id) role_id = str(role.id) # The @everyone role (also @here iiuc) cannot be given/taken if role.is_everyone: await ctx.send(error('umm... no')) return if guild_id not in self.conf: # if server does not have a list yet create it self.conf[guild_id] = {'pub_roles': [role_id]} elif 'pub_roles' not in self.conf[guild_id]: # if list is corruptted self.conf[guild_id]['pub_roles'] = [role_id] # fix it elif role_id in self.conf[guild_id][ 'pub_roles']: # if role is already there await ctx.send('role already in list') # report and stop return else: # otherwise add it to the list and end self.conf[guild_id]['pub_roles'].append(role_id) # save any changes to config file, and report success self.conf.save() await ctx.send(ok('role added to public role list')) # wrapper function for getting roles that are on the list async def _request_wrap(self, ctx, role, date=''): auth = ctx.message.author chan = ctx.message.channel serv = ctx.message.guild # attempt to find the role if a string was given, # if not found, stop if type(role) != discord.Role: role = dh.get_role(serv, role) if not role: await ctx.send(error("could not find role, ask a mod to create it") ) return # get list of public roles available_roles = self.conf.get(guild_id, {}).get('pub_roles', []) role_id = str(role.id) guild_id = str(serv.id) if role_id in available_roles: # if role is a public role, await auth.add_roles(role) # give it await ctx.send(ok('you now have that role')) else: # otherwise don't await ctx.send( error('I\'m afraid that I can\'t give you that role')) return if date: # if a timeout was specified end_time = dh.get_end_time(date)[0] role_end = RoleRemove(end_time, role_id, str(auth.id), str(chan.id), guild_id) await self.bot.get_cog('HeapCog').push(role_end, self.bot) @perms.pm_or_perms(manage_messages=True) @commands.command(name='cut') async def _cut(self, ctx, cut: str, skip: str = ''): ''' cuts num_to_cut messages from the current channel skips over num_to_skip messages (skips none if not specified) example: User1: first message User2: other message User3: final message Using ".cut 1" will cut User3's message Using ".cut 1 1" will cut User2's message Using ".cut 3" will cut all messages Using ".cut 3:other" will cut User2 and 3's messages Using ".cut id:XXX" will cut id XXX Using ".cut id:XXX id:YYY" will cut messages in range (id XXX, id YYY] messages will not be deleted until paste needs manage_messages perm in the current channel to use see .paste ''' #if num_to_cut > 100: # await ctx.send('Sorry, only up to 100') # return in_id = re.search('^id:(\\d+)$', cut) in_re = re.search('^(\d+):(.+)$', cut) if in_id: cut = await ctx.message.channel.get_message(int(in_id.group(1))) elif in_re: cut = int(in_re.group(1)) in_re = re.compile(in_re.group(2)) elif re.search('^\d+$', cut): cut = int(cut) else: await ctx.send(error('bad cut parameter')) return skip_id = re.search('^id:(\\d+)$', skip) skip_re = re.search('^(\d+):(.+)$', skip) if skip_id: skip = await ctx.message.channel.get_message(int(skip_id.group(1))) elif skip_re: skip = int(skip_re.group(1)) skip_re = re.compile(skip_re.group(2)) elif not skip or re.search('^\d+$', skip): skip = int(skip or '0') else: await ctx.send(error('bad skip parameter')) return if not cut or (type(cut) == int and cut < 1): # can't cut no messages await ctx.send('umm... no') return # store info in easier to access variables chan = ctx.message.channel bef = ctx.message.created_at aid = str(ctx.message.author.id) cid = str(chan.id) # delete the original `.cut` message(not part of cutting) # also sorta serves as confirmation that messages have been cut await ctx.message.delete() # if messages should be skipped when cutting # save the timestamp of the oldest message if skip: if type(skip) == int: run = lambda: chan.history(limit=skip, reverse=True) else: run = lambda: chan.history(after=skip, reverse=True) async for m in run(): if skip_re and not skip_re.search(m.content): continue bef = m.created_at break # save the logs to a list #store true in position 0 of list if channel is a nsfw channel logs = ['nsfw' in chan.name.lower()] if type(cut) == int: run = lambda: chan.history(limit=cut, before=bef, reverse=True) else: run = lambda: chan.history(after=cut, before=bef, reverse=True) async for m in run(): if in_re and in_re.search(m.content): in_re = False elif in_re: continue logs.append(m) # save logs to dict (associate with user) self.cut[aid] = logs @perms.has_perms(manage_messages=True) @commands.command(name='paste') async def _paste(self, ctx): ''' paste cutted messages to current channel needs manage_messages perm in the current channel to use deletes original messages see .cut ''' # get messages that were cut and other variables aid = str(ctx.message.author.id) chan = ctx.message.channel logs = self.cut.pop(aid, []) # if nothing was cut, stop if not logs: await ctx.send('You have not cut anything') return # it messages were cut in a nsfw channel, # do not paste unless this is a nsfw channel # NOTE: cutting/pasting to/from PMs is not possible(for now) if logs[0] and 'nsfw' not in chan.name.lower(): await ctx.send('That which hath been cut in nsfw, ' + \ 'mustn\'t be pasted in such a place' ) return # remove the nsfw indicator(since it's not really part of the logs) logs = logs[1:] # delete the `.paste` message await ctx.message.delete() # compress the messages - many messages can be squished into 1 big message # but ensure that output messages do not exceede the discord message limit buf = '' # current out message that is being compressed to out = [] # output messages that have been compressed for message in logs: # save messages as: # <nick> message # and attachments as(after the message): # filename: url_to_attachment if message.content or message.attachments: tmp = '<{0.author.name}> {0.content}\n'.format(message) for a in message.attachments: tmp += '{0.filename}: {0.url}\n'.format(a) else: tmp = '' # if this message would make the current output buffer too long, # append it to the output message list and reset the buffer # or just append to the buffer if it's not going to be too long if len(buf) + len(tmp) > 1900: out.append(buf) buf = tmp else: buf += tmp # if the message is composed of *only* embeds, # flush buffer, # and append embed to output list if message.embeds and not message.content: if buf: out.append(buf) buf = '' for embed in message.embeds: out.append(embed) # if there is still content in the buffer after messages have been traversed # treat buffer as complete message if buf: out.append(buf) # send each message in output list for mes in out: if type(mes) == str: if mes: await ctx.send(mes) else: # if it's an embed, n await ctx.send(embed=EmWrap(mes)) # it needs to be wrapped # once all messages have been pasted, delete(since cut) the old ones old = False # messages older than 2 weeks cannot be batch deleted while len(logs) > 0: # while there are messages to delete if len(logs) > 1: # if more than one left to delete and not old, if not old: # attempt batch delete [2-100] messages try: await chan.delete_messages(logs[:100]) except: # if problem when batch deleting old = True # then the messages must be old if old: # if old, traverse and delete individually for entry in logs[:100]: await entry.delete() logs = logs[100:] else: # if only one message, delete individually await logs[0].delete() logs.remove(logs[0]) # remove cut entry from dict of cuts if aid in self.cut: del self.cut[aid] @commands.command(name='topic') async def _topic(self, ctx, *, new_topic=''): """manage topic if a new_topic is specified, changes the topic otherwise, displays the current topic """ # store channel in tmp pointer c = ctx.message.channel if new_topic: # if a topic was passed, # change it if user has the permisssions to do so # or tell user that they can't do that if perms.check_permissions(ctx.message, manage_channels=True): await c.edit(topic=new_topic) await ctx.send( ok('Topic for #{} has been changed'.format(c.name))) else: await ctx.send( error('You cannot change the topic for #{}'.format(c.name)) ) elif c.topic: # if no topic has been passed, # say the topic await ctx.send('Topic for #{}: `{}`'.format(c.name, c.topic)) else: # if not topic in channel, # say so await ctx.send('#{} has no topic'.format(c.name)) @perms.has_perms(manage_roles=True) @commands.command(name='timeout_send', aliases=['ts']) async def _timeout_send(self, ctx, member: discord.Member, time: float = 300): """puts a member in timeout for a duration(default 5 min) usage `.timeout [add] @member [time in seconds]` """ heap = self.bot.get_cog('HeapCog') if not perms.is_owner() and \ ctx.message.author.server_permissions < member.server_permissions: await ctx.send('Can\'t send higher ranking members to timeout') return server = ctx.message.guild channel = ctx.message.channel if perms.in_group('timeout') and not perms.is_owner(): await ctx.send('You\'re in timeout... No.') return if not ctx.message.guild: await ctx.send('not in a server at the moment') return if time < 10: await ctx.send('And what would the point of that be?') return if time > 10000: await ctx.send('Too long, at this point consider banning them') return criteria = lambda m: re.search('(?i)^time?[ _-]?out.*', m.name) to_role = discord.utils.find(criteria, server.roles) to_chan = discord.utils.find(criteria, server.channels) try: timeout_obj = Timeout(channel, server, member, time) await heap.push(timeout_obj, self.bot, to_role, to_chan) except: for index, obj in enumerate(heap): if obj == timeout_obj: heap.pop(index) break await ctx.send( 'There was an error sending {}\'s to timeout \n({}{}\n)'. format( member.name, '\n - do I have permission to manage roles(and possibly channels)?', '\n - is my highest role above {}\'s highest role?'. format(member.name))) #raise @perms.has_perms(manage_roles=True) @commands.command(name='timeout_end', aliases=['te']) async def _timeout_end(self, ctx, member: discord.Member): """removes a member from timeout usage `.timeout end @member` """ server = ctx.message.guild channel = ctx.message.channel if perms.in_group('timeout') and not perms.is_owner(): await ctx.send('You\'re in timeout... No.') return if not ctx.message.guild: await ctx.send('not in a server at the moment') return # test timeout object for comparison test = namedtuple({ 'server_id': int(server.id), 'user_id': int(member.id) }) index = 0 # inext is used to more efficently pop from heap # error message in case ending timeout fails error_msg = 'There was an error ending {}\'s timeout \n({}{}\n)'.format( member.name, '\n - do I have permission to manage roles(and possibly channels)?', '\n - is my highest role above {}\'s highest role?'.format( member.name)) for timeout in Timeout.conf['timeouts']: # look trhough all timouts if timeout == test: # if found try: await timeout.end(self.bot, index) # attempt to end except: await ctx.send(error_msg ) # if error when ending, report return index += 1 # not found increment index else: # not found at all, report await ctx.send('{} is not in timeout...'.format(member.name)) return # checks timeouts and restores perms when timout expires async def check_timeouts(self): if 'timeouts' not in Timeout.conf: #create timeouts list if needed Timeout.conf['timeouts'] = [] while self == self.bot.get_cog('Server'): # in case of cog reload # while timeouts exist, and the next one's time has come, # end it while Timeout.conf['timeouts'] and \ Timeout.conf['timeouts'][0].time_left < 1: await Timeout.conf['timeouts'][0].end(self.bot, 0) # wait a bit and check again # if the next one ends in < 15s, wait that much instead of 15s if Timeout.conf['timeouts']: delay = min(Timeout.conf['timeouts'].time_left, 15) else: delay = 15 await asyncio.sleep(delay + 0.5)
if message.author.bot: logger.debug(' ignoring message (reason: bot)') return # check if user is not the ignore list perms = Config('configs/perms.json') if str(message.author.id) in perms.get('ignore', []): logger.debug(' ignoring message (reason: user in ingore list)') return # check if command is a valid one if not re.search('^[\\.!\\?\\$]{2,}', message.content): await bot.process_commands(message) logger.debug('global on message end') # load token and start bot # if not token, ask while len(auth.get('token', '')) < 30: auth['token'] = input("Please enter bot's token: ") auth.save() if len(auth.get('cogs', [])) == 0: is_cog = lambda fname: ord(fname[0]) <= ord('z') \ and ord(fname[0]) >= ord('a') \ and fname.endswith('.py') auth['cogs'] = ['cogs.'+x[:-3] for x in os.listdir('cogs/') if is_cog(x)] #start bot bot.run(auth['token'])
class AZ: def __init__(self, test=False): self.last = {} self.conf = Config('configs/az.json', save=(not test)) if 'lenny' not in self.conf: self.conf['lenny'] = {} if 'img-reps' not in self.conf: self.conf['img-reps'] = {} if 'repeat_after' not in self.conf: self.conf['repeat_after'] = 3 self.conf.save() def lenny(self, first=''): out = None try: num = int(first) if num < 1: num = 1 if num > 10: num = 10 except: num = 1 out = self.conf['lenny'].get(first.lower(), None) out = formatter.code(out) if out else '\n( ͡° ͜ʖ ͡° )' return out * num def shrug(self): return '¯\_(ツ)_/¯' @staticmethod def renderLatex(text, fntsz=12, dpi=300, fsz=.01, fmt='svg', file=None, **kargs): if type(file) == str and file: if not file.endswith(fmt): file += '.' + fmt with open(file, 'w') as f: return renderLatex(text, fntsz, dpi, fsz, fmt, f, **kargs) text = text.strip().replace('\n', '\\\\') if text.startswith('\\begin'): text = f'\\[{text}\\]' elif not text.startswith('$') and not text.startswith('\\['): text = f'\\[\\begin{{split}}{text}\\end{{split}}\\]' logger.debug(f'attempting to render latex string: \"{text}\"') plt.rc('text', usetex=True) plt.rcParams['text.latex.preamble'] = [ r'\usepackage{amsmath}', r'\usepackage{amssymb}', r'\usepackage{tikz}', r'\usepackage{xcolor}', r'\usepackage[mathscr]{euscript}', r'\usepackage{mathrsfs}', ] fig = plt.figure(figsize=(fsz, fsz)) fig.text(0, 0, text, fontsize=fntsz, ha='center', ma='center', linespacing=1, **kargs) output = BytesIO() if file is None else file fig.savefig(output, dpi=dpi, transparent=True, format=fmt, bbox_inches='tight', pad_inches=0.1, frameon=False) plt.close(fig) if file is None: output.seek(0) return output def get_colour(self, colour): colour = colour.lower().strip() match = re.search('^(0[hx])?([a-f0-9]{6})$', colour) if colour in dh.colours: c = dh.colours[colour] elif match: c = discord.Colour(int(match.group(2), 16)) else: return None return c def img(self, *search): if not os.path.exists(self.conf.get('path', '')): logger.debug('could not find images') raise IOError('No images found') #try: # git_sync(self.conf.get('path')) #except: # pass logger.debug('input: %s', ', '.join(search)) search = [re.sub(r'[^\w\./#\* -]+', '', i).lower() for i in search] search = dh.remove_comments(search) logger.debug('uncomment: %s', ', '.join(search)) try: path = azfind.search(self.conf['path'], search) except: path = '' if not path.strip(): return None, None # fix line apngs a bit (they only loop once) loop_apng(path) try: logger.info(path) if self.conf.get('path-rep'): url = path.replace(self.conf['path'], self.conf['path-rep']) else: url = 'No url, `pat-rep` not set' return path, url except: raise async def censor(self, bot, message): reps = self.conf.get('censor', {}) cont = message.content chan = message.channel auth = message.author.mention send = False for pat, rep in reps.items(): if re.search(pat, cont): send = True cont = re.sub(pat, rep, cont) if send: await message.delete() await chan.send(f'<{auth}> {cont}') async def repeat(self, bot, message): chan = message.channel data = self.last.get(chan, ['', 0]) if not message.content: return if data[0] == message.content.lower(): data[1] += 1 else: data = [message.content.lower(), 1] if data[1] == self.conf.get('repeat_after', 3): await chan.send(message.content) data[1] = 0 self.last[chan] = data