Geoffrey-Django/GeoffreyApp/api/commands.py

844 lines
26 KiB
Python

from django.db.models import Q, F
from GeoffreyApp.models import *
from GeoffreyApp.errors import *
from GeoffreyApp.minecraft_api import *
import inspect
import datetime
import re
import enum
from GeoffreyApp.util import objects_list_to_json
class RequestTypes(enum.Enum):
POST = "POST"
GET = "GET"
command_dict = {RequestTypes.GET: {}, RequestTypes.POST: {}}
class Command:
def __init__(self, func, permission_level, command_name=None):
self.func = func
self.params = self.get_required_args(func)
self.help = self.parse_help(func)
self.permission_level = permission_level
if command_name is None:
self.name = func.__name__
else:
self.name = command_name
@staticmethod
def parse_help(func):
try:
match = re.search(".*:help:.*", func.__doc__)
return match.group(0).partition(":help: ")[-1]
except:
return ' '
@staticmethod
def get_required_args(func):
args = inspect.getfullargspec(func)
return args.args + args.kwonlyargs
@property
def json(self):
return {"command": self.name, "help": self.help, "permission_level": self.permission_level.name}
def command(type, permission_level=PermissionLevel.PLAYER, command_name=None):
def command_dec(func):
def add_command():
command_obj = Command(func, permission_level, command_name=command_name)
command_dict[type][command_obj.name] = command_obj
return func
return add_command()
return command_dec
def match_tunnel(tunnel_direction):
for direction in Tunnel.TUNNEL_NAMES:
if re.search("{}.*".format(tunnel_direction), direction[1], re.IGNORECASE):
return direction[0]
raise InvalidTunnelError
def get_player_by_name(mc_username):
try:
uuid = grab_UUID(mc_username)
return get_player(mc_uuid=uuid)
except UsernameLookupFailed:
raise PlayerNotFound
def get_player(discord_uuid=None, mc_uuid=None):
try:
discord_uuid = str(discord_uuid)
mc_uuid = str(mc_uuid)
if discord_uuid is not None:
player = Player.objects.get(discord_uuid__iexact=discord_uuid)
elif mc_uuid is not None:
player = Player.objects.get(mc_uuid__iexact=mc_uuid)
else:
raise AttributeError
except Player.DoesNotExist:
raise PlayerNotFound
return player
def get_location(owner, name=None, loc_type=Location):
if name is None:
loc_list = loc_type.objects.filter(owner=owner).all()
if len(loc_list) == 1:
loc = loc_list[0]
elif len(loc_list) == 0:
raise NoLocationsInDatabase
else:
raise EntryNameNotUniqueError
else:
loc_list = loc_type.objects.filter(owner=owner, name__iexact=name).all()
if len(loc_list) == 1:
loc = loc_list[0]
else:
raise LocationLookUpError
return loc
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)
name_was_none = False
if name is None:
name = "{}'s {}".format(player.name, loc_type.__name__)
name_was_none = True
if Location.objects.filter(name__iexact=name).all().count() > 0:
if name_was_none:
raise LocationLookUpError
else:
raise EntryNameNotUniqueError
else:
location = loc_type.objects.create(name=name, x_coord=x_pos, z_coord=z_pos)
location.owner.add(player)
location.save()
return location.json
@command(RequestTypes.POST)
def register(player_name, discord_uuid):
"""
:request: POST
:param player_name: Minecraft in-game name
:param discord_uuid: Discord UUID if registering from Discord
:return: JSON representation of the new Player
:raise: PlayerInDBError
:help: Registers your Discord and Minecraft account with the the database
"""
mc_uuid = grab_UUID(player_name)
try:
get_player(mc_uuid=mc_uuid)
raise PlayerInDBError
except PlayerNotFound:
player = Player.objects.create(name=player_name, mc_uuid=mc_uuid, discord_uuid=discord_uuid)
player.save()
return player.json
@command(RequestTypes.POST)
def add_base(x_pos, z_pos, name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param x_pos: MC X Coordinate
:param z_pos: MC Z Coordinate
:param name: Base Name (If None, Defaults to Player's Base)
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: JSON representation of the new base
:raises: EntryNameNotUniqueError, PlayerNotFound, LocationLookupError
:help: Adds your base to the database.
"""
return add_location(x_pos, z_pos, name=name, discord_uuid=discord_uuid, mc_uuid=mc_uuid, loc_type=Base)
@command(RequestTypes.POST)
def add_shop(x_pos, z_pos, name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param x_pos: MC X Coordinate
:param z_pos: MC Z Coordinate
:param name: Shop Name (If None, Defaults to Player's Shop)
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: JSON representation of the new shop
:raises: EntryNameNotUniqueError, PlayerNotFound, LocationLookupError
:help: Adds your shop to the database.
"""
return add_location(x_pos, z_pos, name=name, discord_uuid=discord_uuid, mc_uuid=mc_uuid, loc_type=Shop)
@command(RequestTypes.POST)
def add_town(x_pos, z_pos, name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param x_pos: MC X Coordinate
:param z_pos: MC Z Coordinate
:param name: Town Name (If None, Defaults to Player's Town)
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: JSON representation of the new town
:raises: EntryNameNotUniqueError, PlayerNotFound, LocationLookupError
:help: Adds your town to the database.
"""
return add_location(x_pos, z_pos, name=name, discord_uuid=discord_uuid, mc_uuid=mc_uuid, loc_type=Town)
@command(RequestTypes.POST)
def add_farm(x_pos, z_pos, name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param x_pos: MC X Coordinate
:param z_pos: MC Z Coordinate
:param name: Farm Name (If None, Defaults to Player's Farm)
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: JSON representation of the new farm
:raises: EntryNameNotUniqueError, PlayerNotFound, LocationLookupError
:help: Adds your public farm to the database.
"""
return add_location(x_pos, z_pos, name=name, discord_uuid=discord_uuid, mc_uuid=mc_uuid, loc_type=PublicFarm)
@command(RequestTypes.POST)
def add_market(x_pos, z_pos, name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param x_pos: MC X Coordinate
:param z_pos: MC Z Coordinate
:param name: Market Name (If None, Defaults to Player's Market)
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: JSON representation of the market
:raises: EntryNameNotUniqueError, PlayerNotFound, LocationLookupError
:help: Adds your market to the database.
"""
return add_location(x_pos, z_pos, name=name, discord_uuid=discord_uuid, mc_uuid=mc_uuid, loc_type=Market)
@command(RequestTypes.POST)
def add_attraction(x_pos, z_pos, name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param x_pos: MC X Coordinate
:param z_pos: MC Z Coordinate
:param name: Market Name (If None, Defaults to Player's Attraction)
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: JSON representation of the attraction
:raises: EntryNameNotUniqueError, PlayerNotFound, LocationLookupError
:help: Adds your attraction to the database.
"""
return add_location(x_pos, z_pos, name=name, discord_uuid=discord_uuid, mc_uuid=mc_uuid, loc_type=Attraction)
@command(RequestTypes.POST)
def add_tunnel(tunnel_direction, tunnel_number, location_name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param tunnel_direction: Tunnel Direction
:param tunnel_number: Tunnel Coordinate
:param location_name: Name of the Location to attach the tunnel to
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: JSON Representation of the Tunnel
:raises: PlayerNotFound, LocationHasTunnelError, EntryNameNotUniqueError, NoLocationsInDatabase, InvalidTunnelError
:help: Adds your tunnel to the database.
"""
player = get_player(discord_uuid, mc_uuid)
if location_name is None:
loc = get_location(player)
location_name = loc.name
else:
loc = get_location(player, name=location_name)
tunnel_direction = match_tunnel(tunnel_direction)
if Tunnel.objects.filter(location=loc).first():
raise LocationHasTunnelError
tunnel = Tunnel.objects.create(tunnel_direction=tunnel_direction, tunnel_number=tunnel_number,
location=get_location(player, location_name))
return tunnel.json
@command(RequestTypes.GET)
def find_location(search, limit=25):
"""
:request: GET
:param search: Location name or owner to search for
:param limit: How man locations to return
:return: List of the matching locations
:raises: LocationLookupError
:help: Finds all the locations matching the search term
"""
locations = Location.objects.filter(Q(name__icontains=search) | Q(owner__name__icontains=search)).all()[:limit]
if len(locations) == 0:
raise LocationLookUpError
return objects_list_to_json(locations)
@command(RequestTypes.POST)
def delete(name, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param name: Name of location to delete
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: Location Name
:raises: LocationLookUpError
:help: Deletes a location from the database
"""
owner = get_player(discord_uuid, mc_uuid)
try:
if Location.objects.get(name__iexact=name, owner=owner).delete() == 0:
raise Location.DoesNotExist
except Location.DoesNotExist:
raise LocationLookUpError
return name
@command(RequestTypes.GET)
def find_around(x_pos, z_pos, radius=200):
"""
:request: GET
:param x_pos: MC X Coordinate
:param z_pos: MC Z Coordinate
:param radius: Radius to each around (default 200)
:return: List of all locations in the radius
:raises: LocationLookupError
:help: Finds all the locations around a certain point
"""
x_pos = int(x_pos)
z_pos = int(z_pos)
radius = int(radius)
locations = Location.objects.filter(x_coord__range=(x_pos - radius, x_pos + radius),
z_coord__range=(z_pos - radius, z_pos + radius)).all()
if len(locations) == 0:
raise LocationLookUpError
return objects_list_to_json(locations)
@command(RequestTypes.POST)
def add_item(item_name, quantity, diamond_price, shop_name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param item_name: name of the item
:param quantity: number of items being sold
:param diamond_price: price in diamonds
:param shop_name: Name of the shop, can be blank if the user only has one shop
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: Item Listing
:raises: PlayerNotFound, LocationLookupError, EntryNameNotUniqueError, NoLocationsInDatabase
:help: Adds an item to a shop's inventory.
"""
player = get_player(discord_uuid, mc_uuid)
shop = get_location(player, shop_name, Shop).shop
item_listing = ItemListing.objects.create(shop=shop, amount=int(quantity), price=int(diamond_price),
item_name=item_name)
return item_listing.json
@command(RequestTypes.POST)
def add_resource(resource_name, farm_name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param resource_name: name of the resource
:param farm_name: name of the farm to add the resource to. Can be none.
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: Item Listing
:raises: PlayerNotFound, LocationLookupError, EntryNameNotUniqueError, NoLocationsInDatabase
:help: Adds a resource to a farm.
"""
player = get_player(discord_uuid, mc_uuid)
farm = get_location(player, farm_name, PublicFarm).publicfarm
resource = Resource.objects.create(farm=farm, resource_name=resource_name)
return resource.json
@command(RequestTypes.GET)
def find_farm(resource_name):
"""
:request: GET
:param resource_name: Resource to search for
:return: List of top matching farms
:raises: ResourceNotFoundError
:help: Lists farms that produce an object
"""
if len(resource_name) == 0:
raise EmptryString
farms = PublicFarm.objects.filter(resource__resource_name__icontains=resource_name).all()[:10]
if len(farms) == 0:
raise ResourceNotFoundError
return objects_list_to_json(farms)
@command(RequestTypes.GET)
def selling(item_name):
"""
:request: GET
:param item_name: Item name to search for
:return: List of top matching shops, sorted by the date last restocke
:raises: ItemNotFound
:help: Lists shops selling an item. Sorted by when they were last restocked.
"""
return get_selling(item_name, sort="-date_restocked")
@command(RequestTypes.GET)
def selling_price(item_name):
"""
:request: GET
:param item_name: Item name to search for
:return: List of top matching shops, sorted by the
:raises: ItemNotFound
:help: Lists shops selling an item. Sorted lowest price to highest price.
"""
return get_selling(item_name, sort="normalized_price")
def get_selling(item_name, sort):
items = []
if len(item_name) == 0:
raise EmptryString
all_shop_ids = ItemListing.objects.annotate(normalized_price=F('price') / F('amount')) \
.filter(item_name__icontains=item_name).order_by(sort).values('shop_id').all()
if len(all_shop_ids) == 0:
raise ItemNotFound
# Removes duplicates
shop_ids = []
for shop_id in all_shop_ids:
if shop_id not in shop_ids:
shop_ids.append(shop_id)
for shop_id in shop_ids:
shop = Shop.objects.get(pk=shop_id['shop_id']).json
item_query = ItemListing.objects.filter(
item_name__icontains=item_name, shop_id=shop_id['shop_id']).order_by(sort)
item_list = []
for item in item_query:
item_list.append(item)
shop["items"] = objects_list_to_json(item_list)
items.append(shop)
return items
@command(RequestTypes.GET)
def info(location_name):
"""
:request: GET
:param location_name: Name of the location to get info on
:return: JSON representation of location
:raises: LocationLookupError
:help: Finds all the locations matching the search term
"""
location = Location.objects.filter(name__iexact=location_name).first()
if location is None:
location = Location.objects.filter(name__iregex=".*{}.*".format(location_name)).first()
if location is not None:
loc = location.loc_child_obj
loc_json = loc.json
if type(loc) == Shop:
loc_json["items"] = []
for item in loc.shop_selling.all():
loc_json["items"].append(item.json)
elif type(loc) == PublicFarm:
loc_json["resources"] = []
for resource in loc.resource.all():
loc_json["resources"].append(resource.json)
return loc_json
else:
raise LocationLookUpError
@command(RequestTypes.GET)
def tunnel(player_name):
"""
:request: GET
:param player_name: MC player name
:return: List of all the tunnels a user owns
:raises: LocationLookUpError
:raises: LocationLookupError
:help: Finds all the tunnels a player owns
"""
tunnels = Tunnel.objects.filter(location__owner__name__icontains=player_name).all()
if len(tunnels) == 0:
raise LocationLookUpError
return objects_list_to_json(tunnels)
@command(RequestTypes.POST)
def edit_pos(x, z, loc_name, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param x: New MC X coordinate
:param z: New MC Z Coordinate
:param loc_name: Location Name to edit
:param discord_uuid: Discord UUID
:param mc_uuid: MC UUID
:return: Edited Locatio
:raises:
:raises: PlayerNotFound, LocationLookupError, EntryNameNotUniqueError, NoLocationsInDatabase
:help: Edits the position of a location
"""
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
location = get_location(player, loc_name)
location.x_coord = x
location.z_coord = z
location.save()
return location.json
@command(RequestTypes.POST)
def edit_tunnel(tunnel_direction, tunnel_number, loc_name, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param tunnel_direction: New Tunnel Direction
:param tunnel_number: New Tunnel Address
:param loc_name: Location Name to edit
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: Edited Location
:raises: PlayerNotFound, LocationLookupError, EntryNameNotUniqueError, NoLocationsInDatabase
:help: Edits the tunnel of a location
"""
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
location = get_location(player, loc_name)
tunnel_direction = match_tunnel(tunnel_direction)
try:
tunnel = Tunnel.objects.get(location=location)
tunnel.tunnel_direction = tunnel_direction
tunnel.tunnel_number = tunnel_number
tunnel.save()
except Tunnel.DoesNotExist:
Tunnel.objects.create(tunnel_direction=tunnel_direction, tunnel_number=tunnel_number, location=location)
return location.json
@command(RequestTypes.POST)
def edit_name(new_name, loc_name, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param new_name: New Location Name
:param loc_name: Old Location name
:param discord_uuid: Discord UUID
:param mc_uuid: MC UUID
:return: Edited Location
:raises: PlayerNotFound, LocationLookupError, EntryNameNotUniqueError, NoLocationsInDatabase
:help: Edits the name of a location
"""
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
if Location.objects.filter(name__iexact=new_name).count():
raise EntryNameNotUniqueError
location = get_location(player, loc_name)
location.name = new_name
location.save()
return location.json
@command(RequestTypes.POST)
def delete_item(item, shop_name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param item: Item name to delete
:param shop_name: Shop selling item, can be None if the user only has one shop
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: Shop where the item was deleted from
:raises: PlayerNotFound, LocationLookupError, EntryNameNotUniqueError, NoLocationsInDatabase, ItemNotFound
:help: Deletes an item from a shop
"""
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
shop = get_location(player, shop_name, Shop)
delete_list = ItemListing.objects.filter(item_name__iexact=item, shop=shop).all()
if len(delete_list) == 0:
raise ItemNotFound
delete_list.delete()
return shop.json
@command(RequestTypes.POST)
def delete_resource(resource_name, farm_name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param resource: resource to delete
:param farm_name: Farm with resource, can be None if the user only has one farm
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: PublicFarm where the resource was deleted from
:raises: PlayerNotFound, LocationLookupError, EntryNameNotUniqueError, NoLocationsInDatabase, ItemNotFound
:help: Deletes a resource from a farm
"""
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
farm = get_location(player, farm_name, PublicFarm)
delete_list = Resource.objects.filter(resource_name=resource_name, farm=farm).all()
if len(delete_list) == 0:
raise ResourceNotFoundError
delete_list.delete()
return farm.json
@command(RequestTypes.GET)
def me(discord_uuid=None, mc_uuid=None):
"""
:request: GET
:param discord_uuid: Discord UUID
:param mc_uuid: MC UUID
:return: Returns a list of all the locations owned by a user
:raises: NoLocationsInDatabase, PlayerNotFound
:help: Find all the locations in the database
"""
try:
player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid)
except Player.DoesNotExist:
raise PlayerNotFound
locations = Location.objects.filter(owner=player).all()
if len(locations) == 0:
raise NoLocationsInDatabase
return objects_list_to_json(locations)
@command(RequestTypes.POST)
def restock(item_name, shop_name=None, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param item_name: Item to restock
:param shop_name: Shop the item is in, can be none if the only one location is owned by the user
:param discord_uuid: Discord UUID
:param mc_uuid: Minecraft UUID
:return: List of items updated
:raises: PlayerNotFound, LocationLookupError, EntryNameNotUniqueError, NoLocationsInDatabase, ItemNotFound
:help: Restocks items matching the item name in your shop
"""
owner = get_player(discord_uuid, mc_uuid)
shop = get_location(owner, shop_name, Shop)
items = ItemListing.objects.filter(item_name__iexact=item_name, shop=shop).all()
if len(items) == 0:
raise ItemNotFound
else:
for item in items:
item.date_restocked = datetime.datetime.now()
item.save()
return objects_list_to_json(items)
@command(RequestTypes.POST)
def add_owner(new_owner_name, location_name, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param new_owner_name: The MC username of the new owner
:param location_name: The name of the location to add them to
:param discord_uuid: Discord UUID of the current owner
:param mc_uuid: MC UUID of the current owner
:return: Update Location
:raises: PlayerNotFound, LocationLookupError, IsOwnerError, OwnerNotFound
:help: Adds a co-owner to a location
"""
owner = get_player(discord_uuid, mc_uuid)
try:
new_owner = Player.objects.get(name__iexact=new_owner_name)
except Player.DoesNotExist:
raise OwnerNotFoundError
location = get_location(owner, location_name, Location)
if location.owner.filter(location__owner__name__iexact=new_owner_name):
raise IsOwnerError
location.owner.add(new_owner)
location.save()
return location.json
@command(RequestTypes.POST)
def add_resident(new_resident_name, town_name, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param new_resident_name: The MC username of the new resident
:param town_name: The name of the town to add the resident to, can be blank if the owner has one town
:param discord_uuid: Discord UUID of the town owner
:param mc_uuid: MC UUID of the town owner
:return: Updated Location
:raises: PlayerNotFound, LocationLookupError, IsResidentError, ResidentNotFoundError
:help: Adds a resident to a town
"""
owner = get_player(discord_uuid, mc_uuid)
try:
new_resident = Player.objects.get(name__iexact=new_resident_name)
except Player.DoesNotExist:
raise ResidentNotFoundError
town = get_location(owner, town_name, Town)
if town.residents.filter(Q(town__owner__name__iexact=new_resident_name) |
Q(name__iexact=new_resident_name)).all().count():
raise IsResidentError
town.residents.add(new_resident)
town.save()
return town.json
@command(RequestTypes.POST)
def remove_resident(resident_name, town_name, discord_uuid=None, mc_uuid=None):
"""
:request: POST
:param resident_name: Name of the resident to remove
:param town_name: Name of the town, can be blank if the owner has one town
:param discord_uuid: Owner discord uuid
:param mc_uuid: Owner mc uuid
:raises: PlayerNotFound, LocationLookupError, ResidentNotFoundError
:return: Updated town
:help: Removes a resident from a town
"""
owner = get_player(discord_uuid, mc_uuid)
town = get_location(owner, town_name, Town)
try:
resident = town.residents.get(name__iexact=resident_name)
except Player.DoesNotExist:
raise ResidentNotFoundError
town.residents.remove(resident)
town.save()
return town.json
@command(RequestTypes.POST, permission_level=PermissionLevel.MOD)
def mod_delete(player_name, location_name):
uuid = grab_UUID(player_name)
delete(location_name, mc_uuid=uuid)
@command(RequestTypes.POST, permission_level=PermissionLevel.MOD)
def mod_delete_item(player_name, item_name, shop_name):
uuid = grab_UUID(player_name)
delete_item(item_name, shop_name=shop_name, mc_uuid=uuid)
@command(RequestTypes.POST, permission_level=PermissionLevel.MOD)
def mod_edit_name(player_name, new_name, loc_name):
uuid = grab_UUID(player_name)
edit_name(new_name, loc_name, mc_uuid=uuid)
@command(RequestTypes.POST, permission_level=PermissionLevel.MOD)
def mod_remove_resident(player_name, resident_name, town_name):
uuid = grab_UUID(player_name)
remove_resident(resident_name, town_name, mc_uuid=uuid)
@command(RequestTypes.POST, permission_level=PermissionLevel.MOD)
def mod_remove_owner(player_name, loc_name):
uuid = grab_UUID(player_name)
owner = get_player(mc_uuid=uuid)
location = get_location(owner, loc_name)
location.owner.remove(owner)