Added command API and bot

+Rewrote all of Geoffrey's commands for Django
+Command api allows all the geoffrey commands to be accessed though a REST API
+Added foundation for getting bot to work, no commands at the moment
doc_update
Joey Hines 2018-11-21 20:09:30 -06:00
parent 7748ab4010
commit 803d25d67f
20 changed files with 1227 additions and 16 deletions

0
api/__init__.py 100644
View File

View File

@ -0,0 +1,87 @@
class DataBaseError(Exception):
"""Base class for exceptions in this module."""
pass
class LocationInitError(DataBaseError):
"""Error in initializing Location"""
class TunnelInitError(DataBaseError):
"""Error in initializing Tunnel"""
class NoMatchFoundError(DataBaseError):
"""No matches were found in the database"""
class LocationLookUpError(DataBaseError):
"""Error in finding location in database"""
class DeleteEntryError(DataBaseError):
"""Error in deleting entry"""
class UsernameLookupFailed(Exception):
"""Error in username lookup, is the player's nickname set correctly? *stares at aeskdar*"""
class PlayerNotFound(DataBaseError):
"""Player not found in database."""
class EntryNameNotUniqueError(DataBaseError):
"""A location by that name is already in the database."""
class StringTooLong(DataBaseError):
"""Given string is too long."""
class DatabaseValueError(DataBaseError):
"""'String too long or number too large"""
class ItemNotFound(DataBaseError):
"""No item matches found in database"""
class InvalidDimError(DataBaseError):
"""Invalid dimension name"""
class InvalidTunnelError(DataBaseError):
"""Invalid tunnel name"""
class PlayerInDBError(DataBaseError):
"""Player already registered in database"""
class LocationHasTunnelError(DataBaseError):
"""That location already has a tunnel"""
class NoPermissionError(DataBaseError):
"""You have no permission to run this command"""
class NotOnServerError(DataBaseError):
"""You need to run this command on 24CC"""
class NoLocationsInDatabase(DataBaseError):
"""This player has no locations in the database"""
class FuckyWucky:
"""You made one."""
class EmptryString(DataBaseError):
"""Empty string provided"""
class CommandNotFound(DataBaseError):
"""Command not found"""

View File

@ -0,0 +1,29 @@
from itertools import zip_longest
def get_name(args):
if len(args) > 0:
name = ' '.join(args)
else:
name = None
return name
def get_nickname(discord_user, special_users):
if discord_user.nick is None:
name = discord_user.display_name
else:
name = discord_user.nick
if name in special_users:
return special_users[name]
else:
return name
def get_args_dict(args):
if len(args) != 0:
return dict(zip_longest(*[iter(args)] * 2, fillvalue=" "))
else:
return {}

View File

@ -0,0 +1,36 @@
from simplejson.errors import JSONDecodeError
import requests
from GeoffreyApp.api.bot.BotErrors import UsernameLookupFailed
uuid_lookup_url = 'https://api.mojang.com/users/profiles/minecraft/{}'
username_lookup_url = 'https://api.mojang.com/user/profiles/{}/names'
def grab_json(url):
try:
json = requests.get(url).json()
if 'error' in json:
raise UsernameLookupFailed
except JSONDecodeError:
raise UsernameLookupFailed
return json
def grab_UUID(username):
player_data = grab_json(uuid_lookup_url.format(username))
return player_data['id']
def grab_playername(uuid):
player_data = grab_json(username_lookup_url.format(uuid))
if len(player_data) == 0:
raise UsernameLookupFailed
else:
last_index = len(player_data) - 1
return player_data[last_index]['name']

View File

167
api/bot/bot.py 100644
View File

