Moved some admin commands to plugin.
authorCMDR furrycat <elite@furrycat.net>
Sat, 29 Oct 2016 09:36:19 +0000 (10:36 +0100)
committerCMDR furrycat <elite@furrycat.net>
Sat, 29 Oct 2016 09:36:19 +0000 (10:36 +0100)
app.py
bot.py
db.py
plugin/admin/admin.py [new file with mode: 0644]

diff --git a/app.py b/app.py
index fa13d99..54771f7 100644 (file)
--- a/app.py
+++ b/app.py
@@ -44,14 +44,6 @@ token_file = 'TOKEN'
 if len(sys.argv) > 1:
   token_file = sys.argv[1]
 
-admins = []
-admin_role = 'Cat'
-fd = open('ADMINS', 'r')
-for line in fd.readlines():
-  admin = line.strip()
-  admins.append(admin)
-fd.close()
-
 fd = open(token_file, 'r')
 token = fd.readline().strip()
 fd.close()
@@ -59,6 +51,7 @@ logging.basicConfig(level = logging.DEBUG if bot.get('debug') else logging.WARN,
 discordlog = logging.getLogger('discord')
 log = logging.getLogger('catbot')
 bot.log = log
+bot.db = db
 log.setLevel(logging.DEBUG if bot.get('debug') else logging.INFO)
 plugins.log_level(log.level)
 
@@ -83,17 +76,6 @@ def not_admin(message):
   yield from bot.maybe_say(message.channel, 'hiss!')
 
 @asyncio.coroutine
-def is_admin(server, member):
-  if server is None:
-    return member.id in admins
-  for role in server.roles:
-    if role.name != admin_role:
-      continue
-    if role in member.roles:
-      return True
-  return False
-
-@asyncio.coroutine
 def identify(destination, target):
   do = Identify.member.value | Identify.role.value | Identify.channel.value
   channel_id = None
@@ -248,8 +230,7 @@ def show_help(message, *args):
 @asyncio.coroutine
 def do_commands(message, command, raw, non_admin):
   if not non_admin:
-    result = yield from is_admin(message.server, message.author)
-    if not result:
+    if not bot.is_admin(message.author, message.server):
       yield from not_admin(message)
       return
 
@@ -272,25 +253,6 @@ def do_commands(message, command, raw, non_admin):
       yield from cat.yelp(message.channel)
       return
 
-  elif command == 'debug':
-    # debug
-    m = re.match(r'\bdebug\s+(o(?:ff|n))\b', raw, re.IGNORECASE)
-    if m is None:
-      yield from bot.say(message.channel, 'Sorry!')
-      return
-
-    arg = m.group(1)
-    if arg == 'on':
-      discordlog.setLevel(logging.DEBUG)
-      log.setLevel(logging.DEBUG)
-      db.log_level(logging.DEBUG)
-      plugins.log_level(logging.DEBUG)
-    elif arg == 'off':
-      discordlog.setLevel(logging.WARN)
-      log.setLevel(logging.INFO)
-      db.log_level(logging.WARN)
-      plugins.log_level(logging.INFO)
-
   elif command == 'delete':
     # delete
     if message.channel.is_private:
@@ -306,21 +268,6 @@ def do_commands(message, command, raw, non_admin):
     yield from client.purge_from(message.channel, *args, limit = limit)
     return
 
-  elif command == 'dryrun':
-    # dryrun
-    m = re.match(r'\bdryrun\s+(o(?:ff|n))\b', raw, re.IGNORECASE)
-    if m is None:
-      yield from bot.say(message.channel, 'Sorry!')
-      return
-
-    arg = m.group(1)
-    if arg == 'on':
-      log.info('DRYRUN mode')
-      bot.set('dryrun', True)
-    elif arg == 'off':
-      log.info('Live mode')
-      bot.set('dryrun', False)
-
   elif command == 'id':
     # id
     m = re.match(r'\bid\s+(.*)', raw, re.IGNORECASE)
@@ -416,7 +363,7 @@ modules = {
   'admin': {
     'fn': do_commands,
     'args': [False],
-    'commands': ['avatar', 'debug', 'delete', 'dryrun', 'idle', 'rss']
+    'commands': ['avatar', 'delete', 'idle', 'rss']
   },
   'any': {
     'fn': do_commands,
@@ -439,6 +386,9 @@ client = discord.Client()
 bot.client = client
 bot.db = db
 plugins.set_client(client)
+if 'admin' in plugins.plugins():
+  plugins.get_plugin('admin').db = db
+  plugins.get_plugin('admin').plugins = plugins
 
 @asyncio.coroutine
 def builtin_commands(message, command, raw):
@@ -492,7 +442,6 @@ def on_ready():
     if state['last_spoke']:
       bot.set('last_spoke', state['last_spoke'])
   log.info('Logged in as {}#{}'.format(client.user.name, client.user.id))
-  log.info('Admins are: {}'.format(admins))
   if not threads_ready.value:
     plugins.on_ready()
     asyncio.async(maybe_sleep())
diff --git a/bot.py b/bot.py
index ade6c6c..25f11c4 100644 (file)
--- a/bot.py
+++ b/bot.py
@@ -22,6 +22,23 @@ variables = {
   'playing': multiprocessing.Value('b', False)
 }
 
+def is_admin(user, server = None):
+  ret = False
+  params = ['global']
+  if server is not None:
+    params.append(server.id)
+  sql = 'select id from admins where server_id in ({}) and bot_id in (?, ?) and user_id=?'.format(', '.join(['?'] * len(params)))
+  params.append('global')
+  params.append(client.user.id)
+  params.append(user.id)
+  cursor = db.query(sql, params)
+  for row in cursor.fetchall():
+    ret = True
+    break
+  db.close_db()
+  log.info('User {} {} an administrator{}'.format(user.name, 'is' if ret else 'is not', ' on server {}'.format(server.name) if server is not None else ''))
+  return ret
+
 def get(key):
   if key in variables:
     return variables[key].value
diff --git a/db.py b/db.py
index c37bd20..16f936e 100644 (file)
--- a/db.py
+++ b/db.py
@@ -33,6 +33,9 @@ class DBConnection(object):
     cursor = self.query('create table if not exists state (id char(36) not null, bot_id varchar(32) not null, avatar varchar(128), idle boolean not null default false, last_spoke datetime)')
     cursor = self.query('create unique index if not exists state_id on state (id)')
     cursor = self.query('create unique index if not exists state_bot_id on state (bot_id)')
+    cursor = self.query('create table if not exists admins (id char(36) not null, bot_id varchar(32) not null, server_id varchar(32) not null, user_id varchar(32))')
+    cursor = self.query('create unique index if not exists admins_id on admins (id)')
+    cursor = self.query('create index if not exists admins_bot_id on admins (bot_id)')
     self.dbh.commit()
     self.close_db()
 
diff --git a/plugin/admin/admin.py b/plugin/admin/admin.py
new file mode 100644 (file)
index 0000000..5e604c3
--- /dev/null
@@ -0,0 +1,133 @@
+import asyncio
+import logging
+import shlex
+import sys
+
+from plugins import PluginCommand
+import bot
+import cat
+
+class Admin(object):
+  def __init__(self):
+    self.discordlog = logging.getLogger('discord')
+    self.botlog = logging.getLogger('catbot')
+    self.db = None
+    self.plugins = None
+    return None
+
+  def description(self):
+    return 'Lets privileged users manage the bot.'
+
+  def create_tables(self):
+    self.open_db()
+    self.dbh.commit()
+    self.close_db()
+
+  def valid_commands(self):
+    return ['debug', 'dryrun', 'shutdown']
+
+  @asyncio.coroutine
+  def handle_command(self, message, command, raw):
+    if command not in self.valid_commands():
+      return PluginCommand.ignored
+    yield from self.run_command(message, shlex.split(raw))
+    return PluginCommand.exclusive
+
+  @asyncio.coroutine
+  def handle_help(self, message, command, *args):
+    yield from self.help_command(message, *args)
+
+  @asyncio.coroutine
+  def run_command(self, message, args):
+    if not bot.is_admin(message.author):
+      yield from cat.hiss(message.channel)
+      return
+
+    command = args[0]
+    if command == 'debug':
+      yield from self.handle_debug(message, args[1:])
+    elif command == 'dryrun':
+      yield from self.handle_dryrun(message, args[1:])
+    elif command == 'shutdown':
+      yield from self.handle_shutdown(message)
+
+  @asyncio.coroutine
+  def help_command(self, message, command):
+    if command == 'debug':
+      lines = [
+        'Turn debugging on or off.',
+        '```',
+        'debug on',
+        'debug off',
+        '```',
+        '',
+        'You will need access to my logs to see the debugging output!'
+      ]
+    elif command == 'dryrun':
+      lines = [
+        'Enable or disable plugins (if supported).',
+        '```',
+        'dryrun on',
+        'dryrun off',
+        '```'
+      ]
+    elif command == 'shutdown':
+      lines = [
+        'Shut the bot down!',
+        '```shutdown```'
+      ]
+    else:
+      lines = ['*shrugs*']
+    yield from bot.say_many(message.channel, lines)
+
+  @asyncio.coroutine
+  def handle_debug(self, message, args):
+    if len(args):
+      arg = args[0].lower()
+      if arg == 'on':
+        self.discordlog.setLevel(logging.DEBUG)
+        self.botlog.setLevel(logging.DEBUG)
+        if self.db is not None:
+          self.db.log_level(logging.DEBUG)
+        if self.plugins is not None:
+          self.plugins.log_level(logging.DEBUG)
+      elif arg == 'off':
+        self.discordlog.setLevel(logging.WARN)
+        self.botlog.setLevel(logging.INFO)
+        if self.db is not None:
+          self.dblog.log_level(logging.WARN)
+        if self.plugins is not None:
+          self.plugins.log_level(logging.INFO)
+      else:
+        yield from cat.yelp(message.channel)
+        return
+      yield from bot.maybe_say(message.channel, 'purr', wake = False)
+    else:
+      yield from cat.yelp(message.channel)
+
+  @asyncio.coroutine
+  def handle_dryrun(self, message, args):
+    if len(args):
+      arg = args[0].lower()
+      if arg == 'on':
+        log.info('DRYRUN mode.')
+        bot.set('dryrun', True)
+      elif arg == 'off':
+        log.info('Live mode.')
+        bot.set('dryrun', False)
+      else:
+        yield from cat.yelp(message.channel)
+        return
+      yield from bot.maybe_say(message.channel, 'purr', wake = False)
+    else:
+      yield from cat.yelp(message.channel)
+
+  @asyncio.coroutine
+  def handle_shutdown(self, message):
+    log.warning('Shutdown initiated!')
+    result = yield from bot.say(message.channel, 'Goodbye!')
+    result = yield from client.logout()
+    try:
+      sys.exit(0)
+    except:
+      pass