Plugin to prune old members.
authorCMDR furrycat <elite@furrycat.net>
Sun, 9 Dec 2018 11:43:05 +0000 (11:43 +0000)
committerCMDR furrycat <elite@furrycat.net>
Sun, 9 Dec 2018 11:53:16 +0000 (11:53 +0000)
plugin/prune/__init__.py [new file with mode: 0644]

diff --git a/plugin/prune/__init__.py b/plugin/prune/__init__.py
new file mode 100644 (file)
index 0000000..f7bed65
--- /dev/null
@@ -0,0 +1,195 @@
+import asyncio
+import datetime
+import shlex
+
+from plugins import PluginCommand
+import bot
+import cat
+
+class Prune(object):
+  def description(self):
+    return 'Prunes inactive members.'
+
+  def valid_commands(self):
+    return ['prune']
+
+  async def handle_command(self, message, command, raw):
+    if command not in self.valid_commands():
+      return PluginCommand.ignored
+    await self.run_command(message, shlex.split(raw))
+    return PluginCommand.exclusive
+
+  async def handle_help(self, message, command, *args):
+    await self.help_command(message, command, *args)
+
+  async def run_command(self, message, args):
+    command = args[0]
+
+    if command == 'prune':
+      await self.handle_prune(message, args[1:])
+
+  async def help_command(self, message, command):
+    if command == 'prune':
+      lines = [
+        'Identify and optionally kick inactive members.',
+        '```',
+        'prune server SERVER [only] role ROLE days DAYS',
+        'prune server SERVER [only] role ROLE days DAYS kick',
+        '```',
+        '',
+        'Here are the parameters:',
+        '```server SERVER```',
+        'Specify the server on which members will be searched.',
+        '',
+        '```role ROLE```',
+        'Only find members who have the given role.',
+        '```only role ROLE```',
+        'Omit members who have other roles as well as the given role.  Include them if they have the role and no others.',
+        '',
+        '```days DAYS```',
+        'Omit members who have been on the server for less than `DAYS` days.',
+        '',
+        '```kick```',
+        'Kick matching members.  Without `kick` they will just be listed'
+      ]
+    else:
+      lines = ['*shrugs*']
+    await bot.say_many(message.channel, lines)
+
+  async def handle_prune(self, message, args):
+    if args[0].lower() == 'help':
+      await self.help_command(message, 'prune')
+      return
+
+    if not bot.is_admin(message.author):
+      await cat.hiss(message.channel)
+      return
+
+    parsed = {
+      'kick': False,
+      'only': False
+    }
+    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 == 'days':
+        if param is not None:
+          try:
+            parsed['days'] = int(param)
+            if parsed['days'] < 1:
+              await bot.say(message.channel, 'days must be positive!')
+              return
+            ok = True
+          except:
+            await bot.say(message.channel, '{}?'.format(param))
+            return
+      elif arg == 'kick':
+        parsed['kick'] = True
+        i -= 1
+      elif arg == 'only':
+        ok = True
+        parsed['only'] = True
+        i -= 1
+      elif arg == 'role':
+        if param is not None:
+          if param in ['everyone', 'here']:
+            await bot.say(message.channel, 'role must not be *everyone* or *here*!')
+            return None
+          ok = True
+          parsed['role'] = await bot.parse_role(param, author = message.author)
+      elif arg == 'server':
+        if param is not None:
+          ok = True
+        parsed['server'] = bot.parse_server(param, author = message.author)
+
+      if ok:
+        i += 2
+      else:
+        break
+
+    if not ok:
+      log.info('Failed to parse prune.  Got: {}'.format(parsed))
+      await bot.say(message.channel, '{}?'.format(arg))
+      return None
+
+    if 'days' not in parsed:
+      await bot.say(message.channel, 'How long?')
+      return None
+
+    if 'role' in parsed:
+      if parsed['role'] is None:
+        await bot.say(message.channel, 'Role?')
+        return None
+    if 'server' not in parsed:
+      if message.channel.is_private:
+        await bot.say(message.channel, 'Which server?')
+        return None
+      parsed['server'] = message.channel.server
+    elif parsed['server'] is None:
+      await bot.say(message.channel, 'Server?')
+      return None
+
+    if 'role' in parsed:
+      # Prune by role.
+      members = await self.find_members(**parsed)
+      if not len(members):
+        await cat.shrug(message.channel)
+      else:
+        await bot.say_many(message.channel, [member.name for member in members])
+        if parsed['kick']:
+          for member in members:
+            try:
+              await client.kick(member)
+            except:
+              log.exception('Failed to kick {}'.format(member.name))
+              await cat.yelp(message.channel)
+              break
+        else:
+          await bot.say(message.channel, 'Use `kick` option to kick the above members!')
+    else:
+      # Use server prune_members API call.
+      pruned = None
+      if parsed['kick']:
+        try:
+          log.info('Pruning members older than {} day{} from server {}.'.format(parsed['days'], 's' if parsed['days'] != 1 else '', parsed['server'].name))
+          pruned = await client.prune_members(parsed['server'], days = parsed['days'])
+        except:
+          log.exception('Failed to prune members from {}'.format(parsed['server'].name))
+          await cat.yelp(message.channel)
+      else:
+        try:
+          log.info('Estimating pruned members older than {} day{} from server {}.'.format(parsed['days'], 's' if parsed['days'] != 1 else '', parsed['server'].name))
+          pruned = await client.estimate_pruned_members(parsed['server'], days = parsed['days'])
+        except:
+          log.exception('Failed to estimate pruned members from {}'.format(parsed['server'].name))
+          await cat.yelp(message.channel)
+      if pruned is not None:
+        await bot.say(message.channel, str(pruned))
+  async def find_members(self, days = None, kick = False, only = False, role = None, server = None):
+    cutoff = datetime.datetime.utcnow() - datetime.timedelta(days)
+
+    members = []
+    for member in server.members:
+      if member.joined_at > cutoff:
+        log.debug('Member {} on server {} joined more recently than {}'.format(member.name, server.name, cutoff))
+        continue
+      if role is not None:
+        if role not in member.roles:
+          log.debug('Member {} on server {} does not have required role {}'.format(member.name, server.name, role.name))
+          continue
+        if only:
+          if len([other_role for other_role in member.roles if other_role not in [role, server.default_role]]):
+            log.debug('Member {} on server {} has roles other than {}'.format(member.name, server.name, role.name))
+            continue
+      members.append(member)
+    return members