async def kick_member(bot, context): """Deletes member from spreadsheet.""" global gc family_name = context.arguments[0] family_name = family_name.lower() author_id = context.author.id response = Response() # VAR index_of_member_list = None list_of_family_names = [] index_of_user = None # Check for malicious family name. if not (len(family_name) > 1 and '=' not in family_name[0]): response.content = f'{family_name} is not a valid family name.' return response # opens spreadsheet sh = await utilities.future(gc.open, SPREADSHEET_NAME) # gets all available worksheets worksheet = await utilities.future(sh.worksheets) # get index of member list for i, sheet in enumerate(worksheet): if MEMBER_LIST_NAME in str(sheet): index_of_member_list = i if index_of_member_list == None: response.content = 'Something went wrong when initalizing sheets (check member list name).' return response # get member list form_member_data = await utilities.future(sh.get_worksheet, index_of_member_list) form_member_list = await utilities.future(form_member_data.get_all_values) for i, row in enumerate(form_member_list): if i >= 4: # names start on row 4 here if len(row[0]) != 0: list_of_family_names.append(row[0].lower()) # invalid family name if family_name not in list_of_family_names: response.content = f'{family_name} not a valid family name.' return response # get index of user index_of_user = list_of_family_names.index(family_name) index_of_user += 5 # offset, sheet specific # deletes entries in that row await utilities.future(form_member_data.update, f'A{index_of_user}:E{index_of_user}', [['', '', '', '', '']], value_input_option='user_entered') form_member_data.sort((1, 'asc'), range='A5:Z105') current_time = datetime.datetime.now().strftime('%m/%d/%Y %H:%M:%S') response.content = f'<@{author_id}> successfully kicked {family_name} on {current_time} from the spreadsheet.' return response
async def take_attendance(bot, context): """Takes all users in voice channel and logs it.""" response = Response() # VARS attendance_list = [] day_of_week = None # 0 = Monday, 6 = Sunday index_of_nw_attendance = None # checks to see if user is in voice channel if not context.author.voice: raise CBException('You must be in a voice channel to use this feature.') voice_channel = context.author.voice.channel # get all members within voice channel for member in voice_channel.members: attendance_list.append(member) if len(attendance_list) == 1: raise CBException('Are you really node warring with yourself? Loser.') # get day day_of_week = datetime.datetime.today().weekday() if day_of_week == 5: raise CBException('Since when did Influence siege? lmfao pvx guild btw') # opens spreadsheet + nw attendance sh = await utilities.future(gc.open, SPREADSHEET_NAME) worksheet = await utilities.future(sh.worksheets) # get index of nw attendance for i, sheet in enumerate(worksheet): if NW_ATTENDANCE_NAME in str(sheet): index_of_nw_attendance = i nw_attendance = await utilities.future(sh.get_worksheet, index_of_nw_attendance) # error handling if index_of_nw_attendance == None: response.content = 'Something went wrong when initializing sheets (check nw attendance name).' return response for index in range(len(attendance_list)): if day_of_week == 6: # if sunday await utilities.future(nw_attendance.update_cell, 3+index, NW_SUNDAY, str(attendance_list[index])) else: await utilities.future(nw_attendance.update_cell, 3+index, NW_MONDAY+day_of_week, str(attendance_list[index])) response.content = 'Attendance successfully taken.' return response
async def get_response(bot, context): response = Response() if context.author.voice: voice_channel = context.author.voice.channel voice_client = await utilities.join_and_ready(bot, voice_channel) options = {'format': 'bestaudio/best', 'noplaylist': True} downloader = YoutubeDL(options) url = context.arguments[0] try: file_location = data.get_from_cache(bot, None, url=url) if not file_location: logger.info("Not found in cache. Downloading...") info = await utilities.future(downloader.extract_info, url, download=False) download_url = info['formats'][0]['url'] file_location = await data.add_to_cache(bot, download_url, name=url) ffmpeg_options = '-protocol_whitelist "file,http,https,tcp,tls"' voice_client.play( discord.FFmpegPCMAudio(file_location, before_options=ffmpeg_options)) except BotException as e: raise e # Pass up except Exception as e: raise CBException("Something bad happened.", e=e) response.content = "Playing your stuff." else: raise CBException("You're not in a voice channel.") return response
async def to_be_belled(bot, context): server = context.arguments[0] bell_minutes = context.arguments[1] response = Response() # check inputs if server not in SERVERS: response.content = '{} is not a valid server.'.format(server) return response if bell_minutes < 1 or bell_minutes > 60: response.content = 'The bell time must be between 1 and 60 minutes.' return response # passes checks, update BELL_SERVERS BELL_SERVERS[server] = ['YES', bell_minutes, round(time.time())]
async def get_response(bot, context): response = Response() if context.author.voice: voice_channel = context.author.voice.channel voice_client = await utilities.join_and_ready(bot, voice_channel) options = {'format': 'bestaudio/best', 'noplaylist': True} downloader = YoutubeDL(options) url = context.arguments[0] try: file_location = data.get_from_cache(bot, None, url=url) if not file_location: logger.info("Not found in cache. Downloading...") info = await utilities.future(downloader.extract_info, url, download=False) download_url = info['formats'][0]['url'] file_location = await data.add_to_cache(bot, download_url, name=url) ffmpeg_options = '-protocol_whitelist "file,http,https,tcp,tls"' voice_client.play(discord.FFmpegPCMAudio(file_location, before_options=ffmpeg_options)) except BotException as e: raise e # Pass up except Exception as e: raise CBException("Something bad happened.", e=e) response.content = "Playing your stuff." else: raise CBException("You're not in a voice channel.") return response
async def _get_response(self, context): """Takes a context and builds a response.""" response = await commands.execute(self, context) if response is None: response = Response() elif self.selfbot and response.content: response.content = '\u200b' + response.content return response
async def get_response(bot, context): response = Response() use_plugin = configurations.get(bot, __name__, key='enable') if not use_plugin: response.content = ( "The GDQ plugin is currently disabled. (GDQ is probably over or hasn't started yet)") return response embed_template = discord.Embed( title='Games Done Quick', url='https://gamesdonequick.com/', colour=discord.Colour(0x00aff0), description='\[ [Stream]({}) ] \[ [Schedule]({}) ] \[ [Donate]({}) ]'.format( configurations.get(bot, __name__, 'stream_url'), configurations.get(bot, __name__, 'schedule_url'), configurations.get(bot, __name__, 'donate_url'))) embed_template.set_thumbnail(url='http://i.imgur.com/GcdqhUR.png') guild_id = context.guild.id if context.guild else None if context.index == 0: embed_template.add_field(name='Donation stats', value='Loading...', inline=False) response.game_index = data.get(bot, __name__, 'current_index', volatile=True, default=0) schedule_data = data.get(bot, __name__, 'schedule', volatile=True) games_list = schedule_data[response.game_index:response.game_index + 5] game_data = _embed_games_information(bot, games_list, guild_id) value = '\n\n'.join('**{}**\n{}'.format(*it) for it in game_data) embed_template.add_field(name='Schedule', value=value, inline=False) response.update_stats = True response.update_task = None response.message_type = MessageTypes.INTERACTIVE response.extra_function = gdq_menu response.extra = {'buttons': ['⬅', '⏺', '➡']} elif context.index == 1: # About embed_template.add_field(name='About', value=( "Games Done Quick (GDQ) is a week-long charity gaming marathon that " "brings together speedrunners from around the globe to raise money on a " "livestream. They are currently supporting {0}, and all donations go " "directly to the charity.\n\nCheck out the links above for the Twitch " "stream, games schedule, and the donation portal!").format( configurations.get(bot, __name__, 'charity'))) elif context.index == 2: # Status status_text = await _get_status(bot) embed_template.add_field(name='Status', value=status_text, inline=False) elif context.index == 3: # Current game embed_data = _get_current_game(bot, guild_id)[0] embed_template.add_field(name=embed_data[0], value=embed_data[1], inline=False) elif context.index == 4: # Next game(s) embed_data = _get_next_games(bot, context.arguments[0], guild_id) for name, value in embed_data: embed_template.add_field(name=name, value=value, inline=False) elif context.index == 5: # Search embed_data = _search_games(bot, context.arguments[0], guild_id=guild_id) embed_template.add_field(name=embed_data[0], value=embed_data[1], inline=False) elif context.index == 6: # Notify game = _search_games(bot, context.arguments[0], return_game=True) response.content = _toggle_notification( bot, game, context, use_channel='channel' in context.options) embed_template = None response.embed = embed_template return response
async def on_message(self, message, replacement_message=None): # Ensure bot can respond properly try: initial_data = self.can_respond(message) except Exception as e: # General error logger.error(e) logger.error(traceback.format_exc()) self.last_exception = e return if not initial_data: return # Ensure command is valid content = initial_data[0] elevation = 3 - (initial_data[4:0:-1] + [True]).index(True) split_content = content.split(' ', 1) if len(split_content) == 1: # No spaces split_content.append('') base, parameters = split_content base = base.lower() try: command = self.commands[base] except KeyError: logger.debug("Suitable command not found: " + base) return # Check that user is not spamming author_id = message.author.id direct = isinstance(message.channel, PrivateChannel) spam_value = self.spam_dictionary.get(author_id, 0) if elevation > 0 or direct: # Moderators ignore custom limit spam_limit = self.spam_limit else: spam_limit = min( self.spam_limit, data.get(self, 'core', 'spam_limit', guild_id=message.guild.id, default=self.spam_limit)) if spam_value >= spam_limit: if spam_value == spam_limit: self.spam_dictionary[author_id] = spam_limit + 1 plugins.broadcast_event(self, 'bot_on_user_ratelimit', message.author) await self.send_message( message.channel, "{0}, you appear to be issuing/editing " "commands too quickly. Please wait {1} seconds.". format(message.author.mention, self.spam_timeout)) return # Parse command and reply try: context = None with message.channel.typing(): logger.debug(message.author.name + ': ' + message.content) subcommand, options, arguments = parser.parse( self, command, parameters, message) context = self.Context( message, base, subcommand, options, arguments, subcommand.command.keywords, initial_data[0], elevation, message.guild, message.channel, message.author, direct, subcommand.index, subcommand.id, self) plugins.broadcast_event(self, 'bot_on_command', context) logger.info([subcommand, options, arguments]) response = await commands.execute(self, context) if response is None: response = Response() if self.selfbot and response.content: response.content = '\u200b' + response.content except Exception as e: # General error response = Response() destination = message.channel message_reference = await self.handle_error( e, message, context, response, edit=replacement_message, command_editable=True) else: # Attempt to respond send_arguments = response.get_send_kwargs(replacement_message) try: destination = response.destination if response.destination else message.channel message_reference = None if replacement_message: try: await replacement_message.edit(**send_arguments) message_reference = replacement_message except discord.NotFound: # Message deleted response = Response() message_reference = None elif (not response.is_empty() and not (self.selfbot and response.message_type is MessageTypes.REPLACE)): message_reference = await destination.send( **send_arguments) response.message = message_reference plugins.broadcast_event(self, 'bot_on_response', response, context) except Exception as e: message_reference = await self.handle_error( e, message, context, response, edit=replacement_message, command_editable=True) # Incremement the spam dictionary entry if author_id in self.spam_dictionary: self.spam_dictionary[author_id] += 1 else: self.spam_dictionary[author_id] = 1 # MessageTypes: # NORMAL - Normal. The issuing command can be edited. # PERMANENT - Message is not added to the edit dictionary. # REPLACE - Deletes the issuing command after 'extra' seconds. Defaults # to 0 seconds if 'extra' is not given. # ACTIVE - The message reference is passed back to the function defined # with 'extra_function'. If 'extra_function' is not defined, it will call # plugin.handle_active_message. # INTERACTIVE - Assembles reaction buttons given by extra['buttons'] and # calls 'extra_function' whenever one is pressed. # WAIT - Wait for event. Calls 'extra_function' with the result, or None # if the wait timed out. # # Only the NORMAL message type can be edited. response.message = message_reference if message_reference and isinstance(message_reference.channel, PrivateChannel): permissions = self.user.permissions_in( message_reference.channel) elif message_reference: permissions = message_reference.guild.me.permissions_in( message_reference.channel) else: permissions = None self.last_response = message_reference if response.message_type is MessageTypes.NORMAL: # Edited commands are handled in base.py if message_reference is None: # Forbidden exception return wait_time = self.edit_timeout if wait_time: self.edit_dictionary[str(message.id)] = message_reference await asyncio.sleep(wait_time) if str(message.id) in self.edit_dictionary: del self.edit_dictionary[str(message.id)] if message_reference.embeds: embed = message_reference.embeds[0] if embed.footer.text and embed.footer.text.startswith( '\u200b' * 3): embed.set_footer() try: await message_reference.edit(embed=embed) except: pass elif response.message_type is MessageTypes.REPLACE: try: if self.selfbot and not replacement_message: # Edit instead await message.edit(**send_arguments) else: if response.extra: await asyncio.sleep(response.extra) try: await message.delete(reason='Automatic') except: # Ignore permissions errors pass except Exception as e: message_reference = await self.handle_error( e, message, context, response, edit=message_reference) self.last_response = message_reference elif response.message_type is MessageTypes.ACTIVE: if message_reference is None: # Forbidden exception return try: await response.extra_function(self, context, response) except Exception as e: # General error message_reference = await self.handle_error( e, message, context, response, edit=message_reference) self.last_response = message_reference elif response.message_type is MessageTypes.INTERACTIVE: try: buttons = response.extra['buttons'] kwargs = response.extra.get('kwargs', {}) if 'timeout' not in kwargs: kwargs['timeout'] = 300 if 'check' not in kwargs: kwargs['check'] = (lambda r, u: r.message.id == message_reference.id and not u.bot) for button in buttons: await message_reference.add_reaction(button) reaction_check = await destination.get_message( message_reference.id) for reaction in reaction_check.reactions: if not reaction.me or reaction.count > 1: async for user in reaction.users(): if user != self.user and permissions.manage_messages: asyncio.ensure_future( message_reference.remove_reaction( reaction, user)) await response.extra_function(self, context, response, None, False) process_result = True while process_result is not False: try: if not permissions.manage_messages: add_task = self.wait_for( 'reaction_add', **kwargs) remove_task = self.wait_for( 'reaction_remove', **kwargs) done, pending = await asyncio.wait( [add_task, remove_task], return_when=FIRST_COMPLETED) result = next(iter(done)).result() for future in pending: future.cancel() else: # Can remove reactions result = await self.wait_for( 'reaction_add', **kwargs) if result[1] != self.user: asyncio.ensure_future( message_reference.remove_reaction( *result)) else: continue is_mod = data.is_mod(self, message.guild, result[1].id) if (response.extra.get('reactionlock', True) and not result[0].me or (response.extra.get('userlock', True) and not (result[1] == message.author or is_mod))): continue except (asyncio.futures.TimeoutError, asyncio.TimeoutError): await response.extra_function( self, context, response, None, True) process_result = False else: process_result = await response.extra_function( self, context, response, result, False) try: await response.message.clear_reactions() except: pass except Exception as e: message_reference = await self.handle_error( e, message, context, response, edit=message_reference) self.last_response = message_reference elif response.message_type is MessageTypes.WAIT: try: kwargs = response.extra.get('kwargs', {}) if 'timeout' not in kwargs: kwargs['timeout'] = 300 process_result = True while process_result is not False: try: result = await self.wait_for( response.extra['event'], **kwargs) except asyncio.TimeoutError: await response.extra_function( self, context, response, None) process_result = False else: process_result = await response.extra_function( self, context, response, result) if not response.extra.get('loop', False): process_result = False except Exception as e: message_reference = await self.handle_error( e, message, context, response, edit=message_reference) self.last_response = message_reference else: logger.error("Unknown message type: {}".format( response.message_type)) '''
async def get_response(bot, context): response = Response() use_plugin = configurations.get(bot, __name__, key='enable') if not use_plugin: response.content = ( "The GDQ plugin is currently disabled. (GDQ is probably over or hasn't started yet)" ) return response embed_template = discord.Embed( title='Games Done Quick', url='https://gamesdonequick.com/', colour=discord.Colour(0x00aff0), description='\[ [Stream]({}) ] \[ [Schedule]({}) ] \[ [Donate]({}) ]'. format(configurations.get(bot, __name__, 'stream_url'), configurations.get(bot, __name__, 'schedule_url'), configurations.get(bot, __name__, 'donate_url'))) embed_template.set_thumbnail(url='http://i.imgur.com/GcdqhUR.png') guild_id = context.guild.id if context.guild else None if context.index == 0: embed_template.add_field(name='Donation stats', value='Loading...', inline=False) response.game_index = data.get(bot, __name__, 'current_index', volatile=True, default=0) schedule_data = data.get(bot, __name__, 'schedule', volatile=True) games_list = schedule_data[response.game_index:response.game_index + 5] game_data = _embed_games_information(bot, games_list, guild_id) value = '\n\n'.join('**{}**\n{}'.format(*it) for it in game_data) embed_template.add_field(name='Schedule', value=value, inline=False) response.update_stats = True response.update_task = None response.message_type = MessageTypes.INTERACTIVE response.extra_function = gdq_menu response.extra = {'buttons': ['⬅', '⏺', '➡']} elif context.index == 1: # About embed_template.add_field( name='About', value= ("Games Done Quick (GDQ) is a week-long charity gaming marathon that " "brings together speedrunners from around the globe to raise money on a " "livestream. They are currently supporting {0}, and all donations go " "directly to the charity.\n\nCheck out the links above for the Twitch " "stream, games schedule, and the donation portal!").format( configurations.get(bot, __name__, 'charity'))) elif context.index == 2: # Status status_text = await _get_status(bot) embed_template.add_field(name='Status', value=status_text, inline=False) elif context.index == 3: # Current game embed_data = _get_current_game(bot, guild_id)[0] embed_template.add_field(name=embed_data[0], value=embed_data[1], inline=False) elif context.index == 4: # Next game(s) embed_data = _get_next_games(bot, context.arguments[0], guild_id) for name, value in embed_data: embed_template.add_field(name=name, value=value, inline=False) elif context.index == 5: # Search embed_data = _search_games(bot, context.arguments[0], guild_id=guild_id) embed_template.add_field(name=embed_data[0], value=embed_data[1], inline=False) elif context.index == 6: # Notify game = _search_games(bot, context.arguments[0], return_game=True) response.content = _toggle_notification(bot, game, context, use_channel='channel' in context.options) embed_template = None response.embed = embed_template return response
async def get_response(bot, context): """Gets a response given the parsed input. context attributes: bot -- A reference to the bot itself. message -- The discord.message object obtained from on_message. base -- The base command name that immediately follows the invoker. subcommand -- The subcommand that matched the parameters. index -- The index of the found subcommand. options -- A dictionary representing the options and potential positional arguments that are attached to them. arguments -- A list of strings that follow the syntax of the blueprint index for arguments following the options. keywords -- Another list of strings that holds all option keywords. These can be used to prevent database conflicts with user commands. cleaned_content -- Simply the message content without the invoker. """ # This is what the bot will say when it returns from this function. # The response object can be manipulated in many ways. The attributes of # the response will be passed into the send function. response = Response() response.content = '' # Default # Set to True if you want your message read with /tts (not recommended). response.tts = False # Default # The message type dictates how the bot handles your returned message. # # NORMAL - Normal. The issuing command can be edited. # PERMANENT - Message is not added to the edit dictionary. # REPLACE - Deletes the issuing command after 'extra' seconds. Defaults # to 0 seconds if 'extra' is not given. # ACTIVE - The message reference is passed back to the function defined # with 'extra_function'. If 'extra_function' is not defined, it will call # plugin.handle_active_message. # INTERACTIVE - Assembles reaction buttons given by extra['buttons'] and # calls 'extra_function' whenever one is pressed. # WAIT - Wait for event. Calls 'extra_function' with the result, or None # if the wait timed out. # # Only the NORMAL message type can be edited. response.message_type = MessageTypes.NORMAL # Default # The extra variable is used for some message types. response.extra = None # Default # Initially, check to make sure that you've matched the proper command. # If there is only one command specified, this may not be necessary. index, options, arguments = context.index, context.options, context.arguments if context.base == 'mycommand': # Then, the subcommand index will tell you which command syntax was # satisfied. The order is the same as was specified initially. if index == 0: # myoption response.content = "You called the first subcommand!" # Do other stuff... elif index == 1: # custom/attached # To see if an optional option was included in the command, use: if 'custom' in options: response.content += "You included the \"custom\" flag!\n" # Do stuff relevant to this flag here... # To get the parameter attached to an option, simply access it from # the options dictionary. if 'attached' in options: response.content += "The attached parmeter: {}\n".format( options['attached']) # In case somebody was looking for the help... if len(options) == 0: invoker = utilities.get_invoker(bot, guild=context.guild) response.content += ( "You didn't use either flag...\n" "For help, try `{}help mycommand`".format(invoker)) elif index == 2: # trailing arguments # If arguments are specified as trailing, they will be in a list. response.content += "The list of trailing arguments: {}".format( arguments) elif index == 3: # grouped arguments # All arguments are grouped together as the first element response.message_type = MessageTypes.PERMANENT response.content = ("You can't edit your command here.\n" "Single grouped argument: {}").format( arguments[0]) elif index == 4: # complex # This mixes elements of both examples seen above. response.content = ("The argument attached to the complex " "option: {}").format(options['complex']) if 'other' in options: response.content += "\nThe other option has attached: {}".format( options['other']) response.content += "\nLastly, the trailing arguments: {}".format( arguments) elif index == 5: # (Very slow) marquee # This demonstrates the active message type. # Check active_marquee to see how it works. response.message_type = MessageTypes.ACTIVE response.extra_function = active_marquee # The function to call response.extra = arguments[0] # The text to use response.content = "Setting up marquee..." # This will be shown first # Here's another command base. elif context.base == 'myothercommand': if index == 0: # keyword checker text = arguments[0] if not text: response.content = "You didn't say anything...\n" else: response.content = "This is your input: {}\n".format(text) if text in context.keywords: response.content += "Your input was in the list of keywords!\n" else: response.content += ( "Your input was not in the list of keywords. " "They are: {}\n").format(context.keywords) response.message_type = MessageTypes.PERMANENT response.delete_after = 15 response.content += "This message will self destruct in 15 seconds." else: # impossible command??? raise CBException( "This is a bug! You should never see this message.") elif context.base == 'wait': response.message_type = MessageTypes.WAIT # The extra attribute should consist of a dictionary containing the # event and any other kwargs. Most notably, you will likely want to # define the check used in wait_for. response.extra_function = custom_interaction response.extra = { 'event': 'message', 'kwargs': { 'timeout': 30, # Default 300 'check': lambda m: m.author == context.author, } } response.content = "Say something, {}.".format(context.author) return response
async def invite_member(bot, context): """Adds member to spreadsheet.""" global gc #family_name, discord_name = context.arguments family_name = context.arguments[0] family_name = family_name.lower() discord_name = context.arguments[1] author_id = context.author.id response = Response() # VAR index_of_member_list = None list_of_family_names = [] index_of_user = None empty_index = 0 # Check for malicious family name. if not (len(family_name) > 1 and '=' not in family_name[0]): response.content = f'{family_name} is not a valid family name.' return response if '=' == discord_name[0] or '#' not in discord_name: response.content = f'{discord_name} is not a valid Discord name. Cannot start with = or does not have a numeric identifier.' return response # opens spreadsheet sh = await utilities.future(gc.open, SPREADSHEET_NAME) # gets all available worksheets worksheet = await utilities.future(sh.worksheets) # get index of member list for i, sheet in enumerate(worksheet): if MEMBER_LIST_NAME in str(sheet): index_of_member_list = i if index_of_member_list == None: response.content = 'Something went wrong when initalizing sheets (check member list name).' return response # get member list form_member_data = await utilities.future(sh.get_worksheet, index_of_member_list) form_member_list = await utilities.future(form_member_data.get_all_values) for i, row in enumerate(form_member_list): if i >= 4: # names start on row 4 here if len(row[0]) != 0: list_of_family_names.append(row[0].lower()) if family_name in list_of_family_names: response.content = f'{family_name} is already on the sheet!' return response for i, element in enumerate(form_member_list): if len(element[0]) > 1: empty_index = i print(element[0]) print(empty_index) print(form_member_list[i]) empty_index += 2 # index starts at 1 for sheets and +1 for offset if empty_index == 106: response.content = 'The guild is currently full!' return response current_time = datetime.datetime.now().strftime('%m/%d/%Y %H:%M:%S') current_date = datetime.datetime.now().strftime('%Y-%m-%d') # fill sheet await utilities.future(form_member_data.update_cell, empty_index, 1, family_name) await utilities.future(form_member_data.update_cell, empty_index, 2, discord_name) await utilities.future(form_member_data.update_cell, empty_index, 3, current_date) await utilities.future(form_member_data.update_cell, empty_index, 4, 'Apprentice') await utilities.future(form_member_data.update_cell, empty_index, 5, 'Active') form_member_data.sort((1, 'asc'), range='A5:Z105') response.content = f'<@{author_id}> successfully added {family_name} to sheet on {current_time}.' return response
async def update_to_sheet(bot, context): """Updates family name to spreadsheet.""" global gc family_name, daily_pay_value = context.arguments family_name = family_name.lower() author_id = context.author.id response = Response() # check family name for malicious character if not (len(family_name) > 1 and '=' not in family_name[0]): response.content = f'{family_name} is not a valid family name.' return response # VAR index_of_form = None next_index = 0 index_of_member_list = None list_of_family_names = [] # opens spreadsheet sh = await utilities.future(gc.open, SPREADSHEET_NAME) # gets all available worksheets worksheet = await utilities.future(sh.worksheets) # get the index of the form we want for i, sheet in enumerate(worksheet): if FORM_NAME in str(sheet): index_of_form = i if MEMBER_LIST_NAME in str(sheet): index_of_member_list = i # checks to see if one of these doesn't exist if ((index_of_form is None) or (index_of_member_list is None)): response.content = 'Something went wrong when initializing sheets.' return response # getting member list form_member_data = await utilities.future(sh.get_worksheet, index_of_member_list) form_member_list = await utilities.future(form_member_data.get_all_values) for i, row in enumerate(form_member_list): if i > 4: # names start on row 4 here, first column if len(row[0]) != 0: list_of_family_names.append(row[0].lower()) # open the form sheet form_data = await utilities.future(sh.get_worksheet, index_of_form) form_values = await utilities.future(form_data.get_all_values) form = await utilities.future(sh.get_worksheet, index_of_form) # iterate over rows till we find an empty index for i, row in enumerate(form_values): if len(row[0]) != 0: next_index = i next_index += 2 # this is the new empty row, +1 for next row and +1 because index starts at 1 current_time = datetime.datetime.now().strftime('%m/%d/%Y %H:%M:%S') # fill the sheet await utilities.future(form.update_cell, next_index, SHEET_TIMESTAMP, current_time) # row 1 await utilities.future(form.update_cell, next_index, SHEET_FAMILY_NAME, family_name) # row 2 await utilities.future(form.update_cell, next_index, SHEET_OFFICER_NAME, str(context.author)) # row 4 await utilities.future(form.update_cell, next_index, SHEET_DAILY_PAY_VALUE, daily_pay_value) # row 5 response.content = f'<@{author_id}> renewed {family_name} on {current_time} with a daily pay value of {daily_pay_value}' # well f**k if family_name not in list_of_family_names: response.content += f', however, {family_name} does not exist. Check spreadsheet/ask officer for help.' form.sort((1, 'des')) return response
async def get_response(bot, context): """Gets a response given the parsed input. context attributes: bot -- A reference to the bot itself. message -- The discord.message object obtained from on_message. base -- The base command name that immediately follows the invoker. subcommand -- The subcommand that matched the parameters. index -- The index of the found subcommand. options -- A dictionary representing the options and potential positional arguments that are attached to them. arguments -- A list of strings that follow the syntax of the blueprint index for arguments following the options. keywords -- Another list of strings that holds all option keywords. These can be used to prevent database conflicts with user commands. cleaned_content -- Simply the message content without the invoker. """ # This is what the bot will say when it returns from this function. # The response object can be manipulated in many ways. The attributes of # the response will be passed into the send function. response = Response() response.content = '' # Default # Set to True if you want your message read with /tts (not recommended). response.tts = False # Default # The message type dictates how the bot handles your returned message. # # NORMAL - Normal. The issuing command can be edited. # PERMANENT - Message is not added to the edit dictionary. # REPLACE - Deletes the issuing command after 'extra' seconds. Defaults # to 0 seconds if 'extra' is not given. # ACTIVE - The message reference is passed back to the function defined # with 'extra_function'. If 'extra_function' is not defined, it will call # plugin.handle_active_message. # INTERACTIVE - Assembles reaction buttons given by extra['buttons'] and # calls 'extra_function' whenever one is pressed. # WAIT - Wait for event. Calls 'extra_function' with the result, or None # if the wait timed out. # # Only the NORMAL message type can be edited. response.message_type = MessageTypes.NORMAL # Default # The extra variable is used for some message types. response.extra = None # Default # Initially, check to make sure that you've matched the proper command. # If there is only one command specified, this may not be necessary. index, options, arguments = context.index, context.options, context.arguments if context.base == 'mycommand': # Then, the subcommand index will tell you which command syntax was # satisfied. The order is the same as was specified initially. if index == 0: # myoption response.content = "You called the first subcommand!" # Do other stuff... elif index == 1: # custom/attached # To see if an optional option was included in the command, use: if 'custom' in options: response.content += "You included the \"custom\" flag!\n" # Do stuff relevant to this flag here... # To get the parameter attached to an option, simply access it from # the options dictionary. if 'attached' in options: response.content += "The attached parmeter: {}\n".format(options['attached']) # In case somebody was looking for the help... if len(options) == 0: invoker = utilities.get_invoker(bot, guild=context.guild) response.content += ("You didn't use either flag...\n" "For help, try `{}help mycommand`".format(invoker)) elif index == 2: # trailing arguments # If arguments are specified as trailing, they will be in a list. response.content += "The list of trailing arguments: {}".format(arguments) elif index == 3: # grouped arguments # All arguments are grouped together as the first element response.message_type = MessageTypes.PERMANENT response.content = ("You can't edit your command here.\n" "Single grouped argument: {}").format(arguments[0]) elif index == 4: # complex # This mixes elements of both examples seen above. response.content = ("The argument attached to the complex " "option: {}").format(options['complex']) if 'other' in options: response.content += "\nThe other option has attached: {}".format(options['other']) response.content += "\nLastly, the trailing arguments: {}".format(arguments) elif index == 5: # (Very slow) marquee # This demonstrates the active message type. # Check active_marquee to see how it works. response.message_type = MessageTypes.ACTIVE response.extra_function = active_marquee # The function to call response.extra = arguments[0] # The text to use response.content = "Setting up marquee..." # This will be shown first # Here's another command base. elif context.base == 'myothercommand': if index == 0: # keyword checker text = arguments[0] if not text: response.content = "You didn't say anything...\n" else: response.content = "This is your input: {}\n".format(text) if text in context.keywords: response.content += "Your input was in the list of keywords!\n" else: response.content += ("Your input was not in the list of keywords. " "They are: {}\n").format(context.keywords) response.message_type = MessageTypes.PERMANENT response.delete_after = 15 response.content += "This message will self destruct in 15 seconds." else: # impossible command??? raise CBException("This is a bug! You should never see this message.") elif context.base == 'wait': response.message_type = MessageTypes.WAIT # The extra attribute should consist of a dictionary containing the # event and any other kwargs. Most notably, you will likely want to # define the check used in wait_for. response.extra_function = custom_interaction response.extra = { 'event': 'message', 'kwargs': { 'timeout': 30, # Default 300 'check': lambda m: m.author == context.author, } } response.content = "Say something, {}.".format(context.author) return response