diff --git a/cogs/help.py b/cogs/help.py index bed434d..ffea820 100644 --- a/cogs/help.py +++ b/cogs/help.py @@ -49,7 +49,7 @@ class Help: if page == '🏠': ## POLL CREATION SHORT embed.add_field(name='🆕 Making New Polls', - value=f'`{pre}quick` | `{pre}new` | `{pre}prepare`', inline=False) + value=f'`{pre}quick` | `{pre}new` | `{pre}prepare` | `{pre}cmd `', inline=False) # embed.add_field(name='Commands', value=f'`{pre}quick` | `{pre}new` | `{pre}prepared`', inline=False) # embed.add_field(name='Arguments', value=f'Arguments: `` (optional)', inline=False) # embed.add_field(name='Examples', value=f'Examples: `{pre}new` | `{pre}quick What is the greenest color?`', @@ -108,6 +108,14 @@ class Help: f'and/or if you would like to manually `{pre}activate` it. ' 'Perfect if you are preparing for a team meeting!', inline=False) + embed.add_field(name=f'🔹 **-Advanced- Commandline:** `{pre}cmd `', + value=f'For the full syntax type `{pre}cmd help`\n' + f'Similar to version 1 of the bot, with this command you can create a poll in one message. ' + f'Pass all the options you need via command line arguments, the rest will be set to ' + f'default values. The wizard will step in for invalid arguments.\n' + f'Example: `{pre}cmd -q "Which colors?" -l colors -o "green, blue, red" -mc -a`', + inline=False) + elif page == '🔍': embed.add_field(name='🔍 Show Polls', value='All users can display and list polls, with the exception of prepared polls. ' diff --git a/cogs/poll.py b/cogs/poll.py index 8ecf461..ec69370 100644 --- a/cogs/poll.py +++ b/cogs/poll.py @@ -165,7 +165,11 @@ class Poll: while True: try: - reply = await self.get_user_reply() + if force: + reply = force + force = None + else: + reply = await self.get_user_reply() self.name = await get_valid(reply) await self.add_vaild(message, self.name) break @@ -203,7 +207,11 @@ class Poll: while True: try: - reply = await self.get_user_reply() + if force: + reply = force + force = None + else: + reply = await self.get_user_reply() self.short = await get_valid(reply) await self.add_vaild(message, self.short) break @@ -263,7 +271,11 @@ class Poll: while True: try: - reply = await self.get_user_reply() + if force: + reply = force + force = None + else: + reply = await self.get_user_reply() dt = await get_valid(reply) self.activation = dt if self.activation == 0: @@ -315,7 +327,11 @@ class Poll: while True: try: - reply = await self.get_user_reply() + if force: + reply = force + force = None + else: + reply = await self.get_user_reply() self.anonymous = await get_valid(reply) await self.add_vaild(message, f'{"Yes" if self.anonymous else "No"}') break @@ -382,7 +398,11 @@ class Poll: while True: try: - reply = await self.get_user_reply() + if force: + reply = force + force = None + else: + reply = await self.get_user_reply() self.multiple_choice = await get_valid(reply) await self.add_vaild(message, f'{"Yes" if self.multiple_choice else "No"}') break @@ -450,7 +470,11 @@ class Poll: while True: try: - reply = await self.get_user_reply() + if force: + reply = force + force = None + else: + reply = await self.get_user_reply() options = await get_valid(reply) self.options_reaction_default = False if isinstance(options, int): @@ -527,7 +551,11 @@ class Poll: while True: try: - reply = await self.get_user_reply() + if force: + reply = force + force = None + else: + reply = await self.get_user_reply() self.roles = await get_valid(reply, roles) await self.add_vaild(message, f'{", ".join(self.roles)}') break @@ -540,43 +568,6 @@ class Poll: except InvalidRoles as e: await self.add_error(message, f'**The following roles are invalid: {e.roles}**') - # async def set_options_traditional(self, force=None): - # '''Currently not used as everything is reaction based''' - # if force is not None: - # self.options_traditional = force - # return - # if self.stopped: return - # text = """**Next you chose from the possible set of options/answers for your poll.** - # Type the corresponding number or type your own options, separated by commas. - # - # **1** - yes, no - # **2** - in favour, against, abstain - # **3** - love it, like it, don't care, meh, hate it - # - # If you write your own options they will be listed and can be voted for. - # To use your custom options type them like this: - # **apple juice, banana ice cream, kiwi slices**""" - # message = await self.wizard_says(text) - # - # reply = '' - # while reply == '' or (reply.split(",").__len__() < 2 and reply not in ['1', '2', '3']) \ - # or (reply.split(",").__len__() > 99 and reply not in ['1', '2', '3']): - # if reply != '': - # await self.add_error(message, - # '**Invalid entry. Type `1` `2` or `3` or a comma separated list (max. 99 options).**') - # reply = await self.get_user_reply() - # if self.stopped: break - # - # if reply == '1': - # self.options_traditional = ['yes, no'] - # elif reply == '2': - # self.options_traditional = ['in favour', 'against', 'abstain'] - # elif reply == '3': - # self.options_traditional = ['love it', 'like it', 'don\'t care', 'meh', 'hate it'] - # else: - # self.options_traditional = [r.strip() for r in reply.split(",")] - # return self.options_traditional - async def set_weights(self, force=None): """Set role weights for the poll.""" async def get_valid(in_reply, server_roles): @@ -623,7 +614,11 @@ class Poll: while True: try: - reply = await self.get_user_reply() + if force: + reply = force + force = None + else: + reply = await self.get_user_reply() w_n = await get_valid(reply, self.server.roles) self.weights_roles = w_n[0] self.weights_numbers = w_n[1] @@ -686,7 +681,11 @@ class Poll: while True: try: - reply = await self.get_user_reply() + if force: + reply = force + force = None + else: + reply = await self.get_user_reply() dt = await get_valid(reply) self.duration = dt if self.duration == 0: @@ -705,7 +704,6 @@ class Poll: def finalize(self): self.time_created = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) - async def to_dict(self): if self.channel is None: cid = 0 @@ -965,7 +963,7 @@ class Poll: # else: # embed = await self.add_field_custom(name='**Options**', value=', '.join(self.get_options()), embed=embed) - embed.set_footer(text='bot is in development') + # embed.set_footer(text='bot is in development') return embed @@ -994,12 +992,6 @@ class Poll: else: return msg - # def get_options(self): - # if self.reaction: - # return self.options_reaction - # else: - # return self.options_traditional - def get_duration_with_tz(self): if self.duration == 0: return 0 @@ -1077,7 +1069,6 @@ class Poll: else: return sum([1 for c in [u for u in self.votes] if option in self.votes[c]['choices']]) - async def vote(self, user, option, message): if not await self.is_open(): # refresh to show closed poll diff --git a/cogs/poll_controls.py b/cogs/poll_controls.py index 6cbee4a..da6f82a 100644 --- a/cogs/poll_controls.py +++ b/cogs/poll_controls.py @@ -1,9 +1,14 @@ +import argparse import copy import json import logging +import shlex + import discord from discord.ext import commands + +from utils.misc import CustomFormatter from .poll import Poll from utils.paginator import embed_list_paginated from essentials.multi_server import get_server_pre, ask_for_server, ask_for_channel @@ -34,14 +39,14 @@ class PollControls: async def say_error(self, ctx, error_text, footer_text=None): embed = discord.Embed(title='', description=error_text, colour=SETTINGS.color) - embed.set_author(name='Error', icon_url=SETTINGS.title_icon) + embed.set_author(name='Error', icon_url=SETTINGS.author_icon) if footer_text is not None: embed.set_footer(text=footer_text) await self.bot.say(embed=embed) async def say_embed(self, ctx, say_text='', title='Pollmaster', footer_text=None): embed = discord.Embed(title='', description=say_text, colour=SETTINGS.color) - embed.set_author(name=title, icon_url=SETTINGS.title_icon) + embed.set_author(name=title, icon_url=SETTINGS.author_icon) if footer_text is not None: embed.set_footer(text=footer_text) await self.bot.say(embed=embed) @@ -222,7 +227,7 @@ class PollControls: title = f' Listing {short} polls' embed = discord.Embed(title='', description='', colour=SETTINGS.color) - embed.set_author(name=title, icon_url=SETTINGS.title_icon) + embed.set_author(name=title, icon_url=SETTINGS.author_icon) # await self.bot.say(embed=await self.embed_list_paginated(polls, item_fct, embed)) # msg = await self.embed_list_paginated(ctx, polls, item_fct, embed, per_page=8) pre = await get_server_pre(self.bot, server) @@ -242,13 +247,76 @@ class PollControls: footer = f'Type {pre}show to display all polls' await self.say_error(ctx, error, footer) + @commands.command(pass_context=True) + async def cmd(self, ctx, *, cmd=None): + '''The old, command style way paired with the wizard.''' + server = await ask_for_server(self.bot, ctx.message) + if not server: + return + pre = await get_server_pre(self.bot, server) + + # generate the argparser and handle invalid stuff + descr = 'Accept poll settings via commandstring. \n\n' \ + '**Wrap all arguments in quotes like this:** \n' \ + f'{pre}cmd -question \"What tea do you like?\" -o \"green, black, chai\"\n\n' \ + 'The Order of arguments doesn\'t matter. If an argument is missing, it will use the default value. ' \ + 'If an argument is invalid, the wizard will step in. ' \ + 'If the command string is invalid, you will get this error :)' + parser = argparse.ArgumentParser(description=descr, formatter_class=CustomFormatter, add_help=False) + parser.add_argument('-question', '-q') + parser.add_argument('-label', '-l', default=str(await generate_word(self.bot, server.id))) + parser.add_argument('-options', '-o') + parser.add_argument('-roles', '-r', default='all') + parser.add_argument('-weights', '-w', default='none') + parser.add_argument('-duration', '-d', default='0') + parser.add_argument('-anonymous', '-a', action="store_true") + parser.add_argument('-multiple_choice', '-mc', action="store_true") + + helpstring = parser.format_help() + helpstring = helpstring.replace("pollmaster.py", f"{pre}cmd ") + + if cmd and cmd == 'help': + await self.say_embed(ctx, say_text=helpstring) + return + + try: + cmds = shlex.split(cmd) + except ValueError: + await self.say_error(ctx, error_text=helpstring) + return + + try: + args = parser.parse_args(cmds) + except SystemExit: + await self.say_error(ctx, error_text=helpstring) + return + + # pass arguments to the wizard + async def route(poll): + await poll.set_name(force=args.question) + await poll.set_short(force=args.label) + await poll.set_anonymous(force=f'{"yes" if args.anonymous else "no"}') + await poll.set_multiple_choice(force=f'{"yes" if args.multiple_choice else "no"}') + await poll.set_options_reaction(force=args.options) + await poll.set_roles(force=args.roles) + await poll.set_weights(force=args.weights) + await poll.set_duration(force=args.duration) + + poll = await self.wizard(ctx, route, server) + if poll: + await poll.post_embed() + + @commands.command(pass_context=True) async def quick(self, ctx, *, cmd=None): '''Create a quick poll with just a question and some options. Parameters: (optional)''' + server = await ask_for_server(self.bot, ctx.message) + if not server: + return async def route(poll): await poll.set_name(force=cmd) - await poll.set_short(force=str(await generate_word(self.bot, ctx.message.server.id))) + await poll.set_short(force=str(await generate_word(self.bot, server.id))) await poll.set_anonymous(force='no') await poll.set_multiple_choice(force='no') await poll.set_options_reaction() @@ -256,13 +324,16 @@ class PollControls: await poll.set_weights(force='none') await poll.set_duration(force='0') - poll = await self.wizard(ctx, route) + poll = await self.wizard(ctx, route, server) if poll: await poll.post_embed() @commands.command(pass_context=True) async def prepare(self, ctx, *, cmd=None): '''Prepare a poll to use later. Parameters: (optional) ''' + server = await ask_for_server(self.bot, ctx.message) + if not server: + return async def route(poll): await poll.set_name(force=cmd) @@ -278,13 +349,16 @@ class PollControls: await poll.set_weights() await poll.set_duration() - poll = await self.wizard(ctx, route) + poll = await self.wizard(ctx, route, server) if poll: await poll.post_embed(destination=ctx.message.author) @commands.command(pass_context=True) async def new(self, ctx, *, cmd=None): '''Start the poll wizard to create a new poll step by step. Parameters: (optional) ''' + server = await ask_for_server(self.bot, ctx.message) + if not server: + return async def route(poll): await poll.set_name(force=cmd) @@ -299,16 +373,12 @@ class PollControls: await poll.set_weights() await poll.set_duration() - poll = await self.wizard(ctx, route) + poll = await self.wizard(ctx, route, server) if poll: await poll.post_embed() # The Wizard! - async def wizard(self, ctx, route): - server = await ask_for_server(self.bot, ctx.message) - if not server: - return - + async def wizard(self, ctx, route, server): channel = await ask_for_channel(self.bot, server, ctx.message) if not channel: return diff --git a/utils/misc.py b/utils/misc.py index a7ef100..26f9ba1 100644 --- a/utils/misc.py +++ b/utils/misc.py @@ -1,6 +1,35 @@ +import argparse + import pytz import datetime as dt + +class CustomFormatter(argparse.RawTextHelpFormatter): + def _format_action_invocation(self, action): + if not action.option_strings: + metavar, = self._metavar_formatter(action, action.dest)(1) + return metavar + else: + parts = [] + # if the Optional doesn't take a value, format is: + # -s, --long + if action.nargs == 0: + parts.extend(action.option_strings) + + # if the Optional takes a value, format is: + # -s ARGS, --long ARGS + # change to + # -s, --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + for option_string in action.option_strings: + # parts.append('%s %s' % (option_string, args_string)) + parts.append('%s' % option_string) + parts[-1] += ' %s' % args_string + return ', '.join(parts) + + def possible_timezones(tz_offset, common_only=True): # pick one of the timezone collections timezones = pytz.common_timezones if common_only else pytz.all_timezones