From: CMDR furrycat Date: Thu, 27 Oct 2016 20:41:22 +0000 (+0100) Subject: Commands to manage greetings. X-Git-Url: http://git.furryclan.net/?a=commitdiff_plain;h=bf2c20e183409cd6622a4878eaa59c3eebf1f3fe;p=furrycat%2Fcatbot.git Commands to manage greetings. --- diff --git a/plugin/greetings/greetings.py b/plugin/greetings/greetings.py index 9859959..615a30d 100644 --- a/plugin/greetings/greetings.py +++ b/plugin/greetings/greetings.py @@ -1,4 +1,6 @@ import asyncio +import re +import shlex from db import DBConnection from plugins import PluginCommand @@ -38,6 +40,26 @@ class Greetings(DBConnection): def on_member_join(self, member): result = yield from self.maybe_greet(member) + def valid_commands(self): + return ['greet', 'greeting', 'greetings'] + + @asyncio.coroutine + def handle_command(self, message, command, raw): + if command not in self.valid_commands(): + return PluginCommand.ignored + yield from self.manage_greetings(message, raw) + return PluginCommand.exclusive + + @asyncio.coroutine + def handle_help(self, message, command, *args): + yield from self.help_greetings(message, *args) + + def get_all_greetings(self, client, servers = []): + yield from self.get_all_from_table(client, 'greetings', servers) + + def get_greeting(self, client, id): + return self.get_from_table(client, 'greetings', id) + def get_greetings(self, client): now = self.now() cursor = self.query("select id, server_id, channel_id, voice_id, sound, message, role_id, maximum from greetings where bot_id=? and enabled='true'", [client.user.id]) @@ -45,6 +67,443 @@ class Greetings(DBConnection): yield dict(row) self.close_db() + def create_greeting(self, client, **args): + return self.insert_into_table(client, 'greetings', **args) + + def update_greeting(self, client, id, **args): + return self.update_table(client, 'greetings', id, **args) + + def delete_greeting(self, client, id): + return self.delete_from_table(client, 'greetings', id) + + @asyncio.coroutine + def can_manage_greetings(self, author, channel, command, **args): + # Anyone can list greetings. + if command == 'list': + log.debug('Anyone can list greetings.') + return True + + if 'id' in args: + greeting = self.get_greeting(client, args['id']) + elif 'create' in args: + greeting = args['create'] + else: + greeting = None + + # Anyone on the server can show details of a greeting. + if command == 'show': + if greeting is not None: + server = client.get_server(greeting['server_id']) + if server is not None: + member = server.get_member(author.id) + if member in server.members: + log.debug('Member {} on server {} can show greeting {}.'.format(member.name, server.name, greeting['id'])) + return greeting + + if command in ['create', 'edit', 'delete', 'schedule']: + if greeting is not None: + server = client.get_server(greeting['server_id']) + member = server.get_member(author.id) + bot_member = server.get_member(client.user.id) + member_role = bot.highest_role(member.roles) + bot_role = bot.highest_role(bot_member.roles) + if member_role.position >= bot_role.position: + log.debug('Member {} with role {} on server {} can manage greetings.'.format(member.name, member_role.name, server.name)) + return greeting + + yield from cat.hiss(channel) + return False + + @asyncio.coroutine + def list_greetings(self, message): + result = yield from self.can_manage_greetings(message.author, message.channel, 'list') + if not result: + return + servers = [] + if message.channel.is_private: + for server in client.servers: + if message.author in server.members: + servers.append(server) + else: + servers = [message.channel.server] + + results = [] + for greeting in self.get_all_greetings(client, servers): + server = client.get_server(greeting['server_id']) + text = '**{}** in <#{}>'.format(greeting['id'], greeting['channel_id']) + if greeting['role_id']: + text += ' assign &{}'.format(greeting['role_id']) + if greeting['voice_id']: + if greeting['sound']: + text += ' sound {} in <#{}>'.format(greeting['sound'], greeting['voice_id']) + short = greeting['message'][:100] + if short != greeting['message']: + short += '...' + text += ': {}'.format(short.replace('\n', ' ')) + results.append(text) + + if len(results): + yield from bot.say_many(message.channel, results) + else: + yield from cat.shrug(message.channel) + + @asyncio.coroutine + def show_greeting(self, message, id): + greeting = yield from self.can_manage_greetings(message.author, message.channel, 'show', id = id) + if not greeting: + return + lines = [] + text = '**greeting {}'.format(id) + if not bot.parse_boolean(greeting['enabled']): + text += ' paused' + text += '**' + lines.append(text) + + text = 'greeting create' + text += ' in <#{}>'.format(greeting['channel_id']) + if greeting['role_id']: + text += ' assign &{}'.format(greeting['role_id']) + if greeting['voice_id']: + text += ' voice <#{}>'.format(greeting['voice_id']) + text += ' sound {}'.format(greeting['sound']) + text += ':' + lines.append(text) + lines.append(greeting['message']) + + print(lines) + yield from bot.say_many(message.channel, lines) + + # Map a database key to a syntax parameter. + def greeting_key(self, k): + d = { + 'channel_id': 'in', + 'role_id': 'assign', + 'voice_id': 'voice' + } + if k in d: + return d[k] + else: + return k + + @asyncio.coroutine + def parse_greeting(self, message, raw, editing = False): + parts = raw.split(':') + params = parts[0] + if len(parts) > 1: + text = ':'.join(parts[1:]).strip() + else: + text = None + + parsed = {} + if editing: + command = 'edit' + else: + command = 'create' + if text: + parsed['message'] = text + + log.debug('announce {} params {} text {}'.format(command, params, text)) + args = shlex.split(params) + log.info(args) + ok = False + i = 0 + while i < len(args): + arg = args[i].lower() + if i > len(args) - 1: + break + try: + param = args[i + 1] + except IndexError: + param = None + log.info('{}: {}={}'.format(i, arg, param)) + ok = False + + if arg == command: + # UUID missing 'id'. + if editing and re.match(r'[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$', param): + parsed['id'] = param + else: + i -= 1 + ok = True + elif arg == 'id': + if editing: + parsed['id'] = param + ok = True + else: + break + elif arg in ['in', 'voice']: + k = 'channel_id' if arg == 'in' else 'voice_id' + if editing and arg == 'voice' and param == 'none': + parsed[k] = None + ok = True + elif param: + m = re.match(r'<#(\d+)>', param) + if m is None: + m = re.match(r'#(\d+)', param) + if m is not None: + parsed[k] = m.group(1) + ok = True + else: + m = re.match(r'#(.+)', param) + if m is None: + break + # Look for a channel with that name. + for channel in client.get_all_channels(): + if channel.name == m.group(1): + parsed[k] = channel.id + ok = True + break + elif arg == 'sound': + k = arg + if editing and param == 'none': + parsed[k] = None + ok = True + else: + parsed[k] = param + ok = True + elif arg == 'assign': + k = 'role_id' + if editing and param == 'none': + parsed[k] = None + ok = True + elif param: + m = re.match(r'<@&(\d+)>', param) + if m is None: + m = re.match(r'@?&(\d+)', param) + if m is not None: + parsed[k] = m.group(1) + ok = True + else: + yield from bot.say(message.channel, 'What is {}?'.format(arg)) + return + + if ok: + i += 2 + else: + break + + if not ok: + log.info('Failed to parse greeting. Got: {}'.format(parsed)) + yield from bot.say(message.channel, '{}?'.format(arg)) + return None + + if 'channel_id' in parsed: + channel = client.get_channel(parsed['channel_id']) + if not channel: + yield from bot.say(message.channel, 'Invalid channel!') + return None + server = channel.server + parsed['server_id'] = server.id + + if 'message' not in parsed: + yield from bot.say(message.channel, 'Missing message!') + return None + + if editing: + if 'id' not in parsed: + yield from bot.say(message.channel, 'Missing ID!') + return None + else: + if 'channel_id' not in parsed: + yield from bot.say(message.channel, 'Missing channel!') + return None + + return parsed + + @asyncio.coroutine + def create_new_greeting(self, message, raw): + # greeting create [params]: + create = yield from self.parse_greeting(message, raw) + if create is None: + return + + greeting = yield from self.can_manage_greetings(message.author, message.channel, 'create', create = create) + if not greeting: + return + + id = self.create_greeting(client, **create) + if id: + create['id'] = id + log.info('Created greeting: {}'.format(create)) + yield from bot.say(message.channel, id) + yield from cat.purr(None, voice_channel_for_user(message.author), join = False) + else: + log.info('Failed to create greeting: {}'.format(create)) + yield from cat.yelp(message.channel) + + @asyncio.coroutine + def edit_existing_greeting(self, message, raw): + update = yield from self.parse_greeting(message, raw, editing = True) + if update is None: + return + + id = update['id'] + del(update['id']) + greeting = yield from self.can_manage_greetings(message.author, message.channel, 'edit', id = id) + if not greeting: + return + + if not len(update.keys()): + yield from bot.say(message.channel, '?') + return + + if self.update_greeting(client, id, **update): + log.info('Edited greeting {}: {}'.format(id, update)) + yield from bot.say(message.channel, ', '.join([self.greeting_key(k) for k in update])) + yield from cat.purr(None, voice_channel_for_user(message.author), join = False) + else: + log.info('Failed to edit greeting {}: {}'.format(id, update)) + yield from cat.yelp(message.channel) + + @asyncio.coroutine + def delete_existing_greeting(self, message, id): + greeting = yield from self.can_manage_greetings(message.author, message.channel, 'delete', id = id) + if not greeting: + return + if bot.get('dryrun'): + log.info('Not deleting greeting {}'.format(id)) + else: + log.info('Deleting greeting {}'.format(id)) + if self.delete_greeting(client, id): + yield from cat.purr(message.channel) + else: + yield from cat.yelp(message.channel) + + @asyncio.coroutine + def schedule_greeting(self, message, id, **args): + greeting = yield from self.can_manage_greetings(message.author, message.channel, 'schedule', id = id) + if not greeting: + return + + update = {} + + # Pause greeting. + if 'pause' in args: + if args['pause']: + update['enabled'] = 'false' + else: + update['enabled'] = 'true' + + if bot.get('dryrun'): + log.info('Not updating greeting {}: {}'.format(id, update)) + else: + log.info('Updating greeting {}: {}'.format(id, update)) + if self.update_greeting(client, id, **update): + yield from cat.purr(message.channel) + else: + yield from cat.yelp(message.channel) + + @asyncio.coroutine + def manage_greetings(self, message, raw): + m = re.match(r'greet(?:ings?)?\s+(.+)', raw, re.IGNORECASE | re.DOTALL) + if m is None: + yield from self.list_greetings(message) + return + text = m.group(1) + args = shlex.split(text) + command = args[0].lower() + if len(args) == 1: + if command == 'list': + yield from self.list_greetings(message) + return + elif command == 'help': + yield from self.help_greetings(message) + return + else: + yield from cat.yelp(message.channel) + return + + if command == 'show': + yield from self.show_greeting(message, args[1]) + elif command == 'help': + yield from self.help_greetings(message, args[1]) + elif command == 'delete': + yield from self.delete_existing_greeting(message, args[1]) + elif command == 'pause': + yield from self.schedule_greeting(message, args[1], pause = True) + elif command == 'resume': + yield from self.schedule_greeting(message, args[1], pause = False) + elif command == 'create': + yield from self.create_new_greeting(message, text) + elif command == 'edit': + yield from self.edit_existing_greeting(message, text) + + @asyncio.coroutine + def help_greetings(self, message, command = None): + if command is None: + lines = [ + 'Commands to manage greetings are:', + '```', + 'create', + 'delete', + 'edit', + 'list', + 'pause', + 'show', + 'resume', + '```', + 'Send `greeting help COMMAND` for help on a specific command.' + ] + elif command == 'create': + lines = [ + 'Create a new greeting.', + '```greeting create OPTIONS: MESSAGE```', + 'Use the `OPTIONS` to define when and where to send the greeting.', + 'You need to include the **:** after the OPTIONS. Anything you include after it will be part of the MESSAGE!', + '', + 'Here are the OPTIONS you can use:', + '', + '```in CHANNEL```', + 'Post the greeting to the specified #channel.', + '', + '```assign ROLE```', + 'Assign the `ROLE` to new members with no other roles.', + '', + '```voice CHANNEL```', + 'Play a sound to the specified voice #CHANNEL.', + '', + '```sound FILE```', + "File to play.!", + '', + ] + elif command == 'delete': + lines = [ + 'Delete the greeting with the given ID.', + '```greeting delete ID```', + ] + elif command == 'edit': + lines = [ + 'Edit a greeting.', + '```greeting edit ID OPTIONS```', + 'Change one or more `OPTIONS` for the greeting with the given ID.', + 'See the help for `greeting create` for details of the OPTIONS you can set.', + ] + elif command == 'help': + lines = ['grr!'] + elif command == 'list': + lines = [ + 'List all greetings, one `ID` per line. You can use the `ID` in other commands.', + "When listing IDs to a public channel I won't show IDs for greetings that are for channels on another server. Send `greeting list` to me in a private message to see them.", + ] + elif command == 'show': + lines = [ + 'Show the greeting with the given ID.', + '```greeting show ID```', + 'I will tell you the details in a format which you could copy and paste to create a new greeting.', + ] + elif command in ['pause', 'resume']: + lines = [ + 'Schedule the greeting with the given ID.', + '```', + 'greeting pause ID', + 'greeting resume ID', + '```', + 'Use `pause` and `resume` to put a greeting on hold temporarily.' + ] + else: + lines = ['*shrugs*'] + yield from bot.say_many(message.channel, lines) + def greeting_for_member(self, member, channel, candidate): if candidate is None: greetings = [dict(greeting) for greeting in self.get_greetings(client)]