Merge branch 'captcha' of birbmc.com:BirbMC/minecraft_manager
# Conflicts: # models.py # overview.pypull/2/head
commit
dead183dad
59
api/api.py
59
api/api.py
|
@ -22,48 +22,47 @@ 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):
|
||||
def discord_mcm(message='', embed=None, ping=False):
|
||||
discord_mcm_webhook = getattr(settings, 'DISCORD_MCM_WEBHOOK', None)
|
||||
if discord_mcm_webhook:
|
||||
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 post_webhook(discord_mcm_webhook, message, embed, ping)
|
||||
return None
|
||||
|
||||
|
||||
def discord_notification(message='', embeds=None, ping=False):
|
||||
def discord_notification(message='', embed=None, ping=False):
|
||||
discord_notification_webhook = getattr(settings, 'DISCORD_NOTIFICATION_WEBHOOK', None)
|
||||
if discord_notification_webhook:
|
||||
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 post_webhook(discord_notification_webhook, message, embed, ping)
|
||||
return None
|
||||
|
||||
|
||||
def post_webhook(webhook_url, message, embed, ping):
|
||||
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 embed:
|
||||
data['embeds'] = [embed.to_dict()]
|
||||
return requests.post(webhook_url, json=data)
|
||||
|
||||
|
||||
def strip_format(message):
|
||||
return message.replace("§0", "").replace("§1", "").replace("§2", "").replace("§3", "").replace("§4", "") \
|
||||
|
|
212
api/bot.py
212
api/bot.py
|
@ -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 <username>".format(self.prefix), value="Search for applications by partial or exact username.")
|
||||
embed.add_field(name="{}[app ]info <app ID>".format(self.prefix), value="Get detailed information about a specific application.")
|
||||
embed.add_field(name="{}[app ]accept|deny <app ID>".format(self.prefix), value="Take action on an application.")
|
||||
embed.add_field(name="{}demote <username>".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)
|
||||
|
||||
|
||||
|
48
api/views.py
48
api/views.py
|
@ -46,7 +46,7 @@ def clean(model, data):
|
|||
attr = d
|
||||
if '__' in d:
|
||||
attr = d.split('__')[0]
|
||||
if hasattr(model, attr):
|
||||
if hasattr(model, attr) and attr != "api":
|
||||
cleaned[d] = data[d]
|
||||
return cleaned
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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'])
|
||||
|
@ -279,11 +279,13 @@ class PluginAPI(View):
|
|||
json['message'] = "Updated {0}".format(post['username'])
|
||||
elif "register" == keyword:
|
||||
player = Player.objects.get(uuid=post['uuid'])
|
||||
password = mcm_api.generate_password()
|
||||
if player.auth_user:
|
||||
json['status'] = False
|
||||
json['message'] = "You are already registered. To change your password, contact an Admin."
|
||||
player.auth_user.password = password
|
||||
player.auth_user.is_active = True
|
||||
player.auth_user.save()
|
||||
json['message'] = password
|
||||
else:
|
||||
password = mcm_api.generate_password()
|
||||
user = User.objects.create_user(username=player.username.lower(), password=password)
|
||||
user.save()
|
||||
player.auth_user = user
|
||||
|
@ -298,7 +300,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 +313,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."
|
||||
|
@ -396,7 +398,31 @@ class ModelAPI(View):
|
|||
return JsonResponse(json, safe=False)
|
||||
|
||||
def post(self, request, request_model):
|
||||
pass
|
||||
json = {"success": False, "message": ""}
|
||||
if request_allowed(request, 'model_post_permission'):
|
||||
post = request.POST
|
||||
model = None
|
||||
for m in apps.get_app_config('minecraft_manager').get_models():
|
||||
if m._meta.model_name.upper() == request_model.upper():
|
||||
model = m
|
||||
break
|
||||
if model:
|
||||
keywords = clean(model, post)
|
||||
if "id" in keywords:
|
||||
try:
|
||||
obj = model.objects.get(id=keywords["id"])
|
||||
for key in keywords.keys():
|
||||
setattr(obj, key, keywords[key])
|
||||
obj.save()
|
||||
json["success"] = True
|
||||
json["message"] = "Model updated"
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
json["message"] = "Could not update model"
|
||||
else:
|
||||
json["message"] = "Must provide an ID"
|
||||
|
||||
return JsonResponse(json)
|
||||
|
||||
|
||||
class StatsAPI(View):
|
||||
|
|
|
@ -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 <coreprotect database> -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
|
|
@ -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
|
||||
;
|
|
@ -1,18 +1,29 @@
|
|||
import os, sys, django
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# This block is assuming you will use this exact file
|
||||
sep = os.sep
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
path = path.split(sep)[:-3]
|
||||
project = path[-1]
|
||||
path = sep.join(path)
|
||||
|
||||
# What you need here is
|
||||
# project = name of your main django project
|
||||
# path = path to the root of your django project
|
||||
# e.g. If your project is at /home/mcm/django1 and settings.py is at /home/mcm/django1/django2/settings.py
|
||||
# project = django2
|
||||
# path = /home/mcm/django1
|
||||
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.api.bot import Discord
|
||||
from minecraft_manager.bot.discord import Discord
|
||||
|
||||
token = getattr(settings, 'DISCORD_BOT_TOKEN', None)
|
||||
bot = Discord(token)
|
||||
|
||||
bot.run_bot()
|
|
@ -0,0 +1,240 @@
|
|||
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
|
||||
from minecraft_manager.utils import build_application
|
||||
from minecraft_manager.models import Application, Player
|
||||
|
||||
|
||||
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:
|
||||
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 <username>".format(self.bot.prefix),
|
||||
value="Search for applications by partial or exact username.")
|
||||
embed.add_field(name="{}app info <app ID>".format(self.bot.prefix),
|
||||
value="Get detailed information about a specific application.")
|
||||
embed.add_field(name="{}app accept|deny <app ID>".format(self.bot.prefix), value="Take action on an application.")
|
||||
embed.add_field(name="{}demote <username>".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.")
|
||||
|
||||
@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.")
|
||||
|
||||
key = args[0]
|
||||
is_id = True
|
||||
try:
|
||||
int(key)
|
||||
except:
|
||||
is_id = False
|
||||
|
||||
application = None
|
||||
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_application(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:
|
||||
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?")
|
||||
|
||||
@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:
|
||||
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?")
|
||||
|
||||
@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()
|
||||
if count > 0:
|
||||
if count == 1:
|
||||
info = build_application(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_application(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))
|
|
@ -0,0 +1,95 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from django.conf import settings
|
||||
|
||||
from minecraft_manager.models import Application, Ticket
|
||||
from minecraft_manager.utils import url_path
|
||||
|
||||
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__))
|
||||
|
||||
channel_id = getattr(settings, 'DISCORD_MCM_CHANNEL', None)
|
||||
if channel_id:
|
||||
channel = self.get_channel(channel_id)
|
||||
embed = discord.Embed(color=8311585)
|
||||
content = ""
|
||||
unanswered_applications = Application.objects.filter(accepted=None)
|
||||
if len(unanswered_applications) > 0:
|
||||
link = url_path(settings.MCM_BASE_LINK, 'dashboard/application')
|
||||
content += "[Unanswered Applications: {}]({})".format(len(unanswered_applications), link)
|
||||
unclaimed_tickets = Ticket.objects.filter(staff=None, resolved=False)
|
||||
if len(unclaimed_tickets) > 0:
|
||||
link = url_path(settings.MCM_BASE_LINK, 'dashboard/ticket')
|
||||
if content:
|
||||
content += "\n\n"
|
||||
content += "[Unclaimed Tickets: {}]({})".format(len(unclaimed_tickets), link)
|
||||
if content:
|
||||
embed.title = "MCM Reminder"
|
||||
embed.description = content
|
||||
await self.discord_message(channel, embed)
|
||||
|
||||
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 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...")
|
|
@ -0,0 +1,8 @@
|
|||
from minecraft_manager.models import Application
|
||||
|
||||
|
||||
def get_application(app_id):
|
||||
try:
|
||||
return Application.objects.get(id=app_id)
|
||||
except:
|
||||
return None
|
|
@ -25,11 +25,27 @@ Optional
|
|||
|
||||
``DISCORD_NOTIFICATION_WEBHOOK`` - The URL for the webhook used for notifications.
|
||||
|
||||
``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_INVITE`` - The invite code to your Discord, for after a player applies on the web form.
|
||||
``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_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.
|
||||
|
||||
``INVITE_LINK`` - The invite link to your community.
|
||||
|
||||
``INVITE_LABEL`` - The invite label for your community.
|
||||
|
||||
``DYNMAP_URL`` - The URL to your dynmap if you have one. Leave blank if you'd rather use a static background for web forms.
|
||||
|
||||
|
@ -51,14 +67,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
|
|
@ -1,5 +1,5 @@
|
|||
from django.views.generic import View
|
||||
from django.shortcuts import render, reverse, redirect
|
||||
from django.shortcuts import render
|
||||
from django.conf import settings
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
@ -8,12 +8,13 @@ import minecraft_manager.api.api as mcm_api
|
|||
import minecraft_manager.utils as mcm_utils
|
||||
import minecraft_manager.external.stats as mcm_stats
|
||||
from minecraft_manager.models import Player
|
||||
import random, yaml, os, json, datetime, pytz
|
||||
import random, yaml, os
|
||||
|
||||
|
||||
def config():
|
||||
data = {}
|
||||
data['discord_invite'] = getattr(settings, "DISCORD_INVITE", "#")
|
||||
data['invite_link'] = getattr(settings, "INVITE_LINK", "#")
|
||||
data['invite_label'] = getattr(settings, "INVITE_LABEL", "community")
|
||||
|
||||
dynmap_url = getattr(settings, "DYNMAP_URL", "")
|
||||
data['dynmap_url'] = dynmap_url
|
||||
|
@ -45,12 +46,16 @@ def config():
|
|||
def rules():
|
||||
path = os.path.join(settings.MINECRAFT_BASE_DIR, "plugins/MinecraftManager/config.yml")
|
||||
with open(path) as config_file:
|
||||
config = yaml.load(config_file)
|
||||
data = config['rules']['rules']
|
||||
if config['rules']['application']['validate']:
|
||||
data.append("The answer to the final question is \"{}\"".format(config['rules']['application']['answer']))
|
||||
cfg = yaml.safe_load(config_file)
|
||||
data = cfg['rules']['rules']
|
||||
if cfg['rules']['application']['validate']:
|
||||
data.append("The answer to the final question is \"{}\"".format(cfg['rules']['application']['answer']))
|
||||
|
||||
return data
|
||||
return {
|
||||
"rules": data,
|
||||
"validate": cfg['rules']['application']['validate'],
|
||||
"answer": cfg['rules']['application']['answer']
|
||||
}
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
|
@ -59,27 +64,31 @@ class Apply(View):
|
|||
def get(self, request):
|
||||
form = ApplicationForm()
|
||||
return render(request, 'minecraft_manager/external/apply.html',
|
||||
{'form': form.as_p(), 'rules': rules(), 'valid': False, 'map': config(),
|
||||
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
|
||||
{'form': form.as_p(), 'rules': rules()["rules"], 'valid': False, 'map': config(),
|
||||
'captcha': getattr(settings, "CAPTCHA_SITE", "")})
|
||||
|
||||
def post(self, request):
|
||||
form = ApplicationForm(request.POST)
|
||||
valid_username = mcm_utils.validate_username(form.data['username'])
|
||||
captcha = mcm_utils.Captcha(request.POST)
|
||||
valid = form.is_valid()
|
||||
if valid and valid_username and captcha.success:
|
||||
r = rules()
|
||||
valid_answer = not r["validate"] or r["answer"] == form.data['read_rules']
|
||||
if valid and valid_username and valid_answer 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:
|
||||
form.add_error(None, error)
|
||||
if not valid_username:
|
||||
form.add_error(None, "That username is not a premium Minecraft account")
|
||||
if not valid_answer:
|
||||
form.add_error(None, "Please read the rules again")
|
||||
return render(request, 'minecraft_manager/external/apply.html',
|
||||
{'form': form.as_p(), 'rules': rules(), 'valid': valid and valid_username and captcha.success, 'map': config(),
|
||||
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
|
||||
{'form': form.as_p(), 'rules': r["rules"], 'valid': valid and valid_username and valid_answer and captcha.success, 'map': config(),
|
||||
'captcha': getattr(settings, "CAPTCHA_SITE", "")})
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
|
@ -89,7 +98,7 @@ class Ticket(View):
|
|||
form = TicketForm()
|
||||
return render(request, 'minecraft_manager/external/ticket.html',
|
||||
{'form': form.as_p(), 'valid': False, 'map': config(),
|
||||
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
|
||||
'captcha': getattr(settings, "CAPTCHA_SITE", "")})
|
||||
|
||||
def post(self, request):
|
||||
post = request.POST.copy()
|
||||
|
@ -109,7 +118,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:
|
||||
|
@ -120,7 +129,7 @@ class Ticket(View):
|
|||
form.data['player'] = username
|
||||
return render(request, 'minecraft_manager/external/ticket.html',
|
||||
{'form': form.as_p(), 'valid': valid and captcha.success, 'map': config(),
|
||||
'captcha': hasattr(settings, "CAPTCHA_SECRET")})
|
||||
'captcha': getattr(settings, "CAPTCHA_SITE", "")})
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -13,8 +13,8 @@ class MinecraftManagerUser(User):
|
|||
class Meta:
|
||||
proxy = True
|
||||
permissions = (
|
||||
('minecraft_manager_bots', 'Can use the bot control page'),
|
||||
('minecraft_manager_chat', 'Can use chat page'),
|
||||
('bots', 'Can use the bot control page'),
|
||||
('chat', 'Can use chat page'),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -46,11 +46,11 @@ def overview_data():
|
|||
# Percentage
|
||||
data['percentage'] = {
|
||||
'accepted': round((data['total']['application']['accepted'] /
|
||||
data['total']['application']['all'] if data['total']['application']['all'] != 0 else 1) * 100, 2),
|
||||
data['total']['application']['all'] if data['total']['application']['all'] != 0 else 1) * 100, 2),
|
||||
'banned': round((data['total']['player']['banned'] /
|
||||
data['total']['player']['all'] if data['total']['player']['all'] != 0 else 1) * 100, 2),
|
||||
data['total']['player']['all'] if data['total']['player']['all'] != 0 else 1) * 100, 2),
|
||||
'applied': round((data['total']['application']['all'] /
|
||||
data['total']['player']['all'] if data['total']['player']['all'] != 0 else 1) * 100, 2)
|
||||
data['total']['player']['all'] if data['total']['player']['all'] != 0 else 1) * 100, 2)
|
||||
}
|
||||
|
||||
# Unique logins
|
||||
|
|
|
@ -67,4 +67,9 @@
|
|||
|
||||
.rule {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.errorlist {
|
||||
color: #D8000C;
|
||||
background-color: #FFD2D2;
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
{% extends "minecraft_manager/dashboard.html" %}
|
||||
{% load csrf_html %}
|
||||
{% load getattribute %}
|
||||
{% block title %}Bots{% endblock %}
|
||||
{% block section %}
|
||||
<div id="content">
|
||||
<!-- {{ form.status }} -->
|
||||
{% for bot in form.bots %}
|
||||
<p>{{ bot.name }}: <span class="label {% if form|getattribute:bot.name is True %}label-success{% else %}label-danger{% endif %}">{% if form|getattribute:bot.name is True %}Started{% else %}Stopped{% endif %}</span></p>
|
||||
{% for bot in bots %}
|
||||
<p>{{ bot.name }}: <span class="label {% if bot.status is True %}label-success{% else %}label-danger{% endif %}">{{ bot.display }}</span></p>
|
||||
<form action="" method="post">{% autoescape off %}{% get_csrf_html request %}{% endautoescape %}
|
||||
<button class="btn btn-primary{% if form|getattribute:bot.name is True %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="start" {% if form|getattribute:bot.name is True %} disabled="disabled"{% endif %}>Start</button>
|
||||
<button class="btn btn-primary{% if form|getattribute:bot.name is False %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="stop" {% if form|getattribute:bot.name is False %}disabled="disabled"{% endif %}>Stop</button>
|
||||
<button class="btn btn-primary{% if form|getattribute:bot.name is False %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="restart" {% if form|getattribute:bot.name is False %}disabled="disabled"{% endif %}>Restart</button>
|
||||
<button class="btn btn-primary{% if bot.status is True %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="start" {% if bot.status is True %} disabled="disabled"{% endif %}>Start</button>
|
||||
<button class="btn btn-primary{% if bot.status is False %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="stop" {% if bot.status is False %}disabled="disabled"{% endif %}>Stop</button>
|
||||
<button class="btn btn-primary{% if bot.status is False %} disabled{% endif %}" type="submit" name="{{ bot.name }}" value="restart" {% if bot.status is False %}disabled="disabled"{% endif %}>Restart</button>
|
||||
</form>
|
||||
<br/>
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
{% extends "minecraft_manager/dashboard.html" %}
|
||||
{% load template_settings %}
|
||||
{% block title %}CoreProtect GUI{% endblock %}
|
||||
{% block section %}
|
||||
<a href="{% template_settings 'COREPROTECT_WEB_URL' %}?username={{ user.username }}" target="_blank">Fullscreen</a>
|
||||
<iframe id="cpgui" width="100%" height="750px" src="{% template_settings 'COREPROTECT_WEB_URL' %}?username={{ user.username }}">Loading...</iframe>
|
||||
|
||||
<script>
|
||||
var height = $(window).height();
|
||||
//$("#cpgui").height(height * .8);
|
||||
</script>
|
||||
{% endblock section %}
|
||||
{% if show_gui and show_activity %}
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle " href="#" role="button" data-toggle="dropdown" id="cpDropdown">CoreProtect</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="cpDropdown">
|
||||
<li><a target="_blank" href="{{ url_gui }}">Web GUI</a></li>
|
||||
<li><a target="_blank" href="{{ url_activity }}">Activity Monitor</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% elif show_gui %}
|
||||
<li><a target="_blank" class="dropdown-item btn" href="{{ url_gui }}">CoreProtect GUI</a></li>
|
||||
{% elif show_activity %}
|
||||
<li><a target="_blank" class="dropdown-item btn" href="{{ url_activity }}">Activity Monitor</a></li>
|
||||
{% endif %}
|
|
@ -3,6 +3,7 @@
|
|||
{% load csrf_html %}
|
||||
{% load sidebar %}
|
||||
{% load template_settings %}
|
||||
{% load coreprotect %}
|
||||
{% block bootstrap %}
|
||||
{% if user.usersettings.default_theme == 'DA' %}
|
||||
<link rel="stylesheet" href="{% static "minecraft_manager/css/bootswatch-darkly.css" %}">
|
||||
|
@ -23,21 +24,7 @@
|
|||
</div>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% template_settings 'COREPROTECT_WEB_URL' as CPW %}
|
||||
{% template_settings 'COREPROTECT_ACTIVITY_URL' as CPA %}
|
||||
{% if CPW %}
|
||||
{% if perms.auth.coreprotect_activity and CPA %}
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle " href="#" role="button" data-toggle="dropdown" id="cpDropdown">CoreProtect</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="cpDropdown">
|
||||
<li><a href="{% url 'coreprotect' %}">Web GUI</a></li>
|
||||
<li><a href="{% url 'activity' %}">Activity Monitor</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% elif perms.auth.coreprotect_partial or perms.auth.coreprotect_full %}
|
||||
<li><a class="dropdown-item btn" href="{% url 'coreprotect' %}">CoreProtect</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% coreprotect %}
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle " href="#" role="button" data-toggle="dropdown" id="accountDropdown">{{ user.username }}</a>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="accountDropdown">
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<br/>
|
||||
We will get back to you soon.
|
||||
<br/>
|
||||
Consider joining our <a href="https://discord.gg/{{ map.discord_invite }}">Discord</a></h2>
|
||||
Consider joining our <a href="{{ map.invite_link }}">{{ map.invite_label }}</a></h2>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
{% else %}
|
||||
<form method="{% block method %}POST{% endblock %}">
|
||||
{% block form %}{{ form }}{% endblock %}<br/><br/>
|
||||
{% if captcha %}<div class="g-recaptcha" data-sitekey="6LeLcGEUAAAAAMMpHR-7VL6Q3nNV5v03S5sq1Ddz"></div>{% endif %}
|
||||
{% if captcha %}<div class="g-recaptcha" data-sitekey="{{ captcha }}"></div>{% endif %}
|
||||
<button type="submit">{% block submit %}Submit{% endblock %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
|
|
@ -3,22 +3,37 @@
|
|||
{% block title %}Overview{% endblock %}
|
||||
{% block head %}
|
||||
<script src="{% static 'minecraft_manager/js/chart.min.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$(".staff-inactive").hide();
|
||||
$("#active-only").change(function() {
|
||||
if ($(this)[0].checked) {
|
||||
$(".staff-inactive").hide();
|
||||
} else {
|
||||
$(".staff-inactive").show();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block section %}
|
||||
<div id="content">
|
||||
{% if request.user.is_staff %}
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-body">
|
||||
<h4><span class="label label-danger">Admin Area</span></h4>
|
||||
<h3>Resolved Tickets</h3>
|
||||
<div class="row">
|
||||
{% for staff in data.resolved %}
|
||||
<div class="col-xs-6 col-md-4">
|
||||
<p><span class="label label-{% if staff.active %}success{% else %}danger{% endif %}">{% if staff.active %}Active{% else %}Inactive{% endif %}</span> {{ staff.username }}: {{ staff.tickets }}</p>
|
||||
<div class="panel-body">
|
||||
<h4><span class="label label-danger">Admin Area</span></h4>
|
||||
<h3>Resolved Tickets</h3>
|
||||
<label>Active Only
|
||||
<input id="active-only" type="checkbox" checked/>
|
||||
</label>
|
||||
<div class="row">
|
||||
{% for staff in data.resolved %}
|
||||
<div class="col-xs-6 col-md-4{% if not staff.active %} staff-inactive{% endif %}">
|
||||
<p><span class="label label-{% if staff.active %}success{% else %}danger{% endif %}">{% if staff.active %}Active{% else %}Inactive{% endif %}</span> {{ staff.username }}: {{ staff.tickets }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
{% endif %}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
from django.template import Library
|
||||
from django.shortcuts import reverse
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
@register.inclusion_tag('minecraft_manager/coreprotect.html', takes_context=True)
|
||||
def coreprotect(context):
|
||||
user = context.get("user", None)
|
||||
data = {"show_gui": False, "show_activity": False, "url_gui": "", "url_activity": ""}
|
||||
if user:
|
||||
try:
|
||||
data["show_gui"] = user.has_perm("django_coreprotect.gui")
|
||||
data["url_gui"] = reverse("coreprotect_gui")
|
||||
data["show_activity"] = user.has_perm("django_coreprotect.activity")
|
||||
data["url_activity"] = reverse("coreprotect_activity")
|
||||
except:
|
||||
pass
|
||||
|
||||
return data
|
|
@ -31,8 +31,7 @@ def get_sidebar(current_app, request):
|
|||
ret += '<li {}><a href="{}"><span class="glyphicon glyphicon-wrench"></span> Report</a></li>'.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 += '<li {}><a href="{}"><span class="glyphicon glyphicon-comment"></span> Chat</a></li>'.format('class="active"' if current_app == 'chat' else '', reverse('chat'))
|
||||
if request.user.has_perm('auth.bots'):
|
||||
ret += '<li {}><a href="{}"><span class="glyphicon glyphicon-flash"></span> Bots</a></li>'.format('class="active"' if current_app == 'bots' else '', reverse('bots'))
|
||||
|
||||
return ret
|
||||
|
|
71
urls.py
71
urls.py
|
@ -1,74 +1,43 @@
|
|||
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 = [
|
||||
url(r'^$', RedirectView.as_view(pattern_name='overview')),
|
||||
#Dashboard
|
||||
|
||||
# Dashboard
|
||||
url(r'^dashboard/overview/$', login_required(mcm.Overview.as_view()), name="overview"),
|
||||
url(r'^dashboard/ban/$', login_required(mcm.Ban.as_view()), name="ban"),
|
||||
#CoreProtect
|
||||
url(r'^dashboard/coreprotect/$', login_required(mcm.CoreProtect.as_view()), name="coreprotect"),
|
||||
url(r'^dashboard/activity/$', login_required(mcm.Activity.as_view()), name="activity"),
|
||||
#Alerts
|
||||
|
||||
# Alerts
|
||||
url(r'^dashboard/alert/$', login_required(mcm.Alert.as_view()), name="alert"),
|
||||
url(r'^dashboard/alert/(?P<alert_id>[0-9]{1,5})/$', login_required(mcm.AlertInfo.as_view())),
|
||||
#Applications
|
||||
|
||||
# Applications
|
||||
url(r'^dashboard/application/$', login_required(mcm.Application.as_view()), name="application"),
|
||||
url(r'^dashboard/reference/$', login_required(mcm.Reference.as_view()), name="reference"),
|
||||
url(r'^dashboard/application/(?P<application_id>[0-9]{1,5})/$', login_required(mcm.ApplicationInfo.as_view())),
|
||||
#Players
|
||||
|
||||
# Players
|
||||
url(r'^dashboard/player/$', login_required(mcm.Player.as_view()), name="player"),
|
||||
url(r'^dashboard/player/(?P<player_id>[0-9]{1,5})/$', login_required(mcm.PlayerInfo.as_view())),
|
||||
#Tickets
|
||||
|
||||
# Tickets
|
||||
url(r'^dashboard/ticket/$', login_required(mcm.Ticket.as_view()), name="ticket"),
|
||||
url(r'^dashboard/ticket/(?P<ticket_id>[0-9]{1,5})/$', login_required(mcm.TicketInfo.as_view())),
|
||||
#Warnings
|
||||
|
||||
# Warnings
|
||||
url(r'^dashboard/note/$', login_required(mcm.Note.as_view()), name="note"),
|
||||
url(r'^dashboard/note/(?P<note_id>[0-9]{1,5})/$', login_required(mcm.NoteInfo.as_view())),
|
||||
url(r'^dashboard/note/add$', login_required(mcm.NoteAdd.as_view()), name="note_add"),
|
||||
#IP
|
||||
|
||||
# IP
|
||||
url(r'^dashboard/ip/(?P<ip_id>[0-9]{1,5})/$', login_required(mcm.IP.as_view()), name="ip"),
|
||||
#Report
|
||||
|
||||
# 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"),
|
||||
#Bots
|
||||
url(r'^dashboard/bots/$', login_required(mcm.Bots.as_view()), name="bots"),
|
||||
|
||||
# Chat
|
||||
url(r'^dashboard/chat/$', permission_required('minecraft_manager.chat')(mcm.Chat.as_view()), name="chat"),
|
||||
]
|
||||
|
||||
|
||||
|
||||
# Possible future feature
|
||||
# from django.conf import settings
|
||||
#
|
||||
# LOGIN_REQUIRED = settings.LOGIN_REQUIRED if hasattr(settings, 'LOGIN_REQUIRED') else False
|
||||
#
|
||||
# urlpatterns = [
|
||||
# #Dashboard
|
||||
# url(r'^dashboard/overview/$', login_required(mcm.Overview.as_view()) if LOGIN_REQUIRED else mcm.Overview.as_view(), name="overview"),
|
||||
# url(r'^dashboard/coreprotect/$', login_required(mcm.CoreProtect.as_view()) if LOGIN_REQUIRED else mcm.CoreProtect.as_view(), name="coreprotect"),
|
||||
# url(r'^dashboard/activity/$', login_required(mcm.Activity.as_view()) if LOGIN_REQUIRED else mcm.Activity.as_view(), name="activity"),
|
||||
# url(r'^dashboard/ban/$', login_required(mcm.Ban.as_view()) if LOGIN_REQUIRED else mcm.Ban.as_view(), name="ban"),
|
||||
# #Alerts
|
||||
# url(r'^dashboard/alert/$', login_required(mcm.Alert.as_view()) if LOGIN_REQUIRED else mcm.Alert.as_view(), name="alert"),
|
||||
# url(r'^dashboard/alert/(?P<alert_id>[0-9]{1,5})/$', login_required(mcm.AlertInfo.as_view()) if LOGIN_REQUIRED else mcm.AlertInfo.as_view()),
|
||||
# #Applications
|
||||
# url(r'^dashboard/application/$', login_required(mcm.Application.as_view()) if LOGIN_REQUIRED else mcm.Application.as_view(), name="application"),
|
||||
# url(r'^dashboard/application/(?P<application_id>[0-9]{1,5})/$', login_required(mcm.ApplicationInfo.as_view()) if LOGIN_REQUIRED else mcm.ApplicationInfo.as_view()),
|
||||
# #Players
|
||||
# url(r'^dashboard/player/$', login_required(mcm.Player.as_view()) if LOGIN_REQUIRED else mcm.Player.as_view(), name="player"),
|
||||
# url(r'^dashboard/player/(?P<player_id>[0-9]{1,5})/$', login_required(mcm.PlayerInfo.as_view()) if LOGIN_REQUIRED else mcm.PlayerInfo.as_view()),
|
||||
# #Tickets
|
||||
# url(r'^dashboard/ticket/$', login_required(mcm.Ticket.as_view()) if LOGIN_REQUIRED else mcm.Ticket.as_view(), name="ticket"),
|
||||
# url(r'^dashboard/ticket/(?P<ticket_id>[0-9]{1,5})/$', login_required(mcm.TicketInfo.as_view()) if LOGIN_REQUIRED else mcm.TicketInfo.as_view()),
|
||||
# #Warnings
|
||||
# url(r'^dashboard/warning/$', login_required(mcm.Warning.as_view()) if LOGIN_REQUIRED else mcm.Warning.as_view(), name="warning"),
|
||||
# url(r'^dashboard/warning/(?P<warning_id>[0-9]{1,5})/$', login_required(mcm.WarningInfo.as_view()) if LOGIN_REQUIRED else mcm.WarningInfo.as_view()),
|
||||
# url(r'^dashboard/warning/add$', login_required(mcm.WarningAdd.as_view()) if LOGIN_REQUIRED else mcm.WarningAdd.as_view(), name="warning_add"),
|
||||
# #Chat
|
||||
# url(r'^dashboard/chat/$', login_required(mcm.Chat.as_view()) if LOGIN_REQUIRED else mcm.Chat.as_view(), name="chat"),
|
||||
# #Bots
|
||||
# url(r'^dashboard/bots/$', login_required(mcm.Bots.as_view()) if LOGIN_REQUIRED else mcm.Bots.as_view(), name="bots"),
|
||||
# ]
|
||||
|
|
7
utils.py
7
utils.py
|
@ -38,7 +38,8 @@ 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()]
|
||||
embed.add_field(name="Link", value="{}".format(url_path(settings.MCM_BASE_LINK, 'dashboard/application', application.id)))
|
||||
return embed
|
||||
|
||||
|
||||
def build_ticket(ticket, link):
|
||||
|
@ -53,7 +54,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 +67,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):
|
||||
|
|
58
views.py
58
views.py
|
@ -18,8 +18,6 @@ from minecraft_manager.overview import overview_data
|
|||
from minecraft_manager.utils import resolve_player
|
||||
import minecraft_manager.api.api as API
|
||||
|
||||
import subprocess
|
||||
|
||||
|
||||
class Overview(View):
|
||||
|
||||
|
@ -448,59 +446,3 @@ class Chat(View):
|
|||
else:
|
||||
data = {'success': False, 'message': 'No chat type or message set.'}
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
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 = []
|
||||
if bot_dir:
|
||||
for file in os.listdir(bot_dir):
|
||||
if file.endswith('.bot.py'):
|
||||
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})
|
||||
else:
|
||||
bots.append({'name': file.replace('.bot.py', ''), 'asset': False, 'executable': 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})
|
||||
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()})
|
||||
|
||||
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()})
|
||||
|
|
Loading…
Reference in New Issue