async def awoo_leaderboard(bot, context): """Displays the top 10 violators.""" cursor = data.db_select(bot, from_arg='awoo', additional='ORDER BY debt DESC', limit=10) entries = cursor.fetchall() if cursor else [] if not entries: raise CBException("Nobody has made any awoo violations yet!") stats = [[], []] # debt/violations, user for index, entry in enumerate(entries): stats[0].append('`{0}.` ${1.debt} | {1.violations}'.format( index + 1, entry)) user = data.get_member(bot, entry.user_id, safe=True, attribute='mention') user = user or 'Unknown ({})'.format(entry.user_id) stats[1].append('`\u200b`{}'.format(user)) embed = discord.Embed(title=':scales: Awoo violation leaderboard') embed.add_field(name='Debt | Violations', value='\n'.join(stats[0])) embed.add_field(name='User', value='\n'.join(stats[1])) return Response(embed=embed)
def setup_characters_table(bot): """Creates the characters table and updates outdated entries.""" data.db_create_table(bot, 'characters', template='characters_template') cursor = data.db_execute( bot, 'SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = %s', input_args=['characters']) columns = [it[0] for it in cursor.fetchall()] if 'tags' not in columns: data.db_execute(bot, 'ALTER TABLE characters ADD COLUMN tags text[]') if 'modified' not in columns: data.db_execute(bot, 'ALTER TABLE characters ADD COLUMN modified timestamp') name_index = 'IX_character_order' if not data.db_exists(bot, name_index): data.db_execute(bot, 'CREATE INDEX {} ON characters (clean_name ASC)'.format(name_index)) # Select all entries and convert cursor = data.db_select(bot, from_arg='characters') for entry in cursor.fetchall(): if entry.data['version'] == 1: # NOTE: Change per version bump dt = datetime.datetime.utcfromtimestamp(entry.data['created']) new_data = entry.data tags = [new_data['type'], entry.clean_name] new_data['attribute_order'] = list(new_data['attributes'].keys()) new_data['images'] = [[it, '', ''] for it in new_data['images']] new_data['tags'] = tags new_data['version'] = DATA_VERSION data.db_update( bot, 'characters', set_arg='data=%s, tags=%s, modified=%s', where_arg='clean_name=%s AND owner_id=%s', input_args=(Json(new_data), tags, dt, entry.clean_name, entry.owner_id))
async def character_create(bot, context): """Creates a new character entry.""" # Check if an entry is currently being created/edited tracker = data.get(bot, __name__, 'tracker', user_id=context.author.id, volatile=True) if tracker: return Response( content=( "You are currently already creating or editing a character entry. " "Would you like to cancel your current session?"), message_type=MessageTypes.INTERACTIVE, extra_function=_cancel_menu, extra={'buttons': ['🇾', '🇳']}) # 10 character limit cursor = data.db_select( bot, from_arg='characters', where_arg='owner_id=%s', input_args=[context.author.id]) characters = cursor.fetchall() if cursor else [] if len(characters) >= 10: raise CBException("Cannot create more than 10 characters.") # Use the provided character file if context.message.attachments: content = await _process_data( bot, context.author, context.message.attachments[0].url, propagate_error=True) return Response(content=content) # Use the online entry creator else: await _create_session(bot, context.author) if not context.direct: await context.message.add_reaction('📨')
def get_schedule_entries(bot, plugin_name, search=None, destination=None, custom_match=None, custom_args=[]): """Gets the entries given the search or match arguments.""" if custom_match: where_arg = custom_match input_args = custom_args else: where_arg = 'plugin = %s' input_args = [plugin_name] if search is not None: where_arg += ' AND search = %s' input_args.append(search) if destination is not None: where_arg += ' AND destination = %s' input_args.append(destination) cursor = data.db_select(bot, from_arg='schedule', where_arg=where_arg, additional='ORDER BY time ASC', input_args=input_args, safe=False) entries = cursor.fetchall() converted = [] for entry in entries: if entry[3]: payload = json.loads(entry[3]) else: payload = entry[3] converted.append(entry[:3] + (payload, ) + entry[4:]) return converted
async def character_browse(bot, context): """Browses a list of all characters.""" character_listing = data.db_select( bot, from_arg='characters', additional='ORDER BY clean_name ASC').fetchall() if not character_listing: raise CBException("There are no characters in the database.") page_index = 0 if context.arguments[0]: search = context.arguments[0] closest_index = 0 for index, character in enumerate(character_listing): if search <= character.clean_name: closest_index = index else: break page_index = int(closest_index / 10) # 10 entries per page embed = discord.Embed( title=':book: Character browser', description='{} total character{}'.format( len(character_listing), '' if len(character_listing) == 1 else 's')) state_data = [page_index, character_listing] return Response( embed=_build_browser_menu(bot, embed, *state_data), message_type=MessageTypes.INTERACTIVE, extra_function=_browser_menu, extra={'buttons': ['⬅', '➡'], 'userlock': False}, state_data=state_data)
def _user_character_search(bot, command_author, owner=None, character_search=None): """Finds characters under the given owner, search, or both.""" # Setup select arguments where_args, input_args = [], [] if not (owner or character_search): where_args.append('owner_id=%s') input_args.append(command_author.id) if owner: where_args.append('owner_id=%s') input_args.append(owner.id) if character_search: where_args.append('clean_name=%s') input_args.append(character_search) # Get character list cursor = data.db_select( bot, from_arg='characters', where_arg=' AND '.join(where_args), input_args=input_args) characters = cursor.fetchall() if cursor else [] if not characters: if owner: cursor = data.db_select( bot, from_arg='characters', where_arg='owner_id=%s', input_args=[owner.id]) owner_characters = cursor.fetchall() if cursor else [] if owner_characters: raise CBException( "{} has no character named \"{}\".".format( owner.mention, character_search)) else: raise CBException("{} has no character entries.".format(owner.mention)) elif character_search: raise CBException("No character named \"{}\" was found.".format(character_search)) else: raise CBException( "You have no character entries!\n" "You can create one with `{}character create`".format( utilities.get_invoker(bot, getattr(command_author, 'guild', None)))) # Check if character list contains characters made by the command author character_index = 0 if not owner: for index, character in enumerate(characters): if character.owner_id == command_author.id: character_index = index break return [character_index, characters]
async def list_text(bot, context): list_lines = [] for text_type in TYPE_NAMES: list_lines.append('\n\n' + text_type) cursor = data.db_select(bot, from_arg='txyz_' + text_type) for key, value in cursor.fetchall(): list_lines.append('\t{}: {}'.format(key, value)) text_file = utilities.get_text_as_file('\n'.join(list_lines)) discord_file = discord.File(text_file, 'txyz_list.txt') return Response(content='Table contents:', file=discord_file)
def __call__(self, bot, message, value, *a): if len(value) > 50: raise CBException("Names cannot be longer than 50 characters.") clean_name = _clean_text_wrapper(value) cursor = data.db_select( bot, from_arg='characters', where_arg='clean_name=%s AND owner_id=%s', input_args=(clean_name, message.author.id)) match = cursor.fetchone() if not match: raise CBException("You don't have a character by that name.") return Character(message.author, match.data, match.tags)
async def awoo_stats(bot, context): """Pulls stats on the given user.""" user = context.arguments[0] or context.author cursor = data.db_select(bot, from_arg='awoo', where_arg='user_id=%s', input_args=[user.id]) entry = cursor.fetchone() if cursor else None if not entry: raise CBException( '{} has not made a single awoo violation. What a good cookie.'.format(user.mention)) embed = discord.Embed(title=':scales: Awoo violation statistics', description=user.mention) embed.add_field(name='Debt', value='${}'.format(entry.debt)) embed.add_field(name='Violations', value='{}'.format(entry.violations)) return Response(embed=embed)
async def _cycle_specific(bot, cycle_type): table_name = 'txyz_' + TYPE_NAMES[cycle_type] cursor = data.db_select(bot, from_arg=table_name, additional='ORDER BY RANDOM()', limit=1) result = cursor.fetchone() if result: result = result[1] else: result = DEFAULTS[cycle_type] txyz_guild = bot.get_guild(TXYZ_GUILD) selected_channel = txyz_guild.voice_channels[cycle_type] await selected_channel.edit(name='_' + result) logger.debug('New %s: %s', table_name[5:-1], result) return result
async def _start_scheduler(bot): """Starts the interal scheduler.""" await bot.wait_until_ready() if bot.schedule_timer: # Scheduler already running bot.schedule_timer.cancel() bot.schedule_timer = None cursor = data.db_select( bot, from_arg='schedule', additional='ORDER BY time ASC', limit=1, safe=False) result = cursor.fetchone() if result: delta = result.time - time.time() logger.debug("Starting scheduled event %s", result.id) bot.schedule_timer = asyncio.ensure_future(_schedule_timer(bot, result, delta)) else: logger.debug("No pending scheduled event available.")
async def awoo_stats(bot, context): """Pulls stats on the given user.""" user = context.arguments[0] or context.author cursor = data.db_select(bot, from_arg='awoo', where_arg='user_id=%s', input_args=[user.id]) entry = cursor.fetchone() if cursor else None if not entry: raise CBException( '{} has not made a single awoo violation. What a good cookie.'. format(user.mention)) embed = discord.Embed(title=':scales: Awoo violation statistics', description=user.mention) embed.add_field(name='Debt', value='${}'.format(entry.debt)) embed.add_field(name='Violations', value='{}'.format(entry.violations)) return Response(embed=embed)
async def awoo_leaderboard(bot, context): """Displays the top 10 violators.""" cursor = data.db_select(bot, from_arg='awoo', additional='ORDER BY debt DESC', limit=10) entries = cursor.fetchall() if cursor else [] if not entries: raise CBException("Nobody has made any awoo violations yet!") stats = [[], []] # debt/violations, user for index, entry in enumerate(entries): stats[0].append('`{0}.` ${1.debt} | {1.violations}'.format(index + 1, entry)) user = data.get_member(bot, entry.user_id, safe=True, attribute='mention') user = user or 'Unknown ({})'.format(entry.user_id) stats[1].append('`\u200b`{}'.format(user)) embed = discord.Embed(title=':scales: Awoo violation leaderboard') embed.add_field(name='Debt | Violations', value='\n'.join(stats[0])) embed.add_field(name='User', value='\n'.join(stats[1])) return Response(embed=embed)
async def character_search(bot, context): """Searches for characters with the given list of tags.""" tags = [_clean_text_wrapper(it) for it in context.arguments] cursor = data.db_select( bot, from_arg='characters', where_arg='tags @> %s', input_args=[tags], additional='ORDER BY clean_name ASC') character_listing = cursor.fetchall() if cursor else [] if not character_listing: raise CBException("No characters found matching those tags.") embed = discord.Embed( title=':book: Character search', description='{} character{} matching: #{}'.format( len(character_listing), '' if len(character_listing) == 1 else 's', ' #'.join(tags))) state_data = [0, character_listing] return Response( embed=_build_browser_menu(bot, embed, *state_data), message_type=MessageTypes.INTERACTIVE, extra_function=_browser_menu, extra={'buttons': ['⬅', '➡'], 'userlock': False}, state_data=state_data)
async def _start_scheduler(bot): """Starts the interal scheduler.""" if bot.schedule_timer: # Scheduler already running bot.schedule_timer.cancel() bot.schedule_timer = None cursor = data.db_select(bot, from_arg='schedule', additional='ORDER BY time ASC', limit=1, safe=False) result = cursor.fetchone() if result: delta = result[0] - time.time() logger.debug("_start_scheduler is starting a scheduled event.") bot.schedule_timer = asyncio.ensure_future( _schedule_timer(bot, result, delta)) else: logger.debug( "_start_scheduler could not find a pending scheduled event.")
def get_schedule_entries( bot, plugin_name, search=None, destination=None, custom_match=None, custom_args=[]): """Gets the entries given the search or match arguments.""" if custom_match: where_arg = custom_match input_args = custom_args else: where_arg = 'plugin = %s' input_args = [plugin_name] if search is not None: where_arg += ' AND search = %s' input_args.append(search) if destination is not None: where_arg += ' AND destination = %s' input_args.append(destination) cursor = data.db_select( bot, from_arg='schedule', where_arg=where_arg, additional='ORDER BY time ASC', input_args=input_args, safe=False) return cursor.fetchall()
def _get_tag_dictionary(bot, guild): """Retrieves the tag dictionary of the server.""" if configurations.get(bot, 'tags.py', 'global_tags'): table_suffix = 'global' else: table_suffix = str(guild.id) tags_plugin = bot.plugins['tags.py'] sound_bit = tags_plugin._get_flag_bits(['sound']) private_bit = tags_plugin._get_flag_bits(['private']) cursor = data.db_select( bot, from_arg='tags', table_suffix=table_suffix, where_arg='flags & %s = %s AND flags & %s = 0', input_args=[sound_bit, sound_bit, private_bit]) raw_tag_list = cursor.fetchall() if cursor else [] if not raw_tag_list: raise CBException("No sound tags available.") tag_dictionary = {} for tag in raw_tag_list: tag_dictionary[tag.key] = {'name': tag.name, 'hits': tag.hits} return tag_dictionary
async def _create_session(bot, owner, editing=None): """Creates a session for character creation or editing""" webhook = await DATA_CHANNEL.create_webhook(name='ready:{}'.format(owner.id)) # Upload data as a single file cursor = data.db_select( bot, from_arg='characters', where_arg='owner_id=%s', input_args=[owner.id]) characters = cursor.fetchall() if cursor else [] create_data = utilities.get_text_as_file(json.dumps({ "version": DATA_VERSION, "webhook": [str(webhook.id), webhook.token], "existing_names": list(character.clean_name for character in characters), "editing": editing })) url = await utilities.upload_to_discord(bot, create_data, filename='data', close=True) url_segments = [it[::-1] for it in url[::-1].split('/')[2:0:-1]] # sorry session_code = '{}:{}'.format(*url_segments) # Track webhook usage data.add(bot, __name__, 'tracker', webhook, user_id=owner.id, volatile=True) data.add(bot, __name__, 'owner', owner, user_id=webhook.id, volatile=True) data.add(bot, __name__, 'stage', 0, user_id=webhook.id, volatile=True) # Add webhook ID to global IDs global DATA_CHANNEL_WEBHOOK_IDS DATA_CHANNEL_WEBHOOK_IDS.append(webhook.id) # Send the owner the link embed = discord.Embed( title='Click here to access the character creator', url='https://jkchen2.github.io/character-template/#{}'.format(session_code), description='Your session code is:\n`{}`'.format(session_code)) await owner.send(embed=embed) # Schedule a session timeout utilities.schedule( bot, __name__, time.time() + 7200, _session_timeout_notify, search=str(webhook.id), destination='u{}'.format(owner.id), info='Character creator session timeout') return session_code
async def character_list(bot, context): """Lists the characters of the given user.""" owner = context.arguments[0] if context.arguments[0] else context.author cursor = data.db_select( bot, from_arg='characters', where_arg='owner_id=%s', input_args=[owner.id]) characters = cursor.fetchall() if cursor else [] if not characters: raise CBException("{} has no characters.".format(owner.mention)) embed = discord.Embed( title='Character list', description='Owner: {}\n{} character{}'.format( owner.mention, len(characters), '' if len(characters) == 1 else 's')) for character in characters: attributes = character.data['attributes'] common_attributes = [] for key in COMMON_ATTRIBUTES: if key in attributes: common_attributes.append('{}: {}'.format(key, attributes[key])) if not common_attributes: common_attributes = ['No common attributes'] embed.add_field(name=character.name, value='\u200b{}'.format('\n'.join(common_attributes))) return Response(embed=embed)
async def _schedule_timer(bot, raw_entry, delay): task_comparison = bot.schedule_timer await asyncio.sleep(0.5) logger.debug("_schedule_timer sleeping for %s seconds...", delay) await asyncio.sleep(delay) if task_comparison is not bot.schedule_timer: logger.debug( "_schedule_timer was not cancelled! Cancelling this scheduler...") return try: cursor = data.db_select(bot, select_arg='min(time)', from_arg='schedule') minimum_time = cursor.fetchone()[0] data.db_delete(bot, 'schedule', where_arg='time=%s', input_args=[minimum_time], safe=False) except Exception as e: logger.warn("_schedule_timer failed to delete schedule entry. %s", e) raise e try: logger.debug("_schedule_timer done sleeping for %s seconds!", delay) scheduled_time, plugin, function, payload, search, destination, info = raw_entry if payload: payload = json.loads(payload) plugin = bot.plugins[plugin] function = getattr(plugin, function) late = delay < -60 asyncio.ensure_future( function(bot, scheduled_time, payload, search, destination, late)) except Exception as e: logger.warn("Failed to execute scheduled function: %s", e) raise e asyncio.ensure_future(_start_scheduler(bot))
async def _violation_notification(bot, message, awoo_tier, send_message=True): """ Logs the violation and (optionally) sends the user a notification. Standard notification: once per violation, up to 1 time None: 2 violations Silence notification: 1 violation Reset period for notifications is 1 minute. Stress indicates a number of users making a violation within a 60 second period. Tier 1: 3 members Tier 2: 5 members Tier 3: 8 members """ author, channel = message.author, message.channel current_time = time.time() violation_data = data.get( bot, __name__, 'user_violation', user_id=author.id, volatile=True) channel_violation_data = data.get( bot, __name__, 'channel_violation', channel_id=channel.id, volatile=True) if not violation_data or current_time - violation_data['time'] >= 60: violation_data = {'time': 0, 'violations': 0} data.add(bot, __name__, 'user_violation', violation_data, user_id=author.id, volatile=True) if not channel_violation_data or current_time - channel_violation_data['time'] >= 60: channel_violation_data = {'time': 0, 'violators': set(), 'sent_tier': 0} data.add( bot, __name__, 'channel_violation', channel_violation_data, channel_id=channel.id, volatile=True) violation_data['violations'] += 1 violation_data['time'] = current_time channel_violation_data['violators'].add(author.id) channel_violation_data['time'] = current_time # Update table set_arg = 'debt = debt+%s, violations = violations+1' if awoo_tier == 2: set_arg += ', sneaky = sneaky+1' cursor = data.db_select(bot, from_arg='awoo', where_arg='user_id=%s', input_args=[author.id]) entry = cursor.fetchone() if cursor else None if entry: data.db_update( bot, 'awoo', set_arg=set_arg, where_arg='user_id=%s', input_args=[fine, author.id]) else: data.db_insert(bot, 'awoo', input_args=[author.id, fine, 1, 1 if awoo_tier == 2 else 0]) # Add a snarky message depending on the tier if awoo_tier == 2: # Attempted bypass snark = random.choice(statements['bypass']) + '\n' elif awoo_tier == 3: # Legalization plea snark = random.choice(statements['legalize']) + '\n' else: snark = '' # Notify user logger.debug("Violations: %s", violation_data['violations']) text = '' if violation_data['violations'] <= 1: text = "{}{} has been fined ${} for an awoo violation.".format(snark, author.mention, fine) elif violation_data['violations'] == 4: text = "{} {}".format(author.mention, random.choice(statements['silence'])) elif awoo_tier == 3 and violation_data['violations'] <= 3: # Legalization plea, but silent text = snark if send_message and text: await channel.send(content=text) else: await message.add_reaction(random.choice(['🚩', '🛑', '�', '⛔', '🚫'])) # Stress violators, sent_tier = channel_violation_data['violators'], channel_violation_data['sent_tier'] if (len(violators) == 3 and sent_tier == 0 or len(violators) == 5 and sent_tier == 1 or len(violators) == 8 and sent_tier == 2): if send_message: await message.channel.send(random.choice(statements['stress'][sent_tier])) channel_violation_data['sent_tier'] += 1
async def _violation_notification(bot, message, awoo_tier, send_message=True): """ Logs the violation and (optionally) sends the user a notification. Standard notification: once per violation, up to 1 time None: 2 violations Silence notification: 1 violation Reset period for notifications is 1 minute. Stress indicates a number of users making a violation within a 60 second period. Tier 1: 3 members Tier 2: 5 members Tier 3: 8 members """ author, channel = message.author, message.channel current_time = time.time() violation_data = data.get(bot, __name__, 'user_violation', user_id=author.id, volatile=True) channel_violation_data = data.get(bot, __name__, 'channel_violation', channel_id=channel.id, volatile=True) if not violation_data or current_time - violation_data['time'] >= 60: violation_data = {'time': 0, 'violations': 0} data.add(bot, __name__, 'user_violation', violation_data, user_id=author.id, volatile=True) if not channel_violation_data or current_time - channel_violation_data[ 'time'] >= 60: channel_violation_data = { 'time': 0, 'violators': set(), 'sent_tier': 0 } data.add(bot, __name__, 'channel_violation', channel_violation_data, channel_id=channel.id, volatile=True) violation_data['violations'] += 1 violation_data['time'] = current_time channel_violation_data['violators'].add(author.id) channel_violation_data['time'] = current_time # Update table set_arg = 'debt = debt+%s, violations = violations+1' if awoo_tier == 2: set_arg += ', sneaky = sneaky+1' cursor = data.db_select(bot, from_arg='awoo', where_arg='user_id=%s', input_args=[author.id]) entry = cursor.fetchone() if cursor else None if entry: data.db_update(bot, 'awoo', set_arg=set_arg, where_arg='user_id=%s', input_args=[fine, author.id]) else: data.db_insert( bot, 'awoo', input_args=[author.id, fine, 1, 1 if awoo_tier == 2 else 0]) # Add a snarky message depending on the tier if awoo_tier == 2: # Attempted bypass snark = random.choice(statements['bypass']) + '\n' elif awoo_tier == 3: # Legalization plea snark = random.choice(statements['legalize']) + '\n' else: snark = '' # Notify user logger.debug("Violations: %s", violation_data['violations']) text = '' if violation_data['violations'] <= 1: text = "{}{} has been fined ${} for an awoo violation.".format( snark, author.mention, fine) elif violation_data['violations'] == 4: text = "{} {}".format(author.mention, random.choice(statements['silence'])) elif awoo_tier == 3 and violation_data[ 'violations'] <= 3: # Legalization plea, but silent text = snark if send_message and text: await channel.send(content=text) else: await message.add_reaction( random.choice(['🚩', '🛑', '�', '⛔', '🚫'])) # Stress violators, sent_tier = channel_violation_data[ 'violators'], channel_violation_data['sent_tier'] if (len(violators) == 3 and sent_tier == 0 or len(violators) == 5 and sent_tier == 1 or len(violators) == 8 and sent_tier == 2): if send_message: await message.channel.send( random.choice(statements['stress'][sent_tier])) channel_violation_data['sent_tier'] += 1
async def _process_data(bot, author, url, propagate_error=False): """Checks the given data and edits/adds an entry to the database.""" error_code = 1 raw_data = await utilities.download_url(bot, url, use_fp=True) reader = codecs.getreader('utf-8') # SO: 6862770 try: parsed = json.load(reader(raw_data)) except Exception as e: raise CBException("Failed to load the raw data.", e=e) # Check that values are within proper ranges try: if 'version' not in parsed: raise CBException("Missing version number.") if 'type' not in parsed: raise CBException("Missing character type.") if 'name' not in parsed: raise CBException("Missing name.") if 'attributes' not in parsed: raise CBException("Missing attributes.") if 'attribute_order' not in parsed: raise CBException("Missing attribute order.") if 'thumbnail' not in parsed: raise CBException("Missing thumbnail.") if 'images' not in parsed: raise CBException("Missing images.") if 'embed_color' not in parsed: raise CBException("Missing embed color.") if 'tags' not in parsed: raise CBException("Missing tags.") # Check version total_characters = 0 version = parsed['version'] if not isinstance(version, int): raise CBException("Invalid version type. [int]") if version != DATA_VERSION: error_code = 4 raise CBException( "Invalid or outdated data format. Please use the character creator site.") # Check type and name character_type = parsed['type'] if character_type not in CHARACTER_TYPES: raise CBException("Invalid character type.") name = parsed['name'] clean_name = _clean_text_wrapper(name) if not isinstance(name, str): raise CBException("Invalid name type. [string]") if not 1 <= len(name) <= 100: raise CBException("Invalid name length. [1-100]") total_characters += len(name) # Check attributes attributes = parsed['attributes'] if not isinstance(attributes, dict): raise CBException("Invalid attributes type. [dictionary]") if not 0 <= len(attributes) <= 20: raise CBException("Invalid number of attributes. [1-20]") for key, value in attributes.items(): if not isinstance(value, str): raise CBException("An attribute has an invalid type. [string]") if not 1 <= len(key) <= 50: raise CBException("Invalid attribute name length. [1-50]") if key in COMMON_ATTRIBUTES and not 1 <= len(value) <= 50: raise CBException("Invalid common attribute value length. [1-50]") elif not 1 <= len(value) <= 1000: raise CBException("Invalid attribute value length. [1-1000]") total_characters += len(key) + len(value) # Check thumbnail thumbnail = parsed['thumbnail'] if not isinstance(thumbnail, (str, type(None))): raise CBException("Invalid thumbnail type. [string]") if isinstance(thumbnail, str) and not _valid_url(thumbnail): error_code = 2 raise CBException("Invalid thumbnail URL.") # Check images images = parsed['images'] if not isinstance(images, list): raise CBException("Invalid images type. [list]") if not 0 <= len(images) <= 10: raise CBException("Invalid number of images. [0-10]") for image in images: if not isinstance(image, list): raise CBException("Invalid image type. [list]") if len(image) != 3: raise CBException("Invalid image metadata length. [3]") for meta in image: if not isinstance(meta, str): raise CBException("Invalid image metadata type. [string]") if not 1 <= len(image[0]) <= 500: # Direct image URL raise CBException("Invalid direct image URL length. [1-500]") if not _valid_url(image[0]): error_code = 3 raise CBException("Invalid direct image URL.") if not 0 <= len(image[1]) <= 500: # Source URL raise CBException("Invalid source URL length. [0-500]") if not 0 <= len(image[2]) <= 100: # Caption raise CBException("Invalid image caption length. [0-100]") # Check embed color embed_color = parsed['embed_color'] if not isinstance(embed_color, (int, type(None))): raise CBException("Invalid embed color type. [int]") if isinstance(embed_color, int) and not 0x0 <= embed_color <= 0xffffff: raise CBException("Invalid embed color range. [0x0-0xffffff]") # Version 2 stuff attribute_order = parsed['attribute_order'] if not isinstance(attribute_order, list): raise CBException("Invalid attribute order type. [list]") tags = parsed['tags'] if not isinstance(tags, list): raise CBException("Invalid tags type. [list]") # Check attribute_order order_set = set(attribute_order) attribute_set = set(attributes) if len(attribute_order) != len(order_set): raise CBException("Duplicate attribute order entry.") if order_set != attribute_set: raise CBException("Attribute order does not match attribute set.") # Check tags tags = parsed['tags'] tags_raw = parsed['tags_raw'] if not 0 <= len('#'.join(tags)) <= 260: # +60 for name and type raise CBException("Invalid tags length. [0-200]") if clean_name not in tags: raise CBException("Character name not in tags.") for character_type in CHARACTER_TYPES: if character_type in tags: break else: raise CBException("Character type not in tags.") if len(set(tags)) != len(tags): raise CBException("Duplicate tags exist.") for tag in tags: test = _clean_text_wrapper(tag, lowercase=False) if test != tag: raise CBException("Invalid tag.") total_characters += len(tag) if total_characters > 3000: raise CBException("Total characters exceeded 3000.") except BotException as e: if propagate_error: raise e else: await author.send("The data checks failed. Error:\n{}".format(e.error_details)) return error_code created_time = int(time.time()) dt = datetime.datetime.utcfromtimestamp(created_time) json_data = Json({ 'type': character_type, 'version': DATA_VERSION, 'name': name, 'clean_name': clean_name, 'owner_id': author.id, 'attributes': attributes, 'attribute_order': attribute_order, 'thumbnail': thumbnail, 'images': images, 'embed_color': embed_color, 'tags': tags, 'tags_raw': tags_raw, 'created': created_time }) # Check for edit or entry creation cursor = data.db_select( bot, select_arg='clean_name', from_arg='characters', where_arg='owner_id=%s', input_args=[author.id]) existing_names = [it[0] for it in cursor.fetchall()] if cursor else [] if clean_name in existing_names: # Edit data.db_update( bot, 'characters', set_arg='name=%s, data=%s, tags=%s, modified=%s', where_arg='owner_id=%s AND clean_name=%s', input_args=(name, json_data, tags, dt, author.id, clean_name)) content = "Edited the entry for {}.".format(name) else: # Create data.db_insert( bot, 'characters', input_args=[author.id, name, clean_name, json_data, tags, dt]) content = "Created a new entry for {}.".format(name) if propagate_error: return content else: await author.send(content) return 0