minecraft_manager/api/bot.py

273 lines
14 KiB
Python

import discord, logging, re, sys, traceback, asyncio, datetime, os, time
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
from threading import Thread
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 NOT A MEMBER YET
if len(member_roles) == 1:
# REGISTER
match = re.match("[{0}]register (\S+)?$".format(self.prefix), message.content)
if match:
search = match.group(1)
count = Application.objects.filter(username__iexact=search, accepted=True).count()
if count == 0:
count = Player.objects.filter(username__iexact=search, application__accepted=True).count()
if count > 0:
count = Player.objects.filter(username__iexact=search, application__accepted=True).count()
if count == 0:
msg = "{0}, please join the server once before joining the discord.".format(message.author.mention)
yield from self.discord_message(message.channel, msg)
return
player = Player.objects.filter(username__iexact=search, application__accepted=True).all()[0]
nickname = player.username
if not player.is_banned:
on_server = False
for member in message.server.members:
if member is message.author:
continue
if member.display_name == nickname:
on_server = True
if on_server:
msg = "{0}, a member with that name is already exists, please contact the staff".format(message.author.mention)
yield from self.discord_message(message.channel, msg)
else:
for role_id in self.new_member_roles:
role = discord.utils.get(message.server.roles, id=role_id)
yield from self.add_roles(message.author, role)
msg = "Successfully added {0} as a member".format(nickname)
yield from self.change_nickname(message.author, nickname)
yield from self.discord_message(message.channel, msg)
else:
msg = "{0} You are currently banned.".format(message.author.mention)
yield from self.discord_message(message.channel, msg)
return
else:
app = Application.objects.filter(username__iexact=search).first()
if app is None:
msg = "{0}, an application for {1} could not be found, please check your username and make sure you have applied.".format(message.author.mention, search)
elif app.accepted is None:
msg = "{0}, your application is still in review, please try again when you have been accepted on the server.".format(message.author.mention)
elif not app.accepted:
msg = "{0}, your application has been denied. Best of luck finding a new server!".format(message.author.mention)
else:
msg = "{0}, an error has occurred. Please try again in a few minutes.".format(message.author.mention)
yield from self.discord_message(message.channel, msg)
return
# 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="{}register <username>".format(self.prefix), value="Allows new members to join the Discord server if they have applied and been accepted.")
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()
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)