@ -0,0 +1,167 @@
import asyncio
import logging
from discord import Game
from discord.ext import commands
from discord.utils import oauth_url
import logging.handlers as handlers
from sys import stdout
from os import path
from GeoffreyApp.api.bot.BotErrors import *
from GeoffreyApp.api.commands import *
from django.conf import settings
logger = logging.getLogger(__name__)
description = '''
Geoffrey (pronounced JOFF-ree) started his life as an inside joke none of you will understand.
At some point, she was to become an airhorn bot. Now, they know where your stuff is.
Please respect Geoffrey, the bot is very sensitive.
All commands must be prefaced with '?'
If have a suggestion or if something is borked, you can PM my ding dong of a creator BirbHD.
*You must use ?register before adding things to Geoffrey*
For a better a explanation on how this bot works go the following link:
https://github.com/joeyahines/Geoffrey/blob/master/README.md
'''
bad_error_message = 'OOPSIE WOOPSIE!! Uwu We made a fucky wucky!! A wittle fucko boingo! The admins at our ' \
'headquarters are working VEWY HAWD to fix this! (Error in command {})'
extensions = []
'''
extensions = ['GeoffreyApp.cogs.Add_Commands',
'GeoffreyApp.cogs.Delete_Commands',
'GeoffreyApp.cogs.Edit_Commands',
'GeoffreyApp.cogs.Search_Commands',
'GeoffreyApp.cogs.Admin_Commands']
'''
class GeoffreyBot(commands.Bot):
def __init__(self):
super().__init__(command_prefix=getattr(settings, 'BOT_PREFIX', '?'), description=description, pm_help=True, case_insensitive=True)
self.error_users = getattr(settings, 'ERROR_USERS', [])
self.admin_users = getattr(settings, 'MOD_RANK', [])
self.special_users = getattr(settings, 'SPECIAL_USERS', [])
self.default_status = getattr(settings, 'DEFAULT_STATUS', 'sed')
for extension in extensions:
try:
self.load_extension(extension)
except Exception as e:
logger.info('Failed to load extension {}'.format(extension))
raise e
async def on_ready(self):
logger.info("%s Online, ID: %s", self.user.name, self.user.id)
info = await self.application_info()
url = oauth_url(info.id)
logger.info("Bot url: %s", url)
await self.change_presence(activity=Game(self.default_status))
async def on_command(self, ctx):
if ctx.invoked_subcommand is None:
subcommand = ""
else:
subcommand = ":" + ctx.invoked_subcommand.__str__()
logger.info("User %s, used command %s%s with context: %s", ctx.message.author, ctx.command.name, subcommand,
ctx.args)
if ctx.invoked_with.lower() == 'help' and ctx.message.guild is not None:
await ctx.send("{}, I sent you some help in the DMs.".format(ctx.message.author.mention))
async def on_command_error(self, ctx, error):
error_str = ''
if hasattr(ctx, 'cog'):
if "Admin_Commands" in ctx.cog.__str__():
return
if hasattr(error, 'original'):
if isinstance(error.original, NoPermissionError):
error_str = 'You don\'t have permission for that cool command.'
elif isinstance(error.original, UsernameLookupFailed):
error_str = 'Your user name was not found, either Mojang is having a fucky wucky ' \
'or your nickname is not set correctly. *stares at the Mods*'
elif isinstance(error.original, PlayerNotFound):
error_str = 'Make sure to use ?register first you ding dong.'
elif isinstance(error.original, EntryNameNotUniqueError):
error_str = 'An entry in the database already has that name you ding dong.'
elif isinstance(error.original, DatabaseValueError):
error_str = 'Use a shorter name or a smaller value, dong ding.'
elif isinstance(error.original, NotOnServerError):
error_str = 'Command needs to be run on 24CC. Run this command there whoever you are.'.format()
elif isinstance(error.original, EmptryString):
error_str = 'Do not not pass empty string to Geoffrey. Ding dong.'
elif isinstance(error, commands.CommandOnCooldown):
return
elif isinstance(error, commands.UserInputError):
error_str = 'Invalid syntax for **{}** you ding dong:' \
.format(ctx.invoked_with, ctx.invoked_with)
pages = await self.formatter.format_help_for(ctx, ctx.command)
for page in pages:
error_str = error_str + '\n' + page
elif isinstance(error, commands.CommandNotFound):
return
if error_str is '':
await self.send_error_message(
'Geoffrey encountered unhandled exception: {} Command: **{}** Context: {}'.format(error,
ctx.command.name,
ctx.args))
error_str = bad_error_message.format(ctx.invoked_with)
logger.error("Geoffrey encountered exception: %s", error)
await ctx.message.channel.send('{} **Error Running Command:** {}'.format(
ctx.message.author.mention, error_str))
async def send_error_message(self, msg):
for user_id in self.error_users:
user = await self.get_user_info(user_id)
await user.send(msg)
def setup_logging():
discord_logger = logging.getLogger('discord')
discord_logger.setLevel(logging.INFO)
bot_info_logger = logging.getLogger('GeoffreyApp.api.bot.bot')
bot_info_logger.setLevel(logging.INFO)
console = logging.StreamHandler(stdout)
console.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
bot_info_logger.addHandler(console)
def start_bot():
bot = None
try:
bot = GeoffreyBot()
@bot.command(pass_context=True)
async def test(ctx):
"""
Checks if the bot is alive.
"""
await ctx.send('I\'m here you ding dong')
setup_logging()
bot.run(getattr(settings, 'DISCORD_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:
if bot is not None:
bot.loop.stop()
logger.info("Bot shutting down...")

View File

@ -0,0 +1,146 @@
from discord.ext import commands
from geoffrey.BotErrors import *
from geoffrey.DiscordHelperFunctions import *
@commands.cooldown(5, 60, commands.BucketType.user)
class Add_Commands:
"""
Commands for adding things to Geoffrey.
*You must use ?register before using any of these commands!*
"""
def __init__(self, bot):
self.bot = bot
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def register(self, ctx):
"""
Registers your Discord and Minecraft account with the the database
You must do this before adding entries to the database.
"""
try:
player_name = get_nickname(ctx.message.author, self.bot.special_users)
self.bot.bot_commands.register(player_name, ctx.message.author.id)
await ctx.send('{}, you have been added to the database. Use ?help to see all the commands this bot can do.'
.format(ctx.message.author.mention))
except AttributeError:
raise NotOnServerError
except PlayerInDBError:
await ctx.send('{}, you are already in the database. Ding dong.'.format(ctx.message.author.mention))
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def add_base(self, ctx, x_pos: int, z_pos: int, *args):
"""
Adds your base to the database. The base name is optional if this is your first base
?add_base [X Coordinate] [Z Coordinate] [Base Name]
"""
name = get_name(args)
try:
base = self.bot.bot_commands.add_base(x_pos, z_pos, base_name=name, discord_uuid=ctx.message.author.id)
await ctx.send(
'{}, your base has been added to the database: \n\n{}'.format(ctx.message.author.mention, base))
except LocationInitError:
raise commands.UserInputError
except EntryNameNotUniqueError:
if name is None:
await ctx.send('{}, you already have one base in the database, you need to specify a base'
' name'.format(ctx.message.author.mention))
else:
await ctx.send(
'{}, a base called **{}** already exists. You need to specify a different name.'.format(
ctx.message.author.mention, name))
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def add_shop(self, ctx, x_pos: int, z_pos: int, *args):
"""
Adds your shop to the database. The name is optional if this is your first shop
?add_shop [X Coordinate] [Z Coordinate] [Shop Name]
"""
name = get_name(args)
try:
shop = self.bot.bot_commands.add_shop(x_pos, z_pos, shop_name=name, discord_uuid=ctx.message.author.id)
await ctx.send(
'{}, your shop has been added to the database: \n\n{}'.format(ctx.message.author.mention, shop))
except LocationInitError:
raise commands.UserInputError
except EntryNameNotUniqueError:
if name is None:
await ctx.send(
'{}, you already have one shop in the database, you need to specify a shop name'.format(
ctx.message.author.mention))
else:
await ctx.send(
'{}, a shop called **{}** already exists. You need to specify a different name.'.format(
ctx.message.author.mention, name))
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def add_tunnel(self, ctx, tunnel_direction: str, tunnel_number: int, *args):
"""
Adds your tunnel to the database. If you only have one location, you do not need to specify a location name
Directions: North South East West
?tunnel [Tunnel Direction] [Tunnel Number] [Location Name]
"""
loc_name = get_name(args)
try:
self.bot.bot_commands.add_tunnel(tunnel_direction, tunnel_number, discord_uuid=ctx.message.author.id,
location_name=loc_name)
await ctx.send('{}, your tunnel has been added to the database'.format(ctx.message.author.mention))
except LocationLookUpError:
await ctx.send('{}, you do not have a location called **{}**.'.format(
ctx.message.author.mention, loc_name))
except NoLocationsInDatabase:
await ctx.send('{}, you do not have a location in the database.'.format(
ctx.message.author.mention, loc_name))
except LocationHasTunnelError:
await ctx.send('{}, **{}** already has a tunnel.'.format(ctx.message.author.mention, loc_name))
except TunnelInitError:
await ctx.send('{}, invalid tunnel direction.'.format(ctx.message.author.mention))
except EntryNameNotUniqueError:
await ctx.send('{}, you have more than one location, you need to specify a location.'
.format(ctx.message.author.mention))
except InvalidTunnelError:
await ctx.send(
'{}, **{}** is an invalid tunnel direction.'.format(ctx.message.author.mention, tunnel_direction))
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def add_item(self, ctx, item_name: str, quantity: int, diamond_price: int, *args):
"""
Adds an item to a shop's inventory. If you have one shop, the shop name is not required
Quantity for Diamond Price. eg. 32 Dirt for 1D. If the item name has spaces in wrap in in quotes eg "Silk Touch"
?add_item [Item Name] [Quantity] [Price] [Shop name]
"""
shop_name = get_name(args)
try:
self.bot.bot_commands.add_item(item_name, quantity, diamond_price, shop_name=shop_name,
discord_uuid=ctx.message.author.id)
await ctx.send(
'{}, **{}** has been added to the inventory of your shop.'.format(ctx.message.author.mention,
item_name))
except NoLocationsInDatabase:
await ctx.send('{}, you don\'t have any shops in the database.'.format(ctx.message.author.mention))
except EntryNameNotUniqueError:
await ctx.send('{}, you have more than one shop in the database, please specify a shop name.'
.format(ctx.message.author.mention))
except LocationLookUpError:
await ctx.send(
'{}, you don\'t have any shops named **{}** in the database.'.format(ctx.message.author.mention,
shop_name))
def setup(bot):
bot.add_cog(Add_Commands(bot))

View File

@ -0,0 +1,166 @@
from discord import Game
from discord.ext import commands
from geoffrey.BotErrors import *
from geoffrey.DiscordHelperFunctions import get_name
def check_mod(user, admin_users):
try:
for role in user.roles:
if str(role.id) in admin_users:
return True
except AttributeError:
raise NotOnServerError
return False
class Admin_Commands:
"""
Commands for cool people only.
"""
def __init__(self, bot):
self.bot = bot
async def error(self, ctx, error):
error_str = ""
if hasattr(error, "original"):
if isinstance(error.original, PlayerNotFound):
error_str = 'that player is not in the database.'
elif isinstance(error.original, DeleteEntryError) or isinstance(error.original, LocationLookUpError):
error_str = 'that player does not have a location by that name.'
if error_str is "":
error_str = 'the bot encountered the following error: {}'.format(error.__str__())
await ctx.send('{}, {}'.format(ctx.message.author.mention, error_str))
@commands.command(pass_context=True)
async def test(self, ctx):
"""
Checks if the bot is alive.
"""
if check_mod(ctx.message.author, self.bot.admin_users):
await ctx.send('I\'m here you ding dong')
else:
raise NoPermissionError
@commands.group(pass_context=True)
async def mod(self, ctx):
"""
Bot moderation tools.
"""
if check_mod(ctx.message.author, self.bot.admin_users):
if ctx.invoked_subcommand is None:
await ctx.send('{}, invalid sub-command for command **mod**.'.format(ctx.message.author.mention))
else:
raise NoPermissionError
@mod.command(pass_context=True)
async def delete(self, ctx, discord_uuid: str, location_name: str):
"""
Deletes a location in the database.
"""
self.bot.bot_commands.delete(location_name, discord_uuid=discord_uuid)
await ctx.send('{}, **{}** has been deleted.'.format(ctx.message.author.mention, location_name))
@delete.error
async def delete_error(self, ctx, error):
await self.error(ctx, error)
@mod.command(pass_context=True)
async def edit_name(self, ctx, discord_uuid: str, new_name: str, current_name: str):
"""
Edits the name of a location in the database.
"""
self.bot.bot_commands.edit_name(new_name, current_name, discord_uuid=discord_uuid)
await ctx.send('{}, **{}** has been rename to **{}**.'.format(ctx.message.author.mention, current_name,
new_name))
@edit_name.error
async def edit_error(self, ctx, error):
await self.error(ctx, error)
@mod.command(pass_context=True)
async def update_mc_uuid(self, ctx, discord_uuid: str, mc_uuid: str):
"""
Updates a user's MC UUID
"""
self.bot.bot_commands.update_mc_uuid(discord_uuid, mc_uuid)
await ctx.send('{}, **{}** has been updated.'.format(ctx.message.author.mention, discord_uuid))
@update_mc_uuid.error
async def update_mc_uuid_error(self, ctx, error):
await self.error(ctx, error)
@mod.command(pass_context=True)
async def update_discord_uuid(self, ctx, new_discord_uuid: str, current_discord_uuid: str):
"""
Updates a user's Discord UUID
"""
self.bot.bot_commands.update_mc_uuid(current_discord_uuid, new_discord_uuid)
await ctx.send('{}, user **{}** has been updated.'.format(ctx.message.author.mention, current_discord_uuid))
@update_discord_uuid.error
async def update_discord_uuid_error(self, ctx, error):
await self.error(ctx, error)
@mod.command(pass_context=True)
async def update_mc_name(self, ctx, discord_uuid: str):
"""
Updates a user's MC name to the current name on the MC UUID
"""
self.bot.bot_commands.update_mc_name(discord_uuid)
await ctx.send('{}, user **{}**\'s MC name has update.'.format(ctx.message.author.mention, discord_uuid))
@update_mc_name.error
async def update_mc_name_error(self, ctx, error):
await self.error(ctx, error)
@mod.command(pass_context=True)
async def status(self, ctx, *args):
"""
Updates "playing [game]" status of the bot
"""
status = get_name(args)
await self.bot.change_presence(activity=Game(status))
await ctx.send('{}, status has been changed'.format(ctx.message.author.mention))
@mod.command(pass_context=True)
async def add_player(self, ctx, discord_uuid: str, mc_name: str):
"""
Manually add a player to the database
"""
try:
db_id = self.bot.bot_commands.add_player(discord_uuid, mc_name)
await ctx.send('{}, user **{}** been added to the data base with id {}.'.format(ctx.message.author.mention,
mc_name, db_id))
except PlayerInDBError:
await ctx.send('{}, user **{}** is already in the database.'.format(ctx.message.author.mention, mc_name))
@add_player.error
async def add_player_error(self, ctx, error):
await self.error(ctx, error)
@mod.command(pass_context=True)
async def find_player(self, ctx, discord_uuid: str):
"""
Finds a player in the database
"""
try:
db_id, username, discord_uuid, minecraft_uuid = self.bot.bot_commands.find_player(discord_uuid)
await ctx.send('Username: {}, id: {}, Discord UUID: {}, Minecraft UUID: {}'
.format(username, db_id, discord_uuid, minecraft_uuid))
except PlayerNotFound:
await ctx.send('That player is not in the database...')
@find_player.error
async def find_player_error(self, ctx, error):
await self.error(ctx, error)
def setup(bot):
bot.add_cog(Admin_Commands(bot))

View File

@ -0,0 +1,65 @@
from discord.ext import commands
from geoffrey.BotErrors import *
from geoffrey.DiscordHelperFunctions import *
class Delete_Commands:
"""
Commands to help Geoffrey forget.
*You must use ?register before using any of these commands!*
"""
def __init__(self, bot):
self.bot = bot
@commands.command(pass_context=True)
async def delete(self, ctx, *args):
"""
Deletes a location from the database
?delete [Location name]
"""
loc = get_name(args)
try:
if loc is None:
raise commands.UserInputError
self.bot.bot_commands.delete(loc, discord_uuid=ctx.message.author.id)
await ctx.send(
'{}, your location named **{}** has been deleted.'.format(ctx.message.author.mention, loc))
except (DeleteEntryError, PlayerNotFound):
await ctx.send('{}, you do not have a location named **{}**.'.format(ctx.message.author.mention, loc))
@commands.command(pass_context=True)
async def delete_item(self, ctx, item: str, *args):
"""
Deletes an item listing from a shop inventory
The item name must be wrapped in quotes if it has a space in it
?delete_name [Item] [Shop Name]
"""
shop = get_name(args)
try:
shop_name = self.bot.bot_commands.delete_item(item, shop, discord_uuid=ctx.message.author.id)
await ctx.send('{}, **{}** has been removed from the inventory of **{}**.'.
format(ctx.message.author.mention, item, shop_name))
except LocationLookUpError:
await ctx.send('{}, you do not have a shop called **{}**.'.format(ctx.message.author.mention, shop))
except NoLocationsInDatabase:
await ctx.send('{}, you do have any shops in the database.'.format(ctx.message.author.mention))
except EntryNameNotUniqueError:
await ctx.send('{}, you have more than one shop in the database, please specify a shop name.'
.format(ctx.message.author.mention))
except DeleteEntryError:
if shop is not None:
await ctx.send('{}, **{}** does not sell **{}**.'.format(ctx.message.author.mention, shop, item))
else:
await ctx.send('{}, your shop does not sell **{}**.'.format(ctx.message.author.mention, item))
def setup(bot):
bot.add_cog(Delete_Commands(bot))

View File

@ -0,0 +1,76 @@
from discord.ext import commands
from geoffrey.BotErrors import *
from geoffrey.DiscordHelperFunctions import *
class Edit_Commands:
"""
Commands for editing your stuff in Geoffrey.
"""
def __init__(self, bot):
self.bot = bot
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def edit_pos(self, ctx, x_pos: int, z_pos: int, *args):
"""
Edits the position of a location
?edit_pos [X Coordinate] [Z Coordinate] [Location Name]
"""
loc = get_name(args)
try:
loc_str = self.bot.bot_commands.edit_pos(x_pos, z_pos, loc, discord_uuid=ctx.message.author.id)
await ctx.send(
'{}, the following location has been updated: \n\n{}'.format(ctx.message.author.mention, loc_str))
except LocationLookUpError:
await ctx.send('{}, you do not have a location called **{}**.'.format(
ctx.message.author.mention, loc))
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def edit_tunnel(self, ctx, tunnel_direction: str, tunnel_number: int, *args):
"""
Edits the tunnel of a location
Directions: North South East West
?edit_tunnel [Tunnel Direction] [Tunnel Number] [Location Name]
"""
loc = get_name(args)
try:
loc_str = self.bot.bot_commands.edit_tunnel(tunnel_direction, tunnel_number, loc,
discord_uuid=ctx.message.author.id)
await ctx.send(
'{}, the following location has been updated: \n\n{}'.format(ctx.message.author.mention, loc_str))
except LocationLookUpError:
await ctx.send('{}, you do not have a location called **{}**.'.format(
ctx.message.author.mention, loc))
except InvalidTunnelError:
await ctx.send(
'{}, **{}** is an invalid tunnel direction.'.format(ctx.message.author.mention, tunnel_direction))
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def edit_name(self, ctx, new_name: str, current_name: str):
"""
Edits the name of a location
IF A NAME HAS SPACES IN IT YOU NEED TO WRAP IT IN QUOTATION MARKS. eg. "Cool Shop 123"
?edit_name [New Name] [Current Name]
"""
try:
loc_str = self.bot.bot_commands.edit_name(new_name, current_name, discord_uuid=ctx.message.author.id)
await ctx.send(
'{}, the following location has been updated: \n\n{}'.format(ctx.message.author.mention, loc_str))
except LocationLookUpError:
await ctx.send('{}, you do not have a location called **{}**.'.format(
ctx.message.author.mention, current_name))
def setup(bot):
bot.add_cog(Edit_Commands(bot))

View File

@ -0,0 +1,139 @@
from discord.ext import commands
from geoffrey.BotErrors import *
from geoffrey.DiscordHelperFunctions import *
class Search_Commands:
"""
Commands to find stuff.
"""
def __init__(self, bot):
self.bot = bot
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def find(self, ctx, *args):
"""
Finds all the locations matching the search term
?find [Search]
"""
search = get_name(args)
try:
if search is None:
raise commands.UserInputError
result = self.bot.bot_commands.find(search)
await ctx.send(
'{}, The following entries match **{}**:\n{}'.format(ctx.message.author.mention, search, result))
except LocationLookUpError:
await ctx.send(
'{}, no matches to **{}** were found in the database.'.format(ctx.message.author.mention, search))
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def tunnel(self, ctx, player: str):
"""
Finds all the tunnels a player owns
?tunnel [Player]
"""
try:
result = self.bot.bot_commands.tunnel(player)
await ctx.send(
'{}, **{}** owns the following tunnel(s): \n{}'.format(ctx.message.author.mention, player, result))
except LocationLookUpError:
await ctx.send('{}, no tunnels for **{}** were found in the database.'
.format(ctx.message.author.mention, player))
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def find_around(self, ctx, x_pos: int, z_pos: int, *args):
"""
Finds all the locations around a certain point.
The radius defaults to 200 blocks if no value is given
Default dimension is the overworld
?find_around [X Coordinate] [Z Coordinate] [Radius]
"""
radius = 200
dimension = 'Overworld'
try:
if len(args) > 0:
radius = int(args[0])
base_string = self.bot.bot_commands.find_around(x_pos, z_pos, radius, dimension)
if len(base_string) != 0:
await ctx.send('{}, the following locations(s) are within **{}** blocks of that point: \n {}'.format(
ctx.message.author.mention, radius, base_string))
else:
await ctx.send('{}, there are no locations within {} blocks of that point'
.format(ctx.message.author.mention, radius))
except ValueError:
await ctx.send(
'{}, invalid radius, the radius must be a whole number.'.format(ctx.message.author.mention,
radius))
except InvalidDimError:
await ctx.send('{}, {} is an invalid dimension.'.format(ctx.message.author.mention, dimension))
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def selling(self, ctx, *args):
"""
Lists all the shops selling an item
?selling [item]
"""
item_name = get_name(args)
if item_name is None:
raise commands.UserInputError
try:
result = self.bot.bot_commands.selling(item_name)
await ctx.send(
'{}, the following shop(s) sell **{}**: \n{}'.format(ctx.message.author.mention, item_name, result))
except ItemNotFound:
await ctx.send('{}, no shop sells **{}**.'.format(ctx.message.author.mention, item_name))
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def info(self, ctx, *args):
"""
Displays info about a location.
If the location is a shop, it displays the shop's inventory
?info [Location Name]
"""
loc = get_name(args)
try:
if loc is None:
raise commands.UserInputError
info_str = self.bot.bot_commands.info(loc)
await ctx.send(info_str)
except LocationLookUpError:
await ctx.send('{}, no locations in the database match **{}**.'.format(ctx.message.author.mention, loc))
@commands.command(pass_context=True)
@commands.cooldown(5, 60, commands.BucketType.user)
async def me(self, ctx):
"""
Displays all your locations in the database
"""
try:
loc_str = self.bot.bot_commands.me(discord_uuid=ctx.message.author.id)
await ctx.send('{}, here are your location(s) in the database: \n {}'.format(ctx.message.author.mention,
loc_str))
except PlayerNotFound:
await ctx.send('{}, you don\'t have any locations in the database.'.format(ctx.message.author.mention))
def setup(bot):
bot.add_cog(Search_Commands(bot))

View File

@ -0,0 +1 @@

228
api/commands.py 100644
View File

@ -0,0 +1,228 @@
from GeoffreyApp.api.bot.BotErrors import *
from django.db.models import Q, F
from GeoffreyApp.models import *
from GeoffreyApp.api.bot.MinecraftAccountInfoGrabber import *
post_list = []
get_list = []
delete_list = []
def post(func):
def command():
post_list.append(func)
return command()
def delete(func):
def command():
delete_list.append(func)
return command()
def get(func):
def command():
get_list.append(func)
return command()
def get_player(discord_uuid=None, mc_uuid=None):
if discord_uuid is not None:
player = Player.objects.get(discord_uuid=discord_uuid)
elif mc_uuid is not None:
player = Player.objects.get(mc_uuid=discord_uuid)
else:
raise AttributeError
return player
def get_location(owner, name=None, loc_type=Location):
if name is None:
loc_list = Location.objects.all().select_related(loc_type.__name__).get(owner=owner)
if len(loc_list) == 1:
loc = loc_list[0]
elif len(loc_list) == 0:
raise NoLocationsInDatabase
else:
raise EntryNameNotUniqueError
else:
loc_list = Location.objects.all().select_related(loc_type.__name__).get(owner=owner, name=name)
if len(loc_list) == 1:
loc = loc_list[0]
else:
raise LocationLookUpError
return loc
@post
def register(player_name, discord_uuid):
mc_uuid = grab_UUID(player_name)
player = Player.objects.create(name=player_name, mc_uuid=mc_uuid, discord_uuid=discord_uuid)
return player.json
@post
def add_location(x_pos, z_pos, name=None, discord_uuid=None, mc_uuid=None, loc_type=Location):
player = get_player(discord_uuid, mc_uuid)
try:
get_location(player, name, loc_type=loc_type)
raise EntryNameNotUniqueError
except (NoLocationsInDatabase, LocationLookUpError):
if name is None:
name = "{}'s {}".format(player.name, loc_type.__name__)
if loc_type == Base:
location = Base.objects.create(owner=player, name=name, x_coord=x_pos, z_coord=z_pos)
elif loc_type == Shop:
location = Shop.objects.create(owner=player, name=name, x_coord=x_pos, z_coord=z_pos)
else:
raise DataBaseError
return location
@post
def add_tunnel(tunnel_direction, tunnel_number, location_name=None, discord_uuid=None, mc_uuid=None):
player = get_player(discord_uuid, mc_uuid)
if location_name is None:
loc = get_location(player, name=location_name)
location_name = loc.name
tunnel = Tunnel.objects.create(tunnel_direction, tunnel_number, Location=get_location(player, location_name))
return tunnel
@get
def find_location(search):
limit = 25
locations = Location.objects.filter(Q(name__icontains=search) | Q(owner__name__icontains=search))[:limit]
if len(locations) == 0:
raise LocationLookUpError
return locations
@delete
def delete(name, discord_uuid=None, mc_uuid=None):
owner = get_player(discord_uuid, mc_uuid)
Location.objects.get(name__iexact=name, owner=owner)
@get
def find_around(x_pos, z_pos, radius=200):
locations = Location.objects.get(x_coord__range=(x_pos - radius, x_pos + radius),
z_coord__range=(z_pos - radius, z_pos + radius), dimension='O')
return locations
@post
def add_item(item_name, quantity, diamond_price, shop_name=None, discord_uuid=None, mc_uuid=None):
player = get_player(discord_uuid, mc_uuid)
shop = get_location(player, shop_name, Shop)
item_listing = ItemListing.objects.create(shop=shop, quantity=quantity, price=diamond_price, item_name=item_name)
return item_listing
# TODO Re-implement selling shop search
@get
def selling(item_name):
if len(item_name) == 0:
raise EmptryString
items = ItemListing.objects.annotate(normalized_price=F('price') / F('amount')).filter(item_name__icontains=item_name).order_by('normalized_price')
if len(items) == 0:
raise ItemNotFound
return items
@get
def info(location_name):
loc = Location.objects.get(name__iexact=location_name)
return loc
@get
def tunnel(player_name):
tunnels = Tunnel.objects.get(location__owner__name__icontains=player_name)
if len(tunnels) == 0:
raise LocationLookUpError
return tunnel
@post
def edit_pos(x, z, loc_name, discord_uuid=None, mc_uuid=None):
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
location = get_location(player, loc_name)
location.x = x
location.z = z
location.save()
return location
@post
def edit_tunnel(tunnel_direction, tunnel_number, loc_name, discord_uuid=None, mc_uuid=None):
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
location = get_location(player, loc_name)
if location.tunnel is not None:
location.tunnel.tunnel_direction = tunnel_direction
location.tunnel.tunnel_number = tunnel_number
else:
Tunnel.objects.create(tunnel_direction=tunnel_direction, tunnel_number=tunnel_number, location=location)
return location
@post
def edit_name(new_name, loc_name, discord_uuid=None, mc_uuid=None):
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
location = get_location(player, loc_name)
location.name = new_name
location.save()
return location
@post
def delete_item(item, shop_name, discord_uuid=None, mc_uuid=None):
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
shop = get_location(player, shop_name, Shop)
ItemListing.objects.filter(item_name__iexact=item, shop=shop).delete()
return shop
@get
def me(discord_uuid=None, mc_uuid=None):
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
locations = Location.objects.get(owner=player)
if len(locations) == 0:
raise PlayerNotFound
return locations

7
api/urls.py 100644
View File

@ -0,0 +1,7 @@
from django.conf.urls import url
from django.views.decorators.csrf import csrf_exempt
import GeoffreyApp.api.views as api
urlpatterns = [
url(r'^command/(?P<command>\w{1,20})/$', csrf_exempt(api.CommandAPI.as_view()), name="api-command"),
]

36
api/views.py 100644
View File

@ -0,0 +1,36 @@
from django.views.generic import View
from django.http import JsonResponse
import GeoffreyApp.api.commands as commands
from GeoffreyApp.api.bot.BotErrors import *
def run_command(request, command, command_list):
command = command.lower()
response = []
try:
for c in command_list:
if command == c.__name__:
ret = c(**request.dict())
response.append(ret)
return JsonResponse(response, safe=False)
raise CommandNotFound
except Exception as e:
response.append({"error": e.__class__.__name__})
return JsonResponse(response, safe=False)
class CommandAPI(View):
def get(self, request, command):
get = request.GET
return run_command(get, command, commands.get_list)
def post(self, request, command):
post = request.POST
return run_command(post, command, commands.post_list)
def delete(self, request, command):
delete = request.DELETE
return run_command(delete, command, commands.delete_list)

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class GeoffreyConfig(AppConfig):
name = 'geoffrey'

View File

@ -0,0 +1,14 @@
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Geoffrey.settings")
if __name__ == '__main__':
import django
django.setup()
from GeoffreyApp.api.bot.bot import start_bot
start_bot()

View File

@ -51,30 +51,30 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Base',
fields=[
('location_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='geoffrey.Location')),
('location_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='GeoffreyApp.Location')),
],
bases=('geoffrey.location',),
bases=('GeoffreyApp.location',),
),
migrations.CreateModel(
name='Shop',
fields=[
('location_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='geoffrey.Location')),
('location_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='GeoffreyApp.Location')),
],
bases=('geoffrey.location',),
bases=('GeoffreyApp.location',),
),
migrations.AddField(
model_name='tunnel',
name='location',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tunnel_location', to='geoffrey.Location'),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tunnel_location', to='GeoffreyApp.Location'),
),
migrations.AddField(
model_name='location',
name='owner',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owner_player', to='geoffrey.Player'),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owner_player', to='GeoffreyApp.Player'),
),
migrations.AddField(
model_name='itemlisting',
name='shop',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shop_selling', to='geoffrey.Shop'),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shop_selling', to='GeoffreyApp.Shop'),
),
]

View File

@ -1,13 +1,17 @@
from django.db import models
from django.conf import settings
from sys import maxsize
# Create your models here.
class Player(models.Model):
name = models.CharField(max_length=30)
mc_uuid = models.CharField(max_length=36)
discord_uuid = models.CharField(max_length=50)
name = models.CharField(max_length=30, unique=True)
mc_uuid = models.CharField(max_length=36, unique=True)
discord_uuid = models.CharField(max_length=50, unique=True)
@property
def json(self):
return {"Name": self.name, "MC UUID": self.mc_uuid, "Discord UUID": self.discord_uuid}
class Location(models.Model):
@ -25,6 +29,11 @@ class Location(models.Model):
owner = models.ForeignKey(Player, related_name='owner_player', on_delete=models.CASCADE)
@property
def json(self):
return {"Type": str(type(self)), "Name": self.name, "x_coord": self.x_coord, "z_coord": self.z_coord,
"dimension": self.dimension, "Owner": self.owner.json}
class Shop(Location):
def __str__(self):
@ -43,6 +52,13 @@ class ItemListing(models.Model):
shop = models.ForeignKey(Shop, related_name="shop_selling", on_delete=models.CASCADE)
@property
def normalized_price(self):
if self.amount == 0:
return maxsize
else:
return self.price/self.amount
def __str__(self):
return "Item: %d %s for %d" % (self.amount, self.item_name, self.amount)

View File

@ -1,6 +1,9 @@
from django.shortcuts import render
from django.http import HttpResponse
import subprocess, os
# Create your views here.
def index(request):
return HttpResponse("Geoffrey is here!")