From 85befc2394cdbaa040ce71f848db766f8636fa65 Mon Sep 17 00:00:00 2001 From: CMDR furrycat Date: Thu, 6 Oct 2016 12:44:49 +0100 Subject: [PATCH] Users can create announcements. --- bot.py | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- db.py | 27 +++++++- 2 files changed, 236 insertions(+), 24 deletions(-) diff --git a/bot.py b/bot.py index 232a03a..d681463 100755 --- a/bot.py +++ b/bot.py @@ -722,34 +722,54 @@ def can_manage_announcements(member, channel, command, **args): if 'id' in args: announcement = db.get_announcement(client, args['id']) + elif 'create' in args: + announcement = args['create'] else: announcement = None # Anyone on the server can show details of an announcement. if command == 'show': if announcement is not None: - server = client.get_server(announcement['server_id']) - if server is not None: - if member in server.members: - log.debug('Member {} on server {} can show announcement {}.'.format(member.name, server.name, announcement['id'])) - return announcement + if announcement['member_id'] == member.id: + log.debug('Anyone can show own announcements.') + return announcement + if announcement['channel_id'] != 'private': + server = client.get_server(announcement['server_id']) + if server is not None: + if member in server.members: + log.debug('Member {} on server {} can show announcement {}.'.format(member.name, server.name, announcement['id'])) + return announcement if command in ['delete', 'schedule']: if announcement is not None: - server = client.get_server(announcement['server_id']) - announcer = server.get_member(announcement['member_id']) - if member.id == announcer.id: - log.debug('Member {} on server {} can edit own announcements.'.format(member.name, server.name)) - return announcement - announcer_role = highest_role(announcer.roles) - member_role = highest_role(member.roles) - if member_role > announcer_role: - log.debug('Member {} on server {} can edit announcements from lower role.'.format(member.name, server.name)) + if announcement['member_id'] == member.id: + log.debug('Anyone can manage own announcements.') return announcement - elif member_role == announcer_role: - if member_role == highest_role(server.roles): - log.debug('Member {} with role {} on server {} can edit announcements.'.format(member.name, member_role.name, server.name)) + if announcement['channel_id'] != 'private': + server = client.get_server(announcement['server_id']) + announcer = server.get_member(announcement['member_id']) + announcer_role = highest_role(announcer.roles) + member_role = highest_role(member.roles) + if member_role > announcer_role: + log.debug('Member {} on server {} can edit announcements from lower role.'.format(member.name, server.name)) return announcement + elif member_role == announcer_role: + if member_role == highest_role(server.roles): + log.debug('Member {} with role {} on server {} can edit announcements.'.format(member.name, member_role.name, server.name)) + return announcement + + if command == 'create': + if announcement['channel_id'] == 'private': + log.debug('Anyone can schedule a private announcement.') + return announcement + server = client.get_server(announcement['server_id']) + member = server.get_member(announcement['member_id']) + bot = server.get_member(client.user.id) + member_role = highest_role(member.roles) + bot_role = highest_role(bot.roles) + if member_role > bot_role: + log.debug('Member {} with role {} on server {} can create announcements.'.format(member.name, member_role.name, server.name)) + return announcement yield from say(channel, 'hiss!') return False @@ -761,6 +781,7 @@ def list_announcements(message): return servers = [] if message.channel.is_private: + servers.append('private') for server in client.servers: if message.author in server.members: servers.append(server) @@ -786,8 +807,15 @@ def show_announcement(message, id): return lines = [] - server = client.get_server(announcement['server_id']) - member = server.get_member(announcement['member_id']) + if announcement['channel_id'] == 'private': + for server in client.servers: + try: + member = server.get_member(announcement['member_id']) + except: + pass + else: + server = client.get_server(announcement['server_id']) + member = server.get_member(announcement['member_id']) text = '**announcement {} by {}'.format(id, member.name) now = int(time.time()) if announcement['last_spoke']: @@ -829,7 +857,10 @@ def show_announcement(message, id): else: text += ' tell <@{}>'.format(mention) if announcement['channel_id'] != announcement['voice_id']: - text += ' in <#{}>'.format(announcement['channel_id']) + if announcement['channel_id'] == 'private': + text += ' in private' + else: + text += ' in <#{}>'.format(announcement['channel_id']) if announcement['voice_id']: text += ' voice <#{}>'.format(announcement['voice_id']) if announcement['sound']: @@ -846,6 +877,148 @@ def show_announcement(message, id): yield from say(message.channel, '\n'.join(lines)) @asyncio.coroutine +def create_announcement(message, raw): + # announcement create [params]: + parts = raw.split(':') + params = parts[0] + if len(parts) > 1: + text = ':'.join(parts[1:]).strip() + else: + text = None + log.debug('announce create params {} text {}'.format(params, text)) + + create = { 'member_id': message.author.id } + if text: + create['message'] = 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 + param = args[i + 1] + log.info('{}: {}={}'.format(i, arg, param)) + ok = False + + if arg == 'create': + i -= 1 + ok = True + elif arg == 'once': + create['interval'] = 0 + i -= 1 + ok = True + elif arg in ['from', 'to']: + k = 'start_date' if arg == 'from' else 'end_date' + if re.match(r'\d\d\d\d-\d\d-\d\d[T\s]\d\d:\d\d:\d\dZ?', param): + create[k] = param + ok = True + else: + break + elif arg == 'every': + m = re.match(r'(\d+)s?', param) + if m is not None: + create['interval'] = m.group(1) + ok = True + else: + break + elif arg == 'tell': + if param == 'me': + create['mention'] = message.author.id + ok = True + else: + m = re.match(r'<@((?:&?|#)\d+)>', param) + if m is None: + 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) + ok = True + else: + break + elif arg in ['in', 'voice']: + k = 'channel_id' if arg == 'in' else 'voice_id' + if param == 'here': + if arg != 'in': + break + if message.channel.is_private: + create[k] = 'private' + ok = True + else: + create[k] = message.channel.id + ok = True + elif param == 'private': + if arg != 'in': + break + create[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) + ok = True + else: + break + elif arg == 'sound': + create['sound'] = param + ok = True + elif arg == 'probability': + create['probability'] = param + ok = True + else: + yield from say(message.channel, 'What is {}?'.format(arg)) + return + + if ok: + i += 2 + else: + break + + if not ok: + log.info('Failed to parse announcement. Got: {}'.format(create)) + yield from say(message.channel, '{}?'.format(arg)) + return + + if 'channel_id' not in create: + yield from say(message.channel, 'Missing channel!') + return + + 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 + 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 + + announce = yield from can_manage_announcements(message.author, message.channel, 'create', create = create) + if not announce: + return + + log.info('Creating announcement: {}'.format(create)) + id = db.create_announcement(client, **create) + if id: + yield from say(message.channel, id) + yield from play_sound(voice_channel_for_channel(message.channel), 'purr.wav', join = False) + else: + 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: @@ -895,7 +1068,8 @@ def manage_announcements(message, command, raw): if m is None: yield from list_announcements(message) return - args = shlex.split(m.group(1)) + text = m.group(1) + args = shlex.split(text) if len(args) == 1: if args[0] == 'list': yield from list_announcements(message) @@ -914,12 +1088,27 @@ def manage_announcements(message, command, raw): yield from schedule_announcement(message, args[1], pause = True) elif args[0] == 'resume': yield from schedule_announcement(message, args[1], pause = False) + elif args[0] == 'create': + yield from create_announcement(message, text) @asyncio.coroutine def announce(announcement): - channel = client.get_channel(str(announcement['channel_id'])) + channel = None + channel_id = str(announcement['channel_id']) + if channel_id == 'private': + for server in client.servers: + try: + member = server.get_member(announcement['member_id']) + channel = yield from client.start_private_message(member) + break + except: + pass + else: + channel = client.get_channel(channel_id) if channel is None: log.warning("Can't get channel for announcement {}".format(announcement['message'])) + # Set last_spoke so we don't spam. + db.set_announcement(client, announcement['id'], announcement['digest'] if 'digest' in announcement else None) return voice_only = announcement['voice_id'] == announcement['channel_id'] diff --git a/db.py b/db.py index f87e0d4..1217bdf 100644 --- a/db.py +++ b/db.py @@ -80,7 +80,10 @@ class DBConnection(object): if len(servers): sql += ' and server_id in ({})'.format(', '.join(['?'] * len(servers))) for server in servers: - params.append(server.id) + if type(server) == discord.server.Server: + params.append(server.id) + else: + params.append(server) cursor = self.query(sql, params) for row in cursor.fetchall(): yield dict(row) @@ -98,7 +101,7 @@ class DBConnection(object): def get_announcements(self, client): now = self.now() - cursor = self.query('select id, server_id, channel_id, voice_id, sound, mention, start_date, end_date, interval, probability, last_spoke, message, digest from announcements where (start_date is null or start_date <= ?) and (end_date is null or end_date > ? or end_date=start_date) and (last_spoke is null or last_spoke < ? - interval) and bot_id=?', [now, now, now, client.user.id]) + cursor = self.query('select id, server_id, member_id, channel_id, voice_id, sound, mention, start_date, end_date, interval, probability, last_spoke, message, digest from announcements where (start_date is null or start_date <= ?) and (end_date is null or end_date > ? or end_date=start_date) and (last_spoke is null or last_spoke < ? - interval) and (last_spoke is null or interval > 0) and bot_id=?', [now, now, now, client.user.id]) for row in cursor.fetchall(): yield dict(row) self.close_db() @@ -109,6 +112,26 @@ class DBConnection(object): self.dbh.commit() self.close_db() + def create_announcement(self, client, **args): + id = self.uuid() + + values = [] + params = [id, client.user.id] + for k, v in args.items(): + values.append(k) + params.append(v) + sql = 'insert into announcements (id, bot_id, {}) values (?, ?, {})'.format(', '.join(values), ', '.join(['?'] * len(values))) + cursor = self.query(sql, params) + if cursor.rowcount: + self.dbh.commit() + ret = id + else: + log.error('Failed to create announcement {}: {}'.format(id, args)) + self.dbh.rollback() + ret = None + self.close_db() + return ret + def update_announcement(self, client, id, **args): values = [] params = [] -- 2.7.4