def config_create(guild): """ Get the config file for the server and give to the user :param guild: <Discord.guild object> :return: <String> Describing file location """ with ENGINE.connect() as conn: select_st = select([Config]).where(Config.c.id == guild.id) result = conn.execute(select_st).fetchone() if result: if VERBOSE >= 2: print('[+] Found guild config for {}'.format(guild.name)) # Create an in-memory version of the config columns = [] for each in Config.c: columns.append(each.name) dict_ = dict(zip(columns, result)) # For pretty printing, make the user's life easier for each in jsonFormatter[0]: dict_['schedule'] = dict_['schedule'].replace(each[0], each[1]) for each in jsonFormatter[1]: dict_[each[1]] = dict_.pop(each[0]) with open('{}/docs/config/{}.json'.format(DEFAULT_DIR, guild.id), 'w') as f: json.dump(dict_, f, indent=4) f.write('\n\n{}'.format(fetch_file('help', 'config'))) return '{}/docs/config/{}.json'.format(DEFAULT_DIR, guild.id) else: # Guild has no config entry, create one and try again config_create_default(guild) return config_create(guild)
async def delete_command(message): """ Permanently remove a custom command if it exists :param message: <Discord.message object> :return: <String> Notifying command has been removed """ admin = await is_admin_test(message.author, message) if admin: args = message.content.split() if VERBOSE >= 2: print("Args:") print(args) with ENGINE.connect() as conn: select_st = select([Commands]).where( and_(Commands.c.guild_id == message.guild.id, Commands.c.name == args[0])) result = conn.execute(select_st).fetchone() if VERBOSE >= 2: print("Result:") print(result) if result: if VERBOSE >= 2: print("Result:") print(result) del_st = Commands.delete().where( and_(Commands.c.guild_id == message.guild.id, Commands.c.name == args[0])) conn.execute(del_st) import_custom_commands(message.guild.id) return 'Deleted {}'.format(args[0]) else: return 'Command `{}` does not exist.'.format(args[0])
def get_quote_log(guild): """ Return an xlsx of all quotes in the guild to the user. :param guild: :return: """ df = [None, None] for idx, Table in enumerate({Quotes, Lore}): select_st = select([Table]).where(Table.c.guild == guild) with ENGINE.connect() as conn: result = conn.execute(select_st).fetchall() keys = conn.execute(select_st).keys() entries = [each.values() for each in result] for each in entries: each[0] = 'id_{}'.format(each[0]) each[4] = 'g_{}'.format(each[4]) df[idx] = pd.DataFrame(entries, columns=keys) with pd.ExcelWriter('{}/log/quoteLog_{}.xlsx'.format(DEFAULT_DIR, guild), engine='xlsxwriter') as writer: df[1].to_excel(writer, sheet_name='Sheet_1') df[0].to_excel(writer, sheet_name='Sheet_2') return [ 'Log of all quotes and lore for this guild:', '{}/log/quoteLog_{}.xlsx'.format(DEFAULT_DIR, guild) ]
def delete_quote(guild, msg_id): """ Remove a quote from the database :param guild: <Int> Discord guild ID :param msg_id: <Int> Discord message ID :return: <String> Notify if quote has been removed """ with ENGINE.connect() as conn: for Table in {Quotes, Lore}: select_st = select([Table]).where( and_(Table.c.id == msg_id, Table.c.guild == guild)) try: result = conn.execute(select_st).fetchone() if result: quote = '{}\n ---{} on {}'.format(result[2], result[1], result[3]) ins = Table.delete().where( and_(Table.c.id == msg_id, Table.c.guild == guild)) conn.execute(ins) return "Deleted quote: {}".format(quote) except Exception as e: if VERBOSE >= 1: print('[!] Exception in tquote: {}'.format(e)) return None
def config_load(guild): """ Load the JSON file supplied by user into the database :param guild: <Int> Discord guild ID :return: <None> """ # Undo the pretty printing with open('{}/docs/config/{}.json'.format(DEFAULT_DIR, guild), 'r') as f: dict_ = json.loads(f.read().split('```', maxsplit=1)[0]) for each in jsonFormatter[1]: dict_[each[0]] = dict_.pop(each[1]) for each in jsonFormatter[0]: dict_['schedule'] = dict_['schedule'].replace(each[1], each[0]) with ENGINE.connect() as conn: ins = Config.update().where(Config.c.id == guild).values( locale=dict_['locale'], schedule=dict_['schedule'], quote_format=dict_['quote_format'], lore_format=dict_['lore_format'], url=dict_['url'], qAdd_format=dict_['qAdd_format'], filtered=dict_['filtered'], mod_roles=dict_['mod_roles'], anonymous=dict_['anonymous'], timer_channel=dict_['timer_channel'], ) conn.execute(ins) # TODO ensure to lower modRoles[guild] = fetch_value(guild, 9, ';') if VERBOSE >= 1: print('[+] Loaded new config for {}'.format(guild.name))
def guild_list(): """ Get a list of all guilds ids :return: <List> IDs for all guilds the bot is active in """ with ENGINE.connect() as conn: select_st = select([Config]) res = conn.execute(select_st) result = res.fetchall() return [each[0] for each in result]
def load_config(guild): """ Retrieve any formatting options from database :param guild: <Int> Discord guild ID :return: <List> SQLAlchemy row entry from Config Table """ result = None with ENGINE.connect() as conn: select_st = select([Config]).where(Config.c.id == guild) result = conn.execute(select_st).fetchone() return result
def config_reset(guild): """ Return the config to default values :param guild: <Discord.guild object> :return: <None> """ with ENGINE.connect() as conn: ins = Config.delete().where(Config.c.id == guild.id) conn.execute(ins) if VERBOSE >= 1: print('[+] Reset config for: {}'.format(guild.name)) return config_create(guild)
def load_config(guild): """ Internal function to get Guild configuration data for schedule formatting and default locale :param guild: <Int> Discord guild ID :return: <List> SQLAlchemy row result from database """ with ENGINE.connect() as conn: select_st = select([Config]).where(Config.c.id == guild) res = conn.execute(select_st) result = res.fetchone() if result: return result
def get_user(id_): """ Internal function to get values from database for a single user :param id_: <Int> User ID :return: <List> SQLAlchemy row result from database """ with ENGINE.connect() as conn: select_st = select([Users]).where(Users.c.id == id_, ) res = conn.execute(select_st) result = res.fetchone() if result: return result
def check_if_exists(guild, msg_id): """ Internal function toeEnsure that we do not add the same message to the database multiple times :param guild: <Int> Discord guild ID :param msg_id: <Int> Discord message ID :return: <Bool> """ with ENGINE.connect() as conn: select_st = select([Quotes]).where( and_(Quotes.c.id == msg_id, Quotes.c.guild == guild)) if conn.execute(select_st).fetchall(): return True return False
def insert_command(message): """ Add a new custom command to the database. :param message: <Discord.message> Raw message object :return: <str> Banner notifying if new command has been created or exisisting has been updated. """ # if await is_admin(message.author): args = message.content.split() links = str(message.attachments[0].url) if message.attachments else '' for each in args: if each.find('http') != -1: if each.split('.')[-1] in extSet['image']: links = '{}\n{}'.format(links, each) with ENGINE.connect() as conn: select_st = select([Commands]).where( and_(Commands.c.guild_id == message.guild.id, Commands.c.name == args[0])) result = conn.execute(select_st).fetchone() if result: ins = Commands.update().where( and_(Commands.c.guild_name == message.guild.name, Commands.c.name == args[0])).values( value='{} {}'.format(' '.join(args[1:]), links), links=links, ) conn.execute(ins) import_custom_commands(message.guild.id) banner = Embed(title='Updated `{}`'.format( args[0], description=' '.join(args[1:]))) if VERBOSE >= 1: print('[+] Updated {}'.format((args[0]))) return banner else: ins = Commands.insert().values( id=message.id, guild_id=message.guild.id, guild_name=message.guild.name, name=args[0], value='{} {}'.format(' '.join(args[1:]), links), embed=links, ) conn.execute(ins) import_custom_commands(message.guild.id) banner = Embed(title='Added custom command `{}`'.format(args[0]), description=' '.join(args[1:])) if VERBOSE >= 1: print('[+] Updated {}'.format((args[0]))) return banner
async def override(message): """ Admin command to manually change a user's saved location :param message: <Discord.message object> Describing which users to change to which locations :return: <String> Describing if the users' location was set or updated successfully """ increment_usage(message.guild, 'sched') if await is_admin(message.author, message): banner = Embed(title='Schedule Override', description='Change a user\'s location.') args = message.content.split() # Args should translate as: 1 id, 2 name, 3 locale if len(args) < 4: banner.add_field( name='Invalid syntax.', value= 'Formatting is: `!schedule override [TIMEZONE] @USER` you may mention any number of users.' ) else: locale = TZ_ABBRS.get(args[2].lower(), args[2]) success = [[], []] for each in message.mentions: user = get_user(int(each.id)) if user: with ENGINE.connect() as conn: query = Users.update().where( Users.c.id == each.id).values(locale=locale) conn.execute(query) if VERBOSE >= 2: print('[+] {} UPDATED LOCALE TO {} FOR USER {}'.format( message.author, locale, each.name)) success[0].append('Changed {} to: {}'.format( each.name, locale)) else: insert_user(each.id, message.guild, each.name, locale) if VERBOSE >= 2: print('[+] {} SETTING {}\'s SCHEDULE TO: {}'.format( message.author, each.name, locale)) success[1].append('Set {} to: {}'.format( each.name, locale)) banner.add_field(name='Updated schedule location.', value='\n'.join(success[0])) banner.add_field(name='Created schedule location.', value='\n'.join(success[1])) return banner
def insert_user(id_, guild_, name, locale): """ Internal function to set values in database for a single user :param id_: <Int> User ID :param guild_: <Discord.guild object> :param name: <String> Username :param locale: <String> New location for the user :return: <None> """ with ENGINE.connect() as conn: ins = Users.insert().values( id=id_, name=name, locale=locale, guild=guild_.id, guild_name=guild_.name, ) conn.execute(ins)
def fetch_value(guild, val, delim=None): """ Get a specific cell from the guilds config table :param guild: <Int> Discord guild ID :param val: <String> Column name within Config Table :param delim: (Optional) <String> Delimeter for splitting values within the cell :return: <List> Values from within the specified cell """ with ENGINE.connect() as conn: select_st = select([Config]).where(Config.c.id == guild) res = conn.execute(select_st) result = res.fetchone() if result and result[val]: if type(result[val]) is str: result = result[val].split(delim) result[:] = (val for val in result if val not in {'', ' ', '\n', None}) else: result = result[val] return result
def import_custom_commands(guild): """ Internal function to grab any custom commands from the database :param guild: <Int> Discord guild ID :return: <None> """ name = ' '.join(fetch_value(guild, 1)) if VERBOSE >= 2: print(name) with ENGINE.connect() as conn: select_st = select([Commands]).where(Commands.c.guild_name == name) result = conn.execute(select_st).fetchall() if result: commands = dict() for each in result: each = list(each) commands[each[3]] = each[5] custom_commands[guild] = commands
def set_schedule(message): """ User command to set their locale :param message: <Discord.message object> Describing where to set location to :return: <String> Describing new location for user """ increment_usage(message.guild, 'sched') args = message.content.split() config = load_config(message.guild.id) banner = Embed(title='Schedule') if config: server_locale = config[2] else: server_locale = 'Asia/Tokyo' # if no location provided default to server locale # if server has no locale default to Tokyo # if there is no server locale the schedule will not be displayed # however this allows users to still get their locations set locale = server_locale if len(args) < 3 else TZ_ABBRS.get( args[2].lower(), args[2]) user = get_user(message.author.id) old_locale = user[2] if user: with ENGINE.connect() as conn: query = Users.update().where( Users.c.id == message.author.id, ).values(locale=locale) conn.execute(query) banner.add_field(name='Updated your schedule location.', value='From: {}\nTo: {}'.format(old_locale, locale)) else: insert_user(message.author.id, message.guild, message.author.name, locale) banner.add_field(name='Set your schedule location.', value='To: {}'.format(locale)) return banner
def config_create_default(guild): """ Create a new default entry for the given guild :param guild: <Discord.guild object> :return: <None> """ if VERBOSE >= 1: print('[+] Creating new guild config for {}'.format(guild.name)) with ENGINE.connect() as conn: ins = Config.insert().values( id=guild.id, guild_name=guild.name, locale='Asia/Tokyo', schedule='0=10,17:15;1=10,12;2=16,10:15;3=2:44;4=10;5=16:30;', quote_format='{0}\n ---{1} on {2}', lore_format='{0}\n ---Scribed by the Lore Master {1}, on the blessed day of {2}', url='Come hang with us at: <https://www.twitch.tv/>', qAdd_format='Added:\n "{0}"\n by {1} on {2}', filtered='none', mod_roles='mod;admin;discord mod;', anonymous=1, timer_channel=0, ) conn.execute(ins)
def get_quote(message, Table, username=None, raw=False): """ Retrieve a quote from the database. :param guild: <int> message.guild.id :param Table: (Optional) <SQLAlchemy.Table> Quotes or Lore, defaults to Quotes :param username: (Optional) <str> Case sensitive Discord username, without discriminator """ if username: select_user = select([Table]).where( and_(Table.c.name == username, Table.c.guild == message.guild.id)).order_by(func.random()) select_id = select([Table]).where( and_(Table.c.id == username, Table.c.guild == message.guild.id)) else: select_rand = select([ Table ]).where(Table.c.guild == message.guild.id).order_by(func.random()) with ENGINE.connect() as conn: if username: result = conn.execute(select_id).fetchone() if not result: result = conn.execute(select_user).fetchone() else: result = conn.execute(select_rand).fetchone() # Result fields translate as # [0]: message id, [1]: author, [2]: quote, [3]: date, [6]: embed url, [7]: jump_url if result: config = load_config(message.guild.id) if config: if Table.name == 'famQuotes': stm = config[4].replace('\\n', '\n') title = "Quote {}".format(result[0]) context_url = '{}'.format(result[7]) elif Table.name == 'famLore': stm = config[5].replace('\\n', '\n') title = "Lore {}".format(result[0]) else: if Table.name == 'famQuotes': stm = '---{} on {}' title = "Quote {}".format(result[0]) context_url = '{}'.format(result[7]) elif Table.name == 'famLore': stm = '---Scribed by the Lore Master {}, on the blessed day of {}' title = "Lore {}".format(result[0]) if raw: # Check if there is an attached img or file to send as well if len(result) > 6 and result[6]: stm = stm + '\n' + result[6] result[2].replace(result[6], '') # Result fields translate as # [1]: author, [2]: quote, [3]: date, [6]: embed url, [7]: jump_url if result[7]: text = '{} \n\n{}'.format(result[2], context_url) else: text = result[2] return stm.format(title, text, result[1], result[3]) else: stm = stm.format(result[1], result[3]) if len(result) > 6 and result[6]: result[2].replace(result[6], '') banner = Embed(title=title, description=result[2]) banner.add_field(name='Context ', value=context_url, inline=False) if len(result) > 6 and result[6]: banner.set_image(url=result[6]) banner.set_footer(text=stm) return banner
def insert_quote(message, Table, adder=None): """ Insert a quote to the database :param message: <Discord.message object> :param Table: <SQLAlchemy.Table object> :param adder: <String> Username of the member who added the :speech_left: :return: <String> Notifying of message being added """ if Table is None: Table = Quotes config = load_config(message.guild.id) if config: server_locale = config[2] stm = config[7].replace('\\n', '\n') else: server_locale = 'Asia/Tokyo' stm = '--{} on {}' # Suppress any user or role mentions text = message.content for each in message.mentions: text = text.replace('<@!{}>'.format(each.id), each.name) for each in message.role_mentions: text = text.replace('<@&{}>'.format(each.id), each.name) text = text.replace('@everyone', '@ everyone') text = text.replace('@here', '@ here') jump_url = message.jump_url args = text.split() embed = str(message.attachments[0].url) if message.attachments else None if not embed: embed = '' for each in args: if each.find('http') != -1: if each.split('.')[-1] in extSet['image']: embed = '{}\n{}'.format(embed, each) date = pendulum.now(tz=server_locale).to_day_datetime_string() with ENGINE.connect() as conn: if Table.name == 'famQuotes': ins = Table.insert().values( id=message.id, name=message.author.name, text=text, date=date, guild=str(message.guild.id), guild_name=message.guild.name, embed=embed, context=jump_url, ) conn.execute(ins) if not fetch_value(message.guild.id, 10): banner = Embed(title="{} Added Quote: {}".format( adder, message.id), description=text) else: banner = Embed(title="Added Quote: {}".format(message.id), description=text) if embed: banner.set_image(url=embed) banner.set_footer(text=stm.format(message.author.name, date)) elif Table.name == 'famLore': ins = Table.insert().values( id=message.id, name=args[2], text=' '.join(args[3:]), date=date, guild=str(message.guild.id), embed=embed, guild_name=message.guild.name, ) conn.execute(ins) banner = Embed(title="Added Lore: {}".format(message.id), description=' '.join(args[3:])) if embed: banner.set_image(url=embed) banner.set_footer(text=stm.format(args[2], date)) return banner
def increment_usage(guild, command): """Keeps track of how many times various commands have been used""" with ENGINE.connect() as conn: select_st = select([Stats]).where(Stats.c.id == guild.id) result = conn.execute(select_st).fetchone() if result: columns = [] for each in Stats.c: columns.append(each.name) dict_ = dict(zip(columns, result)) dict_[command] = int(dict_[command]) + 1 ins = Stats.update().where(Stats.c.id == guild.id).values( raw_messages=dict_['raw_messages'], quote=dict_['quote'], lore=dict_['lore'], wolf=dict_['wolf'], wotd=dict_['wotd'], dict=dict_['dict'], trans=dict_['trans'], google=dict_['google'], config=dict_['config'], sched=dict_['sched'], filter=dict_['filter'], doip=dict_['doip'], gif=dict_['gif'], stats=dict_['stats'], eight=dict_['eight'], help=dict_['help'], custom=dict_['custom'], ) conn.execute(ins) return 1 else: if VERBOSE >= 2: print('[+] Creating usage counter for {}'.format(guild.name)) ins = Stats.insert().values( id=guild.id, guild_name=guild.name, raw_messages=0, quote=0, lore=0, wolf=0, wotd=0, dict=0, trans=0, google=0, config=0, sched=0, filter=0, doip=0, gif=0, stats=0, eight=0, help=0, custom=0, ) conn.execute(ins) return increment_usage(guild, command)