diff --git a/hippolyzer/lib/base/inventory.py b/hippolyzer/lib/base/inventory.py index 49ed4d1..afe5ece 100644 --- a/hippolyzer/lib/base/inventory.py +++ b/hippolyzer/lib/base/inventory.py @@ -626,6 +626,9 @@ class InventoryItem(InventoryNodeBase): name: Optional[str] = schema_field(SchemaMultilineStr, default=None) desc: Optional[str] = schema_field(SchemaMultilineStr, default=None) metadata: Optional[Dict[str, Any]] = schema_field(SchemaLLSD, default=None, include_none=True) + """Specifically for script metadata, generally just experience info""" + thumbnail: Optional[Dict[str, Any]] = schema_field(SchemaLLSD, default=None, include_none=False) + """Generally just a dict with the thumbnail UUID in it""" creation_date: Optional[dt.datetime] = schema_field(SchemaDate, llsd_name="created_at", default=None) __hash__ = InventoryNodeBase.__hash__ diff --git a/hippolyzer/lib/client/hippo_client.py b/hippolyzer/lib/client/hippo_client.py index 761ebba..696f0ef 100644 --- a/hippolyzer/lib/client/hippo_client.py +++ b/hippolyzer/lib/client/hippo_client.py @@ -330,7 +330,7 @@ class HippoClientSession(BaseClientSession): super().__init__(id, secure_session_id, agent_id, circuit_code, session_manager, login_data=login_data) self.http_session = session_manager.http_session self.objects = ClientWorldObjectManager(proxify(self), session_manager.settings, None) - self.inventory_manager = InventoryManager(proxify(self)) + self.inventory = InventoryManager(proxify(self)) self.transport: Optional[SocketUDPTransport] = None self.protocol: Optional[HippoClientProtocol] = None self.message_handler.take_by_default = False diff --git a/hippolyzer/lib/client/inventory_manager.py b/hippolyzer/lib/client/inventory_manager.py index 40dbe13..a5fa368 100644 --- a/hippolyzer/lib/client/inventory_manager.py +++ b/hippolyzer/lib/client/inventory_manager.py @@ -6,16 +6,18 @@ import gzip import itertools import logging from pathlib import Path -from typing import Union, List, Tuple, Set, Sequence, Dict +from typing import Union, List, Tuple, Set, Sequence, Dict, TYPE_CHECKING from hippolyzer.lib.base import llsd from hippolyzer.lib.base.datatypes import UUID from hippolyzer.lib.base.inventory import InventoryModel, InventoryCategory, InventoryItem, InventoryNodeBase from hippolyzer.lib.base.message.message import Message, Block from hippolyzer.lib.base.templates import AssetType, FolderType, InventoryType, Permissions -from hippolyzer.lib.client.state import BaseClientSession from hippolyzer.lib.base.templates import WearableType +if TYPE_CHECKING: + from hippolyzer.lib.client.state import BaseClientSession + LOG = logging.getLogger(__name__) diff --git a/hippolyzer/lib/client/object_manager.py b/hippolyzer/lib/client/object_manager.py index ce6a332..e947a67 100644 --- a/hippolyzer/lib/client/object_manager.py +++ b/hippolyzer/lib/client/object_manager.py @@ -15,7 +15,7 @@ from typing import * from hippolyzer.lib.base.datatypes import UUID, Vector3 from hippolyzer.lib.base.helpers import proxify -from hippolyzer.lib.base.inventory import InventoryItem, InventoryModel +from hippolyzer.lib.base.inventory import InventoryItem, InventoryModel, InventoryObject from hippolyzer.lib.base.message.message import Block, Message from hippolyzer.lib.base.message.message_handler import MessageHandler from hippolyzer.lib.base.message.msgtypes import PacketFlags @@ -229,7 +229,18 @@ class ClientObjectManager: async def request_object_inv_via_cap(self, obj: Object) -> List[InventoryItem]: async with self._region.caps_client.get("RequestTaskInventory", params={"task_id": obj.FullID}) as resp: resp.raise_for_status() - return [InventoryItem.from_llsd(x) for x in (await resp.read_llsd())["contents"]] + all_items = [InventoryItem.from_llsd(x) for x in (await resp.read_llsd())["contents"]] + # Synthesize the Contents directory so the items can have a parent + parent = InventoryObject( + obj_id=obj.FullID, + name="Contents", + ) + model = InventoryModel() + model.add(parent) + for item in all_items: + model.add(item) + + return all_items async def request_object_inv_via_xfer(self, obj: Object) -> List[InventoryItem]: session = self._region.session() @@ -370,6 +381,10 @@ class ClientWorldObjectManager: futs.extend(region_mgr.request_object_properties(region_objs)) return futs + async def request_object_inv(self, obj: Object) -> List[InventoryItem]: + region_mgr = self._get_region_manager(obj.RegionHandle) + return await region_mgr.request_object_inv(obj) + async def load_ancestors(self, obj: Object, wait_time: float = 1.0): """ Ensure that the entire chain of parents above this object is loaded diff --git a/hippolyzer/lib/client/state.py b/hippolyzer/lib/client/state.py index a34e8c4..01fc023 100644 --- a/hippolyzer/lib/client/state.py +++ b/hippolyzer/lib/client/state.py @@ -18,6 +18,7 @@ from hippolyzer.lib.base.network.caps_client import CapsClient from hippolyzer.lib.base.network.transport import ADDR_TUPLE from hippolyzer.lib.base.objects import handle_to_global_pos from hippolyzer.lib.base.xfer_manager import XferManager +from hippolyzer.lib.client.inventory_manager import InventoryManager from hippolyzer.lib.client.object_manager import ClientObjectManager, ClientWorldObjectManager @@ -91,6 +92,7 @@ class BaseClientSession(abc.ABC): region_by_handle: Callable[[int], Optional[BaseClientRegion]] region_by_circuit_addr: Callable[[ADDR_TUPLE], Optional[BaseClientRegion]] objects: ClientWorldObjectManager + inventory: InventoryManager login_data: Dict[str, Any] REGION_CLS = Type[BaseClientRegion] diff --git a/hippolyzer/lib/proxy/sessions.py b/hippolyzer/lib/proxy/sessions.py index 00370ac..43484ed 100644 --- a/hippolyzer/lib/proxy/sessions.py +++ b/hippolyzer/lib/proxy/sessions.py @@ -34,6 +34,7 @@ if TYPE_CHECKING: class Session(BaseClientSession): regions: MutableSequence[ProxiedRegion] + inventory: ProxyInventoryManager region_by_handle: Callable[[int], Optional[ProxiedRegion]] region_by_circuit_addr: Callable[[ADDR_TUPLE], Optional[ProxiedRegion]] main_region: Optional[ProxiedRegion] diff --git a/tests/client/test_hippo_client.py b/tests/client/test_hippo_client.py index f8782b2..a58baf0 100644 --- a/tests/client/test_hippo_client.py +++ b/tests/client/test_hippo_client.py @@ -159,7 +159,7 @@ class TestHippoClient(unittest.IsolatedAsyncioTestCase): async def test_inventory_manager(self): await self._log_client_in(self.client) - self.assertEqual(self.client.session.inventory_manager.model.root.node_id, UUID(int=4)) + self.assertEqual(self.client.session.inventory.model.root.node_id, UUID(int=4)) async def test_resend_suppression(self): """Make sure the client only handles the first seen copy of a reliable message"""