import asyncio
+import re
+import shlex
from db import DBConnection
from plugins import PluginCommand
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])
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]: <text>
+ 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)]