async def add_text(bot, context): text_type, new_text = context.arguments table_name = 'txyz_' + TYPE_NAMES[text_type] data.db_insert(bot, table_name, specifiers='value', input_args=new_text, safe=False) return Response(content='Added a {}.'.format(table_name[5:-1]))
def schedule(bot, plugin_name, time, function, payload=None, search=None, destination=None, info=None): """Adds the entry to the schedule table and starts the timer. It should be noted that the function CANNOT be a lambda function. It must be a function residing in the plugin. The payload should be a standard dictionary. The search keyword argument is to assist in later deletion or modification. Time should be a number in seconds from the epoch. """ input_args = [ int(time), plugin_name, function.__name__, json.dumps(payload) if payload else None, search, destination, info ] data.db_insert(bot, 'schedule', input_args=input_args, safe=False) asyncio.ensure_future(_start_scheduler(bot))
def schedule( bot, plugin_name, scheduled_time, function, payload=None, search=None, destination=None, info=None): """Adds the entry to the schedule table and starts the timer. It should be noted that the function CANNOT be a lambda function. It must be a function residing in the top level of the plugin. Time should be a number in seconds from the epoch. The asynchronous function should take 6 arguments: bot -- An instance of the bot. scheduled_time -- Time at which the given function should be called. payload -- Same as the keyword argument. search -- Same as the keyword argument. destination -- Same as the keyword argument. late -- Whether or not the function was called late due to bot downtime. info -- Same as the keyword argument. id -- Unique ID assigned to the entry when it was created. Usually unused. Keyword arguments: payload -- Standard json-serializable dictionary search -- Used to assist in later deletion or modification destination -- Starts with either a 'c' or 'u', then the ID of the channel or user This is used to help determine what will need to be messaged. info -- Used as a description for the scheduled event if `!base notifications` is used. """ input_args = [ int(scheduled_time), plugin_name, function.__name__, Json(payload), search, destination, info ] data.db_insert(bot, 'schedule', input_args=input_args, safe=False) asyncio.ensure_future(_start_scheduler(bot))
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
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 _dump(bot, dump_data, log_channel, details='No details provided.', query=None, moderator_id=None, logged_channels=[]): """Dumps the given built dump data to the log channel. logged_channels specifies what channels to log. If no channels are given, this logs all channels by default. """ built_query = '&highlight={}'.format(query) if query else '' logged_channel_ids = [it.id for it in logged_channels] guild = log_channel.guild # Remove extra channels and members if logged_channels: valid_members = set() to_remove = [] total_messages = 0 for channel_id, channel_data in dump_data['channels'].items(): if int(channel_id) in logged_channel_ids: for message in channel_data['messages']: valid_members.add(message['author']) total_messages += len(channel_data['messages']) else: to_remove.append(channel_id) for it in to_remove: del dump_data['channels'][it] to_remove = [ it for it in dump_data['members'] if it not in valid_members ] for it in to_remove: del dump_data['members'][it] else: total_messages = dump_data['total'] # Build full dump string full_dump = json.dumps(dump_data) # Send logs and get session code log_message = await utilities.send_text_as_file(log_channel, full_dump, 'logs') url = log_message.attachments[0].url session_code = '{}:{}'.format( *[it[::-1] for it in url[::-1].split('/')[2:0:-1]]) # Build embed data embed = discord.Embed( title='Click here to view the message logs', url='https://jkchen2.github.io/log-viewer?session={}{}'.format( session_code, built_query), timestamp=datetime.utcnow()) embed.add_field(name='Details', value=details, inline=False) if logged_channels: embed.add_field(name='Channels', value=', '.join(it.mention for it in logged_channels)) # Add incident number entry_data = [ session_code, details, query, moderator_id, None, # messageid int(time.time()), Json({}) ] cursor = data.db_insert(bot, 'autolog', table_suffix=guild.id, input_args=entry_data, create='autolog_template') inserted = cursor.fetchone() embed.set_footer(text='Incident #{}'.format(inserted.id)) # Send embed and update messageid message = await log_channel.send(embed=embed) data.db_update(bot, 'autolog', table_suffix=guild.id, set_arg='messageid=%s', where_arg='id=%s', input_args=[message.id, inserted.id]) return total_messages
async def _dump( bot, dump_data, log_channel, details='No details provided.', query=None, moderator_id=None, logged_channels=[]): """Dumps the given built dump data to the log channel. logged_channels specifies what channels to log. If no channels are given, this logs all channels by default. """ built_query = '&highlight={}'.format(query) if query else '' logged_channel_ids = [it.id for it in logged_channels] guild = log_channel.guild # Remove extra channels and members if logged_channels: valid_members = set() to_remove = [] total_messages = 0 for channel_id, channel_data in dump_data['channels'].items(): if int(channel_id) in logged_channel_ids: for message in channel_data['messages']: valid_members.add(message['author']) total_messages += len(channel_data['messages']) else: to_remove.append(channel_id) for it in to_remove: del dump_data['channels'][it] to_remove = [it for it in dump_data['members'] if it not in valid_members] for it in to_remove: del dump_data['members'][it] else: total_messages = dump_data['total'] # Build full dump string full_dump = json.dumps(dump_data) # Send logs and get session code log_message = await utilities.send_text_as_file(log_channel, full_dump, 'logs') url = log_message.attachments[0].url session_code = '{}:{}'.format(*[it[::-1] for it in url[::-1].split('/')[2:0:-1]]) # Build embed data embed = discord.Embed( title='Click here to view the message logs', url='https://jkchen2.github.io/log-viewer?session={}{}'.format(session_code, built_query), timestamp=datetime.utcnow()) embed.add_field(name='Details', value=details, inline=False) if logged_channels: embed.add_field(name='Channels', value=', '.join(it.mention for it in logged_channels)) # Add incident number entry_data = [ session_code, details, query, moderator_id, None, # messageid int(time.time()), Json({}) ] cursor = data.db_insert( bot, 'autolog', table_suffix=guild.id, input_args=entry_data, create='autolog_template') inserted = cursor.fetchone() embed.set_footer(text='Incident #{}'.format(inserted.id)) # Send embed and update messageid message = await log_channel.send(embed=embed) data.db_update( bot, 'autolog', table_suffix=guild.id, set_arg='messageid=%s', where_arg='id=%s', input_args=[message.id, inserted.id]) return total_messages