From 773efe060662ecf7dda3e57eca64b608a2449ab2 Mon Sep 17 00:00:00 2001 From: Etzelia Date: Tue, 27 Aug 2019 22:44:20 +0200 Subject: [PATCH 01/19] Updates to API and Discord (#28) --- api/api.py | 21 ++-- api/bot.py | 212 --------------------------------- api/views.py | 6 +- assets/bots/Discord-MCM.bot.py | 7 +- assets/coreprotect/blocks.txt | 9 -- bot/__init__.py | 0 bot/commands.py | 205 +++++++++++++++++++++++++++++++ bot/discord.py | 80 +++++++++++++ bot/utils.py | 27 +++++ templatetags/sidebar.py | 4 +- urls.py | 6 +- 11 files changed, 338 insertions(+), 239 deletions(-) delete mode 100644 api/bot.py delete mode 100644 assets/coreprotect/blocks.txt create mode 100644 bot/__init__.py create mode 100644 bot/commands.py create mode 100644 bot/discord.py create mode 100644 bot/utils.py diff --git a/api/api.py b/api/api.py index ab98156..fc04bfb 100644 --- a/api/api.py +++ b/api/api.py @@ -22,14 +22,19 @@ PLUGIN_DEMOTE = 'demote' def plugin(key, command): - host = '127.0.0.1' - port = getattr(settings, 'PLUGIN_PORT', None) - full_command = "{0} {1}".format(key, command) - if port and plugin_exists(): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect((host, port)) - sock.sendall(full_command.encode('utf-8')) - sock.close() + try: + host = '127.0.0.1' + port = getattr(settings, 'PLUGIN_PORT', None) + full_command = "{0} {1}".format(key, command) + if port and plugin_exists(): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((host, port)) + sock.sendall(full_command.encode('utf-8')) + sock.close() + return True + except: + pass + return False def discord_mcm(message='', embeds=None, ping=False): diff --git a/api/bot.py b/api/bot.py deleted file mode 100644 index 2f32173..0000000 --- a/api/bot.py +++ /dev/null @@ -1,212 +0,0 @@ -import discord, logging, re, sys, traceback, asyncio -from minecraft_manager.models import Application, Player -from minecraft_manager.api import api -from django.contrib.auth.models import User -from django.conf import settings -from django.db import close_old_connections - - -logger = logging.getLogger(__name__) - - -class Discord(discord.Client): - discord_game = 'MCM' - prefix = getattr(settings, 'DISCORD_BOT_PREFIX', '!') - auth_roles = getattr(settings, 'DISCORD_BOT_ROLES', []) - error_users = getattr(settings, 'DISCORD_ERROR_USERS', []) - new_member_roles = getattr(settings, 'DISCORD_BOT_NEW_MEMBER_ROLES', []) - token = None - - def __init__(self, token, **kwargs): - super().__init__(**kwargs) - self.token = token - - @asyncio.coroutine - def on_ready(self): - print('Logged in as') - print(self.user.name) - print(self.user.id) - print(discord.__version__) - print('Voice Loaded: {0}'.format(discord.opus.is_loaded())) - print('OAuth URL: https://discordapp.com/api/oauth2/authorize?client_id={0}&permissions=0&scope=bot'.format(self.user.id)) - print('------') - logger.info('Logged in as {0} ({1}) with discord.py v{2}'.format(self.user.name, self.user.id, discord.__version__)) - yield from self.change_presence(game=discord.Game(name=self.discord_game)) - - @asyncio.coroutine - def discord_message(self, channel, message): - if isinstance(message, discord.Embed): - for idx, field in enumerate(message.fields): - if not field.value: - message.set_field_at(idx, name=field.name, value="N/A") - yield from self.send_message(channel, embed=message) - else: - yield from self.send_message(channel, message) - - @asyncio.coroutine - def on_message(self, message): - - # IGNORE DM AND BOTS - if message.author.bot is True or message.channel.is_private is True: - return - - member_roles = [role.id for role in message.author.roles] - - # FIX STALE DB CONNECTIONS - close_old_connections() - - - # IF MEMBER IS NOT AUTHORIZED, IGNORE - if not any(role in self.auth_roles for role in member_roles): - return - - # HELP - match = re.match("[{0}]help$".format(self.prefix), message.content) - if match: - embed = discord.Embed(colour=discord.Colour(0x417505)) - embed.set_thumbnail(url="https://cdn.discordapp.com/avatars/454457830918062081/b5792489bc43d9e17b8f657880a17dd4.png") - embed.add_field(name="Minecraft Manager Help", value="-----------------------------") - embed.add_field(name="{}[app ]search ".format(self.prefix), value="Search for applications by partial or exact username.") - embed.add_field(name="{}[app ]info ".format(self.prefix), value="Get detailed information about a specific application.") - embed.add_field(name="{}[app ]accept|deny ".format(self.prefix), value="Take action on an application.") - embed.add_field(name="{}demote ".format(self.prefix), value="Demote a player to the role given to accepted applications.") - embed.add_field(name="{}compare".format(self.prefix), value="Compare Discord users to the Whitelist.") - yield from self.discord_message(message.channel, embed) - # APP COMMANDS WITH APP ID - match = re.match("[{0}](?:app )?(i|info|a|accept|d|deny) (\d+)$".format(self.prefix), message.content) - if match: - if match.group(1) and match.group(2): - action = match.group(1)[:1] - action_display = "accept" if action == "a" else "deny" if action == "d" else "" - application = None - try: - application = Application.objects.get(id=match.group(2)) - except: - yield from self.discord_message(message.channel, "An Application with that ID doesn't exist.") - return - if action == "i": - # Info - msg = self.build_info(application) - else: - # Action - accept = True if action == "a" else False - if not application.accepted: - application.accepted = accept - application.save() - if Player.objects.filter(username__iexact=application.username).exists(): - player = Player.objects.get(username__iexact=application.username) - player.application_id = application.id - player.save() - msg = "App ID **{0}** was successfully {1}.".format(match.group(2), "accepted" if accept else "denied") - api.plugin("accept" if accept else "deny", application.username) - else: - msg = "App ID **{0}** was already {1}.".format(match.group(2), "accepted" if application.accepted else "denied") - yield from self.discord_message(message.channel, msg) - return - # APP INFO WITH PARTIAL NAME SEARCH - match = re.match("[{0}](?:app )?(?:search|info) (\S+)?$".format(self.prefix), message.content) - if match: - search = match.group(1) - applications = Application.objects.filter(username__icontains=search)[:10] - count = Application.objects.filter(username__icontains=search).count() - if count > 0: - if count == 1: - info = self.build_info(applications[0]) - else: - info = "**Found the following applications**" - for app in applications: - info += "\n{0} - {1} ({2})".format(app.id, app.username.replace("_", "\\_"), app.status) - if count > 10: - info += "\n**This is only 10 applications out of {0} found. Please narrow your search if possible.**".format( - len(applications)) - else: - players = Player.objects.filter(username__icontains=search, application__isnull=False)[:10] - count = Player.objects.filter(username__icontains=search, application__isnull=False).count() - if count > 0: - if count == 1: - yield from self.discord_message(message.channel, "**No applications matched, however there is a player match**") - info = self.build_info(players[0].application) - else: - info = "**No applications matched, however there are player matches**" - for player in players: - app = player.application - info += "\n{0} - {1} AKA {2} ({3})".format(app.id, app.username.replace("_", "\\_"), player.username.replace("_", "\\_"), app.status) - if count > 10: - info += "\n**This is only 10 players out of {0} found. Please narrow your search if possible.**".format( - len(players)) - else: - info = "No applications matched that search." - yield from self.discord_message(message.channel, info) - # DEMOTE A PLAYER TO MEMBER - match = re.match("[{0}]demote (\w+)$".format(self.prefix), message.content) - if match: - yield from self.delete_message(message) - username = match.group(1) - api.plugin(api.PLUGIN_DEMOTE, username) - deactivated = "" - if User.objects.filter(username__iexact=username).exists(): - user = User.objects.get(username__iexact=username) - user.is_active = False - user.save() - deactivated = " and de-activated" - yield from self.discord_message(message.channel, "{} has been demoted{}.".format(username, deactivated)) - # COMPARE DISCORD USERS TO WHITELIST - match = re.match("[{0}]compare".format(self.prefix), message.content) - if match: - yield from self.delete_message(message) - yield from self.send_typing(message.channel) - no_player = [] - no_application = [] - for member in message.server.members: - if member.bot: - continue - name = member.nick if member.nick else member.name - try: - Player.objects.get(username__iexact=name) - except: - no_player.append(name) - try: - Application.objects.get(username__iexact=name) - except: - no_player = no_player[:-1] - no_application.append(name) - header = "**The following users have an application match, but no player match on the whitelist:**\n" - yield from self.discord_message(message.author, "{}```{}```".format(header, "\n".join(no_player))) - header = "**The following users do not have an application or player match on the whitelist:**\n" - yield from self.discord_message(message.author, "{}```{}```".format(header, "\n".join(no_application))) - - def build_info(self, application): - embed = discord.Embed(colour=discord.Colour(0x417505)) - embed.set_thumbnail( - url="https://minotar.net/helm/{0}/100.png".format(application.username)) - embed.add_field(name="Application ID", value=application.id) - embed.add_field(name="Username", value=application.username.replace("_", "\\_")) - embed.add_field(name="Age", value=application.age) - embed.add_field(name="Type of Player", value=application.player_type) - embed.add_field(name="Ever been banned", value=application.ever_banned) - if application.ever_banned: - embed.add_field(name="Reason for being banned", value=application.ever_banned_explanation) - embed.add_field(name="Reference", value=application.reference) - embed.add_field(name="Read the Rules", value=application.read_rules) - embed.add_field(name="Date", value=application.date_display) - embed.add_field(name="Status", value=application.status) - return embed - - @asyncio.coroutine - def on_error(self, event, *args, **kwargs): - print(sys.exc_info()) - print("Exception raised by " + event) - error = '{0}\n{1}'.format(sys.exc_info()[1], ''.join(traceback.format_tb(sys.exc_info()[2]))) - logger.error(error) - for user in self.error_users: - try: - user = discord.User(id=user) - yield from self.discord_message(user, '```python\n{}```'.format(error)) - except: - pass - - def run_bot(self): - self.run(self.token) - - - diff --git a/api/views.py b/api/views.py index 5b702e4..675452e 100644 --- a/api/views.py +++ b/api/views.py @@ -138,11 +138,11 @@ class WebAPI(View): def access_level(self, user): access = {'cpp': False, 'cpf': False, 'cpa': False} - if user.has_perm('auth.coreprotect_partial'): + if user.has_perm('minecraft_manager.coreprotect_partial'): access['cpp'] = True - if user.has_perm('auth.coreprotect_full'): + if user.has_perm('minecraft_manager.coreprotect_full'): access['cpf'] = True - if user.has_perm('auth.coreprotect_activity'): + if user.has_perm('minecraft_manager.coreprotect_activity'): access['cpa'] = True return access diff --git a/assets/bots/Discord-MCM.bot.py b/assets/bots/Discord-MCM.bot.py index ef882c9..092a99e 100644 --- a/assets/bots/Discord-MCM.bot.py +++ b/assets/bots/Discord-MCM.bot.py @@ -1,4 +1,6 @@ -import os, sys, django +import os +import sys +import django sep = os.sep path = os.path.dirname(os.path.abspath(__file__)) @@ -11,8 +13,9 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{}.settings".format(project)) django.setup() from django.conf import settings -from minecraft_manager.api.bot import Discord +from minecraft_manager.bot.discord import Discord token = getattr(settings, 'DISCORD_BOT_TOKEN', None) bot = Discord(token) + bot.run_bot() diff --git a/assets/coreprotect/blocks.txt b/assets/coreprotect/blocks.txt deleted file mode 100644 index 0bdca39..0000000 --- a/assets/coreprotect/blocks.txt +++ /dev/null @@ -1,9 +0,0 @@ -You will need to get this from your CP MySQL tables - -NOTE: CoreProtect does not match up these IDs with the block's in-game ID, so air is not 0, stone is not 1, etc. - -1. mysql -p -e "SELECT id, material FROM co_material_map" > blocks.txt -2. Open blocks.txt -3. Remove the header row -4. Remove spaces/tabs and delimit with a ',' -e.g. 1,minecraft:air \ No newline at end of file diff --git a/bot/__init__.py b/bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/commands.py b/bot/commands.py new file mode 100644 index 0000000..0a3140c --- /dev/null +++ b/bot/commands.py @@ -0,0 +1,205 @@ +import discord +from discord.ext import commands +from django.contrib.auth.models import User + +from minecraft_manager.api import api +from minecraft_manager.bot.utils import get_application, build_info +from minecraft_manager.models import Application, Player + + +class Commands(commands.Cog): + + def __init__(self, bot): + self.bot = bot + + async def cog_check(self, ctx): + # No DMs + if ctx.guild is None: + return False + + # Check roles + if not hasattr(ctx.author, "roles"): + return False + + for role in ctx.author.roles: + for auth_role in self.bot.auth_roles: + if role.id == auth_role: + return True + return False + + def is_superuser(self, member: discord.Member): + for role in member.roles: + for auth_role in self.bot.superuser_roles: + if role.id == auth_role: + return True + return False + + @commands.command() + async def help(self, ctx): + embed = discord.Embed(colour=discord.Colour(0x417505)) + embed.set_thumbnail( + url="https://cdn.discordapp.com/avatars/454457830918062081/b5792489bc43d9e17b8f657880a17dd4.png") + embed.add_field(name="Minecraft Manager Help", value="-----------------------------") + embed.add_field(name="{}app search ".format(self.bot.prefix), + value="Search for applications by partial or exact username.") + embed.add_field(name="{}app info ".format(self.bot.prefix), + value="Get detailed information about a specific application.") + embed.add_field(name="{}app accept|deny ".format(self.bot.prefix), value="Take action on an application.") + embed.add_field(name="{}demote ".format(self.bot.prefix), + value="Demote a player to the role given to accepted applications.") + embed.add_field(name="{}compare".format(self.bot.prefix), value="Compare Discord users to the Whitelist.") + await self.bot.discord_message(ctx.message.channel, embed) + + @commands.group("app", aliases=["application"]) + async def app(self, ctx): + if ctx.invoked_subcommand is None: + await self.bot.discord_message(ctx.message.channel, "No sub-command supplied. Info, Search, Accept, or Deny.") + + @app.command("info") + async def _info(self, ctx, *args): + if len(args) == 0: + await self.bot.discord_message(ctx.message.channel, "Info requires an application ID or username.") + + key = args[0] + is_id = True + try: + int(key) + except: + is_id = False + + if is_id: + application = get_application(key) + if not application: + await self.bot.discord_message(ctx.message.channel, "An Application with that ID doesn't exist.") + return + else: + found = False + applications = Application.objects.filter(username__icontains=key) + if len(applications) == 0: + applications = Application.objects.filter(player__username__icontains=key) + if len(applications) == 1: + await self.bot.discord_message(ctx.message.channel, "**No applications matched, however there is a player match**") + application = applications[0] + found = True + elif len(applications) == 1: + application = applications[0] + found = True + if not found: + await self.bot.discord_message(ctx.message.channel, "An exact Application could not be found. Try search instead.") + return + await self.bot.discord_message(ctx.message.channel, build_info(application)) + + @app.command("accept") + async def _accept(self, ctx, app_id: int): + application = get_application(app_id) + if not application: + await self.bot.discord_message(ctx.message.channel, "An Application with that ID doesn't exist.") + return + + if not application.accepted: + application.accepted = True + application.save() + if Player.objects.filter(username__iexact=application.username).exists(): + player = Player.objects.get(username__iexact=application.username) + player.application_id = application.id + player.save() + await self.bot.discord_message(ctx.message.channel, "App ID **{0}** was successfully accepted.".format(app_id)) + if not api.plugin(api.PLUGIN_ACCEPT, application.username): + await self.bot.discord_message(ctx.message.channel, "Could not accept in-game, is the server running?") + + @app.command("deny") + async def _deny(self, ctx, app_id: int): + application = get_application(app_id) + if not application: + await self.bot.discord_message(ctx.message.channel, "An Application with that ID doesn't exist.") + return + + if not application.accepted: + application.accepted = False + application.save() + if Player.objects.filter(username__iexact=application.username).exists(): + player = Player.objects.get(username__iexact=application.username) + player.application_id = application.id + player.save() + await self.bot.discord_message(ctx.message.channel, "App ID **{0}** was successfully denied.".format(app_id)) + if not api.plugin(api.PLUGIN_DENY, application.username): + await self.bot.discord_message(ctx.message.channel, "Could not deny in-game, is the server running?") + + @app.command("search") + async def _search(self, ctx, search: str): + applications = Application.objects.filter(username__icontains=search)[:10] + count = Application.objects.filter(username__icontains=search).count() + if count > 0: + if count == 1: + info = build_info(applications[0]) + else: + info = "**Found the following applications**" + for app in applications: + info += "\n{0} - {1} ({2})".format(app.id, app.username.replace("_", "\\_"), app.status) + if count > 10: + info += "\n**This is only 10 applications out of {0} found. Please narrow your search if possible.**".format(len(applications)) + else: + players = Player.objects.filter(username__icontains=search, application__isnull=False)[:10] + count = Player.objects.filter(username__icontains=search, application__isnull=False).count() + if count > 0: + if count == 1: + await self.bot.discord_message(ctx.message.channel, "**No applications matched, however there is a player match**") + info = build_info(players[0].application) + else: + info = "**No applications matched, however there are player matches**" + for player in players: + app = player.application + info += "\n{0} - {1} AKA {2} ({3})".format(app.id, app.username.replace("_", "\\_"), + player.username.replace("_", "\\_"), app.status) + if count > 10: + info += "\n**This is only 10 players out of {0} found. Please narrow your search if possible.**".format(len(players)) + else: + info = "No applications matched that search." + await self.bot.discord_message(ctx.message.channel, info) + + @commands.command() + async def demote(self, ctx, username: str): + if not self.is_superuser(ctx.author): + return + await ctx.message.delete() + if api.plugin(api.PLUGIN_DEMOTE, username): + await self.bot.discord_message(ctx.message.channel, "{} has been demoted in-game.".format(username)) + else: + await self.bot.discord_message(ctx.message.channel, "{} could not be demoted in-game, is the server running?".format(username)) + if User.objects.filter(username__iexact=username).exists(): + user = User.objects.get(username__iexact=username) + user.is_active = False + user.save() + await self.bot.discord_message(ctx.message.channel, "{} has been de-activated in MCM.".format(username)) + else: + await self.bot.discord_message(ctx.message.channel, "{} could not be found in MCM, is their account up-to-date?".format(username)) + + @commands.command() + async def compare(self, ctx): + await ctx.message.delete() + await ctx.message.channel.trigger_typing() + no_player = [] + no_application = [] + for member in ctx.message.guild.members: + if member.bot: + continue + name = member.nick if member.nick else member.name + try: + Player.objects.get(username__iexact=name) + except: + no_player.append(name) + try: + Application.objects.get(username__iexact=name) + except: + no_player = no_player[:-1] + no_application.append(name) + if no_player: + header = "**The following users have an application match, but no player match on the whitelist:**\n" + await self.bot.discord_message(ctx.author, "{}```{}```".format(header, "\n".join(no_player))) + if no_application: + header = "**The following users do not have an application or player match on the whitelist:**\n" + await self.bot.discord_message(ctx.author, "{}```{}```".format(header, "\n".join(no_application))) + + +def setup(bot): + bot.add_cog(Commands(bot)) diff --git a/bot/discord.py b/bot/discord.py new file mode 100644 index 0000000..970ccaf --- /dev/null +++ b/bot/discord.py @@ -0,0 +1,80 @@ +import asyncio +import logging +import sys +import traceback + +import discord +from discord.ext import commands +from django.conf import settings +from django.db import close_old_connections + +from minecraft_manager.bot.commands import Commands + +logger = logging.getLogger(__name__) + +description = ''' +A Discord bot connected to an MCM instance. +''' + + +class Discord(commands.Bot): + discord_game = 'MCM' + prefix = getattr(settings, 'DISCORD_BOT_PREFIX', '!') + auth_roles = getattr(settings, 'DISCORD_BOT_ROLES', []) + superuser_roles = getattr(settings, 'DISCORD_SUPERUSER_ROLES', []) + error_users = getattr(settings, 'DISCORD_ERROR_USERS', []) + + def __init__(self, token): + super().__init__(command_prefix=self.prefix, description=description, case_insensitive=True, help_command=None, activity=discord.Game(name=self.discord_game)) + self.token = token + self.load_extension("minecraft_manager.bot.commands") + + async def on_ready(self): + print('Logged in as') + print(self.user.name) + print(self.user.id) + print(discord.__version__) + print('Voice Loaded: {0}'.format(discord.opus.is_loaded())) + print('OAuth URL: https://discordapp.com/api/oauth2/authorize?client_id={0}&permissions=0&scope=bot'.format(self.user.id)) + print('------') + print('Logged in as {0} ({1}) with discord.py v{2}'.format(self.user.name, self.user.id, discord.__version__)) + + async def discord_message(self, dest, message): + if isinstance(message, discord.Embed): + for idx, field in enumerate(message.fields): + if not field.value: + message.set_field_at(idx, name=field.name, value="N/A") + await dest.send(embed=message) + else: + await dest.send(message) + + async def before_invoke(self, coro): + # FIX STALE DB CONNECTIONS + close_old_connections() + + async def on_command_error(self, context, exception): + if not isinstance(exception, commands.CommandInvokeError): + return + if hasattr(exception, "original"): + error = ''.join(traceback.format_tb(exception.original.__traceback__)) + else: + error = exception + logger.error(error) + for user_id in self.error_users: + user = self.get_user(user_id) + if user: + await self.discord_message(user, '```python\n{}```'.format(error)) + + def run_bot(self): + loop = asyncio.get_event_loop() + + try: + loop.run_until_complete(self.start(self.token)) + except KeyboardInterrupt: + logger.info("Bot received keyboard interrupt") + except Exception as e: + print(e) + logger.info('Bot encountered the following unhandled exception %s', e) + finally: + loop.run_until_complete(self.logout()) + logger.info("Bot shutting down...") diff --git a/bot/utils.py b/bot/utils.py new file mode 100644 index 0000000..9a87c2b --- /dev/null +++ b/bot/utils.py @@ -0,0 +1,27 @@ +import discord +from minecraft_manager.models import Application, Player + + +def build_info(application): + embed = discord.Embed(colour=discord.Colour(0x417505)) + embed.set_thumbnail( + url="https://minotar.net/helm/{0}/100.png".format(application.username)) + embed.add_field(name="Application ID", value=application.id) + embed.add_field(name="Username", value=application.username.replace("_", "\\_")) + embed.add_field(name="Age", value=application.age) + embed.add_field(name="Type of Player", value=application.player_type) + embed.add_field(name="Ever been banned", value=application.ever_banned) + if application.ever_banned: + embed.add_field(name="Reason for being banned", value=application.ever_banned_explanation) + embed.add_field(name="Reference", value=application.reference) + embed.add_field(name="Read the Rules", value=application.read_rules) + embed.add_field(name="Date", value=application.date_display) + embed.add_field(name="Status", value=application.status) + return embed + + +def get_application(app_id): + try: + return Application.objects.get(id=app_id) + except: + return None \ No newline at end of file diff --git a/templatetags/sidebar.py b/templatetags/sidebar.py index ca9fddb..77945c0 100644 --- a/templatetags/sidebar.py +++ b/templatetags/sidebar.py @@ -31,8 +31,8 @@ def get_sidebar(current_app, request): ret += '
  •   Report
  • '.format('class="active"' if current_app == 'report' else '', reverse('report')) show_chat = True if getattr(settings, 'GLOBAL_LOG', None) is not None else False - if show_chat and request.user.has_perm('auth.chat'): + if show_chat and request.user.has_perm('minecraft_manager.chat'): ret += '
  •   Chat
  • '.format('class="active"' if current_app == 'chat' else '', reverse('chat')) - if request.user.has_perm('auth.bots'): + if request.user.has_perm('minecraft_manager.bots'): ret += '
  •   Bots
  • '.format('class="active"' if current_app == 'bots' else '', reverse('bots')) return ret diff --git a/urls.py b/urls.py index fe9d640..5be6156 100644 --- a/urls.py +++ b/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import url from django.views.generic import RedirectView -from django.contrib.auth.decorators import login_required +from django.contrib.auth.decorators import login_required, permission_required import minecraft_manager.views as mcm urlpatterns = [ @@ -33,9 +33,9 @@ urlpatterns = [ #Report url(r'^report/$', login_required(mcm.Report.as_view()), name="report"), #Chat - url(r'^dashboard/chat/$', login_required(mcm.Chat.as_view()), name="chat"), + url(r'^dashboard/chat/$', permission_required('minecraft_manager.chat')(mcm.Chat.as_view()), name="chat"), #Bots - url(r'^dashboard/bots/$', login_required(mcm.Bots.as_view()), name="bots"), + url(r'^dashboard/bots/$', permission_required('minecraft_manager.bots')(mcm.Bots.as_view()), name="bots"), ] From 12e41ce9b7533e60f093f9090926b7a44f151270 Mon Sep 17 00:00:00 2001 From: Etzelia Date: Tue, 27 Aug 2019 23:16:44 +0200 Subject: [PATCH 02/19] Re-implement shortcuts (#29) --- bot/commands.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/bot/commands.py b/bot/commands.py index 0a3140c..ef7fae5 100644 --- a/bot/commands.py +++ b/bot/commands.py @@ -55,7 +55,14 @@ class Commands(commands.Cog): if ctx.invoked_subcommand is None: await self.bot.discord_message(ctx.message.channel, "No sub-command supplied. Info, Search, Accept, or Deny.") + @commands.command() + async def info(self, ctx, *args): + await self._info(ctx, *args) + @app.command("info") + async def app_info(self, ctx, *args): + await self._info(ctx, *args) + async def _info(self, ctx, *args): if len(args) == 0: await self.bot.discord_message(ctx.message.channel, "Info requires an application ID or username.") @@ -89,7 +96,14 @@ class Commands(commands.Cog): return await self.bot.discord_message(ctx.message.channel, build_info(application)) + @commands.command() + async def accept(self, ctx, app_id: int): + await self._accept(ctx, app_id) + @app.command("accept") + async def app_accept(self, ctx, app_id: int): + await self._accept(ctx, app_id) + async def _accept(self, ctx, app_id: int): application = get_application(app_id) if not application: @@ -107,7 +121,14 @@ class Commands(commands.Cog): if not api.plugin(api.PLUGIN_ACCEPT, application.username): await self.bot.discord_message(ctx.message.channel, "Could not accept in-game, is the server running?") + @commands.command() + async def deny(self, ctx, app_id: int): + await self._deny(ctx, app_id) + @app.command("deny") + async def app_deny(self, ctx, app_id: int): + await self._deny(ctx, app_id) + async def _deny(self, ctx, app_id: int): application = get_application(app_id) if not application: @@ -125,7 +146,14 @@ class Commands(commands.Cog): if not api.plugin(api.PLUGIN_DENY, application.username): await self.bot.discord_message(ctx.message.channel, "Could not deny in-game, is the server running?") + @commands.command() + async def search(self, ctx, search: str): + await self._search(ctx, search) + @app.command("search") + async def app_search(self, ctx, search: str): + await self._search(ctx, search) + async def _search(self, ctx, search: str): applications = Application.objects.filter(username__icontains=search)[:10] count = Application.objects.filter(username__icontains=search).count() From 8073765a5719743e6a9c6681794cd6c3e79ff7f1 Mon Sep 17 00:00:00 2001 From: ZeroHD Date: Thu, 29 Aug 2019 18:59:34 +0200 Subject: [PATCH 03/19] Move `close_old_connections()` to `cog_before_invoke()` (#30) --- bot/commands.py | 5 +++++ bot/discord.py | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/commands.py b/bot/commands.py index ef7fae5..b084530 100644 --- a/bot/commands.py +++ b/bot/commands.py @@ -1,6 +1,7 @@ import discord from discord.ext import commands from django.contrib.auth.models import User +from django.db import close_old_connections from minecraft_manager.api import api from minecraft_manager.bot.utils import get_application, build_info @@ -12,6 +13,10 @@ class Commands(commands.Cog): def __init__(self, bot): self.bot = bot + async def cog_before_invoke(self, ctx): + # FIX STALE DB CONNECTIONS + close_old_connections() + async def cog_check(self, ctx): # No DMs if ctx.guild is None: diff --git a/bot/discord.py b/bot/discord.py index 970ccaf..89f7ae5 100644 --- a/bot/discord.py +++ b/bot/discord.py @@ -6,7 +6,6 @@ import traceback import discord from discord.ext import commands from django.conf import settings -from django.db import close_old_connections from minecraft_manager.bot.commands import Commands @@ -48,10 +47,6 @@ class Discord(commands.Bot): else: await dest.send(message) - async def before_invoke(self, coro): - # FIX STALE DB CONNECTIONS - close_old_connections() - async def on_command_error(self, context, exception): if not isinstance(exception, commands.CommandInvokeError): return From c81353548996b1712aa797b823049662e9b02d48 Mon Sep 17 00:00:00 2001 From: Etzelia Date: Fri, 30 Aug 2019 20:03:12 +0200 Subject: [PATCH 04/19] Internalize Discord Bot (#31) --- api/api.py | 31 +++----- api/views.py | 6 +- assets/bots/Discord-MCM.bot.py | 21 ----- assets/migrations/001-whitelist.sql | 55 ------------- bot/__init__.py | 38 +++++++++ bot/discord.py | 107 +++++++++++++++++++++++--- docs/source/django-settings.rst | 22 +++--- external/views.py | 4 +- templates/minecraft_manager/bots.html | 12 ++- utils.py | 6 +- views.py | 51 ++++-------- 11 files changed, 184 insertions(+), 169 deletions(-) delete mode 100644 assets/bots/Discord-MCM.bot.py delete mode 100644 assets/migrations/001-whitelist.sql diff --git a/api/api.py b/api/api.py index fc04bfb..98ea7f2 100644 --- a/api/api.py +++ b/api/api.py @@ -1,7 +1,8 @@ -import socket, requests, logging, os, datetime, pytz, mcstatus, random, string +import socket, logging, os, datetime, pytz, mcstatus, random, string from minecraft_manager.models import Alert from django.contrib.auth.models import User from django.conf import settings +from minecraft_manager.bot.discord import send as discord_send, DestType logger = logging.getLogger(__name__) @@ -37,36 +38,24 @@ def plugin(key, command): return False -def discord_mcm(message='', embeds=None, ping=False): - discord_mcm_webhook = getattr(settings, 'DISCORD_MCM_WEBHOOK', None) - if discord_mcm_webhook: +def discord_mcm(message='', embed=None, ping=False): + channel_id = getattr(settings, 'DISCORD_MCM_CHANNEL', None) + if channel_id: ping_list = getattr(settings, 'DISCORD_PING_LIST', []) if ping and ping_list: ping_list = ["<@&{0}>".format(ping) for ping in ping_list] message = "{0}\n{1}".format(" ".join(ping_list), message) - data = {} - if message: - data['content'] = message - if embeds: - data['embeds'] = embeds - return requests.post(discord_mcm_webhook, json=data) - return None + discord_send(DestType.CHANNEL, channel_id, message, embed) -def discord_notification(message='', embeds=None, ping=False): - discord_notification_webhook = getattr(settings, 'DISCORD_NOTIFICATION_WEBHOOK', None) - if discord_notification_webhook: +def discord_notification(message='', embed=None, ping=False): + channel_id = getattr(settings, 'DISCORD_NOTIFICATION_CHANNEL', None) + if channel_id: ping_list = getattr(settings, 'DISCORD_PING_LIST', []) if ping and ping_list: ping_list = ["<@&{0}>".format(ping) for ping in ping_list] message = "{0}\n{1}".format(" ".join(ping_list), message) - data = {} - if message: - data['content'] = message - if embeds: - data['embeds'] = embeds - return requests.post(discord_notification_webhook, json=data) - return None + discord_send(DestType.CHANNEL, channel_id, message, embed) diff --git a/api/views.py b/api/views.py index 675452e..47cd31f 100644 --- a/api/views.py +++ b/api/views.py @@ -192,7 +192,7 @@ class PluginAPI(View): json['message'] = "{0}'s application was submitted.".format(application.username) json['extra'] = application.id msg = mcm_utils.build_application(application) - mcm_api.discord_mcm(message='New Application!', embeds=msg) + mcm_api.discord_mcm(message='New Application!', embed=msg) elif "application_action" == keyword: if Application.objects.filter(id=post['application_id']).exists(): application = Application.objects.get(id=post['application_id']) @@ -298,7 +298,7 @@ class PluginAPI(View): link = "{}".format(mcm_utils.url_path(settings.MCM_BASE_LINK, 'dashboard/ticket', ticket.id)) msg = mcm_utils.build_ticket(ticket, link) json['extra'] = {'id': ticket.id, 'link': link} - mcm_api.discord_mcm(embeds=msg, ping=True) + mcm_api.discord_mcm(embed=msg, ping=True) except: json['status'] = False json['message'] = "Error while submitting ticket." @@ -311,7 +311,7 @@ class PluginAPI(View): json['message'] = "Warning issued." link = "{}".format(mcm_utils.url_path(settings.MCM_BASE_LINK, 'dashboard/note', warning.id)) msg = mcm_utils.build_warning(warning, link) - mcm_api.discord_mcm(embeds=msg) + mcm_api.discord_mcm(embed=msg) except Exception as ex: json['status'] = False json['message'] = "Error while issuing warning." diff --git a/assets/bots/Discord-MCM.bot.py b/assets/bots/Discord-MCM.bot.py deleted file mode 100644 index 092a99e..0000000 --- a/assets/bots/Discord-MCM.bot.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import sys -import django - -sep = os.sep -path = os.path.dirname(os.path.abspath(__file__)) -path = path.split(sep)[:-3] -project = path[-1] -path = sep.join(path) -sys.path.append(path) -print("Setting path for {0}: {1}".format(project, path)) -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{}.settings".format(project)) -django.setup() - -from django.conf import settings -from minecraft_manager.bot.discord import Discord - -token = getattr(settings, 'DISCORD_BOT_TOKEN', None) -bot = Discord(token) - -bot.run_bot() diff --git a/assets/migrations/001-whitelist.sql b/assets/migrations/001-whitelist.sql deleted file mode 100644 index 268b167..0000000 --- a/assets/migrations/001-whitelist.sql +++ /dev/null @@ -1,55 +0,0 @@ --- Alerts -insert into minecraft_manager_alert (select * from whitelist_alert); - --- Applications -insert into minecraft_manager_application (select * from whitelist_application); - --- Players -insert into minecraft_manager_player (uuid, username, application_id, auth_user_id, last_seen, first_seen) -select wp.uuid, wp.username, ( -select mma.id from minecraft_manager_application mma where mma.username = ( -select wa.username from whitelist_application wa where wp.application_id = wa.id -) -), wp.auth_user_id, wp.last_seen, wp.first_seen from whitelist_player wp -; - --- Tickets -insert into minecraft_manager_ticket (message, priority, resolved, world, x, y, z, date, player_id, staff_id) -select wt.message, wt.priority, wt.resolved, wt.world, wt.x, wt.y, wt.z, wt.date, ( -select mmp.id from minecraft_manager_player mmp where mmp.uuid = ( -select wp.uuid from whitelist_player wp where wp.id = wt.player_id -) -), ( -select au.id from auth_user au where au.username = ( -select wp2.username from whitelist_player wp2 where wp2.id = wt.staff_id -) -) from whitelist_ticket wt -; - --- Warnings -insert into minecraft_manager_warning (message, severity, date, player_id, staff_id) -select ww.message, ww.severity, ww.date, ( -select mmp.id from minecraft_manager_player mmp where mmp.uuid = ( -select wp.uuid from whitelist_player wp where wp.id = ww.player_id -) -), ( -select au.id from auth_user au where au.username = ( -select wp2.username from whitelist_player wp2 where wp2.id = ww.staff_id -) -) from whitelist_warning ww -; - --- User Settings -insert into minecraft_manager_usersettings (default_results, default_theme, default_timezone, search_player_ip, show_timestamp_chat, last_ip, auth_user_id) -select default_results, default_theme, default_timezone, search_player_ip, show_timestamp_chat, last_ip, auth_user_id from whitelist_usersettings wu -; - --- Notes (This migration ONLY works if you are using standard whitelist app, aka only Tickets had notes) --- The ignore is because there were some incorrectly encoded characters giving MySQL a hard time -insert ignore into minecraft_manager_note (ref_table, ref_id, message, last_update, author_id) -select wn.ref_table, ( -select mmt.id from minecraft_manager_ticket mmt where mmt.message = ( -select wt.message from whitelist_ticket wt where wt.id = wn.ref_id -) -), wn.message, wn.last_update, wn.author_id from whitelist_note wn where (select count(*) from whitelist_ticket wt2 where wt2.id = wn.ref_id) > 0 -; \ No newline at end of file diff --git a/bot/__init__.py b/bot/__init__.py index e69de29..9e52327 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -0,0 +1,38 @@ +from django.conf import settings +import subprocess + + +class Bot: + plugin_port = getattr(settings, 'PLUGIN_PORT', None) + bot_dir = getattr(settings, 'BOT_DIR', "") + if not bot_dir.endswith("/"): + bot_dir += "/" + + def __init__(self, name, asset, executable=None, start=None, stop=None, restart=None, status=None, display=None): + self.name = name + self.asset = asset + self.executable = executable + self.start = start if start else self._start + self.stop = stop if stop else self._stop + self.restart = restart if restart else self._restart + self.status = status if status else self._status + self.display = display if display else self._display + + def _start(self): + screen = 'screen -S {0}_{1} -d -m {2} {3}{1}.bot.py' + subprocess.run(screen.format(self.plugin_port, self.name, self.executable, self.bot_dir), + shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def _stop(self): + subprocess.run('screen -X -S "{0}_{1}" quit'.format(self.plugin_port, self.name), shell=True) + + def _restart(self): + self.stop() + self.start() + + def _status(self): + screens = subprocess.getoutput("screen -ls") + return True if "{0}_{1}".format(self.plugin_port, self.name) in screens else False + + def _display(self): + return "Started" if self.status() else "Stopped" diff --git a/bot/discord.py b/bot/discord.py index 89f7ae5..c2a8c0b 100644 --- a/bot/discord.py +++ b/bot/discord.py @@ -1,21 +1,38 @@ import asyncio import logging -import sys import traceback +import threading +from enum import Enum import discord from discord.ext import commands from django.conf import settings -from minecraft_manager.bot.commands import Commands - logger = logging.getLogger(__name__) +discord_loop = None +discord_bot = None description = ''' A Discord bot connected to an MCM instance. ''' +class DiscordStatus(Enum): + STOPPED = 0 + STARTING = 1 + STARTED = 2 + STOPPING = 3 + + def __str__(self): + return self.name.title() + + def is_running(self): + return self != DiscordStatus.STOPPED + + +discord_status = DiscordStatus.STOPPED + + class Discord(commands.Bot): discord_game = 'MCM' prefix = getattr(settings, 'DISCORD_BOT_PREFIX', '!') @@ -23,12 +40,14 @@ class Discord(commands.Bot): superuser_roles = getattr(settings, 'DISCORD_SUPERUSER_ROLES', []) error_users = getattr(settings, 'DISCORD_ERROR_USERS', []) - def __init__(self, token): - super().__init__(command_prefix=self.prefix, description=description, case_insensitive=True, help_command=None, activity=discord.Game(name=self.discord_game)) + def __init__(self, token, loop): + super().__init__(command_prefix=self.prefix, description=description, case_insensitive=True, help_command=None, activity=discord.Game(name=self.discord_game), loop=loop) self.token = token self.load_extension("minecraft_manager.bot.commands") async def on_ready(self): + global discord_status + discord_status = DiscordStatus.STARTED print('Logged in as') print(self.user.name) print(self.user.id) @@ -38,6 +57,10 @@ class Discord(commands.Bot): print('------') print('Logged in as {0} ({1}) with discord.py v{2}'.format(self.user.name, self.user.id, discord.__version__)) + async def on_disconnect(self): + global discord_status + discord_status = DiscordStatus.STOPPED + async def discord_message(self, dest, message): if isinstance(message, discord.Embed): for idx, field in enumerate(message.fields): @@ -61,15 +84,77 @@ class Discord(commands.Bot): await self.discord_message(user, '```python\n{}```'.format(error)) def run_bot(self): - loop = asyncio.get_event_loop() - + global discord_loop try: - loop.run_until_complete(self.start(self.token)) + discord_loop.run_until_complete(self.start(self.token)) except KeyboardInterrupt: logger.info("Bot received keyboard interrupt") except Exception as e: print(e) logger.info('Bot encountered the following unhandled exception %s', e) - finally: - loop.run_until_complete(self.logout()) - logger.info("Bot shutting down...") + + +def start(): + global discord_loop, discord_bot, discord_status + if discord_status != DiscordStatus.STOPPED: + return + token = getattr(settings, 'DISCORD_BOT_TOKEN', None) + discord_loop = asyncio.new_event_loop() + discord_bot = Discord(token, discord_loop) + thread = threading.Thread(target=discord_bot.run_bot) + thread.start() + discord_status = DiscordStatus.STARTING + + +def stop(): + global discord_loop, discord_bot, discord_status + if discord_status == DiscordStatus.STARTED: + discord_loop.create_task(discord_bot.close()) + discord_status = DiscordStatus.STOPPING + discord_loop = None + discord_bot = None + + +def restart(): + def _restart(): + stop() + while discord_status.is_running(): + pass + start() + if discord_status != DiscordStatus.STARTED: + return + thread = threading.Thread(target=_restart) + thread.start() + + +def status(): + return discord_status.is_running() + + +def display(): + return str(discord_status) + + +class DestType(Enum): + CHANNEL = 1 + USER = 2 + + +def send(dest_type: DestType, dest_id: int, message: str = "", embed: discord.Embed = None): + async def _send(): + if dest_type == DestType.CHANNEL: + dest = discord_bot.get_channel(dest_id) + elif dest_type == DestType.USER: + dest = discord_bot.get_user(dest_id) + else: + return + if message is not None: + await dest.send(message) + if embed is not None: + for idx, field in enumerate(embed.fields): + if not field.value: + embed.set_field_at(idx, name=field.name, value="N/A") + await dest.send(embed=embed) + global discord_loop, discord_bot + if discord_loop: + discord_loop.create_task(_send()) diff --git a/docs/source/django-settings.rst b/docs/source/django-settings.rst index a7ef2fb..5ef0bea 100644 --- a/docs/source/django-settings.rst +++ b/docs/source/django-settings.rst @@ -23,11 +23,21 @@ Optional ``BOT_DIR`` - The path to your bot directory. -``DISCORD_NOTIFICATION_WEBHOOK`` - The URL for the webhook used for notifications. +``DISCORD_BOT_TOKEN`` - The token to use to run the Discord bot. This must be generated by you in the Discord developer area. ``DISCORD_PING_LIST`` - A list of Discord Role IDs to ping whenever certain messages are sent. -``DISCORD_MCM_WEBHOOK`` - The URL for the webhook used for Applications, Tickets, and Warnings. +``DISCORD_BOT_PREFIX`` - The prefix to use for Discord bot commands. Set to ``!`` by default. + +``DISCORD_BOT_ROLES`` - A list of Discord Roles allowed to use the bot. If this list is empty, no one can use the bot! + +``DISCORD_SUPERUSER_ROLES`` - A list of Discord Roles allowed to use the superuser commands. + +``DISCORD_ERROR_USERS`` - A list of user IDs to send errors to. + +``DISCORD_MCM_CHANNEL`` - The ID for the channel used for Applications, Tickets, and Warnings. + +``DISCORD_NOTIFICATION_CHANNEL`` - The ID for the channel used for notifications. ``DISCORD_INVITE`` - The invite code to your Discord, for after a player applies on the web form. @@ -51,14 +61,6 @@ Optional ``COREPROTECT_ACTIVITY_URL`` - The URL to your CoreProtect Activity Web UI, if it exists. -``DISCORD_BOT_TOKEN`` - The token to use to run the Discord bot. This must be generated by you in the Discord developer area. - -``DISCORD_BOT_PREFIX`` - The prefix to use for Discord bot commands. Set to ``!`` by default. - -``DISCORD_BOT_ROLES`` - A list of Discord Roles allowed to use the bot. If this list is empty, no one can use the bot! - -``DISCORD_BOT_NEW_MEMBER_ROLES`` - A list of Discord Roles to give new players when they register. - ``CAPTCHA_SECRET`` - Your secret key used for reCAPTCHA ``STATS_FILTER`` - A python list of partial strings used to filter out stats. e.g. ``['broken', 'dropped', 'picked_up']`` to filter out broken, dropped and picked up stats \ No newline at end of file diff --git a/external/views.py b/external/views.py index e3d58ee..d6d41e1 100644 --- a/external/views.py +++ b/external/views.py @@ -70,7 +70,7 @@ class Apply(View): if valid and valid_username and captcha.success: app = form.save() msg = mcm_utils.build_application(app) - mcm_api.discord_mcm(message='New Application!', embeds=msg) + mcm_api.discord_mcm(message='New Application!', embed=msg) mcm_api.plugin("application", "{0} {1}".format(form.data['username'], app.id)) else: for error in captcha.errors: @@ -109,7 +109,7 @@ class Ticket(View): # Create the message to send to Discord link = "{}".format(mcm_utils.url_path(settings.MCM_BASE_LINK, 'dashboard/ticket', ticket.id)) msg = mcm_utils.build_ticket(ticket, link) - mcm_api.discord_mcm(message="New Ticket", embeds=msg, ping=True) + mcm_api.discord_mcm(message="New Ticket", embed=msg, ping=True) mcm_api.plugin("ticket", "{0} {1} {2}".format(username, ticket.id, link)) else: for error in captcha.errors: diff --git a/templates/minecraft_manager/bots.html b/templates/minecraft_manager/bots.html index 6460c81..d0ffd81 100644 --- a/templates/minecraft_manager/bots.html +++ b/templates/minecraft_manager/bots.html @@ -1,16 +1,14 @@ {% extends "minecraft_manager/dashboard.html" %} {% load csrf_html %} -{% load getattribute %} {% block title %}Bots{% endblock %} {% block section %}
    - - {% for bot in form.bots %} -

    {{ bot.name }}: {% if form|getattribute:bot.name is True %}Started{% else %}Stopped{% endif %}

    + {% for bot in bots %} +

    {{ bot.name }}: {{ bot.display }}

    {% autoescape off %}{% get_csrf_html request %}{% endautoescape %} - - - + + +

    {% endfor %} diff --git a/utils.py b/utils.py index 71b357a..7c94daa 100644 --- a/utils.py +++ b/utils.py @@ -38,7 +38,7 @@ def build_application(application): embed.add_field(name="Read the Rules", value=application.read_rules) embed.add_field(name="Date", value=application.date_display) embed.add_field(name="Status", value=application.status) - return [embed.to_dict()] + return embed def build_ticket(ticket, link): @@ -53,7 +53,7 @@ def build_ticket(ticket, link): embed.add_field(name="Location", value=ticket.location) embed.add_field(name="Message", value=ticket.message) embed.add_field(name="Link", value=link) - return [embed.to_dict()] + return embed def build_warning(warning, link): @@ -66,7 +66,7 @@ def build_warning(warning, link): embed.add_field(name="Importance", value=warning.importance_display) embed.add_field(name="Message", value=warning.message) embed.add_field(name="Link", value=link) - return [embed.to_dict()] + return embed def validate_username(username): diff --git a/views.py b/views.py index 0be02c5..3e02034 100644 --- a/views.py +++ b/views.py @@ -17,8 +17,9 @@ from minecraft_manager.forms import TicketNoteForm, NoteForm from minecraft_manager.overview import overview_data from minecraft_manager.utils import resolve_player import minecraft_manager.api.api as API - -import subprocess +from minecraft_manager.bot import Bot +from minecraft_manager.bot.discord import start as discord_start, stop as discord_stop, restart as discord_restart, \ + status as discord_status, display as discord_display class Overview(View): @@ -452,11 +453,6 @@ class Chat(View): class Bots(View): - def assets(self): - path = os.path.abspath(os.path.dirname(__file__)) - bot_dir = os.path.join(path, 'assets/bots') - return bot_dir - def get_bots(self): bot_dir = getattr(settings, 'BOT_DIR', None) bots = [] @@ -466,41 +462,24 @@ class Bots(View): ve = file.replace('.bot.py', '') py = os.path.join(bot_dir, ve, 'bin/python') if os.path.isfile(py): - bots.append({'name': file.replace('.bot.py', ''), 'asset': False, 'executable': py}) + bots.append(Bot(file.replace('.bot.py', ''), False, py)) else: - bots.append({'name': file.replace('.bot.py', ''), 'asset': False, 'executable': sys.executable}) + bots.append(Bot(file.replace('.bot.py', ''), False, sys.executable)) # Also get packaged MCM bots - for file in os.listdir(self.assets()): - if file.endswith('.bot.py'): - bots.append({'name': file.replace('.bot.py', ''), 'asset': True, 'executable': sys.executable}) + bots.append(Bot("Discord-MCM", True, None, discord_start, discord_stop, discord_restart, discord_status, discord_display)) return bots - def get_form(self): - bots = self.get_bots() - plugin_port = getattr(settings, 'PLUGIN_PORT', None) - screens = subprocess.getoutput("screen -ls") - form = {'screens': screens, 'bots': bots} - for bot in bots: - form[bot['name']] = True if "{0}_{1}".format(plugin_port, bot['name']) in screens else False - return form - def get(self, request): - return render(request, 'minecraft_manager/bots.html', {'current_app': 'bots', 'form': self.get_form()}) + return render(request, 'minecraft_manager/bots.html', {'current_app': 'bots', 'bots': self.get_bots()}) def post(self, request): post = request.POST - plugin_port = getattr(settings, 'PLUGIN_PORT', None) for bot in self.get_bots(): - if bot['name'] in post: - if post[bot['name']] == "stop": - subprocess.run('screen -X -S "{0}_{1}" quit'.format(plugin_port, bot['name']), shell=True) - elif post[bot['name']] in ('start', 'restart'): - subprocess.run('screen -X -S "{0}_{1}" quit'.format(plugin_port, bot['name']), shell=True) - path = self.assets() if bot['asset'] else getattr(settings, 'BOT_DIR', "") - if not path.endswith("/"): - path += "/" - print('screen -S {0}_{1} -d -m {2} {3}{1}.bot.py'.format(plugin_port, bot['name'], bot['executable'], path)) - subprocess.run( - 'screen -S {0}_{1} -d -m {2} {3}{1}.bot.py'.format(plugin_port, bot['name'], bot['executable'], path), - shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - return render(request, 'minecraft_manager/bots.html', {'current_app': 'bots', 'form': self.get_form()}) + if bot.name in post: + if post[bot.name] == "stop": + bot.stop() + elif post[bot.name] == "start": + bot.start() + elif post[bot.name] == "restart": + bot.restart() + return render(request, 'minecraft_manager/bots.html', {'current_app': 'bots', 'bots': self.get_bots()}) From df30af20e603461b8f0c35f7ad7e66f7118159d1 Mon Sep 17 00:00:00 2001 From: Etzelia Date: Thu, 26 Sep 2019 03:42:56 +0200 Subject: [PATCH 05/19] Hotfix for CoreProtect URLs (#33) --- templates/minecraft_manager/dashboard.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/minecraft_manager/dashboard.html b/templates/minecraft_manager/dashboard.html index 94fae09..752d628 100644 --- a/templates/minecraft_manager/dashboard.html +++ b/templates/minecraft_manager/dashboard.html @@ -30,12 +30,12 @@ {% elif perms.auth.coreprotect_partial or perms.auth.coreprotect_full %} -
  • CoreProtect
  • +
  • CoreProtect
  • {% endif %} {% endif %} +{% elif show_gui %} +
  • CoreProtect GUI
  • +{% elif show_activity %} +
  • Activity Monitor
  • +{% endif %} \ No newline at end of file diff --git a/templates/minecraft_manager/dashboard.html b/templates/minecraft_manager/dashboard.html index 752d628..d63e4d9 100644 --- a/templates/minecraft_manager/dashboard.html +++ b/templates/minecraft_manager/dashboard.html @@ -3,6 +3,7 @@ {% load csrf_html %} {% load sidebar %} {% load template_settings %} +{% load coreprotect %} {% block bootstrap %} {% if user.usersettings.default_theme == 'DA' %} @@ -23,21 +24,7 @@