from django.db.models import Q, F from GeoffreyApp.models import * from GeoffreyApp.errors import * from GeoffreyApp.minecraft_api import * import inspect 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 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)} return func return add_command() return command_dec 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: if loc_type == Location: loc_list = Location.objects.all().filter(owner=owner) else: loc_list = Location.objects.all().select_related(loc_type.__name__.lower()).filter(owner=owner) if len(loc_list) == 1: loc = loc_list[0] elif len(loc_list) == 0: raise NoLocationsInDatabase else: raise EntryNameNotUniqueError else: if loc_type == Location: loc_list = Location.objects.all().filter(owner=owner) else: loc_list = Location.objects.all().select_related(loc_type.__name__.lower()).filter(owner=owner) 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): 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.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 :help: Registers your Discord and Minecraft account with the the database ''' mc_uuid = grab_UUID(player_name) player = Player.objects.create(name=player_name, mc_uuid=mc_uuid, discord_uuid=discord_uuid) 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 :help: Adds your base to the database. The base name is optional if this is your first base ''' 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 :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 :help: Adds your tunnel to the database. If you only have one location, you do not need to specify a location name ''' 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_direction, tunnel_number=tunnel_number, location=get_location(player, location_name)) return tunnel.json @command("GET") def find_location(search): ''' :request: GET :param search: Location name or owner to search for :return: List of the matching locations :help: Finds all the locations matching the search term ''' limit = 25 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("DELETE") def delete(name, discord_uuid=None, mc_uuid=None): ''' :request: DELETE :param name: Name of location to delete :param discord_uuid: Discord UUID :param mc_uuid: Minecraft UUID :return: Location Name :help: Deletes a location from the database ''' owner = get_player(discord_uuid, mc_uuid) Location.objects.get(name__iexact=name, owner=owner).delete() 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 :help: Finds all the locations around a certain point ''' locations = Location.objects.filter(x_coord__range=(x_pos - radius, x_pos + radius), z_coord__range=(z_pos - radius, z_pos + radius)).all() 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 :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=quantity, price=diamond_price, item_name=item_name) return item_listing.json # TODO Re-implement selling shop search @command("GET") def selling(item_name): ''' :request: GET :param item_name: Item name to search for :return: List of top matches shop, sorted by when they were added :help: List shops selling an item. Sorted by most recently added ''' 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('normalized_price').values('shop_id').distinct() if len(shops) == 0: raise ItemNotFound 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('normalized_price') \ .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 :raise LocationLookupError: If no location is found :help: Finds all the locations matching the search term ''' location = Location.objects.get(name__iexact=location_name) if location is None: location = Location.objects.get(name__iregex="*{}*".format(location_name)) 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 :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(tunnel) @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 Location :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.jsone @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 :help: Edits the tunnel of a location ''' 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.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 :help: Edits the name of a location ''' 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.json @command("DELETE") def delete_item(item, shop_name, discord_uuid=None, mc_uuid=None): ''' :request: DELETE :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: Deletes an item from the database ''' 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.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 :help: Find all the locations in the database ''' player = get_player(discord_uuid=discord_uuid, mc_uuid=mc_uuid) locations = Location.objects.filter(owner=player).all() if len(locations) == 0: raise PlayerNotFound return objects_list_to_json(locations)