From: CMDR furrycat Date: Wed, 12 Oct 2016 21:24:16 +0000 (+0100) Subject: Allow editing announcements. X-Git-Url: http://git.furryclan.net/?a=commitdiff_plain;h=d5d6a46839d34f4aa866268d1c4016b44d916fd3;p=furrycat%2Fcatbot.git Allow editing announcements. --- diff --git a/bot.py b/bot.py index 35f454d..a69345c 100755 --- a/bot.py +++ b/bot.py @@ -851,7 +851,7 @@ def can_manage_announcements(author, channel, command, **args): log.debug('Member {} on server {} can show announcement {}.'.format(member.name, server.name, announcement['id'])) return announcement - if command in ['delete', 'schedule']: + if command in ['edit', 'delete', 'schedule']: if announcement is not None: if announcement['member_id'] == author.id: log.debug('Anyone can manage own announcements.') @@ -895,6 +895,7 @@ def help_announcements(message, command = None): 'asap', 'create', 'delete', + 'edit', 'list', 'pause', 'show', @@ -950,6 +951,15 @@ def help_announcements(message, command = None): '```announcement delete ID```', 'You can only delete your own announcements or public announcements created by someone in a lower role.' ] + elif command == 'edit': + lines = [ + 'Edit an announcement.', + '```announcement edit ID OPTIONS: MESSAGE```', + 'Change one or more `OPTIONS` or the `MESSAGE` for the announcement with the given ID.', + 'See the help for `announce create` for details of the OPTIONS you can set.', + 'Some options can be set to *none* to delete that option.', + 'For instance `to none` means that the announcement will no longer have an end date.' + ] elif command == 'help': lines = ['grr!'] elif command == 'list': @@ -1106,9 +1116,23 @@ def show_announcement(message, id): yield from say(message.channel, '\n'.join(lines)) +# Map a database key to a syntax parameter. +def announcement_key(k): + d = { + 'channel_id': 'in', + 'end_date': 'to', + 'interval': 'every', + 'mention': 'tell', + 'start_date': 'from', + 'voice_id': 'voice' + } + if k in d: + return d[k] + else: + return k + @asyncio.coroutine -def create_announcement(message, raw): - # announcement create [params]: +def parse_announcement(message, raw, editing = False): # We want to split by colon but don't want to count any which are part of dates or URLs. m = re.match(r'(?:[^:]*\s+(?:(?:at|from|to)\s+"?\d\d\d\d-?\d\d-?\d\d(?:T|\s+)\d\d:?\d\d:?\d\dZ?"?|sound\s+https?:\S+))+', raw, re.IGNORECASE) if m is not None: @@ -1121,12 +1145,17 @@ def create_announcement(message, raw): text = ':'.join(parts[1:]).strip() else: text = None - log.debug('announce create params {} text {}'.format(params, text)) - create = { 'member_id': message.author.id } + parsed = {} + if editing: + command = 'edit' + else: + command = 'create' + parsed['member_id'] = message.author.id if text: - create['message'] = text + parsed['message'] = text + log.debug('announce {} params {} text {}'.format(command, params, text)) args = shlex.split(params) log.info(args) ok = False @@ -1142,33 +1171,50 @@ def create_announcement(message, raw): log.info('{}: {}={}'.format(i, arg, param)) ok = False - if arg == 'create': - i -= 1 + 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 == 'once': - create['interval'] = 0 + parsed['interval'] = 0 i -= 1 ok = True elif arg in ['at', 'from', 'to']: k = 'end_date' if arg == 'to' else 'start_date' - m = re.match(r'(\d\d\d\d)-?(\d\d)-?(\d\d)(?:T|\s+)(\d\d):?(\d\d):?(\d\d)Z?', param) - if m is not None: - create[k] = int(datetime.datetime(*([int(n) for n in m.groups()] + [0, pytz.UTC])).timestamp()) + if editing and param == 'none': + parsed[k] = None ok = True else: - break + m = re.match(r'(\d\d\d\d)-?(\d\d)-?(\d\d)(?:T|\s+)(\d\d):?(\d\d):?(\d\d)Z?', param) + if m is not None: + parsed[k] = int(datetime.datetime(*([int(n) for n in m.groups()] + [0, pytz.UTC])).timestamp()) + ok = True + else: + break elif arg == 'every': seconds = parse_seconds(param) if seconds is not None: - create['interval'] = seconds + parsed['interval'] = seconds ok = True else: break elif arg == 'tell': - if param == 'me': - create['mention'] = message.author.id - if 'channel_id' not in create: - create['channel_id'] = 'private' + if editing and param == 'none': + parsed['mention'] = None + ok = True + elif param == 'me': + parsed['mention'] = message.author.id + if 'channel_id' not in parsed: + parsed['channel_id'] = 'private' ok = True else: m = re.match(r'<@((?:&?|#)\d+)>', param) @@ -1177,32 +1223,35 @@ def create_announcement(message, raw): if m is None: m = re.match('r@(everyone|here)', param) if m is not None: - create['mention'] = m.group(1) + parsed['mention'] = m.group(1) ok = True else: break elif arg in ['in', 'voice']: k = 'channel_id' if arg == 'in' else 'voice_id' - if param == 'here': + if editing and param == 'none': + parsed['mention'] = None + ok = True + elif param == 'here': if arg != 'in': break if message.channel.is_private: - create[k] = 'private' + parsed[k] = 'private' ok = True else: - create[k] = message.channel.id + parsed[k] = message.channel.id ok = True elif param == 'private': if arg != 'in': break - create[k] = 'private' + parsed[k] = 'private' ok = True else: m = re.match(r'<#(\d+)>', param) if m is None: m = re.match(r'#(\d+)', param) if m is not None: - create[k] = m.group(1) + parsed[k] = m.group(1) ok = True else: m = re.match(r'#(.+)', param) @@ -1211,14 +1260,18 @@ def create_announcement(message, raw): # Look for a channel with that name. for channel in client.get_all_channels(): if channel.name == m.group(1): - create[k] = channel.id + parsed[k] = channel.id ok = True break elif arg == 'sound': - create['sound'] = param - ok = True + if editing and param == 'none': + parsed['mention'] = None + ok = True + else: + parsed['sound'] = param + ok = True elif arg == 'probability': - create['probability'] = param + parsed['probability'] = param ok = True else: yield from say(message.channel, 'What is {}?'.format(arg)) @@ -1230,39 +1283,54 @@ def create_announcement(message, raw): break if not ok: - log.info('Failed to parse announcement. Got: {}'.format(create)) + log.info('Failed to parse announcement. Got: {}'.format(parsed)) yield from say(message.channel, '{}?'.format(arg)) - return + return None - if 'channel_id' not in create: - if 'voice_id' in create: - create['channel_id'] = create['voice_id'] + if 'channel_id' in parsed: + if parsed['channel_id'] == 'private': + parsed['server_id'] = 'private' + if 'mention' in parsed: + if parsed['mention'] != message.author.id: + yield from say(message.channel, "Can't mention someone else in private message!") + return None + parsed['mention'] = None + if 'voice_id' in parsed or 'sound' in parsed: + yield from say(message.channel, 'No sounds for private messages!') + return None else: - yield from say(message.channel, 'Missing channel!') - return + channel = client.get_channel(parsed['channel_id']) + if not channel: + yield from say(message.channel, 'Invalid channel!') + return None + server = channel.server + parsed['server_id'] = server.id + + if 'start_date' in parsed and 'end_date' in parsed: + if parsed['start_date'] > parsed['end_date']: + yield from say(message.channel, "Start date must not be before end date!") + return None - if create['channel_id'] == 'private': - create['server_id'] = 'private' - if 'mention' in create: - if create['mention'] != message.author.id: - yield from say(message.channel, "Can't mention someone else in private message!") - return - create['mention'] = None - if 'voice_id' in create or 'sound' in create: - yield from say(message.channel, 'No sounds for private messages!') - return + if editing: + if 'id' not in parsed: + yield from say(message.channel, 'Missing ID!') + return None else: - channel = client.get_channel(create['channel_id']) - if not channel: - yield from say(message.channel, 'Invalid channel!') - return - server = channel.server - create['server_id'] = server.id + if 'channel_id' not in parsed: + if 'voice_id' in parsed: + parsed['channel_id'] = parsed['voice_id'] + else: + yield from say(message.channel, 'Missing channel!') + return None - if 'start_date' in create and 'end_date' in create: - if create['start_date'] > create['end_date']: - yield from say(message.channel, "Start date must not be before end date!") - return + return parsed + +@asyncio.coroutine +def create_announcement(message, raw): + # announcement create [params]: + create = yield from parse_announcement(message, raw) + if create is None: + return announce = yield from can_manage_announcements(message.author, message.channel, 'create', create = create) if not announce: @@ -1279,6 +1347,27 @@ def create_announcement(message, raw): yield from say(message.channel, 'yelp!') @asyncio.coroutine +def edit_announcement(message, raw): + update = yield from parse_announcement(message, raw, True) + if update is None: + return + + id = update['id'] + del(update['id']) + announce = yield from can_manage_announcements(message.author, message.channel, 'edit', id = id) + if not announce: + return + + if db.update_announcement(client, id, **update): + log.info('Edited announcement {}: {}'.format(id, update)) + yield from say(message.channel, ', '.join([announcement_key(k) for k in update])) + yield from play_sound(voice_channel_for_channel(message.channel), 'purr.wav', join = False) + else: + log.info('Failed to edit announcement {}: {}'.format(id, update)) + yield from say(message.channel, 'yelp!') + + +@asyncio.coroutine def delete_announcement(message, id): announcement = yield from can_manage_announcements(message.author, message.channel, 'delete', id = id) if not announcement: @@ -1356,6 +1445,8 @@ def manage_announcements(message, command, raw): yield from schedule_announcement(message, args[1], pause = False) elif command == 'create': yield from create_announcement(message, text) + elif command == 'edit': + yield from edit_announcement(message, text) @asyncio.coroutine def announce(announcement):