class Template(Cog): def __init__(self, bot: TLDR): self.bot = bot @command( help='General description of the command/what the command does', usage='template_command [arg that is required] (arg that is optional)', examples=['template_command needed_arg', 'template_command needed_arg2 optional_arg'], Admins=commands.Help( # clearance in here will be set to Admin automatically, it doesnt need to be defined help='You can specify different help for people with higher perms, specially defined help like this one needs to be a higher clearance than the base one', usage='template_command [args]', examples=['template_command --arg1'], command_args=[ # the third value in the arg tuple is the type the arg value will be converted to, if the type is a list, multiple of the same arg will be pushed to a list # the type can also be a command, like modules.format_time.parse # If the shorter arg is set to None, the user will only be presented with the long option (('--arg1', '-a1', str), 'Description of arg1'), # These need to be defined when using ParseArgs, otherwise it won't know what to look for (('--arg2', None, list), 'Description of arg2') ] ), cls=commands.Command # here so we can actually use all the custom kwargs\ ) # when giving an arg a type, discord.py will attempt to convert that arg to the given type # rest_of_the_args will attempt to convert to ParseArgs, if it fails, it'll convert to str def template_command(self, ctx: Context, first_arg: str = None, *, rest_of_the_args: Union[ParseArgs, str] = None): # if the command doesnt do anything without args, it's best to include this line in the beginning # it'll send info about the command and how to use it if first_arg is None: return await embed_maker.command_error(ctx) # if you want to send a response, please use embed_maker.message or embed_maker.error # with embed_maker.message you will need to give it `send` kwarg, if send is missing, it'll return an embed if first_arg.isdigit(): return await embed_maker.error(ctx, 'Error message') else: return await embed_maker.message(ctx, description='Description or whatever', send=True) @commands.group( invoke_without_command=True, # this kwarg needs to be here, otherwise subcommands will be ignored help='General description of the command/what the command does', usage='template_group_command [required arg]', examples=['template_command needed_arg 123'], cls=commands.Group # don't forget to change this to cls.Group ) async def template_group_command(self, ctx: Context, *, args: str): pass @template_group_command.command( name='sub_command', # the name needs to be defined on sub commands help='Description', usage='template_group_command sub_command [args]', examples=['template_group_command sub_command hello 123'], cls=commands.Command, ) async def template_group_command_sub_command(self, ctx: Context, *, args: str): pass
class UK(commands.Cog): def __init__(self, bot: TLDR): self.bot = bot self.loaded = False def load(self): self.ukparl_module = self.bot.ukparl_module self.parliament = self.ukparl_module.parliament self.loaded = True @staticmethod async def construct_bills_search_embed( ctx: Context, bills: list[Bill], max_page_num: int, page_limit: int, *, page: int, ): if len(bills) == 0: return await embed_maker.message(ctx, description="No bills found.") bits = [] next_line = "\n" for i, bill in enumerate(bills[page_limit * (page - 1) : page_limit * page]): bill_title = bill.get_title() description = None if bill.get_long_title() is not None: description = bill.get_long_title()[0:160] + "..." bill_id = bill.get_bill_id() bill_url = f"https://bills.parliament.uk/bills/{bill.get_bill_id()}" entry = f"**{(i + 1) + (page_limit * (page - 1))}. [{bill_title}]({bill_url}) | ID: {bill_id}**{next_line}" if description is not None: entry = entry + f"**Description:** {description}" bits.append(entry) embed = await embed_maker.message( ctx, description=next_line.join(bits), author={"name": "UKParliament Bills"}, footer={"text": f"Page {page}/{max_page_num}"}, ) return embed @staticmethod async def construct_bill_info_embed( ctx: Context, bill: Bill, c_divisions: list[CommonsDivision], l_divisions: list[LordsDivision], page_limit: int, *, page: int, ): formatted_bill_information = [ f"**Title:** {bill.get_title()}", f"**Description:** {bill.get_long_title()}", # f"**Introduced:** {bill.get_date_introduced().strftime('%Y-%m-%d %H:%M:%S')}", f"**Last Update:** {bill.get_last_update().strftime('%Y-%m-%d %H:%M:%S')}", f"**Act of Parliament:** {'Yes' if bill.is_act() else 'No'}", ] if bill.has_royal_assent(): formatted_bill_information.append( "**Current Stage:** Received Royal Assent" ) else: formatted_bill_information.extend( [ f"**Current Stage:** {bill.get_current_stage().get_name()}", f"**Current House:** {bill.get_current_house()}", ] ) total_length = ( len(l_divisions) + len(c_divisions) + len(formatted_bill_information) ) max_pages = math.ceil(total_length / page_limit) pages: list[embeds.Embed] = [] async def template(description: str, title: str) -> embeds.Embed: return await embed_maker.message( ctx, title=title, description=description, author={"name": "UKParliament Bill"}, footer={"text": f"Page: {page}/{max_pages}"}, ) # type: ignore next_line = "\n" if len(l_divisions) > 0: bits = [] for i, division in enumerate(l_divisions): did_pass = division.get_aye_count() > division.get_no_count() bits.append( f"**{i + 1}. [{division.get_division_title()}](https://votes.parliament.uk/Votes/" f"Lords/Division/{division.get_id()})**{next_line}" f"**ID:** {division.get_id()}{next_line}" f"**Summary:** " f"{division.get_amendment_motion_notes()[0:150].replace('<p>', '').replace('</p>', '')}..." if division.get_amendment_motion_notes() is not None and division.get_amendment_motion_notes() != "" else "" f"{next_line}**Division Result:** {'Passed' if did_pass else 'Not passed'} by a division" f"of {division.get_aye_count() if did_pass else division.get_no_count()}" f"{'Ayes' if did_pass else 'Noes'} to " f"{division.get_aye_count() if did_pass is False else division.get_no_count()}" f" {'Noes' if did_pass else 'Ayes'}" f"{next_line}**Division Date:** {division.get_division_date().strftime('%Y-%m-%d %H:%M:%S')}" ) if i == (page_limit - 1) or i == (len(l_divisions) - 1): pages.append( await template( title="Lords Divisions", description="\n".join(bits) ) ) bits.clear() if len(c_divisions) > 0: bits = [] for i, division in enumerate(c_divisions): did_pass = division.get_aye_count() > division.get_no_count() bits.append( f"**{i + 1}. [{division.get_division_title()}](https://votes.parliament.uk/" f"Votes/Lords/Division/{division.get_id()})**{next_line}" f"**ID:** {division.get_id()}{next_line}**Division Result:**" f"{'Passed' if did_pass else 'Not passed'} by a division of" f" {division.get_aye_count() if did_pass else division.get_no_count()}" f"{'Ayes' if did_pass else 'Noes'} to " f"{division.get_aye_count() if did_pass is False else division.get_no_count()}" f"{'Noes' if did_pass else 'Ayes'}{next_line}**Division Date:**" f"{division.get_division_date().strftime('%Y-%m-%d %H:%M:%S')}" ) if i == (page_limit - 1) or i == (len(l_divisions) - 1): pages.append( await template( title="Commons Divisions", description="\n".join(bits) ) ) bits.clear() first_page: embeds.Embed = await embed_maker.message( ctx, description="\n".join(formatted_bill_information), author={"name": "UKParliament Bill"}, footer={"text": f"Page: {page}/{max_pages}"}, ) # type: ignore pages.insert(0, first_page) return (pages[(page - 1)], len(pages)) @staticmethod async def construct_divisions_lords_embed( ctx: commands.Context, divisions: list[LordsDivision], page_limit: int, *, page: int, ): max_pages = math.ceil(len(divisions) / page_limit) bits = [] next_line = "\n" for i, division in enumerate( divisions[page_limit * (page - 1) : page_limit * page] ): did_pass = division.get_aye_count() > division.get_no_count() bits.append( f"**{(page_limit * (page -1)) + i + 1}. [{division.get_division_title()}]" f"(https://votes.parliament.uk/Votes/Lords/Division/{division.get_id()})**{next_line}" f"**ID:** {division.get_id()}{next_line}**Summary:** {division.get_amendment_motion_notes()[0:150]}..." f"{next_line}**Division Result:** {'Passed' if did_pass else 'Not passed'} by a division of " f"{division.get_aye_count() if did_pass else division.get_no_count()} {'Ayes' if did_pass else 'Notes'}" f" to {division.get_no_count() if did_pass else division.get_aye_count()} " f"{'Noes' if did_pass else 'Ayes'}{next_line}**Division Date:** " f"{division.get_division_date().strftime('%Y-%m-%d %H:%M:%S')}" ) embed = await embed_maker.message( ctx, description=next_line.join(bits), author={"name": "UKParliament Division"}, footer={"text": f"Page {page}/{max_pages}"}, ) return (embed, max_pages) @staticmethod async def construct_divisions_commons_embed( ctx: commands.Context, divisions: list[CommonsDivision], page_limit: int, *, page: int, ): max_pages = math.ceil(len(divisions) / page_limit) next_line = "\n" bits = [] for i, division in enumerate( divisions[page_limit * (page - 1) : page_limit * page] ): did_pass = division.get_aye_count() > division.get_no_count() bits.append( f"**{(page_limit * (page -1)) + i + 1}. [{division.get_division_title()[0:100]}]" f"(https://votes.parliament.uk/Votes/Commons/Division/{division.get_id()})**{next_line}" f"**ID:** {division.get_id()}{next_line}**Division Result:** {'Passed' if did_pass else 'Not passed'}" f" by a division of {division.get_aye_count() if did_pass else division.get_no_count()} " f"{'Ayes' if did_pass else 'Notes'} to " f"{division.get_no_count() if did_pass else division.get_aye_count()} {'Noes' if did_pass else 'Ayes'}" f"{next_line}**Division Date:** {division.get_division_date().strftime('%Y-%m-%d %H:%M:%S')}" ) embed: embeds.Embed = await embed_maker.message( ctx, description=next_line.join(bits), author={"name": "UKParliament Division"}, footer={"text": f"Page {page}/{max_pages}"}, ) # type: ignore return embed, max_pages @commands.group( help="To access the commands interfacing the UK Parliament Site.", invoke_without_command=True, usage="uk [sub command]", examples=["uk divisions linfo 1234", "uk mpelection Boris Johnson"], Mod=cls.Help( help="To access the commands inferfacing with the UK Parliament Site. And to access commands relevant to" " the configuration of this feature", usage="uk [sub command]", examples=["uk mod tracker channels"], sub_commands=["bills", "divisions", "minfo", "mpelection", "mod"], ), sub_commands=[ "bills", "divisions", "minfo", "mpelection", ], cls=cls.Group, ) async def uk(self, ctx: commands.Context): return await embed_maker.command_error(ctx) @uk.group( help="For commands relating to bills", invoke_without_command=True, usage="uk bills [sub command]", examples=["uk bills search European Withdrawal"], sub_commands=["search"], cls=cls.Group, ) async def bills(self, ctx: commands.Context): return await embed_maker.command_error(ctx) @uk.group( name="divisions", help="For commands relating to divisions", invoke_without_command=True, usage="uk divisions [sub command]", examples=["uk divisions lsearch [args]"], sub_commands=["lsearch", "csearch", "linfo", "cinfo"], cls=cls.Group, ) async def divisions(self, ctx: commands.Context): return await embed_maker.command_error(ctx) @uk.group( name="mod", invoke_without_command=True, help="Moderator level commands for this feature", usage="uk mod [sub command]", examples=["uk mod tracker [args]"], sub_commands=["tracker"], cls=cls.Group, ) async def mod_commands(self, ctx: commands.Context): return await embed_maker.command_error(ctx) @mod_commands.group( name="tracker", invoke_without_command=True, help="Commands related to the trackering section of this feature", usage="uk mod tracker [sub command]", examples=["uk mod tracker channels"], sub_commands=[ "channels", # "load", "statuses", "loop", "dbclear", "dbstats", "ping", ], cls=cls.Group, ) async def mod_cmd_tracker(self, ctx: commands.Context): return await embed_maker.command_error(ctx) @mod_commands.command( name="ping", help="Pings an endpoint on the REST api and returns the latency between the bot and a REST request.", usage="uk mod ping", examples=["uk mod ping"], cls=cls.Command, ) async def mod_cmd_ping(self, ctx: commands.Context): if self.loaded is False: return start = datetime.now() async with self.ukparl_module.get_aiohttp_session().get( f"{URL_BILLS}/BillTypes" ) as resp: if resp.status != 200: return await embed_maker.message( ctx=ctx, description=f"Couldn't get ping, status: {resp.status}", send=True, ) end = datetime.now() difference = end - start await embed_maker.message( ctx=ctx, description=f"{difference.microseconds / 1000} ms", send=True ) @mod_cmd_tracker.command( name="dbclear", help=( "Clear all recorded entries from the two MongoDB collectioned used by the tracker." "Doing this will produce from the bot duplicate tracker announcement from the last few days." ), usage="uk mod tracker dbclear", examples=["uk mod tracker dbclear", "uk mod tracker dbclear [auth code]"], cls=cls.Command, ) async def mod_cmd_tracker_db_clear(self, ctx: commands.Context, code: str = ""): if self.loaded is False: return confirm_manager = self.ukparl_module.confirm_manager if confirm_manager.has_code(ctx.author): if code == "": return await embed_maker.message( ctx, description="Confirm code required to execute this command successfully.", send=True, ) if confirm_manager.confirm_code(ctx.author, code): database.get_connection().clear_bills_tracker_collection() database.get_connection().clear_divisions_tracker_collection() return await embed_maker.message( ctx, description="Cleared bills_tracker and division_tracker collections.", send=True, ) else: return await embed_maker.message( ctx, description="Confirm code incorrect.", send=True ) code = confirm_manager.gen_code(ctx.author) await embed_maker.message( ctx, description="Sending confirmation to delete contents of 'bills_tracker' and 'divisions_tracker'.", send=True, ) await ctx.author.send(f"Code to confirm clear Mongo Collections: {code}") @mod_cmd_tracker.command( name="dbstats", help="At the moment this only displays the amount of documents in both bills_tracker and divisions_tracker collections", usage="uk mod tracker dbstats", clearence="dev", cls=cls.Command, ) async def mod_cmd_tracker_db_stats(self, ctx: commands.Context): if self.loaded is False: return next_line = "\n" await embed_maker.message( ctx, description=( f"**Collection Count**{next_line}- bills_tracker:" f" {database.get_connection().get_bills_tracker_count()}{next_line}- divisions_tracker:" f" {database.get_connection().get_divisions_tracker_count()}" ), send=True, ) @mod_cmd_tracker.command( name="statuses", help="Show the status of each tracker", usage="uk mod tracker statuses", examples=["uk mod tracker statuses"], cls=cls.Command, ) async def mod_cmd_tracker_statuses(self, ctx: commands.Context): if self.loaded is False: return # type: ignore next_line = "\n" statuses = self.bot.ukparl_module.tracker_status bits = [] for key in statuses.keys(): entry = statuses[key] if key == "loop": bits.append( f"**Loop Status**: {'Enabled' if entry is True else 'Disabled'}" ) continue bits.append( f"{next_line}**{key}**:{next_line} - **Started:** {'Yes' if entry['started'] else 'No'}{next_line} - " f"**Confirmed:** {'Yes' if entry['confirmed'] else 'No'}" ) return await embed_maker.message( ctx, description=f"**Status for each Tracker**{next_line}{next_line.join(bits)}", send=True, ) @mod_cmd_tracker.group( name="channels", invoke_without_command=True, usage="uk mod tracker channels [args]", examples=["uk mod tracker channels", "uk mod tracker channels set [args]"], cls=cls.Group, ) async def mod_cmd_tracker_channels(self, ctx: commands.Context): if self.loaded is False: return # type: ignore bits = [] channels = self.ukparl_module.config.get_channel_ids() for key in channels.keys(): channel_id = channels[key] guild = self.ukparl_module.get_guild() if guild is not None: channel = guild.get_channel(int(channel_id)) bits.append( f"- {key}:" f" Couldn't find channel ({channel_id})" if channel is None else f"- {key}: {channel.name}" ) else: bits.append("Couldn't fetch guild.") next_line = "\n" await embed_maker.message( ctx, description=f"**Channels**{next_line}{next_line.join(bits)}", send=True ) @mod_cmd_tracker.group( name="loop", invoke_without_command=True, help="Commands to start and stop the event loop checking the various rss feeds", usage="uk mod tracker loop", examples=["uk mod tracker loop start", "uk mod tracker loop stop"], cls=cls.Group, ) async def mod_cmd_tracker_eventloop(self, ctx: commands.Context): if self.loaded is False: return return await embed_maker.message( ctx, description=( "The event loop is currently " f"{'running' if self.bot.ukparl_module.tracker_event_loop.is_running() else 'not running'}." ), send=True, ) @mod_cmd_tracker_eventloop.command( name="start", help="Start the event loop.", usage="uk mod tracker loop start", examples=["uk mod tracker loop start"], cls=cls.Command, ) async def mod_cmd_tracker_eventloop_start(self, ctx: commands.Context): if self.loaded is False: return # type: ignore tracker_statuses = self.bot.ukparl_module.tracker_status config = self.bot.ukparl_module.config if ( config.get_channel_id("feed") == 0 and tracker_statuses["feed"]["started"] is True ): return await embed_maker.message( ctx, description="A listener registered to the 'feed' tracker doesn't have a channel to output to.", send=True, ) if self.bot.ukparl_module.tracker_event_loop.is_running() is False: self.bot.ukparl_module.tracker_event_loop.start() self.bot.ukparl_module.tracker_status["loop"] = True await embed_maker.message(ctx, description="Started event loop.", send=True) else: await embed_maker.message( ctx, description="Event loop is already running.", send=True ) @mod_cmd_tracker_eventloop.command( name="stop", help="Stop the event loop", usage="uk mod tracker loop stop", examples=["uk mod tracker loop stop"], cls=cls.Command, ) async def mod_cmd_tracker_eventloop_stop(self, ctx: commands.Context): if self.loaded is False: return # type: ignore if self.bot.ukparl_module.tracker_event_loop.is_running() is True: self.bot.ukparl_module.tracker_event_loop.stop() await embed_maker.message(ctx, description="Stopped event loop.", send=True) self.ukparl_module.tracker_status["loop"] = False else: await embed_maker.message( ctx, description="Event loop is already not running.", send=True ) @mod_cmd_tracker_channels.command( name="set", help="Set a channel to one of the four trackers", usage="uk mod tracker channels set [tracker_id] [channel_id or mention]", examples=["uk mod tracker channelts set royalassent #royal-assent"], cls=cls.Command, ) async def mod_cmd_tracker_channels_set( self, ctx: commands.Context, tracker_id: str = "", channel: TextChannelConverter = None, ): if self.loaded is False: return # type: ignore if tracker_id == "": return await embed_maker.command_error(ctx, "tracker_id") if channel == "": return await embed_maker.command_error(ctx, "channel_id/mention") config = self.bot.ukparl_module.config channels = config.get_channel_ids() if tracker_id.lower() not in channels.keys(): next_line = "\n" return await embed_maker.message( ctx, description=f"**Valid tracker ids:**{next_line} - {(next_line + ' - ').join(channels.keys)}", send=True, ) config.set_channel(tracker_id, channel.id) await embed_maker.message( ctx, description=f"Set {tracker_id} to channel {channel.name}", send=True ) @uk.command( name="mpelection", help="View latest election results of an MP by name or constituency name", clearence="User", usage="uk mpelection [mp name] or uk mpelection <argument identifier> [borough name]", examples=[ "uk mpelection Boris Johnson", "uk mpelection --borough Belfast South --nonvoters", ], cls=cls.Command, command_args=[ (("--borough", "-bo", str), "Name of a borough"), (("--nonvoters", "-nv", bool), "Include non-voters in the charts"), ( ("--table", "-t", bool), "Whether or not the chart should be a pie or a table", ), ( ("--name", "-n", str), "The name of the MP (used only if you're using the other arguments", ), (("--historical", "-h", str), "Get list of recorded election results"), ], ) async def mp_elections( self, ctx: commands.Context, *, args: Union[ParseArgs, str] = "" ): if self.loaded is False: return member = None if args == "": return await embed_maker.command_error(ctx) name_arg = args["pre"] if args["pre"] != "" else args["name"] if name_arg != "" and name_arg is not None: for m in self.parliament.get_commons_members(): name = m.get_titled_name() if name is None: name = m.get_addressed_name() if name is None: name = m.get_display_name() if name_arg.lower() in name.lower(): member = m break elif args["borough"] != "" and args["borough"] is not None: for m in self.parliament.get_commons_members(): if args["borough"].lower() in m.get_membership_from().lower(): member = m break if member is None: return await embed_maker.message( ctx, description=f"Couldn't find latest elections results for" f" {'Borough' if args['borough'] != '' and args['borough'] is not None else 'MP'}" f" {args['borough'] if args['borough'] != '' and args['borough'] is not None else args['pre']}", send=True, ) next_line = "\n" results = await self.parliament.get_election_results(member) result = results[0] if args["historical"] != "" and args["historical"] is not None: historical_bits = [ f"- {er.get_election_date().strftime('%Y')}" for er in results ] return await embed_maker.message( ctx, title=f"Recorded Eletion Results of the Borough {member.get_membership_from()}", description=f"**Elections:** {next_line}{next_line.join(historical_bits)}", send=True, ) else: for h_result in results: if h_result.get_election_date().strftime("%Y") == args["historical"]: result = h_result others_formatted = [] the_rest_formatted = [] for candidate in result.get_candidates(): if candidate["votes"] > 1000: the_rest_formatted.append( f"- {candidate['name']}: {candidate['party_name']}" ) else: others_formatted.append( f"- {candidate['name']}: {candidate['party_name']}" ) embed = embeds.Embed = await embed_maker.message( ctx, title=f'{result.get_election_date().strftime("%Y")} Election Results of {member.get_membership_from()}', description=f"**Electorate Size:** {result.get_electorate_size():,}{next_line}" f"**Turnout:** {result.get_turnout():,}{next_line}**Main Candidates:**{next_line}" f"{next_line.join(the_rest_formatted)}{next_line}**Other Candidates (Under 1k):" f"**{next_line}{next_line.join(others_formatted)}" if len(others_formatted) > 0 else "", ) if result is not None: image_file = await self.ukparl_module.generate_election_graphic( result, args["nonvoters"] is not None, args["table"] is not None ) embed.set_image(url="attachment://electionimage.png") # type: ignore await ctx.send( file=image_file, embed=embed, ) else: await ctx.send(embed=embed) @uk.command( name="minfo", help="Get information on a currently serving MP or Lord", usage="uk minfo [mp name / lord name]", examples=[ "uk minfo Boris Johnson", "uk minfo Duke of Norfolk", "uk minfo -n Lord Sugar -p", ], command_args=[ (("--borough", "-b", str), "Get mp info by borough name"), (("--portrait", "-p", bool), "Fetch the portrait of the member"), ( ("--name", "-n", str), "The name of the mp (used only if you're using the other args", ), ], clearence="User", cls=cls.Command, ) async def member_info( self, ctx: commands.Context, *, args: Union[ParseArgs, str] = "" ): if self.loaded is False: return # type: ignore if args == "": return await embed_maker.command_error(ctx) member = None members = self.parliament.get_commons_members() members.extend(self.parliament.get_lords_members()) arg_name = args["pre"] if args["pre"] != "" else args["name"] if arg_name is not None: for m in members: name = m.get_titled_name() if name is None: name = m.get_addressed_name() if name is None: name = m.get_display_name() if arg_name.lower() in name.lower(): member = m else: if args["borough"] is not None: members = list( filter( lambda m: m.get_membership_from().lower() == args["borough"].lower(), self.parliament.get_commons_members(), ) ) if len(members) != 0: member = members[0] if member is None: await embed_maker.message( ctx, description="Couldn't find " + arg_name if args["pre"] is not None or args["name"] is not None else "of borough" + args["borough"] + ".", send=True, ) return biography = await self.parliament.get_biography(member) next_line = "\n" representing_bits = [] for rep in biography.get_representations(): representing_bits.append( ( f"- MP for {rep['constituency_name']} from {rep['started'].strftime('%Y-%m-%d')}" f"{' to ' if rep['ended'] is not None else ''}" f"{rep['ended'].strftime('%Y-%m-%d') if rep['ended'] is not None else ''}" ) ) gov_posts_bits = [] for post in biography.get_government_posts(): gov_posts_bits.append( ( f"- {post['office']} from {post['started'].strftime('%Y-%m-%d')}" f"{' to ' if post['ended'] is not None else ''}" f"{post['ended'].strftime('%Y-%m-%d') if post['ended'] is not None else ''}" ) ) opp_posts_bits = [] for post in biography.get_oppositions_posts(): opp_posts_bits.append( ( f" - {post['office']} from {post['started'].strftime('%Y-%m-%d')}" f"{' to ' if post['ended'] is not None else ''}" f"{post['ended'].strftime('%Y-%m-%d') if post['ended'] is not None else ''}" ) ) other_posts_bits = [] for post in biography.get_other_posts(): other_posts_bits.append( ( f" - {post['office']} from {post['started'].strftime('%Y-%m-%d')}" f"{' to ' if post['ended'] is not None else ''}" f"{post['ended'].strftime('%Y-%m-%d') if post['ended'] is not None else ''}" ) ) cmte_bits = [] for membership in biography.get_committee_memberships(): cmte_bits.append( ( f"- Member of {membership['committee']} from {membership['started'].strftime('%Y-%m-%d')}" f"{' to ' if membership['ended'] is not None else ''}" f"{membership['ended'].strftime('%Y-%m-%d') if membership['ended'] is not None else ''}" ) ) d = ( f"**Name:** {member.get_display_name()}{next_line}**" f"{'Representing:' if member.get_house() == 1 else 'Peer Type'}** {member.get_membership_from()}" f"{next_line}**Gender:** {'Male' if member.get_gender() == 'M' else 'Female'}" + ( f"{next_line}**Represented/Representing:**{next_line}{next_line.join(representing_bits)}" if len(representing_bits) > 0 else "" ) + ( f"{next_line}**Government Posts**{next_line}{next_line.join(gov_posts_bits)}" if len(gov_posts_bits) > 0 else "" ) + ( f"{next_line}**Opposition Posts:**{next_line}{next_line.join(opp_posts_bits)}" if len(opp_posts_bits) > 0 else "" ) + ( f"{next_line}**Other Posts:**{next_line}{next_line.join(other_posts_bits)}" if len(other_posts_bits) > 0 else "" ) + ( f"{next_line}**Committee Posts:**{next_line}{next_line.join(cmte_bits)}" if len(cmte_bits) > 0 else "" ) ) embed: embeds.Embed = await embed_maker.message(ctx, description=d) # type: ignore if args["portrait"] is not None: url = ( member.get_thumbnail_url().replace("Thumbnail", "Portrait") + "?cropType=FullSize&webVersion=false" ) portrait_image = await self.ukparl_module.get_mp_portrait(url) if portrait_image is not None: embed.set_image(url=f"attachment://portrait.jpeg") await ctx.send(file=portrait_image, embed=embed) else: await ctx.send(embed=embed) @divisions.command( name="linfo", help="Get House of Lords Division information", usage="uk divisions linfo [division id]", examples=["uk divisions linfo 1234"], clearence="User", cls=cls.Command, ) async def division_lord_info(self, ctx: commands.Context, division_id: int = -1): if self.loaded is False: return # type: ignore if division_id == -1: await embed_maker.command_error(ctx, "division_id") return division = await self.parliament.get_lords_division(division_id) if division is None: await embed_maker.message( ctx, description=f"Couldn't find division under id {division_id}", send=True, ) division_image = await self.ukparl_module.generate_division_image( self.parliament, division ) next_line = "\n" did_pass = division.get_aye_count() > division.get_no_count() embed: embeds.Embed = await embed_maker.message( ctx, description=f"**Title:** {division.get_division_title()}{next_line}" f"**Division Outcome:** {'Passed' if did_pass else 'Not passed'} by a division of " f"{division.get_aye_count() if did_pass else division.get_no_count()} {'Ayes' if did_pass else 'Noes'}" f" to {division.get_no_count() if did_pass else division.get_aye_count()} {'Noes' if did_pass else 'Ayes'}" f"{next_line}**Division Date:** {division.get_division_date().strftime('%Y-%m-%d %H:%M:%S')}{next_line}" f"**Summary:** {division.get_amendment_motion_notes()[0:250]}", ) # type: ignore embed.set_image(url="attachment://divisionimage.png") await ctx.send(file=division_image, embed=embed) @divisions.command( name="cinfo", help="Get House of Commons Division information", usage="uk divisions cinfo [division id]", examples=["uk divisions cinfo 1234"], clearence="User", cls=cls.Command, ) async def division_common_info(self, ctx: commands.Context, division_id: int): if self.loaded is False: return # type: ignore division = await self.parliament.get_commons_division(division_id) if division is None: await embed_maker.message( ctx, description=f"Couldn't find division under id {division_id}", send=True, ) image_file = await self.ukparl_module.generate_division_image( self.parliament, division ) next_line = "\n" did_pass = division.get_aye_count() > division.get_no_count() embed: embeds.Embed = await embed_maker.message( ctx, description=f"**Title:** {division.get_division_title()}{next_line}**Division Outcome:**" f" {'Passed' if did_pass else 'Not passed'} by a division of " f"{division.get_aye_count() if did_pass else division.get_no_count()} {'Ayes' if did_pass else 'Noes'} " f"to {division.get_no_count() if did_pass else division.get_aye_count()} {'Noes' if did_pass else 'Ayes'}" f"{next_line}**Division Date:** {division.get_division_date().strftime('%Y-%m-%d %H:%M:%S')}", ) # type: ignore embed.set_image(url=f"attachment://{image_file.filename}") await ctx.send(file=image_file, embed=embed) @divisions.command( name="csearch", help="Search for commons divisions", usage="uk divisions csearch [search term]", examples=["uk divisions csearch European"], clearence="User", cls=cls.Command, ) async def division_commons_search(self, ctx: commands.Context, *, search_term=""): if self.loaded is False: return # type: ignore divisions = await self.parliament.search_for_commons_divisions( search_term, result_limit=30 ) if len(divisions) == 0: await embed_maker.message( ctx, description=f"Couldn't find any Commons divisions under search term '{search_term}'.", send=True, ) page_constructor = functools.partial( self.construct_divisions_commons_embed, ctx=ctx, divisions=divisions, page_limit=5, ) pair = await page_constructor(page=1) message = await ctx.send(embed=pair[0]) async def temp_page_constructor(page: int): pair = await page_constructor(page=page) return pair[0] menu = BookMenu( message=message, author=ctx.author, # type: ignore max_page_num=pair[1], page_constructor=temp_page_constructor, page=1, ) self.bot.reaction_menus.add(menu) @divisions.command( name="lsearch", help="Search for lords divisions", usage="uk divisions lsearch [search term]", examples=["uk divisions lsearch European"], clearence="User", cls=cls.Command ) async def division_lords_search(self, ctx: commands.Context, *, search_term=""): if self.loaded is False: return # type: ignore divisions = await self.parliament.search_for_lords_divisions( search_term, result_limit=30 ) if len(divisions) == 0: await embed_maker.message( ctx, description=f"Couldn't find any Lords divisions under the search term '{search_term}'.", send=True, ) return page_constructor = functools.partial( self.construct_divisions_lords_embed, ctx=ctx, divisions=divisions, page_limit=5, ) pair = await page_constructor(page=1) embed = pair[0] max_pages = pair[1] async def temp_page_constructor(page: int): pair = await page_constructor(page=page) return pair[0] message = await ctx.send(embed=embed) menu = BookMenu( message=message, author=ctx.author, # type: ignore max_page_num=max_pages, page_constructor=temp_page_constructor, page=1, ) self.bot.reaction_menus.add(menu) @bills.command( name="info", help="To display in more detail information about a bill.", clearence="User", usage="uk bills info [bill id]", examples=["uk bills info 1234"], cls=cls.Command, ) async def bill_info(self, ctx: commands.Context, bill_id: int): if self.loaded is False: return # type: ignore try: bill = await self.parliament.get_bill(bill_id) c_divisions = await self.parliament.search_for_commons_divisions( bill.get_title() ) l_divisions = await self.parliament.search_for_lords_divisions( bill.get_title() ) page_constructor = functools.partial( self.construct_bill_info_embed, ctx=ctx, bill=bill, l_divisions=l_divisions, c_divisions=c_divisions, page_limit=10, ) pair: tuple[embeds.Embed, int] = await page_constructor(page=1) message = await ctx.send(embed=pair[0]) async def temp_page_constructor( page: int, ): # Due to the unique nature of ther result, this is needed ot return only the embed. pair = await page_constructor(page=page) return pair[0] menu = BookMenu( message, author=ctx.author, # type: ignore page=1, max_page_num=pair[1], page_constructor=temp_page_constructor, ) self.bot.reaction_menus.add(menu) except Exception as ignore: await embed_maker.message( ctx, description=f"Couldn't fetch bill {bill_id}.", send=True ) raise ignore @bills.command( help="Seach for bills using certain values and search terms", usage="uk bills search [search terms]", examples=[ "uk bills search European Withdrawal", "uk bills search --query Finance Bill --currenthouse Lords", "uk bills search --sponsor Rishu Sunak", ], name="search", clearence="User", command_args=[ (("--query", "-q", str), "Search Term to search for"), (("--sponsor", "-s", str), "The name of the bill sponsor"), (("--types", None, "-t"), "The types of bill to search for"), (("--order", None, "-o"), "The order to display the searches in"), (("--currenthouse", "-ch", str), "The house the bill is currently in"), (("--originatinghouse", "-oh", str), "The house the bill originated in"), ], cls=cls.Command, ) async def bills_search( self, ctx: commands.Context, *, args: Union[ParseArgs, str] = "" ): if self.loaded is False: return # type: ignore if args == "": return await embed_maker.command_error(ctx) builder = SearchBillsBuilder.builder() if args is None: return if args["pre"] is not None: builder.set_search_term(args["pre"]) else: if args["query"] is not None: builder.set_search_term(args["query"]) if args["sponsor"] is not None: member = self.parliament.get_member_by_name(args["sponsor"]) if member is None: await embed_maker.message( ctx, description=f"Couldn't find member {args['sponsor']}", send=True, ) builder.set_member_id(member.get_id()) if args["types"] is not None: split_types = args["types"].split(" ") types = self.parliament.get_bill_types() arg_types = [] for t_type in split_types: for b_type in types: if b_type.get_name().lower() == t_type.lower(): arg_types.append(b_type) builder.set_bill_type(arg_types) if args["order"] is not None: formatted_acceptable_args = list( map(lambda order: order.name.lower(), SearchBillsSortOrder) ) next_line = "\n" await embed_maker.message( ctx, description=f"Couldn't find order type {args['order']}. Acceptable arguments: {next_line}" f"{next_line.join(formatted_acceptable_args)}", send=True, ) if args["currenthouse"] is not None: if args["currenthouse"].lower() not in [ "all", "commons", "lords", "unassigned", ]: await embed_maker.message( ctx, description="Incorrect house value. " "Accepted arguments: 'all', 'commons', 'lords', 'unassigned'", send=True, ) return builder.set_current_house(args["currenthouse"]) if args["originatinghouse"] is not None: if args["originatinghouse"] not in [ "all", "commons", "lords", "unassigned", ]: await embed_maker.message( ctx, description="Incorrect house value. " "Accepted arguments: 'all', 'commons', 'lords', 'unassigned'", send=True, ) return builder.set_originating_house(args["originatinghouse"]) bills = await self.parliament.search_bills( builder.set_sort_order(SearchBillsSortOrder.DATE_UPDATED_DESENDING).build() ) next_line = "\n" max_page_size = 4 max_page_num = math.ceil(len(bills) / max_page_size) if max_page_num == 0: max_page_num = 1 page_constructor = functools.partial( self.construct_bills_search_embed, ctx=ctx, bills=bills, max_page_num=max_page_num, page_limit=max_page_size, ) embed = await page_constructor(page=1) message = await ctx.send(embed=embed) menu = BookMenu( message, author=ctx.author, # type: ignore page=1, max_page_num=max_page_num, page_constructor=page_constructor, ) self.bot.reaction_menus.add(menu)
class Leveling(Cog): def __init__(self, bot: TLDR): self.bot = bot # parliamentary points earn cooldown self.pp_cooldown = Cooldown() self.hp_cooldown = Cooldown() @command( help='Show someone you respect them by giving them a reputation point', usage='rep [member] [reason for the rep]', examples=['rep @Hattyot for being an excellent example in this text'], cls=commands.Command, aliases=['reputation'] ) async def rep(self, ctx: Context, *, member_reason: str = None): # check if user has been in server for more than 7 days now_datetime = datetime.datetime.now() joined_at = ctx.author.joined_at diff = now_datetime - joined_at if round(diff.total_seconds()) < 86400 * 7: # 7 days return await embed_maker.error(ctx, f'You need to be on this server for at least 7 days to give rep points') # check if user can give rep point giving_leveling_member = await self.bot.leveling_system.get_member(ctx.guild.id, ctx.author.id) if not giving_leveling_member.rep_timer_expired: time_left = giving_leveling_member.rep_time_left return await embed_maker.message( ctx, description=f'You can give someone a reputation point again in:\n' f'**{format_time.seconds(time_left, accuracy=3)}**', send=True ) if member_reason is None: return await embed_maker.command_error(ctx) receiving_member, reason = await get_member_from_string(ctx, member_reason) if receiving_member is None: return await embed_maker.error(ctx, 'Invalid member') if not reason: return await embed_maker.command_error(ctx, '[reason for the rep]') if receiving_member.id == ctx.author.id: return await embed_maker.error(ctx, f'You can\'t give rep points to yourself') if receiving_member.bot: return await embed_maker.error(ctx, f'You can\'t give rep points to bots') # check last rep if giving_leveling_member.last_rep == receiving_member.id: return await embed_maker.error(ctx, f'You can\'t give rep to the same person twice in a row') receiving_leveling_member = await self.bot.leveling_system.get_member(ctx.guild.id, receiving_member.id) # set rep_time to 24h so user cant spam rep points expire = round(time.time()) + 86400 # 24 hours giving_leveling_member.rep_timer = expire # log who user gave the rep_point to, so that a person can't rep the same person twice in a row giving_leveling_member.last_rep = receiving_member.id # give member rep point receiving_leveling_member.rp += 1 await embed_maker.message(ctx, description=f'Gave +1 rep to <@{receiving_member.id}>', send=True) # send member rep reason msg = f'<@{ctx.author.id}> gave you a reputation point:\n**"{reason}"**' embed = await embed_maker.message( ctx, description=msg, author={'name': 'Rep'} ) # try except because bot might not be able to dm member try: await receiving_member.send(embed=embed) except Exception: pass # start rep timer if giving leveling_member has rep@ enabled if giving_leveling_member.settings.rep_at: self.bot.timers.create( guild_id=ctx.guild.id, expires=round(time.time()) + 86400, # 24 hours event='rep_at', extras={ 'member_id': giving_leveling_member.id } ) # check if user already has rep boost, if they do, extend it by 30 minutes, otherwise add 10% boost for 6h if not receiving_leveling_member.boosts.rep.has_expired(): boost = receiving_leveling_member.boosts.rep # if boost is expired or boost + 30min is bigger than 6 hours set expire to 6 hours if boost.expires < round(time.time()) or (boost.expires + 1800) - round(time.time()) > (3600 * 6): expires = round(time.time()) + (3600 * 6) # otherwise just expand expire by 30 minutes else: expires = boost.expires + 1800 # 30 min else: expires = round(time.time()) + (3600 * 6) # give boost to to receiving leveling member receiving_leveling_member.boosts.rep.expires = expires receiving_leveling_member.boosts.rep.multiplier = 0.1 @group( invoke_without_command=True, help='See current leveling routes', usage='leveling_routes (branch <parliamentary/honours>)', examples=[ 'ranks', 'ranks parliamentary', 'ranks honours' ], Admins=commands.Help( help='See current leveling routes or add, remove or edit roles', usage='ranks (branch) (sub command) (args)', examples=['ranks', 'ranks honours'], ), cls=commands.Group ) async def ranks(self, ctx: Context, branch: str = 'parliamentary'): if ctx.subcommand_passed is None and branch: leveling_guild = self.bot.leveling_system.get_guild(ctx.guild.id) branch = leveling_guild.get_leveling_route(branch) embed = await embed_maker.message(ctx, author={'name': 'Ranks'}) # Looks up how many people have a role count = { role.name: db.leveling_users.count({'guild_id': ctx.guild.id, f'{branch.name[0]}_role': role.name, f'{branch.name[0]}p': {'$gt': 0}}) for role in branch.roles } value = '' for i, role in enumerate(branch.roles): guild_role = await role.get_guild_role() value += f'\n**#{i + 1}:** <@&{guild_role.id}> - {count[guild_role.name]} ' + ('People' if count[guild_role.name] != 1 else 'Person') if not value: value = 'This branch currently has no roles' amount_of_people = sum(count.values()) value += f'\n\nTotal: **{amount_of_people} ' + ('People**' if amount_of_people != 1 else 'Person**') embed.add_field(name=f'>{branch.name.title()} - Every 5 levels you advance a role', value=value, inline=False) return await ctx.send(embed=embed) @ranks.command( name='add', help='Add a role to parliamentary or honours route', usage='ranks add [branch] [role name]', examples=['ranks add honours Pro', 'ranks add honours Knight 2'], cls=commands.Command ) async def ranks_add(self, ctx: Context, branch: str = None, *, role_name: str = None): if branch is None: return await embed_maker.command_error(ctx) leveling_guild = self.bot.leveling_system.get_guild(ctx.guild.id) branch = leveling_guild.get_leveling_route(branch) if branch is None: branch = leveling_guild.leveling_routes.parliamentary if not role_name: return await embed_maker.command_error(ctx, '[role name]') new_role = { 'name': role_name, 'perks': [] } leveling_role = leveling.LevelingRole(ctx.guild, branch, new_role) branch.roles.append(leveling_role) await embed_maker.message( ctx, description=f'`{role_name}` has been added to the list of `{branch.name}` roles', colour='green', send=True ) ctx.invoked_subcommand = '' return await self.ranks(ctx, branch.name) @ranks.command( name='remove', help='Remove a role from the list of parliamentary or honours roles', usage='ranks remove [branch] [role name]', examples=['ranks remove honours Knight'], cls=commands.Command ) async def ranks_remove(self, ctx: Context, branch: str = None, *, role: str): if not branch or type(branch) == int: return await embed_maker.command_error(ctx) leveling_guild = self.bot.leveling_system.get_guild(ctx.guild.id) leveling_role = leveling_guild.get_leveling_role(role) branch = leveling_guild.get_leveling_route(branch) if branch is None: branch = leveling_guild.leveling_routes.parliamentary if leveling_role is None: return await embed_maker.error(ctx, f"Couldn't find a {branch.name} role by the name {role}") branch.roles.remove(leveling_role) await embed_maker.message( ctx, description=f'`{leveling_role.name}` has been remove from the list of `{branch.name}` roles', colour='green', send=True ) ctx.invoked_subcommand = '' return await self.ranks(ctx, branch.name) @group( invoke_without_command=True, help='See all the perks that a role has to offer', usage='perks (role name)', examples=['perks', 'perks Party Member'], Moderators=commands.Help( help='See all the perks that a role has to offer or add or remove them', usage='perks (<sub command/role name>) (args)', examples=['perks', 'perks Party Member'], ), cls=commands.Group ) async def perks(self, ctx: Context, *, role: str = None): if ctx.subcommand_passed is None: leveling_guild = self.bot.leveling_system.get_guild(ctx.guild.id) leveling_routes = leveling_guild.leveling_routes honours_branch = leveling_routes.honours parliamentary_branch = leveling_routes.parliamentary if role is None: # find roles that have perks filtered_parliamentary = list(filter(lambda r: r.perks, parliamentary_branch)) filtered_honours = list(filter(lambda r: r.perks, honours_branch)) embed = await embed_maker.message( ctx, description=f'To view perks of a role type `{ctx.prefix}perks [role name]`', author={'name': f'List of roles with perks'}, ) if filtered_parliamentary: parliamentary_str = '\n'.join(r.name for r in filtered_parliamentary) else: parliamentary_str = 'Currently no Parliamentary roles offer any perks' if filtered_honours: honours_str = '\n'.join(r.name for r in filtered_honours) else: honours_str = 'Currently no Honours roles offer any perks' embed.add_field(name='>Parliamentary Roles With Perks', value=parliamentary_str, inline=False) embed.add_field(name='>Honours Roles With Perks', value=honours_str, inline=False) return await ctx.send(embed=embed) if role: leveling_role = leveling_guild.get_leveling_role(role) if not leveling_role.perks: perks_str = f'**{leveling_role.name}** currently offers no perks' else: perks_str = "\n".join([f'`#{i + 1}` - {perk}' for i, perk in enumerate(leveling_role.perks)]) return await embed_maker.message( ctx, description=perks_str, author={'name': f'{leveling_role.name} - Perks'}, send=True ) async def modify_perks(self, ctx: Context, command: str, args: dict, message: str) -> Optional[Union[dict, discord.Message]]: if args is None: return await embed_maker.command_error(ctx) role_name = args['role'] new_perks = args['perk'] if not role_name: return await embed_maker.error(ctx, "Missing role arg") if not new_perks and command != 'pull': return await embed_maker.error(ctx, "Missing perks arg") leveling_guild = self.bot.leveling_system.get_guild(ctx.guild.id) leveling_role = leveling_guild.get_leveling_role(role_name) if not leveling_role: return await embed_maker.error(ctx, 'Invalid role provided.') if command == 'add': leveling_role.perks += new_perks elif command == 'set': leveling_role.perks = new_perks elif command == 'remove' and not new_perks: leveling_role.perks = [] elif command == 'remove' and new_perks: to_remove = [int(num) - 1 for num in new_perks if num.isdigit() and 0 < int(num) <= len(leveling_role.perks)] leveling_role.perks = [perk for i, perk in enumerate(leveling_role.perks) if i not in to_remove] await embed_maker.message( ctx, description=message.format(**{'role': leveling_role}), colour='green', send=True ) # send embed of role perks perks_str = "\n".join([f'`#{i + 1}` - {perk}' for i, perk in enumerate(leveling_role.perks)]) return await embed_maker.message( ctx, description=perks_str, author={'name': f'{leveling_role.name} - New Perks'}, send=True ) @perks.command( name='set', help='set the perks of a role', usage='perks set [args]', examples=['perks set -r Party Member -p Monthly giveaways -p some cool perk'], command_args=[ (('--role', '-r', str), 'The name of the role you want to set the perks for'), (('--perk', '-p', list), 'Perk for the role') ], cls=commands.Command ) async def perks_set(self, ctx: Context, *, args: Union[ParseArgs, dict] = None): return await self.modify_perks(ctx, 'set', args, 'New perks have been set for role `{role.name}`') @perks.command( name='add', help='Add perks to a role', usage='perks add [args]', examples=['perks add -r Party Member -p Monthly giveaways -p some cool perk'], command_args=[ (('--role', '-r', str), 'The name of the role you want to add the perks to'), (('--perk', '-p', list), 'Perk for the role') ], cls=commands.Command ) async def perks_add(self, ctx: Context, *, args: Union[ParseArgs, dict] = None): return await self.modify_perks(ctx, 'add', args, 'New perks have been added for role `{role.name}`') @perks.command( name='remove', help='remove perks of a role, or remove only some perks', usage='perks remove -r [role name] -p (perk number)', examples=[ 'perks remove -r Party Member -p 1 -p 2', 'perks remove -r Party Member' ], command_args=[ (('--role', '-r', str), 'The name of the role you want to remove perks from'), (('--perk', '-p', list), 'Index of the perk you want to remove, can be seen by doing >perks [role]') ], cls=commands.Command ) async def perks_remove(self, ctx: Context, *, args: Union[ParseArgs, dict] = None): return await self.modify_perks(ctx, 'remove', args, 'Perks have been removed from role `{role.name}`') @group( invoke_without_command=True, help='See all the honours channels', usage='honours_channel', examples=['honours_channels'], Admins=commands.Help( help='See current honours channels or add or remove them', usage='honours_channel (action - <add/remove>) [#channel]', examples=['honours_channels', 'honours_channels add #court', 'honours_channels remove #Mods'], ), cls=commands.Group ) async def honours_channels(self, ctx: Context): if ctx.subcommand_passed is None: leveling_guild = self.bot.leveling_system.get_guild(ctx.guild.id) # display list of honours channels channel_list_str = ', '.join(f'<#{channel}>' for channel in leveling_guild.honours_channels) if leveling_guild.honours_channels else 'None' return await embed_maker.message(ctx, description=channel_list_str, send=True) @honours_channels.command( name='add', help='Add an honours channel', usage='honours_channels add [#channel]', examples=['honours_channels add #Tech-Lobby'], cls=commands.Command ) async def honours_channels_add(self, ctx: Context, channel=None): if channel is None: return await embed_maker.command_error(ctx) if not channel: return await embed_maker.command_error(ctx, '[#channel]') leveling_guild = self.bot.leveling_system.get_guild(ctx.guild.id) if channel.id not in leveling_guild.honours_channels: return await embed_maker.message(ctx, description='That channel is not on the list', colour='red', send=True) leveling_guild.honours_channels.remove(channel.id) msg = f'<#{channel.id}> has been removed from the list of honours channels' return await embed_maker.message(ctx, description=msg, colour='green', send=True) @honours_channels.command( name='remove', help='Remove an honours channel', usage='honours_channels remove [#channel]', examples=['honours_channels remove #Tech-Lobby'], cls=commands.Command ) async def honours_channels_remove(self, ctx: Context, channel=None): if channel is None: return await embed_maker.command_error(ctx) if not channel: return await embed_maker.command_error(ctx, '[#channel]') leveling_guild = self.bot.leveling_system.get_guild(ctx.guild.id) if channel.id in leveling_guild.honours_channels: return await embed_maker.message(ctx, description='That channel is already on the list', colour='red', send=True) leveling_guild.honours_channels.append(channel.id) msg = f'<#{channel.id}> has been added to the list of honours channels' return await embed_maker.message(ctx, description=msg, colour='green', send=True) @command( help='See how many messages you need to send to level up and rank up or see how many messages until you reach a level (dont forget about the 60s cooldown)', usage='mlu (level)', examples=['mlu', 'mlu 60'], cls=commands.Command ) async def mlu(self, ctx: Context, level: Union[float, str] = None): # incase somebody typed something that isnt a level if type(level) == str: level = None leveling_member = await self.bot.leveling_system.get_member(ctx.guild.id, ctx.author.id) user_level = leveling_member.parliamentary.level points = leveling_member.parliamentary.points if not level: # points needed until level_up pp_till_next_level = round((5 / 6) * (user_level + 1) * (2 * (user_level + 1) * (user_level + 1) + 27 * (user_level + 1) + 91)) - points avg_msg_needed = math.ceil(pp_till_next_level / 20) # points needed to rank up user_rank = leveling_member.user_role_level(leveling_member.parliamentary) missing_levels = 6 - user_rank rank_up_level = user_level + missing_levels pp_needed_rank_up = round((5 / 6) * rank_up_level * (2 * rank_up_level * rank_up_level + 27 * rank_up_level + 91)) - points avg_msg_rank_up = math.ceil(pp_needed_rank_up / 20) description = f'Messages needed to:\n'\ f'Level up: **{avg_msg_needed}**\n'\ f'Rank up: **{avg_msg_rank_up}**' else: pp_needed = round((5 / 6) * level * (2 * level * level + 27 * level + 91)) - points avg_msg_needed = math.ceil(pp_needed / 20) description = f'Messages needed to reach level `{level}`: **{avg_msg_needed}**' return await embed_maker.message( ctx, description=description, author={'name': 'MLU'}, send=True ) async def construct_lb_str(self, ctx: Context, branch: leveling.LevelingRoute, sorted_users: list, index: int, your_pos: bool = False): lb_str = '' for i, leveling_user in enumerate(sorted_users): leveling_member = await self.bot.leveling_system.get_member(ctx.guild.id, leveling_user['user_id']) addition = 0 if your_pos else 1 member = leveling_member.member if leveling_member.id == ctx.author.id: lb_str += rf'**`#{index + i + addition}`**\* - {member.display_name}' else: lb_str += f'`#{index + i + addition}` - {member.display_name}' if not your_pos: lb_str += f' [{member}]' pre = '\n' if not your_pos else ' | ' # parliamentary and honours branches need different things from rep if branch.name[0] in ['p', 'h']: user_branch = leveling_member.parliamentary if branch.name[0] == 'p' else leveling_member.honours user_role = await leveling_member.guild.get_leveling_role(user_branch.role).get_guild_role() role_level = leveling_member.user_role_level(user_branch) progress_percent = leveling_member.percent_till_next_level(user_branch) lb_str += f'{pre}**Level {role_level}** <@&{user_role.id}> | Progress: **{progress_percent}%**\n' if not your_pos: lb_str += '\n' else: lb_str += f' | **{leveling_member.rp} Reputation**\n' return lb_str async def construct_lb_embed(self, ctx: Context, branch: leveling.LevelingRoute, user_index: int, sorted_users: list, page_size_limit: int, max_page_num: int, *, page: int): sorted_users_page = sorted_users[page_size_limit * (page - 1):page_size_limit * page] leaderboard_str = await self.construct_lb_str(ctx, branch, sorted_users_page, index=page_size_limit * (page - 1)) description = 'Damn, this place is empty' if not leaderboard_str else leaderboard_str leaderboard_embed = await embed_maker.message( ctx, description=description, footer={'text': f'{ctx.author} | Page {page}/{max_page_num}'}, author={'name': f'{branch.name.title()} Leaderboard'} ) # Displays user position under leaderboard and users above and below them if user is below position 10 if user_index < len(sorted_users) and not (user_index + 1 <= page * page_size_limit): sorted_users_segment = sorted_users[user_index - 1:user_index + 2] your_pos_str = await self.construct_lb_str(ctx, branch, sorted_users_segment, user_index, your_pos=True) leaderboard_embed.add_field(name='Your Position', value=your_pos_str) return leaderboard_embed @command( help='Shows the leveling leaderboards (parliamentary(p)/honours(h)) on the server', usage='leaderboard (branch)', aliases=['lb'], examples=['leaderboard parliamentary', 'lb honours'], cls=commands.Command ) async def leaderboard(self, ctx: Context, branch: str = 'parliamentary', page: int = 1): leveling_guild = self.bot.leveling_system.get_guild(ctx.guild.id) leveling_routes = leveling_guild.leveling_routes if branch.isdigit(): page = int(branch) branch = leveling_guild.leveling_routes.parliamentary else: branch_switch = {'p': leveling_routes.parliamentary, 'h': leveling_routes.honours, 'r': leveling_routes.reputation} branch = branch_switch.get(branch[0], leveling_routes.parliamentary) key = f'{branch.name[0]}p' # get list of users sorted by points who have more than 0 points sorted_users = [u for u in db.leveling_users.find({'guild_id': ctx.guild.id, key: {'$gt': 0}}).sort(key, -1)] page_size_limit = 10 # calculate max page number max_page_num = math.ceil(len(sorted_users) / page_size_limit) if max_page_num == 0: max_page_num = 1 if page > max_page_num: return await embed_maker.error(ctx, 'Exceeded maximum page number') leveling_user = db.get_leveling_user(ctx.guild.id, ctx.author.id) user_index = sorted_users.index(leveling_user) if leveling_user in sorted_users else len(sorted_users) # create function with all the needed values except page, so the function can be called with only the page kwarg page_constructor = functools.partial( self.construct_lb_embed, ctx, branch, user_index, sorted_users, page_size_limit, max_page_num ) # make and send initial leaderboard page leaderboard_embed = await page_constructor(page=page) leaderboard_message = await ctx.send(embed=leaderboard_embed) menu = BookMenu( leaderboard_message, author=ctx.author, page=page, max_page_num=max_page_num, page_constructor=page_constructor, extra_back=5, extra_forward=5 ) self.bot.reaction_menus.add(menu) async def branch_rank_str(self, user_branch: leveling.LevelingUserBranch, leveling_member: leveling.LevelingMember, verbose: bool): role_level = leveling_member.user_role_level(user_branch) # calculate user rank by counting users who have more points than user rank = leveling_member.rank(user_branch) leveling_role = leveling_member.guild.get_leveling_role(user_branch.role) if leveling_role is None: return guild_role = await leveling_role.get_guild_role() progress = leveling_member.percent_till_next_level(user_branch) if verbose: points_till_next_level = round(5 / 6 * (user_branch.level + 1) * (2 * (user_branch.level + 1) * (user_branch.level + 1) + 27 * (user_branch.level + 1) + 91)) cooldown_object = self.pp_cooldown cooldown = f'{cooldown_object.user_cooldown(leveling_member.guild.id, leveling_member.id)} seconds' rank_str = f'**Rank:** `#{rank}`\n' \ f'**Role:** <@&{guild_role.id}>\n' \ f'**Role Level:** {role_level}\n' \ f'**Total Level:** {user_branch.level}\n' \ f'**Points:** {user_branch.points}/{points_till_next_level}\n' \ f'**Progress:** {progress}%\n' \ f'**Cooldown**: {cooldown}' else: rank_str = f'**#{rank}** | **Level** {role_level} <@&{guild_role.id}> | Progress: **{progress}%**' return rank_str @staticmethod async def rep_rank_str(leveling_member: leveling.LevelingMember, verbose: bool): # this is kind of scuffed, but it works rank = leveling_member.rank(leveling_member.reputation) if verbose: rep_time = int(leveling_member.rep_timer) - round(time.time()) if rep_time < 0: rep_time = 0 rep_time_str = format_time.seconds(rep_time, accuracy=10) last_rep = f'<@{leveling_member.last_rep}>' if leveling_member.last_rep else 'None' rep_str = f"**Reputation:** {leveling_member.rp}\n" \ f"**Rep timer:** {rep_time_str}\n" \ f"**Last Rep: {last_rep}**\n" else: rep_str = f'**#{rank}** | **{leveling_member.rp}** reputation' return rep_str @command( help='Shows your (or someone else\'s) rank and level, add -v too see all the data', usage='rank (member) (-v)', examples=['rank', 'rank @Hattyot', 'rank Hattyot -v'], cls=commands.Command ) async def rank(self, ctx: Context, *, user_input: str = ''): verbose = bool(re.findall(r'(?:\s|^)(-v)(?:\s|$)', user_input)) user_input = re.sub(r'((\s|^)-v(\s|$))', '', user_input) # set member to author if user_input is just -v or user_input isn't provided if not user_input: member = ctx.author else: member = await get_member(ctx, user_input) if type(member) == discord.Message: return if member.bot: return await embed_maker.error(ctx, 'No bots allowed >:(') rank_embed = await embed_maker.message( ctx, footer={'text': str(member), 'icon_url': member.avatar_url}, author={'name': f'{member.name} - Rank'} ) leveling_member = await self.bot.leveling_system.get_member(ctx.guild.id, member.id) # inform user of boost, if they have it boost_multiplier = leveling_member.boosts.get_multiplier() if boost_multiplier > 1: boost_percent = round((boost_multiplier - 1) * 100, 1) if boost_percent.is_integer(): boost_percent = int(boost_percent) rank_embed.description = f'Active boost: **{boost_percent}%** parliamentary points gain!' if leveling_member.honours.points > 0: user_branch = leveling_member.honours hp_str = await self.branch_rank_str(user_branch, leveling_member, verbose) rank_embed.add_field(name='>Honours', value=hp_str, inline=False) user_branch = leveling_member.parliamentary pp_str = await self.branch_rank_str(user_branch, leveling_member, verbose) rank_embed.add_field(name='>Parliamentary', value=pp_str, inline=False) if leveling_member.rp: rep_str = await self.rep_rank_str(leveling_member, verbose) rank_embed.add_field(name='>Reputation', value=rep_str, inline=False) # add boosts field if verbose and leveling_member.boosts: boost_str = '' for i, boost in enumerate(leveling_member.boosts): if boost.has_expired(): boost.remove() percent = round(boost.multiplier * 100, 1) boost_str += f'`#{i + 1}` - {percent}% boost | Expires: {format_time.seconds(boost.expires - round(time.time()), accuracy=5)}' boost_str += f' | Type: {boost.boost_type}\n' boost_str = 'None' if not boost_str else boost_str rank_embed.add_field(name='>Boosts', value=boost_str) if verbose: rank_embed.add_field(name='>Settings', value=f'**@me:** {leveling_member.settings.at_me}', inline=False) return await ctx.send(embed=rank_embed) @command( help='Toggle the different leveling settings.', usage='settings (setting)', examples=["settings @me", "settings rep@"], cls=commands.Command ) async def settings(self, ctx: Context, *, setting: str = None): embed = await embed_maker.message(ctx, description=f"To toggle a setting, type `{ctx.prefix}settings [setting]`", author={'name': 'Settings'}) leveling_member = await self.bot.leveling_system.get_member(ctx.guild.id, ctx.author.id) if setting is None: at_me_value = f'You will {"not" * (not leveling_member.settings.at_me)} be @\' when you level up.' embed.add_field(name='| @me', value=f'**{leveling_member.settings.at_me}**\n{at_me_value}', inline=False) rep_at_value = f'You will {"not" * (not leveling_member.settings.rep_at)} be messaged when your rep timer expires and you can give someone a rep point again.' embed.add_field(name='| rep@', value=f'**{leveling_member.settings.rep_at}**\n{rep_at_value}', inline=False) return await ctx.send(embed=embed) elif setting not in ['@me', 'rep@']: return await embed_maker.error(ctx, f'{setting} is not a valid setting\nValid Settings: `@me` | `rep@`') else: if setting == '@me': leveling_member.settings.toggle_at_me() if leveling_member.settings.at_me: msg = 'You will now be @\'d when you level up.' colour = 'green' else: msg = 'You will no longer be @\'d when you level up' colour = 'orange' return await embed_maker.message(ctx, description=msg, colour=colour, send=True) if setting == 'rep@': leveling_member.settings.toggle_rep_at() if leveling_member.settings.rep_at: msg = 'I will now message you when your rep timer expires.' colour = 'green' else: msg = 'I will no longer message you when your rep timer expires.' colour = 'orange' return await embed_maker.message(ctx, description=msg, colour=colour, send=True) async def process_message(self, message: discord.Message): author = message.author guild = message.guild leveling_member = await self.bot.leveling_system.get_member(guild.id, author.id) # level parliamentary route if not self.pp_cooldown.user_cooldown(guild.id, author.id): pp_add = randint(15, 25) await leveling_member.add_points('parliamentary', pp_add) branch = leveling_member.guild.get_leveling_route('parliamentary') current_role, levels_up, roles_up = await leveling_member.level_up(branch) if levels_up: current_role = await current_role.get_guild_role() await leveling_member.level_up_message(message, leveling_member.parliamentary, current_role, roles_up) # level honours route if message.channel.id in leveling_member.guild.honours_channels and not self.hp_cooldown.user_cooldown(guild.id, author.id): hp_add = randint(7, 12) await leveling_member.add_points('honours', hp_add) branch = leveling_member.guild.get_leveling_route('honours') current_role, levels_up, roles_up = await leveling_member.level_up(branch) if levels_up: current_role = await current_role.get_guild_role() await leveling_member.level_up_message(message, leveling_member.honours, current_role, roles_up)