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.')
'asap',
'create',
'delete',
+ 'edit',
'list',
'pause',
'show',
'```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':
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]: <text>
+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:
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
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)
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)
# 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))
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]: <text>
+ 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:
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:
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):