552 lines
16 KiB
Python
552 lines
16 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
|
|
|
|
command_dict = {"GET": {}, "POST": {}, "DELETE": {}}
|
|
|
|
|
|
def objects_list_to_json(obj_list):
|
|
json_list = []
|
|
|
|
for obj in obj_list:
|
|
json_list.append(obj.json)
|
|
|
|
return json_list
|
|
|
|
|
|
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_required_args(func):
|
|
args = inspect.getfullargspec(func)
|
|
|
|
return args.args + args.kwonlyargs
|
|
|
|
|
|
def command(type):
|
|
def command_dec(func):
|
|
def add_command():
|
|
command_dict[type][func.__name__] = {"func": func,
|
|
"params": get_required_args(func),
|
|
"help": parse_help(func)}
|
|
return func
|
|
|
|
return add_command()
|
|
|
|
return command_dec
|
|
|
|
|
|
def parse_help(func):
|
|
try:
|
|
match = re.search(".*:help:.*", func.__doc__)
|
|
|
|
return match.group(0).partition(":help: ")[-1]
|
|
except:
|
|
return ''
|
|
|
|
|
|
def get_player(discord_uuid=None, mc_uuid=None):
|
|
try:
|
|
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)
|
|
try:
|
|
get_location(player, name, loc_type=loc_type)
|
|
raise EntryNameNotUniqueError
|
|
except (Location.DoesNotExist, NoLocationsInDatabase, LocationLookUpError):
|
|
if name is None:
|
|
name = "{}'s {}".format(player.name, loc_type.__name__)
|
|
|
|
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("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("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("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. The name is optional if this is your first shop
|
|
'''
|
|
|
|
return add_location(x_pos, z_pos, name=name, discord_uuid=discord_uuid, mc_uuid=mc_uuid, loc_type=Shop)
|
|
|
|
|
|
@command("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("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("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:
|
|
Location.objects.get(name__iexact=name, owner=owner).delete()
|
|
except Location.DoesNotExist:
|
|
raise LocationLookUpError
|
|
|
|
return name
|
|
|
|
|
|
@command("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("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. If you have one shop, the shop name is not required
|
|
'''
|
|
|
|
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("GET")
|
|
def selling(item_name, sort="-date_restocked"):
|
|
'''
|
|
:request: GET
|
|
:param item_name: Item name to search for
|
|
:param sort: Field to sort shop results by, default is date_restocked
|
|
:return: List of top matching shops, sorted by the
|
|
:raises: ItemNotFound
|
|
:help: Lists shops selling an item.
|
|
'''
|
|
|
|
items = []
|
|
if len(item_name) == 0:
|
|
raise EmptryString
|
|
|
|
shops = ItemListing.objects.annotate(normalized_price=F('price') / F('amount')) \
|
|
.filter(item_name__icontains=item_name).order_by(sort).values('shop_id').all()
|
|
|
|
if len(shops) == 0:
|
|
raise ItemNotFound
|
|
|
|
# Removes duplicates
|
|
shops = [i for n, i in enumerate(shops) if i not in shops[n + 1:]]
|
|
|
|
for shop_id in shops:
|
|
shop = Shop.objects.get(pk=shop_id['shop_id']).json
|
|
|
|
item_query = ItemListing.objects.annotate(normalized_price=F('price') / F('amount')) \
|
|
.filter(item_name__icontains=item_name, shop_id=shop_id['shop_id']) \
|
|
.order_by(sort) \
|
|
.values("item_name", "price", "amount")
|
|
|
|
item_list = []
|
|
for item in item_query:
|
|
item_list.append(item)
|
|
|
|
shop["items"] = item_list
|
|
items.append(shop)
|
|
|
|
return items
|
|
|
|
|
|
@command("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:
|
|
return location.json
|
|
else:
|
|
raise LocationLookUpError
|
|
|
|
|
|
@command("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("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("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("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("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("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("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("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: Updated Location
|
|
:raises: PlayerNotFound, LocationLookupError, PlayerInDBError
|
|
'''
|
|
owner = get_player(discord_uuid, mc_uuid)
|
|
|
|
try:
|
|
new_owner = Player.objects.get(name__iexact=new_owner_name)
|
|
except Player.DoesNotExist:
|
|
raise PlayerNotFound("New Owner Not in DB")
|
|
|
|
location = get_location(owner, location_name, Location)
|
|
|
|
if location.owner.filter(location__owner__name__iexact=new_owner_name):
|
|
raise PlayerInDBError
|
|
|
|
location.owner.add(new_owner)
|
|
|
|
location.save()
|
|
|
|
return location.json
|